FINERACT-1932: Add loan module (WIP)

This commit is contained in:
Adam Saghy
2023-05-25 17:57:28 +02:00
parent 105b4ed154
commit 2e3b96df19
352 changed files with 938 additions and 413 deletions
@@ -0,0 +1,116 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.common;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public final class AccountingEnumerations {
private AccountingEnumerations() {
}
public static EnumOptionData gLAccountType(final int id) {
return gLAccountType(GLAccountType.fromInt(id));
}
public static EnumOptionData gLAccountType(final GLAccountType accountType) {
final EnumOptionData optionData = new EnumOptionData(accountType.getValue().longValue(), accountType.getCode(),
accountType.toString());
return optionData;
}
public static List<EnumOptionData> gLAccountType(final GLAccountType[] accountTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final GLAccountType accountType : accountTypes) {
optionDatas.add(gLAccountType(accountType));
}
return optionDatas;
}
public static EnumOptionData gLAccountUsage(final int id) {
return gLAccountUsage(GLAccountUsage.fromInt(id));
}
public static EnumOptionData gLAccountUsage(final GLAccountUsage accountUsage) {
final EnumOptionData optionData = new EnumOptionData(accountUsage.getValue().longValue(), accountUsage.getCode(),
accountUsage.toString());
return optionData;
}
public static List<EnumOptionData> gLAccountUsage(final GLAccountUsage[] accountUsages) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final GLAccountUsage accountUsage : accountUsages) {
optionDatas.add(gLAccountUsage(accountUsage));
}
return optionDatas;
}
public static EnumOptionData journalEntryType(final int id) {
return journalEntryType(JournalEntryType.fromInt(id));
}
public static EnumOptionData journalEntryType(final JournalEntryType journalEntryType) {
final EnumOptionData optionData = new EnumOptionData(journalEntryType.getValue().longValue(), journalEntryType.getCode(),
journalEntryType.toString());
return optionData;
}
public static List<EnumOptionData> journalEntryTypes(final JournalEntryType[] journalEntryTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final JournalEntryType journalEntryType : journalEntryTypes) {
optionDatas.add(journalEntryType(journalEntryType));
}
return optionDatas;
}
public static EnumOptionData portfolioProductType(final int id) {
return portfolioProductType(PortfolioProductType.fromInt(id));
}
public static EnumOptionData portfolioProductType(final PortfolioProductType portfolioProductType) {
final EnumOptionData optionData = new EnumOptionData(portfolioProductType.getValue().longValue(), portfolioProductType.getCode(),
portfolioProductType.toString());
return optionData;
}
public static EnumOptionData accountingRuleType(final int id) {
return accountingRuleType(AccountingRuleType.fromInt(id));
}
public static EnumOptionData accountingRuleType(final AccountingRuleType type) {
final EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), type.toString());
return optionData;
}
public static List<EnumOptionData> accountingRuleTypes(final AccountingRuleType[] accountingRuleTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final AccountingRuleType accountingRuleType : accountingRuleTypes) {
optionDatas.add(accountingRuleType(accountingRuleType));
}
return optionDatas;
}
}
@@ -0,0 +1,64 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.common;
import java.util.HashMap;
import java.util.Map;
public enum AccountingRuleType {
NONE(1, "accountingRuleType.none"), //
CASH_BASED(2, "accountingRuleType.cash"), //
ACCRUAL_PERIODIC(3, "accountingRuleType.accrual.periodic"), //
ACCRUAL_UPFRONT(4, "accountingRuleType.accrual.upfront"); //
private final Integer value;
private final String code;
private static final Map<Integer, AccountingRuleType> intToEnumMap = new HashMap<>();
static {
for (final AccountingRuleType type : AccountingRuleType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static AccountingRuleType fromInt(final Integer ruleTypeValue) {
final AccountingRuleType type = intToEnumMap.get(ruleTypeValue);
return type;
}
AccountingRuleType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
}
@@ -0,0 +1,58 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.glaccount.api;
import java.util.HashSet;
import java.util.Set;
/***
* Enum of all parameters passed in while creating/updating a GL Account
***/
public enum GLAccountJsonInputParams {
ID("id"), NAME("name"), PARENT_ID("parentId"), GL_CODE("glCode"), DISABLED("disabled"), MANUAL_ENTRIES_ALLOWED(
"manualEntriesAllowed"), TYPE("type"), USAGE("usage"), DESCRIPTION("description"), TAGID("tagId");
private final String value;
GLAccountJsonInputParams(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final GLAccountJsonInputParams type : GLAccountJsonInputParams.values()) {
values.add(type.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
@@ -0,0 +1,98 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.glaccount.data;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.apache.fineract.accounting.common.AccountingEnumerations;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google-gson will produce json from fields of object.
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class GLAccountData implements Serializable {
private Long id;
private String name;
private Long parentId;
private String glCode;
private Boolean disabled;
private Boolean manualEntriesAllowed;
private EnumOptionData type;
private EnumOptionData usage;
private String description;
private String nameDecorated;
private CodeValueData tagId;
private Long organizationRunningBalance;
// templates
private List<EnumOptionData> accountTypeOptions;
private List<EnumOptionData> usageOptions;
private List<GLAccountData> assetHeaderAccountOptions;
private List<GLAccountData> liabilityHeaderAccountOptions;
private List<GLAccountData> equityHeaderAccountOptions;
private List<GLAccountData> incomeHeaderAccountOptions;
private List<GLAccountData> expenseHeaderAccountOptions;
private Collection<CodeValueData> allowedAssetsTagOptions;
private Collection<CodeValueData> allowedLiabilitiesTagOptions;
private Collection<CodeValueData> allowedEquityTagOptions;
private Collection<CodeValueData> allowedIncomeTagOptions;
private Collection<CodeValueData> allowedExpensesTagOptions;
// import fields
private transient Integer rowIndex;
public static GLAccountData importInstance(String name, Long parentId, String glCode, Boolean manualEntriesAllowed, EnumOptionData type,
EnumOptionData usage, String description, CodeValueData tagId, Integer rowIndex) {
return new GLAccountData().setName(name).setParentId(parentId).setGlCode(glCode).setManualEntriesAllowed(manualEntriesAllowed)
.setType(type).setUsage(usage).setDescription(description).setTagId(tagId).setRowIndex(rowIndex);
}
public static GLAccountData createFrom(final Long id) {
return new GLAccountData().setId(id);
}
public static GLAccountData sensibleDefaultsForNewGLAccountCreation(final Integer glAccType) {
final boolean disabled = false;
final boolean manualEntriesAllowed = true;
final EnumOptionData type;
if (glAccType != null && glAccType >= GLAccountType.getMinValue() && glAccType <= GLAccountType.getMaxValue()) {
type = AccountingEnumerations.gLAccountType(glAccType);
} else {
type = AccountingEnumerations.gLAccountType(GLAccountType.ASSET);
}
final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(GLAccountUsage.DETAIL);
return new GLAccountData().setDisabled(disabled).setManualEntriesAllowed(manualEntriesAllowed).setType(type).setUsage(usage);
}
}
@@ -0,0 +1,202 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.glaccount.domain;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.fineract.accounting.glaccount.api.GLAccountJsonInputParams;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "acc_gl_account", uniqueConstraints = { @UniqueConstraint(columnNames = { "gl_code" }, name = "acc_gl_code") })
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
public class GLAccount extends AbstractPersistableCustom {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private GLAccount parent;
@Column(name = "hierarchy", nullable = true, length = 50)
private String hierarchy;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private List<GLAccount> children = new ArrayList<>();
@Column(name = "name", nullable = false, length = 45)
private String name;
@Column(name = "gl_code", nullable = false, length = 100)
private String glCode;
@Column(name = "disabled", nullable = false)
private boolean disabled;
@Column(name = "manual_journal_entries_allowed", nullable = false)
private boolean manualEntriesAllowed = true;
@Column(name = "classification_enum", nullable = false)
private Integer type;
@Column(name = "account_usage", nullable = false)
private Integer usage;
@Column(name = "description", nullable = true, length = 500)
private String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tag_id")
private CodeValue tagId;
public static GLAccount fromJson(final GLAccount parent, final JsonCommand command, final CodeValue glAccountTagType) {
final String name = command.stringValueOfParameterNamed(GLAccountJsonInputParams.NAME.getValue());
final String glCode = command.stringValueOfParameterNamed(GLAccountJsonInputParams.GL_CODE.getValue());
final boolean disabled = command.booleanPrimitiveValueOfParameterNamed(GLAccountJsonInputParams.DISABLED.getValue());
final boolean manualEntriesAllowed = command
.booleanPrimitiveValueOfParameterNamed(GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED.getValue());
final Integer usage = command.integerValueSansLocaleOfParameterNamed(GLAccountJsonInputParams.USAGE.getValue());
final Integer type = command.integerValueSansLocaleOfParameterNamed(GLAccountJsonInputParams.TYPE.getValue());
final String description = command.stringValueOfParameterNamed(GLAccountJsonInputParams.DESCRIPTION.getValue());
return new GLAccount().setParent(parent).setName(name).setGlCode(glCode).setDisabled(disabled)
.setManualEntriesAllowed(manualEntriesAllowed).setType(type).setUsage(usage).setDescription(description)
.setTagId(glAccountTagType);
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(15);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.DESCRIPTION.getValue(), this.description);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.DISABLED.getValue(), this.disabled);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.GL_CODE.getValue(), this.glCode);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED.getValue(), this.manualEntriesAllowed);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.NAME.getValue(), this.name);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.PARENT_ID.getValue(), 0L);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.TYPE.getValue(), this.type, true);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.USAGE.getValue(), this.usage, true);
handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.TAGID.getValue(),
this.tagId == null ? 0L : this.tagId.getId());
return actualChanges;
}
private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
final Integer propertyToBeUpdated, final boolean sansLocale) {
boolean changeDetected = false;
if (sansLocale) {
changeDetected = command.isChangeInIntegerSansLocaleParameterNamed(paramName, propertyToBeUpdated);
} else {
changeDetected = command.isChangeInIntegerParameterNamed(paramName, propertyToBeUpdated);
}
if (changeDetected) {
Integer newValue = null;
if (sansLocale) {
newValue = command.integerValueSansLocaleOfParameterNamed(paramName);
} else {
newValue = command.integerValueOfParameterNamed(paramName);
}
actualChanges.put(paramName, newValue);
// now update actual property
if (paramName.equals(GLAccountJsonInputParams.TYPE.getValue())) {
this.type = newValue;
} else if (paramName.equals(GLAccountJsonInputParams.USAGE.getValue())) {
this.usage = newValue;
}
}
}
private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
final String propertyToBeUpdated) {
if (command.isChangeInStringParameterNamed(paramName, propertyToBeUpdated)) {
final String newValue = command.stringValueOfParameterNamed(paramName);
actualChanges.put(paramName, newValue);
// now update actual property
if (paramName.equals(GLAccountJsonInputParams.DESCRIPTION.getValue())) {
this.description = newValue;
} else if (paramName.equals(GLAccountJsonInputParams.GL_CODE.getValue())) {
this.glCode = newValue;
} else if (paramName.equals(GLAccountJsonInputParams.NAME.getValue())) {
this.name = newValue;
}
}
}
private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
final Long propertyToBeUpdated) {
if (command.isChangeInLongParameterNamed(paramName, propertyToBeUpdated)) {
final Long newValue = command.longValueOfParameterNamed(paramName);
actualChanges.put(paramName, newValue);
// now update actual property
if (paramName.equals(GLAccountJsonInputParams.PARENT_ID.getValue())) {
// do nothing as this is a nested property
}
}
}
private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
final boolean propertyToBeUpdated) {
if (command.isChangeInBooleanParameterNamed(paramName, propertyToBeUpdated)) {
final Boolean newValue = command.booleanObjectValueOfParameterNamed(paramName);
actualChanges.put(paramName, newValue);
// now update actual property
if (paramName.equals(GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED.getValue())) {
this.manualEntriesAllowed = newValue;
} else if (paramName.equals(GLAccountJsonInputParams.DISABLED.getValue())) {
this.disabled = newValue;
}
}
}
public boolean isHeaderAccount() {
return GLAccountUsage.HEADER.getValue().equals(this.usage);
}
public void generateHierarchy() {
if (this.parent != null) {
this.hierarchy = this.parent.hierarchyOf(getId());
} else {
this.hierarchy = ".";
}
}
private String hierarchyOf(final Long id) {
return this.hierarchy + id.toString() + ".";
}
public boolean isDetailAccount() {
return GLAccountUsage.DETAIL.getValue().equals(this.usage);
}
}
@@ -0,0 +1,83 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.glaccount.domain;
import java.util.HashMap;
import java.util.Map;
public enum GLAccountUsage {
DETAIL(1, "accountUsage.detail"), HEADER(2, "accountUsage.header");
private final Integer value;
private final String code;
GLAccountUsage(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, GLAccountUsage> intToEnumMap = new HashMap<>();
private static int minValue;
private static int maxValue;
static {
int i = 0;
for (final GLAccountUsage type : GLAccountUsage.values()) {
if (i == 0) {
minValue = type.value;
}
intToEnumMap.put(type.value, type);
if (minValue >= type.value) {
minValue = type.value;
}
if (maxValue < type.value) {
maxValue = type.value;
}
i = i + 1;
}
}
public static GLAccountUsage fromInt(final int i) {
final GLAccountUsage type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
}
@@ -0,0 +1,69 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry.domain;
import java.util.HashMap;
import java.util.Map;
public enum JournalEntryType {
CREDIT(1, "journalEntryType.credit"), DEBIT(2, "journalEntrytType.debit");
private final Integer value;
private final String code;
JournalEntryType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, JournalEntryType> intToEnumMap = new HashMap<>();
static {
for (final JournalEntryType type : JournalEntryType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static JournalEntryType fromInt(final int i) {
return intToEnumMap.get(i);
}
@Override
public String toString() {
return name();
}
public boolean isDebitType() {
return this.value.equals(JournalEntryType.DEBIT.getValue());
}
public boolean isCreditType() {
return this.value.equals(JournalEntryType.CREDIT.getValue());
}
}
@@ -0,0 +1,38 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.data;
import java.io.Serializable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
@RequiredArgsConstructor
@Getter
@Setter
@Accessors(chain = true)
public class ChargeToGLAccountMapper implements Serializable {
private static final long serialVersionUID = 1L;
private ChargeData charge;
private GLAccountData incomeAccount;
}
@@ -0,0 +1,36 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.data;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class PaymentTypeToGLAccountMapper implements Serializable {
private static final long serialVersionUID = 1L;
private PaymentTypeData paymentType;
private GLAccountData fundSourceAccount;
}
@@ -0,0 +1,79 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.domain;
import java.util.HashMap;
import java.util.Map;
public enum PortfolioProductType {
LOAN(1, "productType.loan"), SAVING(2, "productType.saving"), CLIENT(5, "productType.client"), PROVISIONING(3,
"productType.provisioning"), SHARES(4, "productType.shares");
private final Integer value;
private final String code;
PortfolioProductType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, PortfolioProductType> intToEnumMap = new HashMap<>();
static {
for (final PortfolioProductType type : PortfolioProductType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static PortfolioProductType fromInt(final int i) {
final PortfolioProductType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public boolean isSavingProduct() {
return this.value.equals(PortfolioProductType.SAVING.getValue());
}
public boolean isLoanProduct() {
return this.value.equals(PortfolioProductType.LOAN.getValue());
}
public boolean isClient() {
return this.value.equals(PortfolioProductType.CLIENT.getValue());
}
public boolean isShareProduct() {
return this.value.equals(PortfolioProductType.SHARES.getValue());
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
public interface COBBusinessStep<T extends AbstractPersistableCustom> {
T execute(T input);
String getEnumStyledName();
String getHumanReadableName();
}
@@ -0,0 +1,61 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.codes;
import java.util.HashSet;
import java.util.Set;
public class CodeConstants {
/***
* Enum of all parameters passed in while creating/updating a code and code value
***/
public enum CodevalueJSONinputParams {
CODEVALUE_ID("id"), NAME("name"), POSITION("position"), DESCRIPTION("description"), IS_ACTIVE("isActive"), IS_MANDATORY(
"isMandatory");
private final String value;
CodevalueJSONinputParams(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final CodevalueJSONinputParams type : CodevalueJSONinputParams.values()) {
values.add(type.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
}
@@ -0,0 +1,78 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.codes.data;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* Immutable data object represent code-value data in system.
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class CodeValueData implements Serializable {
private Long id;
private String name;
private Integer position;
private String description;
private boolean active;
private boolean mandatory;
public static CodeValueData instance(final Long id, final String name, final Integer position, final boolean isActive,
final boolean mandatory) {
String description = null;
return new CodeValueData().setId(id).setName(name).setPosition(position).setDescription(description).setActive(isActive)
.setMandatory(mandatory);
}
public static CodeValueData instance(final Long id, final String name, final String description, final boolean isActive,
final boolean mandatory) {
Integer position = null;
return new CodeValueData().setId(id).setName(name).setPosition(position).setDescription(description).setActive(isActive)
.setMandatory(mandatory);
}
public static CodeValueData instance(final Long id, final String name, final String description, final boolean isActive) {
Integer position = null;
boolean mandatory = false;
return new CodeValueData().setId(id).setName(name).setPosition(position).setDescription(description).setActive(isActive)
.setMandatory(mandatory);
}
public static CodeValueData instance(final Long id, final String name) {
String description = null;
Integer position = null;
boolean isActive = false;
boolean mandatory = false;
return new CodeValueData().setId(id).setName(name).setPosition(position).setDescription(description).setActive(isActive)
.setMandatory(mandatory);
}
public static CodeValueData instance(final Long id, final String name, final Integer position, final String description,
final boolean isActive, final boolean mandatory) {
return new CodeValueData().setId(id).setName(name).setPosition(position).setDescription(description).setActive(isActive)
.setMandatory(mandatory);
}
}
@@ -0,0 +1,87 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.codes.domain;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.exception.SystemDefinedCodeCannotBeChangedException;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_code", uniqueConstraints = { @UniqueConstraint(columnNames = { "code_name" }, name = "code_name") })
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
public class Code extends AbstractPersistableCustom {
@Column(name = "code_name", length = 100)
private String name;
@Column(name = "is_system_defined")
private boolean systemDefined;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "code", orphanRemoval = true)
private Set<CodeValue> values;
public static Code fromJson(final JsonCommand command) {
final String name = command.stringValueOfParameterNamed("name");
return new Code().setName(name);
}
public static Code createNew(final String name) {
return new Code().setName(name);
}
public Map<String, Object> update(final JsonCommand command) {
if (this.systemDefined) {
throw new SystemDefinedCodeCannotBeChangedException();
}
final Map<String, Object> actualChanges = new LinkedHashMap<>(1);
final String firstnameParamName = "name";
if (command.isChangeInStringParameterNamed(firstnameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(firstnameParamName);
actualChanges.put(firstnameParamName, newValue);
this.name = StringUtils.defaultIfEmpty(newValue, null);
}
return actualChanges;
}
public boolean remove(final CodeValue codeValueToDelete) {
return this.values.remove(codeValueToDelete);
}
}
@@ -0,0 +1,131 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.codes.domain;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.CodeConstants.CodevalueJSONinputParams;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_code_value", uniqueConstraints = {
@UniqueConstraint(columnNames = { "code_id", "code_value" }, name = "code_value_duplicate") })
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
public class CodeValue extends AbstractPersistableCustom {
@Column(name = "code_value", length = 100)
private String label;
@Column(name = "order_position")
private int position;
@Column(name = "code_description")
private String description;
@ManyToOne
@JoinColumn(name = "code_id", nullable = false)
private Code code;
@Column(name = "is_active")
private boolean isActive;
@Column(name = "is_mandatory")
private boolean mandatory;
public static CodeValue createNew(final Code code, final String label, final int position, final String description,
final boolean isActive, final boolean mandatory) {
return new CodeValue().setCode(code).setLabel(StringUtils.defaultIfEmpty(label, null)).setPosition(position)
.setDescription(description).setActive(isActive).setMandatory(mandatory);
}
public static CodeValue fromJson(final Code code, final JsonCommand command) {
final String label = command.stringValueOfParameterNamed(CodevalueJSONinputParams.NAME.getValue());
Integer position = command.integerValueSansLocaleOfParameterNamed(CodevalueJSONinputParams.POSITION.getValue());
String description = command.stringValueOfParameterNamed(CodevalueJSONinputParams.DESCRIPTION.getValue());
Boolean isActiveObj = command.booleanObjectValueOfParameterNamed(CodevalueJSONinputParams.IS_ACTIVE.getValue());
boolean isActive = true;
if (isActiveObj != null) {
isActive = isActiveObj;
}
if (position == null) {
position = 0;
}
Boolean mandatory = command.booleanPrimitiveValueOfParameterNamed(CodevalueJSONinputParams.IS_MANDATORY.getValue());
return new CodeValue().setCode(code).setLabel(StringUtils.defaultIfEmpty(label, null)).setPosition(position)
.setDescription(description).setActive(isActive).setMandatory(mandatory);
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(2);
final String labelParamName = CodevalueJSONinputParams.NAME.getValue();
if (command.isChangeInStringParameterNamed(labelParamName, this.label)) {
final String newValue = command.stringValueOfParameterNamed(labelParamName);
actualChanges.put(labelParamName, newValue);
this.label = StringUtils.defaultIfEmpty(newValue, null);
}
final String decriptionParamName = CodevalueJSONinputParams.DESCRIPTION.getValue();
if (command.isChangeInStringParameterNamed(decriptionParamName, this.description)) {
final String newValue = command.stringValueOfParameterNamed(decriptionParamName);
actualChanges.put(decriptionParamName, newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null);
}
final String positionParamName = CodevalueJSONinputParams.POSITION.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(positionParamName, this.position)) {
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(positionParamName);
actualChanges.put(positionParamName, newValue);
this.position = newValue;
}
final String isActiveParamName = CodevalueJSONinputParams.IS_ACTIVE.getValue();
if (command.isChangeInBooleanParameterNamed(isActiveParamName, this.isActive)) {
final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isActiveParamName);
actualChanges.put(isActiveParamName, newValue);
this.isActive = newValue;
}
return actualChanges;
}
public CodeValueData toData() {
return CodeValueData.instance(getId(), this.label, this.position, this.isActive, this.mandatory);
}
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.codes.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link AbstractPlatformDomainRuleException} thrown when someone attempts to update or delete a system defined code.
*/
public class SystemDefinedCodeCannotBeChangedException extends AbstractPlatformDomainRuleException {
public SystemDefinedCodeCannotBeChangedException() {
super("error.msg.code.systemdefined", "This code is system defined and cannot be modified or deleted.");
}
}
@@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.configuration.service;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
* Temporary (static) configuration service container.
*
* Provide static access to the configuration service TODO: To be deleted when the code cleanup / refactor finished (of
* Loan.java) and we dont need this workaround anymore
*/
@Component
@RequiredArgsConstructor
public class TemporaryConfigurationServiceContainer implements InitializingBean {
private static volatile ConfigurationDomainService STATIC_REF_CONFIGURATION_SERVICE;
private final ConfigurationDomainService configurationDomainService;
// To avoid any abuse of this temporary solution, only the `isExternalIdAutoGenerationEnabled()` is exposed
public static boolean isExternalIdAutoGenerationEnabled() {
return TemporaryConfigurationServiceContainer.STATIC_REF_CONFIGURATION_SERVICE.isExternalIdAutoGenerationEnabled();
}
public static String getAccrualDateConfigForCharge() {
return TemporaryConfigurationServiceContainer.STATIC_REF_CONFIGURATION_SERVICE.getAccrualDateConfigForCharge();
}
@SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
@Override
public void afterPropertiesSet() throws Exception {
TemporaryConfigurationServiceContainer.STATIC_REF_CONFIGURATION_SERVICE = configurationDomainService;
}
}
@@ -0,0 +1,97 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.domain;
import java.time.LocalDateTime;
import java.util.Optional;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import org.springframework.data.domain.Auditable;
import org.springframework.data.jpa.domain.AbstractAuditable;
/**
* Make sure to modify the same class in the modules (fineract-investor, etc)
*
* A custom copy of {@link AbstractAuditable} to override the column names used on database. It also uses Instant
* instead of LocalDateTime for created and modified.
*
* Abstract base class for auditable entities. Stores the audit values in persistent fields.
*
* @param <U>
* the auditing type. Typically some kind of user.
* @param <PK>
* the type of the auditing type's identifier
*/
@MappedSuperclass
public abstract class AbstractAuditableCustom extends AbstractPersistableCustom implements Auditable<Long, Long, LocalDateTime> {
private static final long serialVersionUID = 141481953116476081L;
@Column(name = "createdby_id")
private Long createdBy;
@Column(name = "created_date")
private LocalDateTime createdDate;
@Column(name = "lastmodifiedby_id")
private Long lastModifiedBy;
@Column(name = "lastmodified_date")
private LocalDateTime lastModifiedDate;
@Override
public Optional<Long> getCreatedBy() {
return Optional.ofNullable(this.createdBy);
}
@Override
public void setCreatedBy(final Long createdBy) {
this.createdBy = createdBy;
}
@Override
public Optional<LocalDateTime> getCreatedDate() {
return null == this.createdDate ? Optional.empty() : Optional.of(this.createdDate);
}
@Override
public void setCreatedDate(final LocalDateTime createdDate) {
this.createdDate = createdDate;
}
@Override
public Optional<Long> getLastModifiedBy() {
return Optional.ofNullable(this.lastModifiedBy);
}
@Override
public void setLastModifiedBy(final Long lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
@Override
public Optional<LocalDateTime> getLastModifiedDate() {
return null == this.lastModifiedDate ? Optional.empty() : Optional.of(this.lastModifiedDate);
}
@Override
public void setLastModifiedDate(final LocalDateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
@@ -0,0 +1,99 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.domain;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY_DB_FIELD;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE_DB_FIELD;
import java.time.OffsetDateTime;
import java.util.Optional;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.springframework.data.domain.Auditable;
import org.springframework.data.jpa.domain.AbstractAuditable;
/**
* A custom copy of {@link AbstractAuditable} to override the column names used on database. It also uses OffsetDateTime
* for created and modified. The datetimes will be converted from tenant TZ to UTC before storing (automatically
* happens) and converted from System TZ to tenant TZ after fetching from DB
*
* Abstract base class for auditable entities. Stores the audit values in persistent fields.
*/
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor
public abstract class AbstractAuditableWithUTCDateTimeCustom extends AbstractPersistableCustom
implements Auditable<Long, Long, OffsetDateTime> {
private static final long serialVersionUID = 141481953116476081L;
@Column(name = CREATED_BY_DB_FIELD, nullable = false)
@Setter(onMethod = @__(@Override))
private Long createdBy;
@Column(name = CREATED_DATE_DB_FIELD, nullable = false)
@Setter(onMethod = @__(@Override))
private OffsetDateTime createdDate;
@Column(name = LAST_MODIFIED_BY_DB_FIELD, nullable = false)
@Setter(onMethod = @__(@Override))
private Long lastModifiedBy;
@Column(name = LAST_MODIFIED_DATE_DB_FIELD, nullable = false)
@Setter(onMethod = @__(@Override))
private OffsetDateTime lastModifiedDate;
@Override
public Optional<Long> getCreatedBy() {
return Optional.ofNullable(this.createdBy);
}
@Override
public Optional<OffsetDateTime> getCreatedDate() {
if (this.createdDate == null) {
return Optional.empty();
} else {
return Optional.of(this.createdDate
.withOffsetSameInstant(DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(this.createdDate.toInstant())));
}
}
@Override
public Optional<Long> getLastModifiedBy() {
return Optional.ofNullable(this.lastModifiedBy);
}
@Override
public Optional<OffsetDateTime> getLastModifiedDate() {
if (this.lastModifiedDate == null) {
return Optional.empty();
} else {
return Optional.of(this.lastModifiedDate
.withOffsetSameInstant(DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(this.lastModifiedDate.toInstant())));
}
}
}
@@ -0,0 +1,70 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.domain;
import java.io.Serializable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PrePersist;
import javax.persistence.Transient;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.domain.Persistable;
/**
* Make sure to modify the same class in the modules (fineract-investor, etc)
*
* Abstract base class for entities.
*
* Inspired by {@link org.springframework.data.jpa.domain.AbstractPersistable}, but Id is always Long (and this class
* thus does not require generic parameterization), and auto-generation is of strategy
* {@link javax.persistence.GenerationType#IDENTITY}.
*
* The {@link #equals(Object)} and {@link #hashCode()} methods are NOT implemented here, which is untypical for JPA
* (it's usually implemented based on the Id), because "we end up with issues on OpenJPA" (TODO clarify this).
*/
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor
public abstract class AbstractPersistableCustom implements Persistable<Long>, Serializable {
private static final long serialVersionUID = 9181640245194392646L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter(onMethod = @__(@Override))
private Long id;
@Transient
@Setter(value = AccessLevel.NONE)
@Getter(onMethod = @__(@Override))
private boolean isNew = true;
@PrePersist
@PostLoad
void markNotNew() {
this.isNew = false;
}
}
@@ -0,0 +1,80 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.domain;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class LocalDateInterval {
private final LocalDate startDate;
private final LocalDate endDate;
public static LocalDateInterval create(final LocalDate startDate, final LocalDate endDate) {
return new LocalDateInterval(startDate, endDate);
}
public LocalDateInterval(final LocalDate startDate, final LocalDate endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
public LocalDate startDate() {
return this.startDate;
}
public LocalDate endDate() {
return this.endDate;
}
public Integer daysInPeriodInclusiveOfEndDate() {
return daysBetween() + 1;
}
private Integer daysBetween() {
return Math.toIntExact(ChronoUnit.DAYS.between(this.startDate, this.endDate));
}
public boolean containsPortionOf(final LocalDateInterval interval) {
return contains(interval.startDate) || contains(interval.endDate);
}
public boolean contains(final LocalDateInterval interval) {
return contains(interval.startDate) && contains(interval.endDate);
}
public boolean contains(final LocalDate target) {
return isBetweenInclusive(this.startDate, this.endDate, target);
}
private boolean isBetweenInclusive(final LocalDate start, final LocalDate end, final LocalDate target) {
return !target.isBefore(start) && !target.isAfter(end);
}
public boolean fallsBefore(final LocalDate dateToCheck) {
return this.endDate.isBefore(dateToCheck);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exception;
/**
* A {@link RuntimeException} thrown when a valid API request ends up violating some domain rule.
*/
public abstract class AbstractPlatformDomainRuleException extends AbstractPlatformException {
protected AbstractPlatformDomainRuleException(String globalisationMessageCode, String defaultUserMessage,
Object... defaultUserMessageArgs) {
super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exception;
/**
* A {@link RuntimeException} thrown when resources that are queried for are not found.
*/
public abstract class AbstractPlatformResourceNotFoundException extends AbstractPlatformException {
protected AbstractPlatformResourceNotFoundException(final String globalisationMessageCode, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exception;
/**
* A {@link RuntimeException} thrown when valid api request end up violating some domain rule.
*/
public final class GeneralPlatformDomainRuleException extends AbstractPlatformDomainRuleException {
public GeneralPlatformDomainRuleException(String globalisationMessageCode, String defaultUserMessage,
Object... defaultUserMessageArgs) {
super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,43 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exception;
/**
* A {@link RuntimeException} thrown when data integrity problems happen due to state modifying actions.
*/
public class PlatformDataIntegrityException extends AbstractPlatformException {
private final String parameterName;
public PlatformDataIntegrityException(final String globalisationMessageCode, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
this.parameterName = null;
}
public PlatformDataIntegrityException(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final Object... defaultUserMessageArgs) {
super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
this.parameterName = parameterName;
}
public String getParameterName() {
return this.parameterName;
}
}
@@ -0,0 +1,572 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.service;
import org.apache.commons.lang3.StringUtils;
public final class SearchParameters {
private final String sqlSearch;
private final Long officeId;
private final String externalId;
private final String name;
private final String hierarchy;
private final String firstname;
private final String lastname;
private final String status;
private final Integer offset;
private final Integer limit;
private final String orderBy;
private final String sortOrder;
private final String accountNo;
private final String currencyCode;
private final Long staffId;
private final Long loanId;
private final Long savingsId;
private final Boolean orphansOnly;
// Provisning Entries Search Params
private final Long provisioningEntryId;
private final Long productId;
private final Long categoryId;
private final boolean isSelfUser;
public static SearchParameters from(final String sqlSearch, final Long officeId, final String externalId, final String name,
final String hierarchy) {
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, officeId, externalId, name, hierarchy, null, null, null, null, null, null, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forClients(final String sqlSearch, final Long officeId, final String externalId,
final String displayName, final String firstname, final String lastname, final String status, final String hierarchy,
final Integer offset, final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly,
final boolean isSelfUser) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
return new SearchParameters(sqlSearch, officeId, externalId, displayName, hierarchy, firstname, lastname, status, offset,
maxLimitAllowed, orderBy, sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forGroups(final Long officeId, final Long staffId, final String externalId, final String name,
final String hierarchy, final Integer offset, final Integer limit, final String orderBy, final String sortOrder,
final Boolean orphansOnly) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final boolean isSelfUser = false;
return new SearchParameters(officeId, externalId, name, hierarchy, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forOffices(final String orderBy, final String sortOrder) {
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, null, null, orderBy, sortOrder, null, null, null, null,
orphansOnly, isSelfUser);
}
public static SearchParameters forLoans(final String sqlSearch, final String externalId, final Integer offset, final Integer limit,
final String orderBy, final String sortOrder, final String accountNo) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final Long loanId, final Long savingsId) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, officeId, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
null, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final Long loanId, final Long savingsId, final String currencyCode) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Boolean orphansOnly = false;
return new SearchParameters(null, officeId, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
null, loanId, savingsId, orphansOnly, currencyCode);
}
public static SearchParameters forPagination(final Integer offset, final Integer limit, final String orderBy, final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forPaginationAndAccountNumberSearch(final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final String accountNumber) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNumber, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forPagination(final Integer offset, final Integer limit) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final String orderBy = null;
final String sortOrder = null;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forProvisioningEntries(final Long provisioningEntryId, final Long officeId, final Long productId,
final Long categoryId, final Integer offset, final Integer limit) {
return new SearchParameters(provisioningEntryId, officeId, productId, categoryId, offset, limit);
}
public static SearchParameters forSavings(final String sqlSearch, final String externalId, final Integer offset, final Integer limit,
final String orderBy, final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forAccountTransfer(final String sqlSearch, final String externalId, final Integer offset,
final Integer limit, final String orderBy, final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forSMSCampaign(final String sqlSearch, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final String externalId = null;
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forEmailCampaign(final String sqlSearch, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final String externalId = null;
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
private SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name,
final String hierarchy, final String firstname, final String lastname, final Integer offset, final Integer limit,
final String orderBy, final String sortOrder, final Long staffId, final String accountNo, final Long loanId,
final Long savingsId, final Boolean orphansOnly, boolean isSelfUser) {
this.sqlSearch = sqlSearch;
this.officeId = officeId;
this.externalId = externalId;
this.name = name;
this.hierarchy = hierarchy;
this.firstname = firstname;
this.lastname = lastname;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
this.staffId = staffId;
this.accountNo = accountNo;
this.loanId = loanId;
this.savingsId = savingsId;
this.orphansOnly = orphansOnly;
this.currencyCode = null;
this.provisioningEntryId = null;
this.productId = null;
this.categoryId = null;
this.isSelfUser = isSelfUser;
this.status = null;
}
private SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name,
final String hierarchy, final String firstname, final String lastname, final String status, final Integer offset,
final Integer limit, final String orderBy, final String sortOrder, final Long staffId, final String accountNo,
final Long loanId, final Long savingsId, final Boolean orphansOnly, boolean isSelfUser) {
this.sqlSearch = sqlSearch;
this.officeId = officeId;
this.externalId = externalId;
this.name = name;
this.hierarchy = hierarchy;
this.firstname = firstname;
this.lastname = lastname;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
this.staffId = staffId;
this.accountNo = accountNo;
this.loanId = loanId;
this.savingsId = savingsId;
this.orphansOnly = orphansOnly;
this.currencyCode = null;
this.provisioningEntryId = null;
this.productId = null;
this.categoryId = null;
this.isSelfUser = isSelfUser;
this.status = status;
}
private SearchParameters(final Long officeId, final String externalId, final String name, final String hierarchy,
final String firstname, final String lastname, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final Long staffId, final String accountNo, final Long loanId, final Long savingsId,
final Boolean orphansOnly, boolean isSelfUser) {
this.sqlSearch = null;
this.officeId = officeId;
this.externalId = externalId;
this.name = name;
this.hierarchy = hierarchy;
this.firstname = firstname;
this.lastname = lastname;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
this.staffId = staffId;
this.accountNo = accountNo;
this.loanId = loanId;
this.savingsId = savingsId;
this.orphansOnly = orphansOnly;
this.currencyCode = null;
this.provisioningEntryId = null;
this.productId = null;
this.categoryId = null;
this.isSelfUser = isSelfUser;
this.status = null;
}
private SearchParameters(final Long provisioningEntryId, final Long officeId, final Long productId, final Long categoryId,
final Integer offset, final Integer limit) {
this.sqlSearch = null;
this.externalId = null;
this.name = null;
this.hierarchy = null;
this.firstname = null;
this.lastname = null;
this.orderBy = null;
this.sortOrder = null;
this.staffId = null;
this.accountNo = null;
this.loanId = null;
this.savingsId = null;
this.orphansOnly = null;
this.currencyCode = null;
this.officeId = officeId;
this.offset = offset;
this.limit = limit;
this.provisioningEntryId = provisioningEntryId;
this.productId = productId;
this.categoryId = categoryId;
this.isSelfUser = false;
this.status = null;
}
public SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name, final String hierarchy,
final String firstname, final String lastname, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final Long staffId, final String accountNo, final Long loanId, final Long savingsId,
final Boolean orphansOnly, final String currencyCode) {
this.sqlSearch = sqlSearch;
this.officeId = officeId;
this.externalId = externalId;
this.name = name;
this.hierarchy = hierarchy;
this.firstname = firstname;
this.lastname = lastname;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
this.staffId = staffId;
this.accountNo = accountNo;
this.loanId = loanId;
this.savingsId = savingsId;
this.orphansOnly = orphansOnly;
this.currencyCode = currencyCode;
this.provisioningEntryId = null;
this.productId = null;
this.categoryId = null;
this.isSelfUser = false;
this.status = null;
}
public boolean isOrderByRequested() {
return StringUtils.isNotBlank(this.orderBy);
}
public boolean isSortOrderProvided() {
return StringUtils.isNotBlank(this.sortOrder);
}
public static Integer getCheckedLimit(final Integer limit) {
final Integer maxLimitAllowed = 200;
// default to max limit first off
Integer checkedLimit = maxLimitAllowed;
if (limit != null && limit > 0) {
checkedLimit = limit;
} else if (limit != null) {
// unlimited case: limit provided and 0 or less
checkedLimit = null;
}
return checkedLimit;
}
public boolean isOfficeIdPassed() {
return this.officeId != null && this.officeId != 0;
}
public boolean isCurrencyCodePassed() {
return this.currencyCode != null;
}
public boolean isLimited() {
return this.limit != null && this.limit.intValue() > 0;
}
public boolean isOffset() {
return this.offset != null;
}
public boolean isScopedByOfficeHierarchy() {
return StringUtils.isNotBlank(this.hierarchy);
}
public String getSqlSearch() {
return this.sqlSearch;
}
public Long getOfficeId() {
return this.officeId;
}
public String getCurrencyCode() {
return this.currencyCode;
}
public String getExternalId() {
return this.externalId;
}
public String getName() {
return this.name;
}
public String getHierarchy() {
return this.hierarchy;
}
public String getFirstname() {
return this.firstname;
}
public String getLastname() {
return this.lastname;
}
public String getStatus() {
return this.status;
}
public Integer getOffset() {
return this.offset;
}
public Integer getLimit() {
return this.limit;
}
public String getOrderBy() {
return this.orderBy;
}
public String getSortOrder() {
return this.sortOrder;
}
public boolean isStaffIdPassed() {
return this.staffId != null && this.staffId != 0;
}
public Long getStaffId() {
return this.staffId;
}
public String getAccountNo() {
return this.accountNo;
}
public boolean isLoanIdPassed() {
return this.loanId != null && this.loanId != 0;
}
public boolean isSavingsIdPassed() {
return this.savingsId != null && this.savingsId != 0;
}
public Long getLoanId() {
return this.loanId;
}
public Long getSavingsId() {
return this.savingsId;
}
public Boolean isOrphansOnly() {
if (this.orphansOnly != null) {
return this.orphansOnly;
}
return false;
}
public Long getProvisioningEntryId() {
return this.provisioningEntryId;
}
public boolean isProvisioningEntryIdPassed() {
return this.provisioningEntryId != null && this.provisioningEntryId != 0;
}
public Long getProductId() {
return this.productId;
}
public boolean isProductIdPassed() {
return this.productId != null && this.productId != 0;
}
public Long getCategoryId() {
return this.categoryId;
}
public boolean isCategoryIdPassed() {
return this.categoryId != null && this.categoryId != 0;
}
public boolean isSelfUser() {
return this.isSelfUser;
}
/**
* creates an instance of the SearchParameters from a request for the report mailing job run history
*
* @return SearchParameters object
**/
public static SearchParameters fromReportMailingJobRunHistory(final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, null, null, null,
null, null, false);
}
/**
* creates an instance of the {@link SearchParameters} from a request for the report mailing job
*
* @return {@link SearchParameters} object
*/
public static SearchParameters fromReportMailingJob(final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, null, null, null,
null, null, false);
}
}
@@ -0,0 +1,68 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.dataqueries.data;
import java.io.Serializable;
import java.util.List;
/**
* Immutable data object representing datatable data.
*/
public final class DatatableData implements Serializable {
@SuppressWarnings("unused")
private final String applicationTableName;
@SuppressWarnings("unused")
private final String registeredTableName;
@SuppressWarnings("unused")
private final String entitySubType;
@SuppressWarnings("unused")
private final List<ResultsetColumnHeaderData> columnHeaderData;
public static DatatableData create(final String applicationTableName, final String registeredTableName, final String entitySubType,
final List<ResultsetColumnHeaderData> columnHeaderData) {
return new DatatableData(applicationTableName, registeredTableName, entitySubType, columnHeaderData);
}
private DatatableData(final String applicationTableName, final String registeredTableName, final String entitySubType,
final List<ResultsetColumnHeaderData> columnHeaderData) {
this.applicationTableName = applicationTableName;
this.registeredTableName = registeredTableName;
this.entitySubType = entitySubType;
this.columnHeaderData = columnHeaderData;
}
public boolean hasColumn(final String columnName) {
for (ResultsetColumnHeaderData c : this.columnHeaderData) {
if (c.getColumnName().equals(columnName)) {
return true;
}
}
return false;
}
public String getRegisteredTableName() {
return registeredTableName;
}
}
@@ -0,0 +1,352 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.dataqueries.data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
/**
* Immutable data object representing a resultset column.
*/
public final class ResultsetColumnHeaderData implements Serializable {
private final String columnName;
private String columnType;
private final Long columnLength;
private final String columnDisplayType;
private final boolean isColumnNullable;
private final boolean isColumnPrimaryKey;
private final boolean isColumnUnique;
private final boolean isColumnIndexed;
private final List<ResultsetColumnValueData> columnValues;
private final String columnCode;
public static ResultsetColumnHeaderData basic(final String columnName, final String columnType) {
final Long columnLength = null;
final boolean columnNullable = false;
final boolean columnIsPrimaryKey = false;
final List<ResultsetColumnValueData> columnValues = new ArrayList<>();
final String columnCode = null;
final boolean columnIsUnique = false;
final boolean columnIsIndexed = false;
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
columnCode, columnIsUnique, columnIsIndexed);
}
public static ResultsetColumnHeaderData detailed(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed) {
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
columnCode, columnIsUnique, columnIsIndexed);
}
private ResultsetColumnHeaderData(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed) {
this.columnName = columnName;
this.columnType = columnType;
this.columnLength = columnLength;
this.isColumnNullable = columnNullable;
this.isColumnPrimaryKey = columnIsPrimaryKey;
this.columnValues = columnValues;
this.columnCode = columnCode;
this.isColumnUnique = columnIsUnique;
this.isColumnIndexed = columnIsIndexed;
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
adjustColumnTypes();
String displayType = null;
if (this.columnCode == null) {
if (isString()) {
displayType = "STRING";
} else if (isAnyInteger()) {
if (isInteger()) {
this.columnType = this.columnType.toUpperCase();
}
displayType = "INTEGER";
} else if (isDate()) {
displayType = "DATE";
} else if (isDateTime()) {
displayType = "DATETIME";
} else if (isDecimal()) {
displayType = "DECIMAL";
} else if (isAnyText()) {
displayType = "TEXT";
} else if (isBit() || isBoolean()) {
displayType = "BOOLEAN";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
} else {
if (isInt() || isInteger()) {
displayType = "CODELOOKUP";
} else if (isVarchar()) {
displayType = "CODEVALUE";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
}
this.columnDisplayType = displayType;
}
private void adjustColumnTypes() {
switch (this.columnType) {
case "NEWDECIMAL":
this.columnType = "DECIMAL";
break;
case "CLOB":
case "ENUM":
case "SET":
this.columnType = "varchar";
break;
case "LONGLONG":
this.columnType = "bigint";
break;
case "SHORT":
this.columnType = "smallint";
break;
case "TINY":
this.columnType = "tinyint";
break;
case "INT24":
this.columnType = "int";
break;
default:
break;
}
}
public boolean isNamed(final String columnName) {
return this.columnName.equalsIgnoreCase(columnName);
}
private boolean isAnyText() {
return isText() || isTinyText() || isMediumText() || isLongText();
}
private boolean isText() {
return "text".equalsIgnoreCase(this.columnType);
}
private boolean isTinyText() {
return "tinytext".equalsIgnoreCase(this.columnType);
}
private boolean isMediumText() {
return "mediumtext".equalsIgnoreCase(this.columnType);
}
private boolean isLongText() {
return "longtext".equalsIgnoreCase(this.columnType);
}
private boolean isDecimal() {
return "decimal".equalsIgnoreCase(this.columnType) || "NEWDECIMAL".equalsIgnoreCase(this.columnType)
|| "numeric".equalsIgnoreCase(this.columnType);
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
}
private boolean isDate() {
return "date".equalsIgnoreCase(this.columnType);
}
private boolean isDateTime() {
return "datetime".equalsIgnoreCase(this.columnType) || "timestamp".equalsIgnoreCase(this.columnType)
|| "timestamp without time zone".equalsIgnoreCase(this.columnType) || "timestamptz".equalsIgnoreCase(this.columnType)
|| "timestamp with time zone".equalsIgnoreCase(this.columnType);
}
public boolean isString() {
return isVarchar() || isChar();
}
private boolean isChar() {
return "char".equalsIgnoreCase(this.columnType) || "CHARACTER VARYING".equalsIgnoreCase(this.columnType)
|| "bpchar".equalsIgnoreCase(this.columnType);
}
private boolean isVarchar() {
return "varchar".equalsIgnoreCase(this.columnType);
}
private boolean isAnyInteger() {
return isInt() || isInteger() || isSmallInt() || isTinyInt() || isMediumInt() || isBigInt() || isLong() || isSerial();
}
private boolean isSerial() {
return "SERIAL".equalsIgnoreCase(this.columnType) || "SERIAL4".equalsIgnoreCase(this.columnType)
|| "SERIAL8".equalsIgnoreCase(this.columnType) || "SMALLSERIAL".equalsIgnoreCase(this.columnType)
|| "BIGSERIAL".equalsIgnoreCase(this.columnType);
}
private boolean isInt() {
return "int".equalsIgnoreCase(this.columnType);
}
private boolean isInteger() {
return "integer".equalsIgnoreCase(this.columnType) || "int2".equalsIgnoreCase(this.columnType)
|| "int4".equalsIgnoreCase(this.columnType);
}
private boolean isSmallInt() {
return "smallint".equalsIgnoreCase(this.columnType);
}
private boolean isTinyInt() {
return "tinyint".equalsIgnoreCase(this.columnType);
}
private boolean isMediumInt() {
return "mediumint".equalsIgnoreCase(this.columnType);
}
private boolean isBigInt() {
return "bigint".equalsIgnoreCase(this.columnType) || "int8".equalsIgnoreCase(this.columnType);
}
private boolean isLong() {
return "LONG".equalsIgnoreCase(this.columnType) || "LONGLONG".equalsIgnoreCase(this.columnType);
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
}
private boolean isBit() {
return "bit".equalsIgnoreCase(this.columnType);
}
private boolean isBoolean() {
return "boolean".equalsIgnoreCase(this.columnType) || "bool".equalsIgnoreCase(this.columnType);
}
public String getColumnName() {
return this.columnName;
}
public String getColumnType() {
return this.columnType;
}
public Long getColumnLength() {
return this.columnLength;
}
public boolean getIsColumnNullable() {
return isColumnNullable;
}
public boolean getIsColumnPrimaryKey() {
return isColumnPrimaryKey;
}
public boolean getIsColumnUnique() {
return isColumnUnique;
}
public boolean getIsColumnIndexed() {
return isColumnIndexed;
}
public String getColumnDisplayType() {
return this.columnDisplayType;
}
public boolean isDateDisplayType() {
return "DATE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isDateTimeDisplayType() {
return "DATETIME".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isIntegerDisplayType() {
return "INTEGER".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isDecimalDisplayType() {
return "DECIMAL".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isBooleanDisplayType() {
return "BOOLEAN".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeValueDisplayType() {
return "CODEVALUE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeLookupDisplayType() {
return "CODELOOKUP".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isMandatory() {
return !isOptional();
}
public boolean isOptional() {
return this.isColumnNullable;
}
public boolean hasColumnValues() {
return !this.columnValues.isEmpty();
}
public boolean isColumnValueAllowed(final String match) {
boolean allowed = false;
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.matches(match)) {
allowed = true;
}
}
return allowed;
}
public boolean isColumnValueNotAllowed(final String match) {
return !isColumnValueAllowed(match);
}
public boolean isColumnCodeNotAllowed(final Integer match) {
return !isColumnCodeAllowed(match);
}
public boolean isColumnCodeAllowed(final Integer match) {
boolean allowed = false;
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.codeMatches(match)) {
allowed = true;
}
}
return allowed;
}
public String getColumnCode() {
return this.columnCode;
}
public List<ResultsetColumnValueData> getColumnValues() {
return this.columnValues;
}
}
@@ -0,0 +1,52 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.dataqueries.data;
import java.io.Serializable;
/**
* Immutable data object representing a possible value for a given resultset column.
*/
public class ResultsetColumnValueData implements Serializable {
private final int id;
private final String value;
@SuppressWarnings("unused")
private final Integer score;
public ResultsetColumnValueData(final int id, final String value) {
this.id = id;
this.value = value;
this.score = null;
}
public ResultsetColumnValueData(final int id, final String value, final int score) {
this.id = id;
this.value = value;
this.score = score;
}
public boolean matches(final String match) {
return match.equalsIgnoreCase(this.value);
}
public boolean codeMatches(final Integer match) {
return match.intValue() == this.id;
}
}
@@ -0,0 +1,61 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.documentmanagement.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_image")
public final class Image extends AbstractPersistableCustom {
@Column(name = "location", length = 500)
private String location;
@Column(name = "storage_type_enum")
private Integer storageType;
public Image(final String location, final StorageType storageType) {
this.location = location;
this.storageType = storageType.getValue();
}
Image() {
}
public String getLocation() {
return this.location;
}
public Integer getStorageType() {
return this.storageType;
}
public void setLocation(final String location) {
this.location = location;
}
public void setStorageType(final Integer storageType) {
this.storageType = storageType;
}
}
@@ -0,0 +1,50 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.documentmanagement.domain;
import java.util.HashMap;
import java.util.Map;
public enum StorageType {
FILE_SYSTEM(1), S3(2);
private final Integer value;
StorageType(final Integer value) {
this.value = value;
}
public Integer getValue() {
return this.value;
}
private static final Map<Integer, StorageType> intToEnumMap = new HashMap<>();
static {
for (final StorageType type : StorageType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static StorageType fromInt(final int i) {
final StorageType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.security.exception;
/**
* A {@link RuntimeException} that is thrown in the case where a user does not have sufficient authorization to execute
* operation on platform.
*/
public class NoAuthorizationException extends RuntimeException {
public NoAuthorizationException(final String message) {
super(message);
}
}
@@ -0,0 +1,40 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.security.service;
import java.security.SecureRandom;
public class RandomPasswordGenerator {
private final int numberOfCharactersInPassword;
private static final SecureRandom secureRandom = new SecureRandom();
public RandomPasswordGenerator(final int numberOfCharactersInPassword) {
this.numberOfCharactersInPassword = numberOfCharactersInPassword;
}
public String generate() {
final StringBuilder passwordBuilder = new StringBuilder(this.numberOfCharactersInPassword);
for (int i = 0; i < this.numberOfCharactersInPassword; i++) {
passwordBuilder.append((char) ((int) (secureRandom.nextDouble() * 26) + 97));
}
return passwordBuilder.toString();
}
}
@@ -0,0 +1,52 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.holiday.api;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public final class HolidayApiConstants {
private 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 idParamName = "id";
public static final String nameParamName = "name";
public static final String fromDateParamName = "fromDate";
public static final String toDateParamName = "toDate";
public static final String descriptionParamName = "description";
public static final String officesParamName = "offices";
public static final String officeIdParamName = "officeId";
public static final String repaymentsRescheduledToParamName = "repaymentsRescheduledTo";
public static final String processed = "processed";
public static final String status = "status";
public static final String reschedulingType = "reschedulingType";
static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, nameParamName, fromDateParamName,
descriptionParamName, toDateParamName, repaymentsRescheduledToParamName, localeParamName, dateFormatParamName, status));
}
@@ -0,0 +1,260 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.holiday.domain;
import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.descriptionParamName;
import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.fromDateParamName;
import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.nameParamName;
import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.officesParamName;
import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.repaymentsRescheduledToParamName;
import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.toDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dateFormatParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
import com.google.gson.JsonArray;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.UniqueConstraint;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.organisation.holiday.api.HolidayApiConstants;
import org.apache.fineract.organisation.office.domain.Office;
@Entity
@Table(name = "m_holiday", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "holiday_name") })
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
public class Holiday extends AbstractPersistableCustom {
@Column(name = "name", unique = true, nullable = false, length = 100)
private String name;
@Column(name = "from_date", nullable = false)
private LocalDate fromDate;
@Column(name = "to_date", nullable = false)
private LocalDate toDate;
@Column(name = "repayments_rescheduled_to", nullable = true)
private LocalDate repaymentsRescheduledTo;
@Column(name = "rescheduling_type", nullable = false)
private int reschedulingType;
@Column(name = "status_enum", nullable = false)
private Integer status;
@Column(name = "processed", nullable = false)
private boolean processed;
@Column(name = "description", length = 100)
private String description;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "m_holiday_office", joinColumns = @JoinColumn(name = "holiday_id"), inverseJoinColumns = @JoinColumn(name = "office_id"))
private Set<Office> offices;
public static Holiday createNew(final Set<Office> offices, final JsonCommand command) {
final String name = command.stringValueOfParameterNamed(HolidayApiConstants.nameParamName);
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
Integer reschedulingType = null;
if (command.parameterExists(HolidayApiConstants.reschedulingType)) {
reschedulingType = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
}
LocalDate repaymentsRescheduledTo = null;
if (reschedulingType == null || reschedulingType.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue())) {
repaymentsRescheduledTo = command.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
}
final Integer status = HolidayStatusType.PENDING_FOR_ACTIVATION.getValue();
final boolean processed = false;// default it to false. Only batch job
// should update this field.
final String description = command.stringValueOfParameterNamed(HolidayApiConstants.descriptionParamName);
return new Holiday().setName(StringUtils.trim(name)).setFromDate(fromDate).setToDate(toDate)
.setRepaymentsRescheduledTo(repaymentsRescheduledTo).setStatus(status).setProcessed(processed)
.setDescription(StringUtils.trim(description)).setOffices(offices).setReschedulingType(reschedulingType);
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("holiday" + ".update");
final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(nameParamName);
actualChanges.put(nameParamName, newValue);
this.name = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
actualChanges.put(descriptionParamName, newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInIntegerParameterNamed(HolidayApiConstants.reschedulingType, this.reschedulingType)) {
final Integer newValue = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
actualChanges.put(HolidayApiConstants.reschedulingType, newValue);
this.reschedulingType = RescheduleType.fromInt(newValue).getValue();
if (newValue.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue())) {
this.repaymentsRescheduledTo = null;
}
}
if (currentStatus.isPendingActivation()) {
if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(fromDateParamName);
actualChanges.put(fromDateParamName, valueAsInput);
actualChanges.put(dateFormatParamName, dateFormatAsInput);
actualChanges.put(localeParamName, localeAsInput);
this.fromDate = command.localDateValueOfParameterNamed(fromDateParamName);
}
if (command.isChangeInLocalDateParameterNamed(toDateParamName, getToDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(toDateParamName);
actualChanges.put(toDateParamName, valueAsInput);
actualChanges.put(dateFormatParamName, dateFormatAsInput);
actualChanges.put(localeParamName, localeAsInput);
this.toDate = command.localDateValueOfParameterNamed(toDateParamName);
}
if (command.isChangeInLocalDateParameterNamed(repaymentsRescheduledToParamName, getRepaymentsRescheduledTo())) {
final String valueAsInput = command.stringValueOfParameterNamed(repaymentsRescheduledToParamName);
actualChanges.put(repaymentsRescheduledToParamName, valueAsInput);
actualChanges.put(dateFormatParamName, dateFormatAsInput);
actualChanges.put(localeParamName, localeAsInput);
this.repaymentsRescheduledTo = command.localDateValueOfParameterNamed(repaymentsRescheduledToParamName);
}
if (command.hasParameter(officesParamName)) {
final JsonArray jsonArray = command.arrayOfParameterNamed(officesParamName);
if (jsonArray != null) {
actualChanges.put(officesParamName, command.jsonFragment(officesParamName));
}
}
} else {
if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDate())) {
baseDataValidator.reset().parameter(fromDateParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (command.isChangeInLocalDateParameterNamed(toDateParamName, getToDate())) {
baseDataValidator.reset().parameter(toDateParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (command.isChangeInLocalDateParameterNamed(repaymentsRescheduledToParamName, getRepaymentsRescheduledTo())) {
baseDataValidator.reset().parameter(repaymentsRescheduledToParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (command.hasParameter(officesParamName)) {
baseDataValidator.reset().parameter(repaymentsRescheduledToParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
return actualChanges;
}
public boolean update(final Set<Office> newOffices) {
if (newOffices == null) {
return false;
}
boolean updated = false;
if (this.offices != null) {
final Set<Office> currentSetOfOffices = new HashSet<>(this.offices);
final Set<Office> newSetOfOffices = new HashSet<>(newOffices);
if (!currentSetOfOffices.equals(newSetOfOffices)) {
updated = true;
this.offices = newOffices;
}
} else {
updated = true;
this.offices = newOffices;
}
return updated;
}
public void activate() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("holiday" + ".activate");
final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
if (!currentStatus.isPendingActivation()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.pending.for.activation.state");
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
this.status = HolidayStatusType.ACTIVE.getValue();
}
public void delete() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("holiday" + ".delete");
final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
if (currentStatus.isDeleted()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("already.in.deleted.state");
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
this.status = HolidayStatusType.DELETED.getValue();
}
public RescheduleType getReScheduleType() {
return RescheduleType.fromInt(this.reschedulingType);
}
}
@@ -0,0 +1,78 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.holiday.domain;
/**
* Enum representation of {@link Holiday} status states.
*/
public enum HolidayStatusType {
INVALID(0, "holidayStatusType.invalid"), //
PENDING_FOR_ACTIVATION(100, "holidayStatusType.pending.for.activation"), //
ACTIVE(300, "holidayStatusType.active"), //
DELETED(600, "savingsAccountStatusType.transfer.in.progress");
private final Integer value;
private final String code;
public static HolidayStatusType fromInt(final Integer type) {
HolidayStatusType enumeration = HolidayStatusType.INVALID;
switch (type) {
case 100:
enumeration = HolidayStatusType.PENDING_FOR_ACTIVATION;
break;
case 300:
enumeration = HolidayStatusType.ACTIVE;
break;
case 600:
enumeration = HolidayStatusType.DELETED;
break;
}
return enumeration;
}
HolidayStatusType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final HolidayStatusType state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isPendingActivation() {
return this.value.equals(HolidayStatusType.PENDING_FOR_ACTIVATION.getValue());
}
public boolean isActive() {
return this.value.equals(HolidayStatusType.ACTIVE.getValue());
}
public boolean isDeleted() {
return this.value.equals(HolidayStatusType.DELETED.getValue());
}
}
@@ -0,0 +1,63 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.holiday.domain;
public enum RescheduleType {
INVALID(0, "rescheduletype.invalid"), RESCHEDULETOSPECIFICDATE(2, "rescheduletype.rescheduletospecificdate"), //
RESCHEDULETONEXTREPAYMENTDATE(1, "rescheduletype.rescheduletonextrepaymentdate");
private final Integer value;
private final String code;
RescheduleType(Integer value, String code) {
this.value = value;
this.code = code;
}
public static RescheduleType fromInt(int rescheduleTypeValue) {
RescheduleType enumerration = RescheduleType.INVALID;
switch (rescheduleTypeValue) {
case 1:
enumerration = RescheduleType.RESCHEDULETONEXTREPAYMENTDATE;
break;
case 2:
enumerration = RescheduleType.RESCHEDULETOSPECIFICDATE;
break;
}
return enumerration;
}
public boolean isRescheduleToSpecificDate() {
return this.value.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue());
}
public boolean isResheduleToNextRepaymentDate() {
return this.value.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
}
@@ -0,0 +1,82 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.holiday.service;
import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
public final class HolidayUtil {
private HolidayUtil() {
}
public static LocalDate getRepaymentRescheduleDateToIfHoliday(LocalDate repaymentDate, final List<Holiday> holidays) {
for (final Holiday holiday : holidays) {
if (repaymentDate.equals(holiday.getFromDate()) || repaymentDate.equals(holiday.getToDate())
|| (repaymentDate.isAfter(holiday.getFromDate()) && repaymentDate.isBefore(holiday.getToDate()))) {
repaymentDate = getRepaymentRescheduleDateIfHoliday(repaymentDate, holidays);
}
}
return repaymentDate;
}
private static LocalDate getRepaymentRescheduleDateIfHoliday(final LocalDate repaymentDate, final List<Holiday> holidays) {
for (final Holiday holiday : holidays) {
if (repaymentDate.equals(holiday.getFromDate()) || repaymentDate.equals(holiday.getToDate())
|| (repaymentDate.isAfter(holiday.getFromDate()) && repaymentDate.isBefore(holiday.getToDate()))) {
// should be take from holiday
return holiday.getRepaymentsRescheduledTo();
}
}
return repaymentDate;
}
public static boolean isHoliday(final LocalDate date, final List<Holiday> holidays) {
for (final Holiday holiday : holidays) {
if (date.isEqual(holiday.getFromDate()) || date.isEqual(holiday.getToDate())
|| (date.isAfter(holiday.getFromDate()) && date.isBefore(holiday.getToDate()))) {
return true;
}
}
return false;
}
public static Holiday getApplicableHoliday(final LocalDate repaymentDate, final List<Holiday> holidays) {
Holiday referedHoliday = null;
for (final Holiday holiday : holidays) {
if (!repaymentDate.isBefore(holiday.getFromDate()) && !repaymentDate.isAfter(holiday.getToDate())) {
referedHoliday = holiday;
}
}
return referedHoliday;
}
public static void updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
final Holiday holiday) {
if (holiday.getReScheduleType().isRescheduleToSpecificDate()) {
adjustedDateDetailsDTO.setChangedScheduleDate(holiday.getRepaymentsRescheduledTo());
}
}
}
@@ -0,0 +1,90 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.monetary.data;
import java.io.Serializable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
/**
* Immutable data object representing currency.
*/
@Getter
@EqualsAndHashCode
public class CurrencyData implements Serializable {
private final String code;
private final String name;
private final int decimalPlaces;
private final Integer inMultiplesOf;
private final String displaySymbol;
private final String nameCode;
private final String displayLabel;
public static CurrencyData blank() {
return new CurrencyData("", "", 0, 0, "", "");
}
public CurrencyData(String code) {
this.code = code;
this.name = null;
this.decimalPlaces = 0;
this.inMultiplesOf = null;
this.displaySymbol = null;
this.nameCode = null;
this.displayLabel = null;
}
public CurrencyData(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
final String displaySymbol, final String nameCode) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
this.inMultiplesOf = inMultiplesOf;
this.displaySymbol = displaySymbol;
this.nameCode = nameCode;
this.displayLabel = generateDisplayLabel();
}
public CurrencyData(final String code, final int decimalPlaces, final Integer inMultiplesOf) {
this.code = code;
this.name = null;
this.decimalPlaces = decimalPlaces;
this.inMultiplesOf = inMultiplesOf;
this.displaySymbol = null;
this.nameCode = null;
this.displayLabel = null;
}
private String generateDisplayLabel() {
final StringBuilder builder = new StringBuilder(20);
if (this.name != null) {
builder.append(this.name).append(' ');
}
if (this.displaySymbol != null && !"".equalsIgnoreCase(this.displaySymbol.trim())) {
builder.append('(').append(this.displaySymbol).append(')');
} else {
builder.append('[').append(this.code).append(']');
}
return builder.toString();
}
}
@@ -0,0 +1,113 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.monetary.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.office.domain.OrganisationCurrency;
@Entity
@Table(name = "m_currency")
public class ApplicationCurrency extends AbstractPersistableCustom {
@Column(name = "code", nullable = false, length = 3)
private String code;
@Column(name = "decimal_places", nullable = false)
private Integer decimalPlaces;
@Column(name = "currency_multiplesof")
private Integer inMultiplesOf;
@Column(name = "name", nullable = false, length = 50)
private String name;
@Column(name = "internationalized_name_code", nullable = false, length = 50)
private String nameCode;
@Column(name = "display_symbol", nullable = true, length = 10)
private String displaySymbol;
protected ApplicationCurrency() {
this.code = null;
this.name = null;
this.decimalPlaces = null;
this.inMultiplesOf = null;
this.nameCode = null;
this.displaySymbol = null;
}
public static ApplicationCurrency from(final ApplicationCurrency currency, final int decimalPlaces, final Integer inMultiplesOf) {
return new ApplicationCurrency(currency.code, currency.name, decimalPlaces, inMultiplesOf, currency.nameCode,
currency.displaySymbol);
}
private ApplicationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
final String nameCode, final String displaySymbol) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
this.inMultiplesOf = inMultiplesOf;
this.nameCode = nameCode;
this.displaySymbol = displaySymbol;
}
public String getCode() {
return this.code;
}
public String getName() {
return this.name;
}
public Integer getDecimalPlaces() {
return this.decimalPlaces;
}
public Integer getCurrencyInMultiplesOf() {
return this.inMultiplesOf;
}
public String getNameCode() {
return this.nameCode;
}
public String getDisplaySymbol() {
return this.displaySymbol;
}
public CurrencyData toData() {
return new CurrencyData(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.displaySymbol, this.nameCode);
}
public CurrencyData toData(final int digitsAfterDecimalSupported, final Integer inMultiplesOf) {
return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, inMultiplesOf, this.displaySymbol, this.nameCode);
}
public OrganisationCurrency toOrganisationCurrency() {
return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.nameCode, this.displaySymbol);
}
public void setCode(final String code) {
this.code = code;
}
}
@@ -0,0 +1,74 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.monetary.domain;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@Embeddable
public class MonetaryCurrency {
@Column(name = "currency_code", length = 3, nullable = false)
private String code;
@Column(name = "currency_digits", nullable = false)
private int digitsAfterDecimal;
@Column(name = "currency_multiplesof")
private Integer inMultiplesOf;
protected MonetaryCurrency() {
this.code = null;
this.digitsAfterDecimal = 0;
this.inMultiplesOf = 0;
}
public MonetaryCurrency(final String code, final int digitsAfterDecimal, final Integer inMultiplesOf) {
this.code = code;
this.digitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf;
}
public MonetaryCurrency copy() {
return new MonetaryCurrency(this.code, this.digitsAfterDecimal, this.inMultiplesOf);
}
public static MonetaryCurrency fromApplicationCurrency(ApplicationCurrency applicationCurrency) {
return new MonetaryCurrency(applicationCurrency.getCode(), applicationCurrency.getDecimalPlaces(),
applicationCurrency.getCurrencyInMultiplesOf());
}
public static MonetaryCurrency fromCurrencyData(final CurrencyData currencyData) {
return new MonetaryCurrency(currencyData.getCode(), currencyData.getDecimalPlaces(), currencyData.getInMultiplesOf());
}
public String getCode() {
return this.code;
}
public int getDigitsAfterDecimal() {
return this.digitsAfterDecimal;
}
public Integer getCurrencyInMultiplesOf() {
return this.inMultiplesOf;
}
}
@@ -0,0 +1,388 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.monetary.domain;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Iterator;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@Embeddable
public class Money implements Comparable<Money> {
@Column(name = "currency_code", length = 3)
private String currencyCode;
@Column(name = "currency_digits")
private int currencyDigitsAfterDecimal;
@Column(name = "currency_multiplesof")
private Integer inMultiplesOf;
@Column(name = "amount", scale = 6, precision = 19)
private BigDecimal amount;
public static Money total(final Money... monies) {
if (monies.length == 0) {
throw new IllegalArgumentException("Money array must not be empty");
}
Money total = monies[0];
for (int i = 1; i < monies.length; i++) {
total = total.plus(monies[i]);
}
return total;
}
public static Money total(final Iterable<? extends Money> monies) {
final Iterator<? extends Money> it = monies.iterator();
if (it.hasNext() == false) {
throw new IllegalArgumentException("Money iterator must not be empty");
}
Money total = it.next();
while (it.hasNext()) {
total = total.plus(it.next());
}
return total;
}
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount),
currency.getCurrencyInMultiplesOf());
}
public static Money of(final CurrencyData currency, final BigDecimal newAmount) {
return new Money(currency.getCode(), currency.getDecimalPlaces(), defaultToZeroIfNull(newAmount), currency.getInMultiplesOf());
}
public static Money zero(final MonetaryCurrency currency) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf());
}
public static Money zero(final CurrencyData currency) {
return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf());
}
protected Money() {
this.currencyCode = null;
this.currencyDigitsAfterDecimal = 0;
this.inMultiplesOf = 0;
this.amount = null;
}
private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf) {
this.currencyCode = currencyCode;
this.currencyDigitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf;
final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
final BigDecimal amountStripped = amountZeroed.stripTrailingZeros();
BigDecimal amountScaled = amountStripped;
// round monetary amounts into multiplesof say 20/50.
if (inMultiplesOf != null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf > 0 && amountScaled.doubleValue() > 0) {
final double existingVal = amountScaled.doubleValue();
amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf));
}
this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, MoneyHelper.getRoundingMode());
}
public static double roundToMultiplesOf(final double existingVal, final Integer inMultiplesOf) {
double amountScaled = existingVal;
final double ceilingOfValue = ceiling(existingVal, inMultiplesOf);
final double floorOfValue = floor(existingVal, inMultiplesOf);
final double floorDiff = existingVal - floorOfValue;
final double ceilDiff = ceilingOfValue - existingVal;
if (ceilDiff > floorDiff) {
amountScaled = floorOfValue;
} else {
amountScaled = ceilingOfValue;
}
return amountScaled;
}
public static double ceiling(final double n, final double s) {
double c;
if ((n < 0 && s > 0) || (n > 0 && s < 0)) {
c = Double.NaN;
} else {
c = (n == 0 || s == 0) ? 0 : Math.ceil(n / s) * s;
}
return c;
}
public static double floor(final double n, final double s) {
double f;
if ((n < 0 && s > 0) || (n > 0 && s < 0) || (s == 0 && n != 0)) {
f = Double.NaN;
} else {
f = (n == 0 || s == 0) ? 0 : Math.floor(n / s) * s;
}
return f;
}
private static BigDecimal defaultToZeroIfNull(final BigDecimal value) {
BigDecimal result = BigDecimal.ZERO;
if (value != null) {
result = value;
}
return result;
}
public Money copy() {
return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf);
}
public Money plus(final Iterable<? extends Money> moniesToAdd) {
BigDecimal total = this.amount;
for (final Money moneyProvider : moniesToAdd) {
final Money money = checkCurrencyEqual(moneyProvider);
total = total.add(money.amount);
}
return Money.of(monetaryCurrency(), total);
}
public Money plus(final Money moneyToAdd) {
final Money toAdd = checkCurrencyEqual(moneyToAdd);
return this.plus(toAdd.getAmount());
}
public Money plus(final BigDecimal amountToAdd) {
if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.add(amountToAdd);
return Money.of(monetaryCurrency(), newAmount);
}
public Money plus(final double amountToAdd) {
if (amountToAdd == 0) {
return this;
}
final BigDecimal newAmount = this.amount.add(BigDecimal.valueOf(amountToAdd));
return Money.of(monetaryCurrency(), newAmount);
}
public Money minus(final Money moneyToSubtract) {
final Money toSubtract = checkCurrencyEqual(moneyToSubtract);
return this.minus(toSubtract.getAmount());
}
public Money add(final Money moneyToAdd) {
final Money toAdd = checkCurrencyEqual(moneyToAdd);
return this.add(toAdd.getAmount());
}
public Money add(final BigDecimal amountToAdd) {
if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.add(amountToAdd);
return Money.of(monetaryCurrency(), newAmount);
}
public Money minus(final BigDecimal amountToSubtract) {
if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.subtract(amountToSubtract);
return Money.of(monetaryCurrency(), newAmount);
}
private Money checkCurrencyEqual(final Money money) {
if (isSameCurrency(money) == false) {
throw new UnsupportedOperationException("currencies are different.");
}
return money;
}
public boolean isSameCurrency(final Money money) {
return this.currencyCode.equals(money.getCurrencyCode());
}
public Money dividedBy(final BigDecimal valueToDivideBy, final RoundingMode roundingMode) {
if (valueToDivideBy.compareTo(BigDecimal.ONE) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.divide(valueToDivideBy, roundingMode);
return Money.of(monetaryCurrency(), newAmount);
}
public Money dividedBy(final double valueToDivideBy, final RoundingMode roundingMode) {
if (valueToDivideBy == 1) {
return this;
}
final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), roundingMode);
return Money.of(monetaryCurrency(), newAmount);
}
public Money dividedBy(final long valueToDivideBy, final RoundingMode roundingMode) {
if (valueToDivideBy == 1) {
return this;
}
final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), roundingMode);
return Money.of(monetaryCurrency(), newAmount);
}
public Money multipliedBy(final BigDecimal valueToMultiplyBy) {
if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy);
return Money.of(monetaryCurrency(), newAmount);
}
public Money multipliedBy(final double valueToMultiplyBy) {
if (valueToMultiplyBy == 1) {
return this;
}
final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy));
return Money.of(monetaryCurrency(), newAmount);
}
public Money multipliedBy(final long valueToMultiplyBy) {
if (valueToMultiplyBy == 1) {
return this;
}
final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy));
return Money.of(monetaryCurrency(), newAmount);
}
public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final RoundingMode roundingMode) {
if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) {
return this;
}
BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy);
newAmount = newAmount.setScale(this.currencyDigitsAfterDecimal, roundingMode);
return Money.of(monetaryCurrency(), newAmount);
}
public Money multiplyRetainScale(final double valueToMultiplyBy, final RoundingMode roundingMode) {
return this.multiplyRetainScale(BigDecimal.valueOf(valueToMultiplyBy), roundingMode);
}
public Money percentageOf(BigDecimal percentage, final RoundingMode roundingMode) {
final BigDecimal newAmount = this.amount.multiply(percentage).divide(BigDecimal.valueOf(100), roundingMode);
return Money.of(monetaryCurrency(), newAmount);
}
@Override
public int compareTo(final Money other) {
final Money otherMoney = other;
if (this.currencyCode.equals(otherMoney.currencyCode) == false) {
throw new UnsupportedOperationException("currencies arent different");
}
return this.amount.compareTo(otherMoney.amount);
}
public boolean isZero() {
return isEqualTo(Money.zero(getCurrency()));
}
public boolean isEqualTo(final Money other) {
return compareTo(other) == 0;
}
public boolean isNotEqualTo(final Money other) {
return !isEqualTo(other);
}
public boolean isGreaterThanOrEqualTo(final Money other) {
return isGreaterThan(other) || isEqualTo(other);
}
public boolean isGreaterThan(final Money other) {
return compareTo(other) > 0;
}
public boolean isGreaterThanZero() {
return isGreaterThan(Money.zero(getCurrency()));
}
public boolean isLessThan(final Money other) {
return compareTo(other) < 0;
}
public boolean isLessThanZero() {
return isLessThan(Money.zero(getCurrency()));
}
public String getCurrencyCode() {
return this.currencyCode;
}
public int getCurrencyDigitsAfterDecimal() {
return this.currencyDigitsAfterDecimal;
}
public Integer getCurrencyInMultiplesOf() {
return this.inMultiplesOf;
}
public BigDecimal getAmount() {
return this.amount;
}
public BigDecimal getAmountDefaultedToNullIfZero() {
return defaultToNullIfZero(this.amount);
}
private static BigDecimal defaultToNullIfZero(final BigDecimal value) {
BigDecimal result = value;
if (value != null && BigDecimal.ZERO.compareTo(value) == 0) {
result = null;
}
return result;
}
@Override
public String toString() {
return new StringBuilder().append(this.currencyCode).append(' ').append(this.amount.toPlainString()).toString();
}
public Money negated() {
if (isZero()) {
return this;
}
return Money.of(monetaryCurrency(), this.amount.negate());
}
public Money abs() {
return isLessThanZero() ? negated() : this;
}
public MonetaryCurrency getCurrency() {
return monetaryCurrency();
}
private MonetaryCurrency monetaryCurrency() {
return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal, this.inMultiplesOf);
}
public Money zero() {
return Money.zero(getCurrency());
}
}
@@ -0,0 +1,62 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.monetary.domain;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.math.MathContext;
import java.math.RoundingMode;
import javax.annotation.PostConstruct;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MoneyHelper {
private static RoundingMode roundingMode = null;
private static MathContext mathContext;
private static final int PRECISION = 12;
private static ConfigurationDomainService staticConfigurationDomainService;
@Autowired
private ConfigurationDomainService configurationDomainService;
@PostConstruct
// This is a hack, but fixing this is not trivial, because some @Entity
// domain classes use this helper
@SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
public void someFunction() {
staticConfigurationDomainService = configurationDomainService;
}
public static RoundingMode getRoundingMode() {
if (roundingMode == null) {
roundingMode = RoundingMode.valueOf(staticConfigurationDomainService.getRoundingMode());
}
return roundingMode;
}
public static MathContext getMathContext() {
if (mathContext == null) {
mathContext = new MathContext(PRECISION, getRoundingMode());
}
return mathContext;
}
}
@@ -0,0 +1,91 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.office.data;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import lombok.Getter;
/**
* Immutable data object for office data.
*/
@Getter
public class OfficeData implements Serializable {
private final Long id;
private final String name;
private final String nameDecorated;
private final String externalId;
private final LocalDate openingDate;
private final String hierarchy;
private final Long parentId;
private final String parentName;
private final Collection<OfficeData> allowedParents;
// import fields
private transient Integer rowIndex;
private String locale;
private String dateFormat;
public static OfficeData importInstance(final String name, final Long parentId, final LocalDate openingDate, final String externalId) {
return new OfficeData(null, name, null, externalId, openingDate, null, parentId, null, null);
}
public void setImportFields(final Integer rowIndex, final String locale, final String dateFormat) {
this.rowIndex = rowIndex;
this.locale = locale;
this.dateFormat = dateFormat;
}
public static OfficeData testInstance(final Long id, final String name) {
return new OfficeData(id, name, null, null, null, null, null, null, null);
}
public static OfficeData dropdown(final Long id, final String name, final String nameDecorated) {
return new OfficeData(id, name, nameDecorated, null, null, null, null, null, null);
}
public static OfficeData template(final List<OfficeData> parentLookups, final LocalDate defaultOpeningDate) {
return new OfficeData(null, null, null, null, defaultOpeningDate, null, null, null, parentLookups);
}
public static OfficeData appendedTemplate(final OfficeData office, final Collection<OfficeData> allowedParents) {
return new OfficeData(office.id, office.name, office.nameDecorated, office.externalId, office.openingDate, office.hierarchy,
office.parentId, office.parentName, allowedParents);
}
public OfficeData(final Long id, final String name, final String nameDecorated, final String externalId, final LocalDate openingDate,
final String hierarchy, final Long parentId, final String parentName, final Collection<OfficeData> allowedParents) {
this.id = id;
this.name = name;
this.nameDecorated = nameDecorated;
this.externalId = externalId;
this.openingDate = openingDate;
this.hierarchy = hierarchy;
this.parentName = parentName;
this.parentId = parentId;
this.allowedParents = allowedParents;
}
public boolean hasIdentifyOf(final Long officeId) {
return this.id.equals(officeId);
}
}
@@ -0,0 +1,244 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.office.domain;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.office.exception.CannotUpdateOfficeWithParentOfficeSameAsSelf;
import org.apache.fineract.organisation.office.exception.RootOfficeParentCannotBeUpdated;
@Entity
@Table(name = "m_office", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "name_org"),
@UniqueConstraint(columnNames = { "external_id" }, name = "externalid_org") })
public class Office extends AbstractPersistableCustom implements Serializable {
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private List<Office> children = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Office parent;
@Column(name = "name", nullable = false, length = 100)
private String name;
@Column(name = "hierarchy", length = 50)
private String hierarchy;
@Column(name = "opening_date", nullable = false)
private LocalDate openingDate;
@Column(name = "external_id", length = 100)
private String externalId;
public static Office headOffice(final String name, final LocalDate openingDate, final String externalId) {
return new Office(null, name, openingDate, externalId);
}
public static Office fromJson(final Office parentOffice, final JsonCommand command) {
final String name = command.stringValueOfParameterNamed("name");
final LocalDate openingDate = command.localDateValueOfParameterNamed("openingDate");
final String externalId = command.stringValueOfParameterNamed("externalId");
return new Office(parentOffice, name, openingDate, externalId);
}
protected Office() {
this.openingDate = null;
this.parent = null;
this.name = null;
this.externalId = null;
}
private Office(final Office parent, final String name, final LocalDate openingDate, final String externalId) {
this.parent = parent;
this.openingDate = openingDate;
if (parent != null) {
this.parent.addChild(this);
}
if (StringUtils.isNotBlank(name)) {
this.name = name.trim();
} else {
this.name = null;
}
if (StringUtils.isNotBlank(externalId)) {
this.externalId = externalId.trim();
} else {
this.externalId = null;
}
}
private void addChild(final Office office) {
this.children.add(office);
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
final String parentIdParamName = "parentId";
if (command.parameterExists(parentIdParamName) && this.parent == null) {
throw new RootOfficeParentCannotBeUpdated();
}
if (this.parent != null && command.isChangeInLongParameterNamed(parentIdParamName, this.parent.getId())) {
final Long newValue = command.longValueOfParameterNamed(parentIdParamName);
actualChanges.put(parentIdParamName, newValue);
}
final String openingDateParamName = "openingDate";
if (command.isChangeInLocalDateParameterNamed(openingDateParamName, getOpeningLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(openingDateParamName);
actualChanges.put(openingDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput);
this.openingDate = command.localDateValueOfParameterNamed(openingDateParamName);
}
final String nameParamName = "name";
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(nameParamName);
actualChanges.put(nameParamName, newValue);
this.name = newValue;
}
final String externalIdParamName = "externalId";
if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
actualChanges.put(externalIdParamName, newValue);
this.externalId = StringUtils.defaultIfEmpty(newValue, null);
}
return actualChanges;
}
public boolean isOpeningDateBefore(final LocalDate baseDate) {
return getOpeningLocalDate().isBefore(baseDate);
}
public boolean isOpeningDateAfter(final LocalDate activationLocalDate) {
return getOpeningLocalDate().isAfter(activationLocalDate);
}
public LocalDate getOpeningLocalDate() {
return this.openingDate;
}
public void update(final Office newParent) {
if (this.parent == null) {
throw new RootOfficeParentCannotBeUpdated();
}
if (identifiedBy(newParent.getId())) {
throw new CannotUpdateOfficeWithParentOfficeSameAsSelf(getId(), newParent.getId());
}
this.parent = newParent;
generateHierarchy();
}
public boolean identifiedBy(final Long id) {
return getId().equals(id);
}
public void generateHierarchy() {
if (this.parent != null) {
this.hierarchy = this.parent.hierarchyOf(getId());
} else {
this.hierarchy = ".";
}
}
private String hierarchyOf(final Long id) {
return this.hierarchy + id.toString() + ".";
}
public String getName() {
return this.name;
}
public String getHierarchy() {
return this.hierarchy;
}
public Office getParent() {
return this.parent;
}
public boolean hasParentOf(final Office office) {
if (this.parent != null) {
return this.parent.equals(office);
}
return false;
}
public boolean doesNotHaveAnOfficeInHierarchyWithId(final Long officeId) {
return !hasAnOfficeInHierarchyWithId(officeId);
}
private boolean hasAnOfficeInHierarchyWithId(final Long officeId) {
boolean match = false;
if (identifiedBy(officeId)) {
match = true;
}
if (!match) {
for (final Office child : this.children) {
final boolean result = child.hasAnOfficeInHierarchyWithId(officeId);
if (result) {
match = true;
break;
}
}
}
return match;
}
public void loadLazyCollections() {
this.children.size();
}
}
@@ -0,0 +1,78 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.office.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
/**
* Represents currencies allowed for this MFI/organisation.
*/
@Entity
@Table(name = "m_organisation_currency")
public class OrganisationCurrency extends AbstractPersistableCustom {
@Column(name = "code", nullable = false, length = 3)
private String code;
@Column(name = "decimal_places", nullable = false)
private Integer decimalPlaces;
@Column(name = "currency_multiplesof")
private Integer inMultiplesOf;
@Column(name = "name", nullable = false, length = 50)
private String name;
@Column(name = "internationalized_name_code", nullable = false, length = 50)
private String nameCode;
@Column(name = "display_symbol", nullable = true, length = 10)
private String displaySymbol;
protected OrganisationCurrency() {
this.code = null;
this.name = null;
this.decimalPlaces = null;
this.inMultiplesOf = null;
this.nameCode = null;
this.displaySymbol = null;
}
public OrganisationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
final String nameCode, final String displaySymbol) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
this.inMultiplesOf = inMultiplesOf;
this.nameCode = nameCode;
this.displaySymbol = displaySymbol;
}
public final String getCode() {
return code;
}
public final MonetaryCurrency toMonetaryCurrency() {
return new MonetaryCurrency(this.code, this.decimalPlaces, this.inMultiplesOf);
}
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.office.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* Exception thrown when an attempt is made update the parent of a root office.
*/
public class CannotUpdateOfficeWithParentOfficeSameAsSelf extends AbstractPlatformDomainRuleException {
public CannotUpdateOfficeWithParentOfficeSameAsSelf(final Long officeId, final Long parentId) {
super("error.msg.office.parentId.same.as.id", "Cannot update office with parent same as self.", officeId, parentId);
}
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.office.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* Exception thrown when an attempt is made update the parent of a root office.
*/
public class RootOfficeParentCannotBeUpdated extends AbstractPlatformDomainRuleException {
public RootOfficeParentCannotBeUpdated() {
super("error.msg.office.cannot.update.parent.office.of.root.office", "The root office must not be set with a parent office.");
}
}
@@ -0,0 +1,142 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.staff.data;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.Collection;
import org.apache.fineract.organisation.office.data.OfficeData;
/**
* Immutable data object representing staff data.
*/
public final class StaffData implements Serializable {
private final Long id;
private final String externalId;
private final String firstname;
private final String lastname;
private final String displayName;
private final String mobileNo;
private final Long officeId;
private final String officeName;
private final Boolean isLoanOfficer;
private final Boolean isActive;
private final LocalDate joiningDate;
// import fields
private transient Integer rowIndex;
private String dateFormat;
private String locale;
public static StaffData importInstance(String externalId, String firstname, String lastname, String mobileNo, Long officeId,
Boolean isLoanOfficer, Boolean isActive, LocalDate joinedOnDate, Integer rowIndex, String locale, String dateFormat) {
return new StaffData(externalId, firstname, lastname, mobileNo, officeId, isLoanOfficer, isActive, joinedOnDate, rowIndex, locale,
dateFormat);
}
private StaffData(String externalId, String firstname, String lastname, String mobileNo, Long officeId, Boolean isLoanOfficer,
Boolean isActive, LocalDate joiningDate, Integer rowIndex, String locale, String dateFormat) {
this.externalId = externalId;
this.firstname = firstname;
this.lastname = lastname;
this.mobileNo = mobileNo;
this.officeId = officeId;
this.isLoanOfficer = isLoanOfficer;
this.isActive = isActive;
this.joiningDate = joiningDate;
this.rowIndex = rowIndex;
this.dateFormat = dateFormat;
this.locale = locale;
this.allowedOffices = null;
this.id = null;
this.officeName = null;
this.displayName = null;
}
public Integer getRowIndex() {
return rowIndex;
}
@SuppressWarnings("unused")
private final Collection<OfficeData> allowedOffices;
public static StaffData templateData(final StaffData staff, final Collection<OfficeData> allowedOffices) {
return new StaffData(staff.id, staff.firstname, staff.lastname, staff.displayName, staff.officeId, staff.officeName,
staff.isLoanOfficer, staff.externalId, staff.mobileNo, allowedOffices, staff.isActive, staff.joiningDate);
}
public static StaffData lookup(final Long id, final String displayName) {
return new StaffData(id, null, null, displayName, null, null, null, null, null, null, null, null);
}
public static StaffData instance(final Long id, final String firstname, final String lastname, final String displayName,
final Long officeId, final String officeName, final Boolean isLoanOfficer, final String externalId, final String mobileNo,
final boolean isActive, final LocalDate joiningDate) {
return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, mobileNo, null,
isActive, joiningDate);
}
private StaffData(final Long id, final String firstname, final String lastname, final String displayName, final Long officeId,
final String officeName, final Boolean isLoanOfficer, final String externalId, final String mobileNo,
final Collection<OfficeData> allowedOffices, final Boolean isActive, final LocalDate joiningDate) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.displayName = displayName;
this.officeName = officeName;
this.isLoanOfficer = isLoanOfficer;
this.externalId = externalId;
this.officeId = officeId;
this.mobileNo = mobileNo;
this.allowedOffices = allowedOffices;
this.isActive = isActive;
this.joiningDate = joiningDate;
}
public Long getId() {
return this.id;
}
public String getDisplayName() {
return this.displayName;
}
public String getFirstname() {
return this.firstname;
}
public String getLastname() {
return this.lastname;
}
public String getOfficeName() {
return this.officeName;
}
public LocalDate getJoiningDate() {
return this.joiningDate;
}
public Long getOfficeId() {
return this.officeId;
}
}
@@ -0,0 +1,269 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.staff.domain;
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
import org.apache.fineract.organisation.office.domain.Office;
@Getter
@Entity
@Table(name = "m_staff", uniqueConstraints = { @UniqueConstraint(columnNames = { "display_name" }, name = "display_name"),
@UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE"),
@UniqueConstraint(columnNames = { "mobile_no" }, name = "mobile_no_UNIQUE") })
public class Staff extends AbstractPersistableCustom {
@Column(name = "firstname", length = 50)
private String firstname;
@Column(name = "lastname", length = 50)
private String lastname;
@Column(name = "display_name", length = 100)
private String displayName;
@Column(name = "mobile_no", length = 50, nullable = false, unique = true)
private String mobileNo;
@Column(name = "external_id", length = 100, unique = true)
private String externalId;
@Column(name = "email_address", length = 50, unique = true)
private String emailAddress;
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@Column(name = "is_loan_officer", nullable = false)
private boolean loanOfficer;
@Column(name = "organisational_role_enum")
private Integer organisationalRoleType;
@Column(name = "is_active", nullable = false)
private boolean active;
@Column(name = "joining_date")
private LocalDate joiningDate;
@ManyToOne
@JoinColumn(name = "organisational_role_parent_staff_id")
private Staff organisationalRoleParentStaff;
@OneToOne(optional = true)
@JoinColumn(name = "image_id")
private Image image;
public static Staff fromJson(final Office staffOffice, final JsonCommand command) {
final String firstnameParamName = "firstname";
final String firstname = command.stringValueOfParameterNamed(firstnameParamName);
final String lastnameParamName = "lastname";
final String lastname = command.stringValueOfParameterNamed(lastnameParamName);
final String externalIdParamName = "externalId";
final String externalId = command.stringValueOfParameterNamedAllowingNull(externalIdParamName);
final String mobileNoParamName = "mobileNo";
final String mobileNo = command.stringValueOfParameterNamedAllowingNull(mobileNoParamName);
final String isLoanOfficerParamName = "isLoanOfficer";
final boolean isLoanOfficer = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
final String isActiveParamName = "isActive";
final Boolean isActive = command.booleanObjectValueOfParameterNamed(isActiveParamName);
LocalDate joiningDate = null;
final String joiningDateParamName = "joiningDate";
if (command.hasParameter(joiningDateParamName)) {
joiningDate = command.localDateValueOfParameterNamed(joiningDateParamName);
}
return new Staff(staffOffice, firstname, lastname, externalId, mobileNo, isLoanOfficer, isActive, joiningDate);
}
protected Staff() {
//
}
private Staff(final Office staffOffice, final String firstname, final String lastname, final String externalId, final String mobileNo,
final boolean isLoanOfficer, final Boolean isActive, final LocalDate joiningDate) {
this.office = staffOffice;
this.firstname = StringUtils.defaultIfEmpty(firstname, null);
this.lastname = StringUtils.defaultIfEmpty(lastname, null);
this.externalId = StringUtils.defaultIfEmpty(externalId, null);
this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
this.loanOfficer = isLoanOfficer;
this.active = isActive == null ? true : isActive;
deriveDisplayName(firstname);
this.joiningDate = joiningDate;
}
public EnumOptionData organisationalRoleData() {
EnumOptionData organisationalRole = null;
if (this.organisationalRoleType != null) {
organisationalRole = StaffEnumerations.organisationalRole(this.organisationalRoleType);
}
return organisationalRole;
}
public void changeOffice(final Office newOffice) {
this.office = newOffice;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
final String officeIdParamName = "officeId";
if (command.isChangeInLongParameterNamed(officeIdParamName, this.office.getId())) {
final Long newValue = command.longValueOfParameterNamed(officeIdParamName);
actualChanges.put(officeIdParamName, newValue);
}
boolean firstnameChanged = false;
final String firstnameParamName = "firstname";
if (command.isChangeInStringParameterNamed(firstnameParamName, this.firstname)) {
final String newValue = command.stringValueOfParameterNamed(firstnameParamName);
actualChanges.put(firstnameParamName, newValue);
this.firstname = newValue;
firstnameChanged = true;
}
boolean lastnameChanged = false;
final String lastnameParamName = "lastname";
if (command.isChangeInStringParameterNamed(lastnameParamName, this.lastname)) {
final String newValue = command.stringValueOfParameterNamed(lastnameParamName);
actualChanges.put(lastnameParamName, newValue);
this.lastname = newValue;
lastnameChanged = true;
}
if (firstnameChanged || lastnameChanged) {
deriveDisplayName(this.firstname);
}
final String externalIdParamName = "externalId";
if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
actualChanges.put(externalIdParamName, newValue);
this.externalId = newValue;
}
final String mobileNoParamName = "mobileNo";
if (command.isChangeInStringParameterNamed(mobileNoParamName, this.mobileNo)) {
final String newValue = command.stringValueOfParameterNamed(mobileNoParamName);
actualChanges.put(mobileNoParamName, newValue);
this.mobileNo = StringUtils.defaultIfEmpty(newValue, null);
}
final String isLoanOfficerParamName = "isLoanOfficer";
if (command.isChangeInBooleanParameterNamed(isLoanOfficerParamName, this.loanOfficer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
actualChanges.put(isLoanOfficerParamName, newValue);
this.loanOfficer = newValue;
}
final String isActiveParamName = "isActive";
if (command.isChangeInBooleanParameterNamed(isActiveParamName, this.active)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isActiveParamName);
actualChanges.put(isActiveParamName, newValue);
this.active = newValue;
}
final String joiningDateParamName = "joiningDate";
if (command.isChangeInDateParameterNamed(joiningDateParamName, this.joiningDate)) {
final String valueAsInput = command.stringValueOfParameterNamed(joiningDateParamName);
actualChanges.put(joiningDateParamName, valueAsInput);
this.joiningDate = command.localDateValueOfParameterNamed(joiningDateParamName);
}
return actualChanges;
}
public boolean isNotLoanOfficer() {
return !isLoanOfficer();
}
public boolean isLoanOfficer() {
return this.loanOfficer;
}
public boolean isNotActive() {
return !isActive();
}
public boolean isActive() {
return this.active;
}
private void deriveDisplayName(final String firstname) {
if (!StringUtils.isBlank(firstname)) {
this.displayName = this.lastname + ", " + this.firstname;
} else {
this.displayName = this.lastname;
}
}
public boolean identifiedBy(final Staff staff) {
return getId().equals(staff.getId());
}
public String emailAddress() {
return emailAddress;
}
public Long officeId() {
return this.office.getId();
}
public String displayName() {
return this.displayName;
}
public String mobileNo() {
return this.mobileNo;
}
public Office office() {
return this.office;
}
public void setImage(Image image) {
this.image = image;
}
}
@@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.staff.domain;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public final class StaffEnumerations {
private StaffEnumerations() {
}
public static EnumOptionData organisationalRole(final Integer id) {
return organisationalRole(StaffOrganisationalRoleType.fromInt(id));
}
public static EnumOptionData organisationalRole(final StaffOrganisationalRoleType type) {
EnumOptionData optionData = new EnumOptionData(StaffOrganisationalRoleType.INVALID.getValue().longValue(),
StaffOrganisationalRoleType.INVALID.getCode(), "Invalid");
switch (type) {
case PROGRAM_DIRECTOR:
optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Program Director");
break;
case BRANCH_MANAGER:
optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Branch Manager");
break;
case FIELD_OFFICER_COORDINATOR:
optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Field Officer Coordinator");
break;
case FIELD_OFFICER:
optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Field Officer");
break;
case INVALID:
break;
}
return optionData;
}
}
@@ -0,0 +1,63 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.staff.domain;
public enum StaffOrganisationalRoleType {
INVALID(0, "staffOrganisationalRoleType.invalid"), //
PROGRAM_DIRECTOR(100, "staffOrganisationalRoleType.programDirector"), //
BRANCH_MANAGER(200, "staffOrganisationalRoleType.branchManager"), //
FIELD_OFFICER_COORDINATOR(300, "staffOrganisationalRoleType.coordinator"), //
FIELD_OFFICER(400, "staffOrganisationalRoleType.fieldAgent");
private final Integer value;
private final String code;
StaffOrganisationalRoleType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static StaffOrganisationalRoleType fromInt(final Integer chargeCalculation) {
StaffOrganisationalRoleType chargeCalculationType = StaffOrganisationalRoleType.INVALID;
switch (chargeCalculation) {
case 100:
chargeCalculationType = PROGRAM_DIRECTOR;
break;
case 200:
chargeCalculationType = BRANCH_MANAGER;
break;
case 300:
chargeCalculationType = FIELD_OFFICER_COORDINATOR;
break;
case 400:
chargeCalculationType = FIELD_OFFICER;
break;
}
return chargeCalculationType;
}
}
@@ -0,0 +1,49 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.workingdays.api;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public final class WorkingDaysApiConstants {
private WorkingDaysApiConstants() {
}
public static final String WORKING_DAYS_RESOURCE_NAME = "workingdays";
// request parameters
public static final String recurrence = "recurrence";
public static final String repayment_rescheduling_enum = "repaymentRescheduleType";
public static final String idParamName = "id";
public static final String rescheduleRepaymentTemplate = "rescheduleRepaymentTemplate";
public static final String localeParamName = "locale";
public static final String extendTermForDailyRepayments = "extendTermForDailyRepayments";
public static final String extendTermForRepaymentsOnHolidays = "extendTermForRepaymentsOnHolidays";
static final Set<String> WORKING_DAYS_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, recurrence,
repayment_rescheduling_enum, extendTermForDailyRepayments, extendTermForRepaymentsOnHolidays));
static final Set<String> WORKING_DAYS_TEMPLATE_PARAMETERS = new HashSet<>(Arrays.asList(rescheduleRepaymentTemplate));
}
@@ -0,0 +1,55 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.workingdays.data;
import java.time.LocalDate;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class AdjustedDateDetailsDTO {
/**
* Variable tracks the current schedule date that has been changed
*/
LocalDate changedScheduleDate;
/**
* Variable tracks If the meeting has been changed , i.e future schedule also changes along with the current
* repayments date.
*/
LocalDate changedActualRepaymentDate;
/**
* Variable tracks the next repayment period due date
*/
LocalDate nextRepaymentPeriodDueDate;
public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate) {
this.changedScheduleDate = changedScheduleDate;
this.changedActualRepaymentDate = changedActualRepaymentDate;
}
public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate,
final LocalDate nextRepaymentPeriodDueDate) {
this.changedScheduleDate = changedScheduleDate;
this.changedActualRepaymentDate = changedActualRepaymentDate;
this.nextRepaymentPeriodDueDate = nextRepaymentPeriodDueDate;
}
}
@@ -0,0 +1,84 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.workingdays.domain;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
@Getter
public enum RepaymentRescheduleType {
INVALID(0, "RepaymentRescheduleType.invalid"), SAME_DAY(1, "RepaymentRescheduleType.same.day"), MOVE_TO_NEXT_WORKING_DAY(2,
"RepaymentRescheduleType.move.to.next.working.day"), MOVE_TO_NEXT_REPAYMENT_MEETING_DAY(3,
"RepaymentRescheduleType.move.to.next.repayment.meeting.day"), MOVE_TO_PREVIOUS_WORKING_DAY(4,
"RepaymentRescheduleType.move.to.previous.working.day"), MOVE_TO_NEXT_MEETING_DAY(5,
"RepaymentRescheduleType.move.to.next.meeting.day");
private final Integer value;
private final String code;
RepaymentRescheduleType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean isMoveToNextRepaymentDay() {
return this.value.equals(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue());
}
private static final Map<Integer, RepaymentRescheduleType> intToEnumMap = new HashMap<>();
private static int minValue;
private static int maxValue;
static {
int i = 0;
for (final RepaymentRescheduleType entityType : RepaymentRescheduleType.values()) {
if (i == 0) {
minValue = entityType.value;
}
intToEnumMap.put(entityType.value, entityType);
if (minValue >= entityType.value) {
minValue = entityType.value;
}
if (maxValue < entityType.value) {
maxValue = entityType.value;
}
i = i + 1;
}
}
public static RepaymentRescheduleType fromInt(final int i) {
final RepaymentRescheduleType repaymentRescheduleType = intToEnumMap.get(Integer.valueOf(i));
return repaymentRescheduleType;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
}
@@ -0,0 +1,97 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.workingdays.domain;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants;
@Getter
@Entity
@Table(name = "m_working_days")
public class WorkingDays extends AbstractPersistableCustom {
@Column(name = "recurrence", length = 100, nullable = true)
private String recurrence;
@Setter
@Column(name = "repayment_rescheduling_enum", nullable = false)
private Integer repaymentReschedulingType;
@Column(name = "extend_term_daily_repayments", nullable = false)
private Boolean extendTermForDailyRepayments;
@Column(name = "extend_term_holiday_repayment", nullable = false)
private Boolean extendTermForRepaymentsOnHolidays;
protected WorkingDays() {
}
protected WorkingDays(final String recurrence, final Integer repaymentReschedulingType, final Boolean extendTermForDailyRepayments,
final Boolean extendTermForRepaymentsOnHolidays) {
this.recurrence = recurrence;
this.repaymentReschedulingType = repaymentReschedulingType;
this.extendTermForDailyRepayments = extendTermForDailyRepayments;
this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
final String recurrenceParamName = "recurrence";
if (command.isChangeInStringParameterNamed(recurrenceParamName, this.recurrence)) {
final String newValue = command.stringValueOfParameterNamed(recurrenceParamName);
actualChanges.put(recurrenceParamName, newValue);
this.recurrence = newValue;
}
final String repaymentRescheduleTypeParamName = "repaymentRescheduleType";
if (command.isChangeInIntegerParameterNamed(repaymentRescheduleTypeParamName, this.repaymentReschedulingType)) {
final Integer newValue = command.integerValueOfParameterNamed(repaymentRescheduleTypeParamName);
actualChanges.put(repaymentRescheduleTypeParamName, WorkingDaysEnumerations.workingDaysStatusType(newValue));
this.repaymentReschedulingType = RepaymentRescheduleType.fromInt(newValue).getValue();
}
if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments,
this.extendTermForDailyRepayments)) {
final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments);
actualChanges.put(WorkingDaysApiConstants.extendTermForDailyRepayments, newValue);
this.extendTermForDailyRepayments = newValue;
}
if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays,
this.extendTermForRepaymentsOnHolidays)) {
final Boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays);
actualChanges.put(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays, newValue);
this.extendTermForRepaymentsOnHolidays = newValue;
}
return actualChanges;
}
}
@@ -0,0 +1,67 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.workingdays.domain;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public final class WorkingDaysEnumerations {
private WorkingDaysEnumerations() {
}
public static EnumOptionData workingDaysStatusType(final int id) {
return repaymentRescheduleType(RepaymentRescheduleType.fromInt(id));
}
public static EnumOptionData repaymentRescheduleType(final RepaymentRescheduleType type) {
EnumOptionData optionData = null;
switch (type) {
case INVALID:
optionData = new EnumOptionData(RepaymentRescheduleType.INVALID.getValue().longValue(),
RepaymentRescheduleType.INVALID.getCode(), "invalid");
break;
case SAME_DAY:
optionData = new EnumOptionData(RepaymentRescheduleType.SAME_DAY.getValue().longValue(),
RepaymentRescheduleType.SAME_DAY.getCode(), "same day");
break;
case MOVE_TO_NEXT_WORKING_DAY:
optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue().longValue(),
RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getCode(), "move to next working day");
break;
case MOVE_TO_NEXT_REPAYMENT_MEETING_DAY:
optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue().longValue(),
RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getCode(), "move to next repayment meeting day");
break;
case MOVE_TO_PREVIOUS_WORKING_DAY:
optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getValue().longValue(),
RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getCode(), "move to previous working day");
break;
case MOVE_TO_NEXT_MEETING_DAY:
optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_MEETING_DAY.getValue().longValue(),
RepaymentRescheduleType.MOVE_TO_NEXT_MEETING_DAY.getCode(), "move to next meeting day");
break;
}
return optionData;
}
}
@@ -0,0 +1,78 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.organisation.workingdays.service;
import java.time.LocalDate;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
public final class WorkingDaysUtil {
private WorkingDaysUtil() {
}
public static LocalDate getOffSetDateIfNonWorkingDay(final LocalDate date, final LocalDate nextMeetingDate,
final WorkingDays workingDays) {
// If date is not a non working day then return date.
if (isWorkingDay(workingDays, date)) {
return date;
}
final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(workingDays.getRepaymentReschedulingType());
switch (rescheduleType) {
case INVALID:
return date;
case SAME_DAY:
return date;
case MOVE_TO_NEXT_WORKING_DAY:
return getOffSetDateIfNonWorkingDay(date.plusDays(1), nextMeetingDate, workingDays);
case MOVE_TO_NEXT_REPAYMENT_MEETING_DAY:
return nextMeetingDate;
case MOVE_TO_PREVIOUS_WORKING_DAY:
return getOffSetDateIfNonWorkingDay(date.minusDays(1), nextMeetingDate, workingDays);
default:
return date;
}
}
public static boolean isWorkingDay(final WorkingDays workingDays, final LocalDate date) {
return CalendarUtils.isValidRedurringDate(workingDays.getRecurrence(), date, date);
}
public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
return !isWorkingDay(workingDays, date);
}
public static void updateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
final WorkingDays workingDays) {
final LocalDate changedScheduleDate = getOffSetDateIfNonWorkingDay(adjustedDateDetailsDTO.getChangedScheduleDate(),
adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), workingDays);
adjustedDateDetailsDTO.setChangedScheduleDate(changedScheduleDate);
}
public static RepaymentRescheduleType getRepaymentRescheduleType(final WorkingDays workingDays, final LocalDate date) {
RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(workingDays.getRepaymentReschedulingType());
return rescheduleType;
}
}
@@ -0,0 +1,80 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.account;
import java.util.ArrayList;
import java.util.List;
public enum PortfolioAccountType {
INVALID(0, "accountType.invalid"), //
LOAN(1, "accountType.loan"), //
SAVINGS(2, "accountType.savings");
private final Integer value;
private final String code;
PortfolioAccountType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static Object[] integerValues() {
final List<Integer> values = new ArrayList<>();
for (final PortfolioAccountType enumType : values()) {
if (enumType.getValue() > 0) {
values.add(enumType.getValue());
}
}
return values.toArray();
}
public static PortfolioAccountType fromInt(final Integer type) {
PortfolioAccountType enumType = PortfolioAccountType.INVALID;
if (type != null) {
switch (type) {
case 1:
enumType = LOAN;
break;
case 2:
enumType = SAVINGS;
break;
}
}
return enumType;
}
public boolean isSavingsAccount() {
return this.value.equals(2);
}
public boolean isLoanAccount() {
return this.value.equals(1);
}
}
@@ -0,0 +1,138 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.account.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.office.data.OfficeData;
import org.apache.fineract.portfolio.client.data.ClientData;
/**
* Immutable data object representing a savings account.
*/
@Getter
public final class AccountTransferData implements Serializable {
private final Long id;
private final Boolean reversed;
private final CurrencyData currency;
private final BigDecimal transferAmount;
private final LocalDate transferDate;
private final String transferDescription;
private final OfficeData fromOffice;
private final ClientData fromClient;
private final EnumOptionData fromAccountType;
private final PortfolioAccountData fromAccount;
private final OfficeData toOffice;
private final ClientData toClient;
private final EnumOptionData toAccountType;
private final PortfolioAccountData toAccount;
// template
private final Collection<OfficeData> fromOfficeOptions;
private final Collection<ClientData> fromClientOptions;
private final Collection<EnumOptionData> fromAccountTypeOptions;
private final Collection<PortfolioAccountData> fromAccountOptions;
private final Collection<OfficeData> toOfficeOptions;
private final Collection<ClientData> toClientOptions;
private final Collection<EnumOptionData> toAccountTypeOptions;
private final Collection<PortfolioAccountData> toAccountOptions;
public static AccountTransferData template(final OfficeData fromOffice, final ClientData fromClient,
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final LocalDate transferDate,
final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions) {
final Long id = null;
CurrencyData currency = null;
BigDecimal transferAmount = BigDecimal.ZERO;
if (fromAccount != null) {
currency = fromAccount.getCurrency();
if (fromAccount.getAmtForTransfer() != null) {
transferAmount = fromAccount.getAmtForTransfer();
}
}
final String transferDescription = null;
final Boolean reversed = null;
return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, fromOfficeOptions, fromClientOptions,
fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
}
public static AccountTransferData instance(final Long id, final Boolean reversed, final LocalDate transferDate,
final CurrencyData currency, final BigDecimal transferAmount, final String transferDescription, final OfficeData fromOffice,
final OfficeData toOffice, final ClientData fromClient, final ClientData toClient, final EnumOptionData fromAccountType,
final PortfolioAccountData fromAccount, final EnumOptionData toAccountType, final PortfolioAccountData toAccount) {
return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, null, null, null, null, null, null, null,
null);
}
public static AccountTransferData transferBasicDetails(final Long id, final CurrencyData currency, final BigDecimal transferAmount,
final LocalDate transferDate, final String description, final Boolean reversed) {
final EnumOptionData fromAccountType = null;
final EnumOptionData toAccountType = null;
return new AccountTransferData(id, reversed, null, null, fromAccountType, null, currency, transferAmount, transferDate, description,
null, null, toAccountType, null, null, null, null, null, null, null, null, null);
}
private AccountTransferData(final Long id, final Boolean reversed, final OfficeData fromOffice, final ClientData fromClient,
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final CurrencyData currency,
final BigDecimal transferAmount, final LocalDate transferDate, final String transferDescription, final OfficeData toOffice,
final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions) {
this.id = id;
this.reversed = reversed;
this.fromOffice = fromOffice;
this.fromClient = fromClient;
this.fromAccountType = fromAccountType;
this.fromAccount = fromAccount;
this.toOffice = toOffice;
this.toClient = toClient;
this.toAccountType = toAccountType;
this.toAccount = toAccount;
this.currency = currency;
this.transferAmount = transferAmount;
this.transferDate = transferDate;
this.transferDescription = transferDescription;
this.fromOfficeOptions = fromOfficeOptions;
this.fromClientOptions = fromClientOptions;
this.fromAccountTypeOptions = fromAccountTypeOptions;
this.fromAccountOptions = fromAccountOptions;
this.toOfficeOptions = toOfficeOptions;
this.toClientOptions = toClientOptions;
this.toAccountTypeOptions = toAccountTypeOptions;
this.toAccountOptions = toAccountOptions;
}
}
@@ -0,0 +1,75 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.account.data;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
/**
* Immutable data object representing a savings account.
*/
@Getter
@EqualsAndHashCode
@RequiredArgsConstructor
public class PortfolioAccountData implements Serializable {
private final Long id;
private final String accountNo;
private final String externalId;
private final Long groupId;
private final String groupName;
private final Long clientId;
private final String clientName;
private final Long productId;
private final String productName;
private final Long fieldOfficerId;
private final String fieldOfficerName;
private final CurrencyData currency;
private final BigDecimal amtForTransfer;
public static PortfolioAccountData lookup(final Long accountId, final String accountNo) {
return new PortfolioAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null);
}
public PortfolioAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName,
final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldOfficerId,
final String fieldOfficerName, final CurrencyData currency) {
this.id = id;
this.accountNo = accountNo;
this.externalId = externalId;
this.groupId = groupId;
this.groupName = groupName;
this.clientId = clientId;
this.clientName = clientName;
this.productId = productId;
this.productName = productName;
this.fieldOfficerId = fieldOfficerId;
this.fieldOfficerName = fieldOfficerName;
this.currency = currency;
this.amtForTransfer = null;
}
public String getCurrencyCode() {
return this.currency.getCode();
}
}
@@ -0,0 +1,50 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.account.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.portfolio.account.PortfolioAccountType;
import org.apache.fineract.portfolio.account.data.AccountTransferData;
public interface AccountTransfersReadPlatformService {
AccountTransferData retrieveTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType, Long toOfficeId,
Long toClientId, Long toAccountId, Integer toAccountType);
Page<AccountTransferData> retrieveAll(SearchParameters searchParameters, Long accountDetailId);
AccountTransferData retrieveOne(Long transferId);
boolean isAccountTransfer(Long transactionId, PortfolioAccountType accountType);
Page<AccountTransferData> retrieveByStandingInstruction(Long id, SearchParameters searchParameters);
Collection<Long> fetchPostInterestTransactionIds(Long accountId);
Collection<Long> fetchPostInterestTransactionIdsWithPivotDate(Long accountId, LocalDate pivotDate);
AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
BigDecimal getTotalTransactionAmount(Long accountId, Integer accountType, LocalDate transactionDate);
}
@@ -0,0 +1,80 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.accountdetails.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData;
/**
* Immutable data object for savings accounts.
*/
@SuppressWarnings("unused")
public class SavingsAccountSummaryData {
private final Long id;
private final String accountNo;
private final String externalId;
private final Long productId;
private final String productName;
private final String shortProductName;
private final SavingsAccountStatusEnumData status;
private final CurrencyData currency;
private final BigDecimal accountBalance;
// differentiate Individual, JLG or Group account
private final EnumOptionData accountType;
private final SavingsAccountApplicationTimelineData timeline;
private final SavingsAccountSubStatusEnumData subStatus;
private final LocalDate lastActiveTransactionDate;
// differentiate deposit accounts Savings, FD and RD accounts
private final EnumOptionData depositType;
public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
final String productName, final String shortProductName, final SavingsAccountStatusEnumData status, final CurrencyData currency,
final BigDecimal accountBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline,
final EnumOptionData depositType, final SavingsAccountSubStatusEnumData subStatus, final LocalDate lastActiveTransactionDate) {
this.id = id;
this.accountNo = accountNo;
this.externalId = externalId;
this.productId = productId;
this.productName = productName;
this.shortProductName = shortProductName;
this.status = status;
this.currency = currency;
this.accountBalance = accountBalance;
this.accountType = accountType;
this.timeline = timeline;
this.depositType = depositType;
this.subStatus = subStatus;
this.lastActiveTransactionDate = lastActiveTransactionDate;
}
public String getAccountNo() {
return accountNo;
}
public Long getId() {
return id;
}
}
@@ -0,0 +1,109 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.accountdetails.domain;
/**
* Enum representation of account types .
*/
public enum AccountType {
INVALID(0, "accountType.invalid"), //
INDIVIDUAL(1, "accountType.individual"), //
GROUP(2, "accountType.group"), //
JLG(3, "accountType.jlg"), // JLG account given in group context
GLIM(4, "accountType.glim"), GSIM(5, "accountType.gsim");
private final Integer value;
private final String code;
AccountType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public static AccountType fromInt(final Integer accountTypeValue) {
AccountType enumeration = AccountType.INVALID;
switch (accountTypeValue) {
case 1:
enumeration = AccountType.INDIVIDUAL;
break;
case 2:
enumeration = AccountType.GROUP;
break;
case 3:
enumeration = AccountType.JLG;
break;
case 4:
enumeration = AccountType.GLIM;
break;
case 5:
enumeration = AccountType.GSIM;
break;
}
return enumeration;
}
public static AccountType fromName(final String name) {
AccountType accountType = AccountType.INVALID;
for (final AccountType type : AccountType.values()) {
if (type.getName().equals(name)) {
accountType = type;
break;
}
}
return accountType;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public String getName() {
return name().toLowerCase();
}
public boolean isInvalid() {
return this.value.equals(AccountType.INVALID.getValue());
}
public boolean isIndividualAccount() {
return this.value.equals(AccountType.INDIVIDUAL.getValue());
}
public boolean isGroupAccount() {
return this.value.equals(AccountType.GROUP.getValue());
}
public boolean isJLGAccount() {
return this.value.equals(AccountType.JLG.getValue());
}
public boolean isGLIMAccount() {
return this.value.equals(AccountType.GLIM.getValue());
}
public boolean isGSIMAccount() {
return this.value.equals(AccountType.GSIM.getValue());
}
}
@@ -0,0 +1,66 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.accountdetails.service;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
public final class AccountEnumerations {
private AccountEnumerations() {
}
public static EnumOptionData loanType(final Integer loanTypeId) {
return loanType(AccountType.fromInt(loanTypeId));
}
public static EnumOptionData loanType(final String name) {
return loanType(AccountType.fromName(name));
}
public static EnumOptionData loanType(final AccountType type) {
EnumOptionData optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(),
"Invalid");
switch (type) {
case INVALID:
optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(), "Invalid");
break;
case INDIVIDUAL:
optionData = new EnumOptionData(AccountType.INDIVIDUAL.getValue().longValue(), AccountType.INDIVIDUAL.getCode(),
"Individual");
break;
case GROUP:
optionData = new EnumOptionData(AccountType.GROUP.getValue().longValue(), AccountType.GROUP.getCode(), "Group");
break;
case JLG:
optionData = new EnumOptionData(AccountType.JLG.getValue().longValue(), AccountType.JLG.getCode(), "JLG");
break;
case GLIM:
optionData = new EnumOptionData(AccountType.GLIM.getValue().longValue(), AccountType.GLIM.getCode(), "GLIM");
break;
case GSIM:
optionData = new EnumOptionData(AccountType.GSIM.getValue().longValue(), AccountType.GSIM.getCode(), "GSIM");
break;
}
return optionData;
}
}
@@ -0,0 +1,216 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.address.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
@SuppressWarnings("unused")
public class AddressData implements Serializable {
private final Long clientID;
private final String addressType;
private final Long addressId;
private final Long addressTypeId;
private final Boolean isActive;
private final String street;
private final String addressLine1;
private final String addressLine2;
private final String addressLine3;
private final String townVillage;
private final String city;
private final String countyDistrict;
private final Long stateProvinceId;
private final String countryName;
private final String stateName;
private final Long countryId;
private final String postalCode;
private final BigDecimal latitude;
private final BigDecimal longitude;
private final String createdBy;
private final LocalDate createdOn;
private final String updatedBy;
private final LocalDate updatedOn;
// template holder
private final Collection<CodeValueData> countryIdOptions;
private final Collection<CodeValueData> stateProvinceIdOptions;
private final Collection<CodeValueData> addressTypeIdOptions;
public AddressData(Long addressTypeId, String street, String addressLine1, String addressLine2, String addressLine3, String city,
String postalCode, Boolean isActive, Long stateProvinceId, Long countryId) {
this.addressTypeId = addressTypeId;
this.isActive = isActive;
this.street = street;
this.addressLine1 = addressLine1;
this.addressLine2 = addressLine2;
this.addressLine3 = addressLine3;
this.countryId = countryId;
this.postalCode = postalCode;
this.stateProvinceId = stateProvinceId;
this.city = city;
this.townVillage = null;
this.clientID = null;
this.addressType = null;
this.addressId = null;
this.countyDistrict = null;
this.countryName = null;
this.stateName = null;
this.latitude = null;
this.longitude = null;
this.createdBy = null;
this.createdOn = null;
this.updatedBy = null;
this.updatedOn = null;
this.countryIdOptions = null;
this.stateProvinceIdOptions = null;
this.addressTypeIdOptions = null;
}
private AddressData(final String addressType, final Long clientID, final Long addressId, final Long addressTypeId,
final Boolean is_active, final String street, final String addressLine1, final String addressLine2, final String addressLine3,
final String townVillage, final String city, final String countyDistrict, final Long stateProvinceId, final Long countryId,
final String stateName, final String countryName, final String postalCode, final BigDecimal latitude,
final BigDecimal longitude, final String createdBy, final LocalDate createdOn, final String updatedBy,
final LocalDate updatedOn, final Collection<CodeValueData> countryIdOptions,
final Collection<CodeValueData> stateProvinceIdOptions, final Collection<CodeValueData> addressTypeIdOptions) {
this.addressType = addressType;
this.clientID = clientID;
this.addressId = addressId;
this.addressTypeId = addressTypeId;
this.isActive = is_active;
this.street = street;
this.addressLine1 = addressLine1;
this.addressLine2 = addressLine2;
this.addressLine3 = addressLine3;
this.townVillage = townVillage;
this.city = city;
this.countyDistrict = countyDistrict;
this.stateProvinceId = stateProvinceId;
this.countryId = countryId;
this.stateName = stateName;
this.countryName = countryName;
this.postalCode = postalCode;
this.latitude = latitude;
this.longitude = longitude;
this.createdBy = createdBy;
this.createdOn = createdOn;
this.updatedBy = updatedBy;
this.updatedOn = updatedOn;
this.countryIdOptions = countryIdOptions;
this.stateProvinceIdOptions = stateProvinceIdOptions;
this.addressTypeIdOptions = addressTypeIdOptions;
}
public static AddressData instance(final String addressType, final Long clientID, final Long addressId, final Long addressTypeId,
final Boolean is_active, final String street, final String addressLine1, final String addressLine2, final String addressLine3,
final String townVillage, final String city, final String countyDistrict, final Long stateProvinceId, final Long countryId,
final String stateName, final String countryName, final String postalCode, final BigDecimal latitude,
final BigDecimal longitude, final String createdBy, final LocalDate createdOn, final String updatedBy,
final LocalDate updatedOn) {
return new AddressData(addressType, clientID, addressId, addressTypeId, is_active, street, addressLine1, addressLine2, addressLine3,
townVillage, city, countyDistrict, stateProvinceId, countryId, stateName, countryName, postalCode, latitude, longitude,
createdBy, createdOn, updatedBy, updatedOn, null, null, null);
}
public static AddressData instance1(final Long addressId, final String street, final String addressLine1, final String addressLine2,
final String addressLine3, final String townVillage, final String city, final String countyDistrict, final Long stateProvinceId,
final Long countryId, final String postalCode, final BigDecimal latitude, final BigDecimal longitude, final String createdBy,
final LocalDate createdOn, final String updatedBy, final LocalDate updatedOn) {
return new AddressData(null, null, addressId, null, false, street, addressLine1, addressLine2, addressLine3, townVillage, city,
countyDistrict, stateProvinceId, countryId, null, null, postalCode, latitude, longitude, createdBy, createdOn, updatedBy,
updatedOn, null, null, null);
}
public static AddressData template(final Collection<CodeValueData> countryIdOptions,
final Collection<CodeValueData> stateProvinceIdOptions, final Collection<CodeValueData> addressTypeIdOptions) {
final Long client_idtemp = null;
final Long addressIdtemp = null;
final Long addressTypeIdtemp = null;
final Boolean is_activetemp = null;
final String streettemp = null;
final String addressLine1temp = null;
final String addressLine2temp = null;
final String addressLine3temp = null;
final String townVillagetemp = null;
final String citytemp = null;
final String countyDistricttemp = null;
final Long stateProvinceIdtemp = null;
final Long countryIdtemp = null;
final String postalCodetemp = null;
final BigDecimal latitudetemp = null;
final BigDecimal longitudetemp = null;
final String createdBytemp = null;
final LocalDate createdOntemp = null;
final String updatedBytemp = null;
final LocalDate updatedOntemp = null;
return new AddressData(null, client_idtemp, addressIdtemp, addressTypeIdtemp, is_activetemp, streettemp, addressLine1temp,
addressLine2temp, addressLine3temp, townVillagetemp, citytemp, countyDistricttemp, stateProvinceIdtemp, countryIdtemp, null,
null, postalCodetemp, latitudetemp, longitudetemp, createdBytemp, createdOntemp, updatedBytemp, updatedOntemp,
countryIdOptions, stateProvinceIdOptions, addressTypeIdOptions);
}
}
@@ -0,0 +1,74 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar;
import java.util.HashSet;
import java.util.Set;
public final class CalendarConstants {
private CalendarConstants() {
}
public static final String CALENDAR_RESOURCE_NAME = "calendar";
public enum CalendarSupportedParameters {
CALENDAR_ID("id"), ENTITY_TYPE("entityType"), ENTITY_ID("entityId"), TITLE("title"), DESCRIPTION("description"), LOCATION(
"location"), START_DATE("startDate"), END_DATE("endDate"), CREATED_DATE("createdDate"), DURATION("duration"), TYPE_ID(
"typeId"), REPEATING("repeating"), REMIND_BY_ID("remindById"), FIRST_REMINDER("firstReminder"), SECOND_REMINDER(
"secondReminder"), LOCALE("locale"), DATE_FORMAT("dateFormat"), FREQUENCY("frequency"), INTERVAL(
"interval"), REPEATS_ON_DAY("repeatsOnDay"), RESCHEDULE_BASED_ON_MEETING_DATES(
"reschedulebasedOnMeetingDates"), PRESENT_MEETING_DATE(
"presentMeetingDate"), NEW_MEETING_DATE("newMeetingDate"), MEETING_TIME(
"meetingtime"), Time_Format("timeFormat"), REPEATS_ON_NTH_DAY_OF_MONTH(
"repeatsOnNthDayOfMonth"), REPEATS_ON_LAST_WEEKDAY_OF_MONTH(
"repeatsOnLastWeekdayOfMonth"), REPEATS_ON_DAY_OF_MONTH(
"repeatsOnDayOfMonth");
private final String value;
CalendarSupportedParameters(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final CalendarSupportedParameters param : CalendarSupportedParameters.values()) {
values.add(param.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
}
@@ -0,0 +1,413 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy;
import org.apache.fineract.portfolio.calendar.domain.CalendarType;
import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.NthDayType;
@Data
@NoArgsConstructor
@Accessors(chain = true)
public final class CalendarData implements Serializable {
private Long id;
private Long calendarInstanceId;
private Long entityId;
private EnumOptionData entityType;
private String title;
private String description;
private String location;
private LocalDate startDate;
private LocalDate endDate;
private LocalTime meetingTime;
private Integer duration;
private EnumOptionData type;
private boolean repeating;
private String recurrence;
private EnumOptionData frequency;
private Integer interval;
private EnumOptionData repeatsOnDay;
private EnumOptionData repeatsOnNthDayOfMonth;
private EnumOptionData remindBy;
private Integer firstReminder;
private Integer secondReminder;
private Collection<LocalDate> recurringDates;
private Collection<LocalDate> nextTenRecurringDates;
private String humanReadable;
private LocalDate recentEligibleMeetingDate;
private OffsetDateTime createdDate;
private OffsetDateTime lastUpdatedDate;
private Long createdByUserId;
private String createdByUsername;
private Long lastUpdatedByUserId;
private String lastUpdatedByUsername;
private Integer repeatsOnDayOfMonth;
// template related
private List<EnumOptionData> entityTypeOptions;
private List<EnumOptionData> calendarTypeOptions;
private List<EnumOptionData> remindByOptions;
private List<EnumOptionData> frequencyOptions;
private List<EnumOptionData> repeatsOnDayOptions;
private List<EnumOptionData> frequencyNthDayTypeOptions;
// import fields
private Integer rowIndex;
private String dateFormat;
private String locale;
private String centerId;
private String typeId;
public static CalendarData importInstanceNoRepeatsOnDay(LocalDate startDate, boolean repeating, EnumOptionData frequency,
Integer interval, Integer rowIndex, String locale, String dateFormat) {
return new CalendarData(startDate, repeating, frequency, interval, rowIndex, locale, dateFormat);
}
public static CalendarData importInstanceWithRepeatsOnDay(LocalDate startDate, boolean repeating, EnumOptionData frequency,
Integer interval, EnumOptionData repeatsOnDay, Integer rowIndex, String locale, String dateFormat) {
return new CalendarData(startDate, repeating, frequency, interval, repeatsOnDay, rowIndex, locale, dateFormat);
}
private CalendarData(LocalDate startDate, boolean repeating, EnumOptionData frequency, Integer interval, Integer rowIndex,
String locale, String dateFormat) {
this.startDate = startDate;
this.repeating = repeating;
this.frequency = frequency;
this.interval = interval;
this.rowIndex = rowIndex;
this.dateFormat = dateFormat;
this.locale = locale;
this.description = "";
this.typeId = "1";
this.id = null;
this.calendarInstanceId = null;
this.entityId = null;
this.entityType = null;
this.title = null;
this.location = null;
this.endDate = null;
this.meetingTime = null;
this.type = null;
this.recurrence = null;
this.repeatsOnDay = null;
this.repeatsOnNthDayOfMonth = null;
this.remindBy = null;
this.firstReminder = null;
this.secondReminder = null;
this.recurringDates = null;
this.nextTenRecurringDates = null;
this.humanReadable = null;
this.recentEligibleMeetingDate = null;
this.createdDate = null;
this.lastUpdatedDate = null;
this.createdByUserId = null;
this.createdByUsername = null;
this.lastUpdatedByUserId = null;
this.lastUpdatedByUsername = null;
this.repeatsOnDayOfMonth = null;
this.entityTypeOptions = null;
this.calendarTypeOptions = null;
this.remindByOptions = null;
this.frequencyOptions = null;
this.repeatsOnDayOptions = null;
this.frequencyNthDayTypeOptions = null;
this.duration = null;
}
private CalendarData(LocalDate startDate, boolean repeating, EnumOptionData frequency, Integer interval, EnumOptionData repeatsOnDay,
Integer rowIndex, String locale, String dateFormat) {
this.startDate = startDate;
this.repeating = repeating;
this.frequency = frequency;
this.interval = interval;
this.repeatsOnDay = repeatsOnDay;
this.rowIndex = rowIndex;
this.dateFormat = dateFormat;
this.locale = locale;
this.description = "";
this.typeId = "1";
this.id = null;
this.calendarInstanceId = null;
this.entityId = null;
this.entityType = null;
this.title = null;
this.location = null;
this.endDate = null;
this.meetingTime = null;
this.type = null;
this.recurrence = null;
this.repeatsOnNthDayOfMonth = null;
this.remindBy = null;
this.firstReminder = null;
this.secondReminder = null;
this.recurringDates = null;
this.nextTenRecurringDates = null;
this.humanReadable = null;
this.recentEligibleMeetingDate = null;
this.createdDate = null;
this.lastUpdatedDate = null;
this.createdByUserId = null;
this.createdByUsername = null;
this.lastUpdatedByUserId = null;
this.lastUpdatedByUsername = null;
this.repeatsOnDayOfMonth = null;
this.entityTypeOptions = null;
this.calendarTypeOptions = null;
this.remindByOptions = null;
this.frequencyOptions = null;
this.repeatsOnDayOptions = null;
this.frequencyNthDayTypeOptions = null;
this.duration = null;
}
public void setCenterId(String centerId) {
this.centerId = centerId;
}
public void setTitle(String title) {
this.title = title;
}
public static CalendarData instance(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType,
final String title, final String description, final String location, final LocalDate startDate, final LocalDate endDate,
final Integer duration, final EnumOptionData type, final boolean repeating, final String recurrence,
final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay,
final EnumOptionData repeatsOnNthDayOfMonth, final EnumOptionData remindBy, final Integer firstReminder,
final Integer secondReminder, final String humanReadable, final OffsetDateTime createdDate,
final OffsetDateTime lastUpdatedDate, final Long createdByUserId, final String createdByUsername,
final Long lastUpdatedByUserId, final String lastUpdatedByUsername, final LocalTime meetingTime,
final Integer repeatsOnDayOfMonth) {
final Collection<LocalDate> recurringDates = null;
final Collection<LocalDate> nextTenRecurringDates = null;
final LocalDate recentEligibleMeetingDate = null;
final List<EnumOptionData> entityTypeOptions = null;
final List<EnumOptionData> calendarTypeOptions = null;
final List<EnumOptionData> remindByOptions = null;
final List<EnumOptionData> frequencyOptions = null;
final List<EnumOptionData> repeatsOnDayOptions = null;
final List<EnumOptionData> frequencyNthDayTypeOptions = null;
return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration,
type, repeating, recurrence, frequency, interval, repeatsOnDay, repeatsOnNthDayOfMonth, remindBy, firstReminder,
secondReminder, recurringDates, nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate,
lastUpdatedDate, createdByUserId, createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, repeatsOnDayOfMonth,
entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions, repeatsOnDayOptions, meetingTime,
frequencyNthDayTypeOptions);
}
public static CalendarData withRecurringDates(final CalendarData calendarData, final Collection<LocalDate> recurringDates,
final Collection<LocalDate> nextTenRecurringDates, final LocalDate recentEligibleMeetingDate) {
return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType,
calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate,
calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency,
calendarData.interval, calendarData.repeatsOnDay, calendarData.repeatsOnNthDayOfMonth, calendarData.remindBy,
calendarData.firstReminder, calendarData.secondReminder, recurringDates, nextTenRecurringDates, calendarData.humanReadable,
recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate, calendarData.createdByUserId,
calendarData.createdByUsername, calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername,
calendarData.repeatsOnDayOfMonth, calendarData.entityTypeOptions, calendarData.calendarTypeOptions,
calendarData.remindByOptions, calendarData.frequencyOptions, calendarData.repeatsOnDayOptions, calendarData.meetingTime,
calendarData.frequencyNthDayTypeOptions);
}
public static CalendarData withRecentEligibleMeetingDate(final CalendarData calendarData, final LocalDate recentEligibleMeetingDate) {
return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType,
calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate,
calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency,
calendarData.interval, calendarData.repeatsOnDay, calendarData.repeatsOnNthDayOfMonth, calendarData.remindBy,
calendarData.firstReminder, calendarData.secondReminder, calendarData.recurringDates, calendarData.nextTenRecurringDates,
calendarData.humanReadable, recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate,
calendarData.createdByUserId, calendarData.createdByUsername, calendarData.lastUpdatedByUserId,
calendarData.lastUpdatedByUsername, calendarData.repeatsOnDayOfMonth, calendarData.entityTypeOptions,
calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions,
calendarData.repeatsOnDayOptions, calendarData.meetingTime, calendarData.frequencyNthDayTypeOptions);
}
public static CalendarData sensibleDefaultsForNewCalendarCreation() {
final Long id = null;
final Long calendarInstanceId = null;
final Long entityId = null;
final EnumOptionData entityType = null;
final String title = null;
final String description = null;
final String location = null;
final LocalDate startDate = null;
final LocalDate endDate = null;
final Integer duration = 0;
final EnumOptionData type = CalendarEnumerations.calendarType(CalendarType.COLLECTION);
final boolean repeating = false;
final String recurrence = null;
final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarFrequencyType.DAILY);
final Integer interval = 1;
final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.MO);
final EnumOptionData repeatsOnNthDayOfMonth = CalendarEnumerations.calendarFrequencyNthDayType(NthDayType.ONE);
final EnumOptionData remindBy = CalendarEnumerations.calendarRemindBy(CalendarRemindBy.EMAIL);
final Integer firstReminder = 0;
final Integer secondReminder = 0;
final String humanReadable = null;
final Collection<LocalDate> recurringDates = null;
final Collection<LocalDate> nextTenRecurringDates = null;
final LocalDate recentEligibleMeetingDate = null;
final List<EnumOptionData> entityTypeOptions = null;
final List<EnumOptionData> calendarTypeOptions = null;
final List<EnumOptionData> remindByOptions = null;
final List<EnumOptionData> frequencyOptions = null;
final List<EnumOptionData> repeatsOnDayOptions = null;
final List<EnumOptionData> frequencyNthDayTypeOptions = null;
final OffsetDateTime createdDate = null;
final OffsetDateTime lastUpdatedDate = null;
final Long createdByUserId = null;
final String createdByUsername = null;
final Long lastUpdatedByUserId = null;
final String lastUpdatedByUsername = null;
final LocalTime meetingTime = null;
final Integer repeatsOnDayOfMonth = null;
return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration,
type, repeating, recurrence, frequency, interval, repeatsOnDay, repeatsOnNthDayOfMonth, remindBy, firstReminder,
secondReminder, recurringDates, nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate,
lastUpdatedDate, createdByUserId, createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, repeatsOnDayOfMonth,
entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions, repeatsOnDayOptions, meetingTime,
frequencyNthDayTypeOptions);
}
public static CalendarData withTemplateOptions(final CalendarData calendarData, final List<EnumOptionData> entityTypeOptions,
final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions,
final List<EnumOptionData> frequencyNthDayTypeOptions) {
return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType,
calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate,
calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency,
calendarData.interval, calendarData.repeatsOnDay, calendarData.repeatsOnNthDayOfMonth, calendarData.remindBy,
calendarData.firstReminder, calendarData.secondReminder, calendarData.recurringDates, calendarData.nextTenRecurringDates,
calendarData.humanReadable, calendarData.recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate,
calendarData.createdByUserId, calendarData.createdByUsername, calendarData.lastUpdatedByUserId,
calendarData.lastUpdatedByUsername, calendarData.repeatsOnDayOfMonth, entityTypeOptions, calendarTypeOptions,
remindByOptions, repeatsOptions, repeatsOnDayOptions, calendarData.meetingTime, frequencyNthDayTypeOptions);
}
private CalendarData(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType,
final String title, final String description, final String location, final LocalDate startDate, final LocalDate endDate,
final Integer duration, final EnumOptionData type, final boolean repeating, final String recurrence,
final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay,
final EnumOptionData repeatsOnNthDayOfMonth, final EnumOptionData remindBy, final Integer firstReminder,
final Integer secondReminder, final Collection<LocalDate> recurringDates, final Collection<LocalDate> nextTenRecurringDates,
final String humanReadable, final LocalDate recentEligibleMeetingDate, final OffsetDateTime createdDate,
final OffsetDateTime lastUpdatedDate, final Long createdByUserId, final String createdByUsername,
final Long lastUpdatedByUserId, final String lastUpdatedByUsername, final Integer repeatsOnDayOfMonth,
final List<EnumOptionData> entityTypeOptions, final List<EnumOptionData> calendarTypeOptions,
final List<EnumOptionData> remindByOptions, final List<EnumOptionData> repeatsOptions,
final List<EnumOptionData> repeatsOnDayOptions, final LocalTime meetingTime,
final List<EnumOptionData> frequencyNthDayTypeOptions) {
this.id = id;
this.calendarInstanceId = calendarInstanceId;
this.entityId = entityId;
this.entityType = entityType;
this.title = title;
this.description = description;
this.location = location;
this.startDate = startDate;
this.endDate = endDate;
this.duration = duration;
this.type = type;
this.repeating = repeating;
this.recurrence = recurrence;
this.frequency = frequency;
this.interval = interval;
this.repeatsOnDay = repeatsOnDay;
this.repeatsOnNthDayOfMonth = repeatsOnNthDayOfMonth;
this.remindBy = remindBy;
this.firstReminder = firstReminder;
this.secondReminder = secondReminder;
this.recurringDates = recurringDates;
this.nextTenRecurringDates = nextTenRecurringDates;
this.humanReadable = humanReadable;
this.recentEligibleMeetingDate = recentEligibleMeetingDate;
this.createdDate = createdDate;
this.lastUpdatedDate = lastUpdatedDate;
this.createdByUserId = createdByUserId;
this.createdByUsername = createdByUsername;
this.lastUpdatedByUserId = lastUpdatedByUserId;
this.lastUpdatedByUsername = lastUpdatedByUsername;
this.repeatsOnDayOfMonth = repeatsOnDayOfMonth;
this.entityTypeOptions = entityTypeOptions;
this.calendarTypeOptions = calendarTypeOptions;
this.remindByOptions = remindByOptions;
this.frequencyOptions = repeatsOptions;
this.repeatsOnDayOptions = repeatsOnDayOptions;
this.meetingTime = meetingTime;
this.frequencyNthDayTypeOptions = frequencyNthDayTypeOptions;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (this.startDate.isBefore(compareDate) || this.startDate.equals(compareDate)) {
return true;
}
}
return false;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (this.endDate.isAfter(compareDate) || this.endDate.isEqual(compareDate)) {
return true;
}
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {
if (isStartDateBeforeOrEqual(compareDate)) {
if (this.endDate == null || isEndDateAfterOrEqual(compareDate)) {
return true;
}
}
return false;
}
public boolean isValidRecurringDate(final LocalDate compareDate, final Boolean isSkipMeetingOnFirstDay, final Integer numberOfDays) {
if (isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(this.getRecurrence(), this.getStartDate(), compareDate, isSkipMeetingOnFirstDay,
numberOfDays);
}
return false;
}
}
@@ -0,0 +1,60 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.data;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
public class CalendarHistoryDataWrapper {
private final List<CalendarHistory> calendarHistoryList;
public CalendarHistoryDataWrapper(final Set<CalendarHistory> calendarHistoryList) {
this.calendarHistoryList = new ArrayList<>();
this.calendarHistoryList.addAll(calendarHistoryList);
final Comparator<CalendarHistory> orderByDate = new Comparator<CalendarHistory>() {
@Override
public int compare(CalendarHistory calendarHistory1, CalendarHistory calendarHistory2) {
return calendarHistory1.getEndDateLocalDate().compareTo(calendarHistory2.getEndDateLocalDate());
}
};
Collections.sort(this.calendarHistoryList, orderByDate);
}
public CalendarHistory getCalendarHistory(final LocalDate dueRepaymentPeriodDate) {
CalendarHistory calendarHistory = null;
for (CalendarHistory history : this.calendarHistoryList) {
if (history.getEndDateLocalDate().isAfter(dueRepaymentPeriodDate)) {
calendarHistory = history;
break;
}
}
return calendarHistory;
}
public List<CalendarHistory> getCalendarHistoryList() {
return this.calendarHistoryList;
}
}
@@ -0,0 +1,652 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import static org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_RESOURCE_NAME;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.calendar.CalendarConstants.CalendarSupportedParameters;
import org.apache.fineract.portfolio.calendar.exception.CalendarDateException;
import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.NthDayType;
@Entity
@Table(name = "m_calendar")
public class Calendar extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "title", length = 50, nullable = false)
private String title;
@Column(name = "description", length = 100, nullable = true)
private String description;
@Column(name = "location", length = 100, nullable = true)
private String location;
@Column(name = "start_date", nullable = false)
private LocalDate startDate;
@Column(name = "end_date", nullable = true)
private LocalDate endDate;
@Column(name = "duration", nullable = true)
private Integer duration;
@Column(name = "calendar_type_enum", nullable = false)
private Integer typeId;
@Column(name = "repeating", nullable = false)
private boolean repeating = false;
@Column(name = "recurrence", length = 100, nullable = true)
private String recurrence;
@Column(name = "remind_by_enum", nullable = true)
private Integer remindById;
@Column(name = "first_reminder", nullable = true)
private Integer firstReminder;
@Column(name = "second_reminder", nullable = true)
private Integer secondReminder;
@Column(name = "meeting_time", nullable = true)
private LocalTime meetingtime;
@OneToMany(mappedBy = "calendar", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private Set<CalendarHistory> calendarHistory = new HashSet<>();
protected Calendar() {
}
public Calendar(final String title, final String description, final String location, final LocalDate startDate, final LocalDate endDate,
final Integer duration, final Integer typeId, final boolean repeating, final String recurrence, final Integer remindById,
final Integer firstReminder, final Integer secondReminder, final LocalTime meetingtime) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
final CalendarType calendarType = CalendarType.fromInt(typeId);
if (calendarType.isCollection() && !repeating) {
baseDataValidator.reset().parameter(CalendarSupportedParameters.REPEATING.getValue())
.failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
this.title = StringUtils.defaultIfEmpty(title, null);
this.description = StringUtils.defaultIfEmpty(description, null);
this.location = StringUtils.defaultIfEmpty(location, null);
this.startDate = startDate;
this.endDate = endDate;
this.duration = duration;
this.typeId = typeId;
this.repeating = repeating;
this.recurrence = StringUtils.defaultIfEmpty(recurrence, null);
this.remindById = remindById;
this.firstReminder = firstReminder;
this.secondReminder = secondReminder;
this.meetingtime = meetingtime;
}
public static Calendar createRepeatingCalendar(final String title, final LocalDate startDate, final Integer typeId,
final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay,
final Integer repeatsOnNthDayOfMonth) {
final String recurrence = constructRecurrence(frequencyType, interval, repeatsOnDay, repeatsOnNthDayOfMonth);
return createRepeatingCalendar(title, startDate, typeId, recurrence);
}
public static Calendar createRepeatingCalendar(final String title, final LocalDate startDate, final Integer typeId,
final String recurrence) {
final String description = null;
final String location = null;
final LocalDate endDate = null;
final Integer duration = null;
final boolean repeating = true;
final Integer remindById = null;
final Integer firstReminder = null;
final Integer secondReminder = null;
final LocalTime meetingtime = null;
return new Calendar(title, description, location, startDate, endDate, duration, typeId, repeating, recurrence, remindById,
firstReminder, secondReminder, meetingtime);
}
public static Calendar fromJson(final JsonCommand command) {
// final Long entityId = command.getSupportedEntityId();
// final Integer entityTypeId =
// CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue();
LocalTime meetingtime = null;
final String title = command.stringValueOfParameterNamed(CalendarSupportedParameters.TITLE.getValue());
final String description = command.stringValueOfParameterNamed(CalendarSupportedParameters.DESCRIPTION.getValue());
final String location = command.stringValueOfParameterNamed(CalendarSupportedParameters.LOCATION.getValue());
final LocalDate startDate = command.localDateValueOfParameterNamed(CalendarSupportedParameters.START_DATE.getValue());
final LocalDate endDate = command.localDateValueOfParameterNamed(CalendarSupportedParameters.END_DATE.getValue());
final Integer duration = command.integerValueSansLocaleOfParameterNamed(CalendarSupportedParameters.DURATION.getValue());
final Integer typeId = command.integerValueSansLocaleOfParameterNamed(CalendarSupportedParameters.TYPE_ID.getValue());
final boolean repeating = command.booleanPrimitiveValueOfParameterNamed(CalendarSupportedParameters.REPEATING.getValue());
final Integer remindById = command.integerValueSansLocaleOfParameterNamed(CalendarSupportedParameters.REMIND_BY_ID.getValue());
final Integer firstReminder = command.integerValueSansLocaleOfParameterNamed(CalendarSupportedParameters.FIRST_REMINDER.getValue());
final Integer secondReminder = command
.integerValueSansLocaleOfParameterNamed(CalendarSupportedParameters.SECOND_REMINDER.getValue());
meetingtime = command.localTimeValueOfParameterNamed(CalendarSupportedParameters.MEETING_TIME.getValue());
final String recurrence = Calendar.constructRecurrence(command, null);
return new Calendar(title, description, location, startDate, endDate, duration, typeId, repeating, recurrence, remindById,
firstReminder, secondReminder, meetingtime);
}
public Map<String, Object> updateStartDateAndDerivedFeilds(final LocalDate newMeetingStartDate) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (newMeetingStartDate.isBefore(currentDate)) {
final String defaultUserMessage = "New meeting effective from date cannot be in past";
throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newMeetingStartDate,
getStartDateLocalDate());
} else if (isStartDateAfter(newMeetingStartDate) && isStartDateBeforeOrEqual(currentDate)) {
// new meeting date should be on or after start date or current
// date
final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date";
throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newMeetingStartDate,
getStartDateLocalDate());
} else {
actualChanges.put(CalendarSupportedParameters.START_DATE.getValue(), newMeetingStartDate.toString());
this.startDate = newMeetingStartDate;
/*
* If meeting start date is changed then there is possibilities of recurring day may change, so derive the
* recurring day and update it if it is changed. For weekly type is weekday and for monthly type it is day
* of the month
*/
CalendarFrequencyType calendarFrequencyType = CalendarUtils.getFrequency(this.recurrence);
Integer interval = CalendarUtils.getInterval(this.recurrence);
Integer repeatsOnDay = null;
/*
* Repeats on day, need to derive based on the start date
*/
if (calendarFrequencyType.isWeekly()) {
repeatsOnDay = newMeetingStartDate.get(ChronoField.DAY_OF_WEEK);
} else if (calendarFrequencyType.isMonthly()) {
repeatsOnDay = newMeetingStartDate.getDayOfMonth();
}
// TODO cover other recurrence also
this.recurrence = constructRecurrence(calendarFrequencyType, interval, repeatsOnDay, null);
}
return actualChanges;
}
public Map<String, Object> update(final JsonCommand command, final Boolean areActiveEntitiesSynced) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
if (command.isChangeInStringParameterNamed(CalendarSupportedParameters.TITLE.getValue(), this.title)) {
final String newValue = command.stringValueOfParameterNamed(CalendarSupportedParameters.TITLE.getValue());
actualChanges.put(CalendarSupportedParameters.TITLE.getValue(), newValue);
this.title = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInStringParameterNamed(CalendarSupportedParameters.DESCRIPTION.getValue(), this.description)) {
final String newValue = command.stringValueOfParameterNamed(CalendarSupportedParameters.DESCRIPTION.getValue());
actualChanges.put(CalendarSupportedParameters.DESCRIPTION.getValue(), newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInStringParameterNamed(CalendarSupportedParameters.LOCATION.getValue(), this.location)) {
final String newValue = command.stringValueOfParameterNamed(CalendarSupportedParameters.LOCATION.getValue());
actualChanges.put(CalendarSupportedParameters.LOCATION.getValue(), newValue);
this.location = StringUtils.defaultIfEmpty(newValue, null);
}
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
final String startDateParamName = CalendarSupportedParameters.START_DATE.getValue();
if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartDateLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (newValue.isBefore(currentDate)) {
final String defaultUserMessage = "New meeting effective from date cannot be in past";
throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newValue, getStartDateLocalDate());
} else if (isStartDateAfter(newValue) && isStartDateBeforeOrEqual(currentDate)) {
// new meeting date should be on or after start date or current
// date
final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date";
throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newValue,
getStartDateLocalDate());
} else {
actualChanges.put(startDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput);
this.startDate = newValue;
}
}
final String endDateParamName = CalendarSupportedParameters.END_DATE.getValue();
if (command.isChangeInLocalDateParameterNamed(endDateParamName, getEndDateLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(endDateParamName);
actualChanges.put(endDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput);
this.endDate = command.localDateValueOfParameterNamed(endDateParamName);
}
final String durationParamName = CalendarSupportedParameters.DURATION.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(durationParamName, this.duration)) {
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(durationParamName);
actualChanges.put(durationParamName, newValue);
this.duration = newValue;
}
// Do not allow to change calendar type
// TODO: AA Instead of throwing an exception, do not allow meeting
// calendar type to update.
final String typeParamName = CalendarSupportedParameters.TYPE_ID.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(typeParamName, this.typeId)) {
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(typeParamName);
final String defaultUserMessage = "Meeting calendar type update is not supported";
final String oldMeeingType = CalendarType.fromInt(this.typeId).name();
final String newMeetingType = CalendarType.fromInt(newValue).name();
throw new CalendarParameterUpdateNotSupportedException("meeting.type", defaultUserMessage, newMeetingType, oldMeeingType);
/*
* final Integer newValue = command.integerValueSansLocaleOfParameterNamed(typeParamName);
* actualChanges.put(typeParamName, newValue); this.typeId = newValue;
*/
}
final String repeatingParamName = CalendarSupportedParameters.REPEATING.getValue();
if (command.isChangeInBooleanParameterNamed(repeatingParamName, this.repeating)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(repeatingParamName);
actualChanges.put(repeatingParamName, newValue);
this.repeating = newValue;
}
// if repeating is false then update recurrence to NULL
if (!this.repeating) {
this.recurrence = null;
}
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
final CalendarType calendarType = CalendarType.fromInt(this.typeId);
if (calendarType.isCollection() && !this.repeating) {
baseDataValidator.reset().parameter(CalendarSupportedParameters.REPEATING.getValue())
.failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
final String newRecurrence = Calendar.constructRecurrence(command, this);
if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
/*
* If active entities like JLG loan or RD accounts are synced to the calendar then do not allow to change
* meeting frequency
*/
if (areActiveEntitiesSynced && !CalendarUtils.isFrequencySame(this.recurrence, newRecurrence)) {
final String defaultUserMessage = "Update of meeting frequency is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.frequency", defaultUserMessage);
}
/*
* If active entities like JLG loan or RD accounts are synced to the calendar then do not allow to change
* meeting interval
*/
if (areActiveEntitiesSynced && !CalendarUtils.isIntervalSame(this.recurrence, newRecurrence)) {
final String defaultUserMessage = "Update of meeting interval is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.interval", defaultUserMessage);
}
actualChanges.put("recurrence", newRecurrence);
this.recurrence = StringUtils.defaultIfEmpty(newRecurrence, null);
}
final String remindByParamName = CalendarSupportedParameters.REMIND_BY_ID.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(remindByParamName, this.remindById)) {
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(remindByParamName);
actualChanges.put(remindByParamName, newValue);
this.remindById = newValue;
}
final String firstRemindarParamName = CalendarSupportedParameters.FIRST_REMINDER.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(firstRemindarParamName, this.firstReminder)) {
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(firstRemindarParamName);
actualChanges.put(firstRemindarParamName, newValue);
this.firstReminder = newValue;
}
final String secondRemindarParamName = CalendarSupportedParameters.SECOND_REMINDER.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(secondRemindarParamName, this.secondReminder)) {
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(secondRemindarParamName);
actualChanges.put(secondRemindarParamName, newValue);
this.secondReminder = newValue;
}
final String timeFormat = command.stringValueOfParameterNamed(CalendarSupportedParameters.Time_Format.getValue());
final String time = CalendarSupportedParameters.MEETING_TIME.getValue();
if (command.isChangeInTimeParameterNamed(CalendarSupportedParameters.MEETING_TIME.getValue(), this.meetingtime, timeFormat)) {
final String newValue = command.stringValueOfParameterNamed(CalendarSupportedParameters.MEETING_TIME.getValue());
actualChanges.put(CalendarSupportedParameters.MEETING_TIME.getValue(), newValue);
LocalTime timeInLocalDateTimeFormat = command.localTimeValueOfParameterNamed(time);
if (timeInLocalDateTimeFormat != null) {
this.meetingtime = timeInLocalDateTimeFormat;
}
}
return actualChanges;
}
@SuppressWarnings("null")
public Map<String, Object> updateRepeatingCalendar(final LocalDate calendarStartDate, final CalendarFrequencyType frequencyType,
final Integer interval, final Integer repeatsOnDay, final Integer repeatsOnNthDay) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
if (calendarStartDate != null && this.startDate != null) {
if (!calendarStartDate.equals(this.getStartDateLocalDate())) {
actualChanges.put("startDate", calendarStartDate);
this.startDate = calendarStartDate;
}
}
final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, repeatsOnDay, repeatsOnNthDay);
if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
actualChanges.put("recurrence", newRecurrence);
this.recurrence = newRecurrence;
}
return actualChanges;
}
public String getTitle() {
return this.title;
}
public String getDescription() {
return this.description;
}
public String getLocation() {
return this.location;
}
public LocalDate getStartDate() {
return this.startDate;
}
public LocalDate getEndDate() {
return this.endDate;
}
public Integer getDuration() {
return this.duration;
}
public Integer getTypeId() {
return this.typeId;
}
public boolean isRepeating() {
return this.repeating;
}
public String getRecurrence() {
return this.recurrence;
}
public Integer getRemindById() {
return this.remindById;
}
public Integer getFirstReminder() {
return this.firstReminder;
}
public Integer getSecondReminder() {
return this.secondReminder;
}
public LocalTime getMeetingTime() {
return this.meetingtime;
}
public LocalDate getStartDateLocalDate() {
return this.startDate;
}
public LocalDate getEndDateLocalDate() {
return this.endDate;
}
public Set<CalendarHistory> history() {
return this.calendarHistory;
}
public boolean isStartDateBefore(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null && getStartDateLocalDate().isBefore(compareDate)) {
return true;
}
return false;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) {
return true;
}
}
return false;
}
public boolean isStartDateAfter(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null && getStartDateLocalDate().isAfter(compareDate)) {
return true;
}
return false;
}
public boolean isStartDateAfterOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isAfter(compareDate) || getStartDateLocalDate().isEqual(compareDate)) {
return true;
}
}
return false;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) {
return true;
}
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {
if (isStartDateBeforeOrEqual(compareDate)) {
if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)) {
return true;
}
}
return false;
}
private static String constructRecurrence(final JsonCommand command, final Calendar calendar) {
final boolean repeating;
if (command.parameterExists(CalendarSupportedParameters.REPEATING.getValue())) {
repeating = command.booleanPrimitiveValueOfParameterNamed(CalendarSupportedParameters.REPEATING.getValue());
} else if (calendar != null) {
repeating = calendar.isRepeating();
} else {
repeating = false;
}
if (repeating) {
final Integer frequency = command.integerValueOfParameterNamed(CalendarSupportedParameters.FREQUENCY.getValue());
final CalendarFrequencyType frequencyType = CalendarFrequencyType.fromInt(frequency);
final Integer interval = command.integerValueOfParameterNamed(CalendarSupportedParameters.INTERVAL.getValue());
Integer repeatsOnDay = null;
if (frequencyType.isWeekly()) {
repeatsOnDay = command.integerValueOfParameterNamed(CalendarSupportedParameters.REPEATS_ON_DAY.getValue());
}
Integer repeatsOnNthDayOfMonth = null;
if (frequencyType.isMonthly()) {
repeatsOnNthDayOfMonth = command
.integerValueOfParameterNamed(CalendarSupportedParameters.REPEATS_ON_NTH_DAY_OF_MONTH.getValue());
final NthDayType nthDay = NthDayType.fromInt(repeatsOnNthDayOfMonth);
repeatsOnDay = command
.integerValueOfParameterNamed(CalendarSupportedParameters.REPEATS_ON_LAST_WEEKDAY_OF_MONTH.getValue());
if (nthDay.isOnDay()) {
repeatsOnNthDayOfMonth = command
.integerValueOfParameterNamed(CalendarSupportedParameters.REPEATS_ON_DAY_OF_MONTH.getValue());
repeatsOnDay = null;
}
}
return constructRecurrence(frequencyType, interval, repeatsOnDay, repeatsOnNthDayOfMonth);
}
return "";
}
private static String constructRecurrence(final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay,
final Integer repeatsOnNthDayOfMonth) {
final StringBuilder recurrenceBuilder = new StringBuilder(200);
recurrenceBuilder.append("FREQ=");
recurrenceBuilder.append(frequencyType.toString().toUpperCase());
if (interval > 1) {
recurrenceBuilder.append(";INTERVAL=");
recurrenceBuilder.append(interval);
}
if (frequencyType.isWeekly()) {
if (repeatsOnDay != null) {
final CalendarWeekDaysType weekDays = CalendarWeekDaysType.fromInt(repeatsOnDay);
if (!weekDays.isInvalid()) {
recurrenceBuilder.append(";BYDAY=");
recurrenceBuilder.append(weekDays.toString().toUpperCase());
}
}
}
if (frequencyType.isMonthly()) {
if (repeatsOnNthDayOfMonth != null && (repeatsOnDay == null || repeatsOnDay.equals(CalendarWeekDaysType.INVALID.getValue()))) {
if (repeatsOnNthDayOfMonth >= -1 && repeatsOnNthDayOfMonth <= 28) {
recurrenceBuilder.append(";BYMONTHDAY=");
recurrenceBuilder.append(repeatsOnNthDayOfMonth);
}
} else if (repeatsOnNthDayOfMonth != null && repeatsOnDay != null
&& !repeatsOnDay.equals(CalendarWeekDaysType.INVALID.getValue())) {
final NthDayType nthDay = NthDayType.fromInt(repeatsOnNthDayOfMonth);
if (!nthDay.isInvalid()) {
recurrenceBuilder.append(";BYSETPOS=");
recurrenceBuilder.append(nthDay.getValue());
}
final CalendarWeekDaysType weekday = CalendarWeekDaysType.fromInt(repeatsOnDay);
if (!weekday.isInvalid()) {
recurrenceBuilder.append(";BYDAY=");
recurrenceBuilder.append(weekday.toString().toUpperCase());
}
}
}
return recurrenceBuilder.toString();
}
public boolean isValidRecurringDate(final LocalDate compareDate, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
if (isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(getRecurrence(), getStartDateLocalDate(), compareDate, isSkipRepaymentOnFirstMonth,
numberOfDays);
}
// validate with history details.
for (CalendarHistory history : history()) {
if (history.isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(history.getRecurrence(), history.getStartDateLocalDate(), compareDate,
isSkipRepaymentOnFirstMonth, numberOfDays);
}
}
return false;
}
public void updateStartAndEndDate(final LocalDate startDate, final LocalDate endDate) {
final CalendarFrequencyType frequencyType = CalendarUtils.getFrequency(this.recurrence);
final Integer interval = Integer.valueOf(CalendarUtils.getInterval(this.recurrence));
final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, startDate.get(ChronoField.DAY_OF_WEEK), null);
this.recurrence = newRecurrence;
this.startDate = startDate;
this.endDate = endDate;
}
public Set<CalendarHistory> getCalendarHistory() {
return this.calendarHistory;
}
public void updateCalendarHistory(final Set<CalendarHistory> calendarHistory) {
this.calendarHistory = calendarHistory;
}
public void setRecurrence(String recurrence) {
this.recurrence = recurrence;
}
}
@@ -0,0 +1,162 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import java.util.HashMap;
import java.util.Map;
public enum CalendarEntityType {
INVALID(0, "calendarEntityType.invalid"), //
CLIENTS(1, "calendarEntityType.clients"), //
GROUPS(2, "calendarEntityType.groups"), //
LOANS(3, "calendarEntityType.loans"), //
CENTERS(4, "calendarEntityType.centers"), //
SAVINGS(5, "calendarEntityType.savings"), //
LOAN_RECALCULATION_REST_DETAIL(6, "calendarEntityType.loan.recalculation.rest.detail"), //
LOAN_RECALCULATION_COMPOUNDING_DETAIL(7, "calendarEntityType.loan.recalculation.compounding.detail");
private final Integer value;
private final String code;
CalendarEntityType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, CalendarEntityType> intToEnumMap = new HashMap<>();
private static int minValue;
private static int maxValue;
static {
int i = 0;
for (final CalendarEntityType entityType : CalendarEntityType.values()) {
if (i == 0) {
minValue = entityType.value;
}
intToEnumMap.put(entityType.value, entityType);
if (minValue >= entityType.value) {
minValue = entityType.value;
}
if (maxValue < entityType.value) {
maxValue = entityType.value;
}
i = i + 1;
}
}
public static CalendarEntityType fromInt(final int i) {
final CalendarEntityType entityType = intToEnumMap.get(Integer.valueOf(i));
return entityType;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
public static boolean isGroup(final Integer value) {
return CalendarEntityType.GROUPS.value.equals(value) ? true : false;
}
public static boolean isGroup(final String name) {
return CalendarEntityType.GROUPS.name().equalsIgnoreCase(name) ? true : false;
}
public static boolean isCenter(final Integer value) {
return CalendarEntityType.CENTERS.value.equals(value) ? true : false;
}
public static boolean isCenter(final String name) {
return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name) ? true : false;
}
public static boolean isLoan(final Integer value) {
return CalendarEntityType.LOANS.value.equals(value) ? true : false;
}
public static boolean isLoan(final String name) {
return CalendarEntityType.LOANS.name().equalsIgnoreCase(name) ? true : false;
}
public static boolean isClient(final Integer value) {
return CalendarEntityType.CLIENTS.value.equals(value) ? true : false;
}
public static boolean isClient(final String name) {
return CalendarEntityType.CLIENTS.name().equalsIgnoreCase(name) ? true : false;
}
public boolean isCenter() {
return this.value.equals(CalendarEntityType.CENTERS.getValue());
}
public boolean isGroup() {
return this.value.equals(CalendarEntityType.GROUPS.getValue());
}
public boolean isLoan() {
return this.value.equals(CalendarEntityType.LOANS.getValue());
}
public boolean isClient() {
return this.value.equals(CalendarEntityType.CLIENTS.getValue());
}
private static final Map<String, CalendarEntityType> entityNameToEnumMap = new HashMap<>();
static {
for (final CalendarEntityType entityType : CalendarEntityType.values()) {
entityNameToEnumMap.put(entityType.name().toLowerCase(), entityType);
}
}
public static CalendarEntityType getEntityType(String entityType) {
return entityNameToEnumMap.get(entityType.toLowerCase());
}
public static boolean isSavings(final Integer value) {
return CalendarEntityType.SAVINGS.value.equals(value) ? true : false;
}
public static boolean isSavings(final String name) {
return CalendarEntityType.SAVINGS.name().equalsIgnoreCase(name) ? true : false;
}
public boolean isSavings() {
return this.value.equals(CalendarEntityType.SAVINGS.getValue());
}
}
@@ -0,0 +1,149 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import java.util.HashMap;
import java.util.Map;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.springframework.util.StringUtils;
public enum CalendarFrequencyType {
INVALID(0, "calendarFrequencyType.invalid"), DAILY(1, "calendarFrequencyType.daily"), WEEKLY(2,
"calendarFrequencyType.weekly"), MONTHLY(3, "calendarFrequencyType.monthly"), YEARLY(4, "calendarFrequencyType.yearly");
private final Integer value;
private final String code;
CalendarFrequencyType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, CalendarFrequencyType> intToEnumMap = new HashMap<>();
private static int minValue = CalendarFrequencyType.DAILY.value;
private static int maxValue = CalendarFrequencyType.YEARLY.value;
static {
for (final CalendarFrequencyType type : CalendarFrequencyType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static CalendarFrequencyType fromInt(final int i) {
final CalendarFrequencyType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public static CalendarFrequencyType fromString(final String frequencyString) {
CalendarFrequencyType frequency = CalendarFrequencyType.INVALID;
if (!StringUtils.hasText(frequencyString)) {
return frequency;
}
if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.DAILY.toString())) {
frequency = CalendarFrequencyType.DAILY;
} else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.WEEKLY.toString())) {
frequency = CalendarFrequencyType.WEEKLY;
} else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.MONTHLY.toString())) {
frequency = CalendarFrequencyType.MONTHLY;
} else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.YEARLY.toString())) {
frequency = CalendarFrequencyType.YEARLY;
}
return frequency;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
public boolean isWeekly() {
return this.value.equals(CalendarFrequencyType.WEEKLY.value);
}
public boolean isMonthly() {
return this.value.equals(CalendarFrequencyType.MONTHLY.value);
}
public boolean isInvalid() {
return this.value.equals(CalendarFrequencyType.INVALID.value);
}
/**
* To convert from period frequency type tp calendar frequency type. This method requires code refactoring.
*
* @param periodFrequencyType
* periodFrequencyType
* @return CalendarFrequencyType
*/
public static CalendarFrequencyType from(final PeriodFrequencyType periodFrequencyType) {
switch (periodFrequencyType) {
case DAYS:
return CalendarFrequencyType.DAILY;
case WEEKS:
return CalendarFrequencyType.WEEKLY;
case MONTHS:
return CalendarFrequencyType.MONTHLY;
case YEARS:
return CalendarFrequencyType.YEARLY;
default:
return CalendarFrequencyType.INVALID;
}
}
/**
* To convert from period frequency type tp calendar frequency type. This method requires code refactoring.
*
* @param frequencyType
* @return
*/
public static PeriodFrequencyType from(final CalendarFrequencyType frequencyType) {
switch (frequencyType) {
case DAILY:
return PeriodFrequencyType.DAYS;
case WEEKLY:
return PeriodFrequencyType.WEEKS;
case MONTHLY:
return PeriodFrequencyType.MONTHS;
case YEARLY:
return PeriodFrequencyType.YEARS;
default:
return PeriodFrequencyType.INVALID;
}
}
}
@@ -0,0 +1,141 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_calendar_history")
public class CalendarHistory extends AbstractPersistableCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "calendar_id", referencedColumnName = "id", nullable = false)
private Calendar calendar;
@Column(name = "title", length = 50, nullable = false)
private String title;
@Column(name = "description", length = 100, nullable = true)
private String description;
@Column(name = "location", length = 100, nullable = true)
private String location;
@Column(name = "start_date", nullable = false)
private LocalDate startDate;
@Column(name = "end_date", nullable = true)
private LocalDate endDate;
@Column(name = "duration", nullable = true)
private Integer duration;
@Column(name = "calendar_type_enum", nullable = false)
private Integer typeId;
@Column(name = "repeating", nullable = false)
private boolean repeating = false;
@Column(name = "recurrence", length = 100, nullable = true)
private String recurrence;
@Column(name = "remind_by_enum", nullable = true)
private Integer remindById;
@Column(name = "first_reminder", nullable = true)
private Integer firstReminder;
@Column(name = "second_reminder", nullable = true)
private Integer secondReminder;
protected CalendarHistory() {
}
public CalendarHistory(Calendar calendar, LocalDate startDate) {
this.calendar = calendar;
this.title = calendar.getTitle();
this.description = calendar.getDescription();
this.location = calendar.getLocation();
this.startDate = startDate;
this.endDate = calendar.getStartDate();// Calendar start date become end
// date for history data.
this.duration = calendar.getDuration();
this.typeId = calendar.getTypeId();
this.repeating = calendar.isRepeating();
this.recurrence = calendar.getRecurrence();
this.remindById = calendar.getRemindById();
this.firstReminder = calendar.getFirstReminder();
this.secondReminder = calendar.getSecondReminder();
}
public String getRecurrence() {
return this.recurrence;
}
public LocalDate getStartDateLocalDate() {
return this.startDate;
}
public LocalDate getEndDateLocalDate() {
return this.endDate;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) {
return true;
}
}
return false;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) {
return true;
}
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {
if (isStartDateBeforeOrEqual(compareDate)) {
if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)) {
return true;
}
}
return false;
}
public void updateEndDate(LocalDate historyCalEndDate) {
this.endDate = historyCalEndDate;
}
public Calendar getCalendar() {
return this.calendar;
}
}
@@ -0,0 +1,73 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_calendar_instance")
public class CalendarInstance extends AbstractPersistableCustom {
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "calendar_id", nullable = false)
private Calendar calendar;
@Column(name = "entity_id", nullable = false)
private Long entityId;
@Column(name = "entity_type_enum", nullable = false)
private Integer entityTypeId;
public CalendarInstance(final Calendar calendar, final Long entityId, final Integer entityTypeId) {
this.calendar = calendar;
this.entityId = entityId;
this.entityTypeId = entityTypeId;
}
protected CalendarInstance() {
}
public static CalendarInstance from(final Calendar calendar, final Long entityId, final Integer entityTypeId) {
return new CalendarInstance(calendar, entityId, entityTypeId);
}
public void updateCalendar(final Calendar calendar) {
this.calendar = calendar;
}
public Calendar getCalendar() {
return this.calendar;
}
public Long getEntityId() {
return this.entityId;
}
public Integer getEntityTypeId() {
return this.entityTypeId;
}
}
@@ -0,0 +1,82 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import java.util.HashMap;
import java.util.Map;
public enum CalendarRemindBy {
SMS(1, "calendarRemindBy.sms"), EMAIL(2, "calendarRemindBy.email"), SYSTEMALERT(3, "calendarRemindBy.systemalert");
private final Integer value;
private final String code;
CalendarRemindBy(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, CalendarRemindBy> intToEnumMap = new HashMap<>();
private static int minValue;
private static int maxValue;
static {
int i = 0;
for (final CalendarRemindBy remindBy : CalendarRemindBy.values()) {
if (i == 0) {
minValue = remindBy.value;
}
intToEnumMap.put(remindBy.value, remindBy);
if (minValue >= remindBy.value) {
minValue = remindBy.value;
}
if (maxValue < remindBy.value) {
maxValue = remindBy.value;
}
i = i + 1;
}
}
public static CalendarRemindBy fromInt(final int i) {
final CalendarRemindBy remindBy = intToEnumMap.get(Integer.valueOf(i));
return remindBy;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
}
@@ -0,0 +1,87 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import java.util.HashMap;
import java.util.Map;
public enum CalendarType {
COLLECTION(1, "calendarType.collection"), TRAINING(2, "calendarType.training"), AUDIT(3, "calendarType.audit"), GENERAL(4,
"calendarType.general");
private final Integer value;
private final String code;
CalendarType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, CalendarType> intToEnumMap = new HashMap<>();
private static int minValue;
private static int maxValue;
static {
int i = 0;
for (final CalendarType type : CalendarType.values()) {
if (i == 0) {
minValue = type.value;
}
intToEnumMap.put(type.value, type);
if (minValue >= type.value) {
minValue = type.value;
}
if (maxValue < type.value) {
maxValue = type.value;
}
i = i + 1;
}
}
public static CalendarType fromInt(final int i) {
final CalendarType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
public boolean isCollection() {
return this.value.equals(CalendarType.COLLECTION.value);
}
}
@@ -0,0 +1,104 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.domain;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.StringUtils;
public enum CalendarWeekDaysType {
INVALID(0, "calendarWeekDaysType.invalid"), MO(1, "calendarWeekDaysType.monday"), TU(2, "calendarWeekDaysType.tuesday"), WE(3,
"calendarWeekDaysType.wednesday"), TH(4, "calendarWeekDaysType.thursday"), FR(5,
"calendarWeekDaysType.friday"), SA(6, "calendarWeekDaysType.saturday"), SU(7, "calendarWeekDaysType.sunday");
private final Integer value;
private final String code;
CalendarWeekDaysType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, CalendarWeekDaysType> intToEnumMap = new HashMap<>();
private static int minValue = CalendarWeekDaysType.MO.value;
private static int maxValue = CalendarWeekDaysType.SU.value;
static {
for (final CalendarWeekDaysType type : CalendarWeekDaysType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static CalendarWeekDaysType fromInt(final int i) {
final CalendarWeekDaysType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
public boolean isInvalid() {
return this.value.equals(CalendarWeekDaysType.INVALID.value);
}
public static CalendarWeekDaysType fromString(final String weekDayString) {
CalendarWeekDaysType weekDay = CalendarWeekDaysType.INVALID;
if (!StringUtils.hasText(weekDayString)) {
return weekDay;
}
if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.MO.toString())) {
weekDay = CalendarWeekDaysType.MO;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.TU.toString())) {
weekDay = CalendarWeekDaysType.TU;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.WE.toString())) {
weekDay = CalendarWeekDaysType.WE;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.TH.toString())) {
weekDay = CalendarWeekDaysType.TH;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.FR.toString())) {
weekDay = CalendarWeekDaysType.FR;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.SA.toString())) {
weekDay = CalendarWeekDaysType.SA;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.SU.toString())) {
weekDay = CalendarWeekDaysType.SU;
}
return weekDay;
}
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when Calendar resources are not found.
*/
public class CalendarDateException extends AbstractPlatformDomainRuleException {
public CalendarDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.calendar." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,32 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when Calendar resources are not found.
*/
public class CalendarParameterUpdateNotSupportedException extends AbstractPlatformDomainRuleException {
public CalendarParameterUpdateNotSupportedException(final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg.calendar.update.of." + postFix + ".is.not.supported", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,153 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy;
import org.apache.fineract.portfolio.calendar.domain.CalendarType;
import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
import org.apache.fineract.portfolio.common.domain.NthDayType;
public final class CalendarEnumerations {
private CalendarEnumerations() {
}
public static EnumOptionData calendarEntityType(final int id) {
return calendarEntityType(CalendarEntityType.fromInt(id));
}
public static EnumOptionData calendarEntityType(final CalendarEntityType entityType) {
final EnumOptionData optionData = new EnumOptionData(entityType.getValue().longValue(), entityType.getCode(),
entityType.toString());
return optionData;
}
public static List<EnumOptionData> calendarEntityType(final CalendarEntityType[] entityTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final CalendarEntityType entityType : entityTypes) {
optionDatas.add(calendarEntityType(entityType));
}
return optionDatas;
}
public static EnumOptionData calendarType(final int id) {
return calendarType(CalendarType.fromInt(id));
}
public static EnumOptionData calendarType(final CalendarType type) {
final EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), type.toString());
return optionData;
}
public static List<EnumOptionData> calendarType(final CalendarType[] types) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final CalendarType type : types) {
optionDatas.add(calendarType(type));
}
return optionDatas;
}
public static EnumOptionData calendarRemindBy(final int id) {
return calendarRemindBy(CalendarRemindBy.fromInt(id));
}
public static EnumOptionData calendarRemindBy(final CalendarRemindBy remindBy) {
final EnumOptionData optionData = new EnumOptionData(remindBy.getValue().longValue(), remindBy.getCode(), remindBy.toString());
return optionData;
}
public static List<EnumOptionData> calendarRemindBy(final CalendarRemindBy[] remindBys) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final CalendarRemindBy remindBy : remindBys) {
optionDatas.add(calendarRemindBy(remindBy));
}
return optionDatas;
}
public static EnumOptionData calendarFrequencyType(final int id) {
return calendarFrequencyType(CalendarFrequencyType.fromInt(id));
}
public static EnumOptionData calendarFrequencyType(final CalendarFrequencyType calendarFrequencyType) {
EnumOptionData optionData = null;
if (!calendarFrequencyType.isInvalid()) {
optionData = new EnumOptionData(calendarFrequencyType.getValue().longValue(), calendarFrequencyType.getCode(),
calendarFrequencyType.toString());
}
return optionData;
}
public static List<EnumOptionData> calendarFrequencyType(final CalendarFrequencyType[] calendarFrequencyTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final CalendarFrequencyType calendarFrequencyType : calendarFrequencyTypes) {
if (!calendarFrequencyType.isInvalid()) {
optionDatas.add(calendarFrequencyType(calendarFrequencyType));
}
}
return optionDatas;
}
public static EnumOptionData calendarWeekDaysType(final int id) {
return calendarWeekDaysType(CalendarWeekDaysType.fromInt(id));
}
public static EnumOptionData calendarWeekDaysType(final CalendarWeekDaysType calendarWeekDaysType) {
EnumOptionData optionData = null;
if (!calendarWeekDaysType.isInvalid()) {
optionData = new EnumOptionData(calendarWeekDaysType.getValue().longValue(), calendarWeekDaysType.getCode(),
calendarWeekDaysType.toString());
}
return optionData;
}
public static List<EnumOptionData> calendarWeekDaysType(final CalendarWeekDaysType[] calendarWeekDaysTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final CalendarWeekDaysType calendarWeekDaysType : calendarWeekDaysTypes) {
if (!calendarWeekDaysType.isInvalid()) {
optionDatas.add(calendarWeekDaysType(calendarWeekDaysType));
}
}
return optionDatas;
}
public static EnumOptionData calendarFrequencyNthDayType(final int id) {
return calendarFrequencyNthDayType(NthDayType.fromInt(id));
}
public static EnumOptionData calendarFrequencyNthDayType(final NthDayType calendarFrequencyNthDayType) {
final EnumOptionData optionData = new EnumOptionData(calendarFrequencyNthDayType.getValue().longValue(),
calendarFrequencyNthDayType.getCode(), calendarFrequencyNthDayType.toString());
return optionData;
}
public static List<EnumOptionData> calendarFrequencyNthDayType() {
final List<EnumOptionData> optionDatas = Arrays.asList(calendarFrequencyNthDayType(NthDayType.ONE),
calendarFrequencyNthDayType(NthDayType.TWO), calendarFrequencyNthDayType(NthDayType.THREE),
calendarFrequencyNthDayType(NthDayType.FOUR), calendarFrequencyNthDayType(NthDayType.LAST),
calendarFrequencyNthDayType(NthDayType.ONDAY));
return optionDatas;
}
}
@@ -0,0 +1,888 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.calendar.service;
import com.google.gson.JsonElement;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.NumberList;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.WeekDay;
import net.fortuna.ical4j.model.WeekDayList;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.RRule;
import net.fortuna.ical4j.validate.ValidationException;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
import org.apache.fineract.portfolio.common.domain.NthDayType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class CalendarUtils {
private CalendarUtils() {
}
private static final Logger LOG = LoggerFactory.getLogger(CalendarUtils.class);
static {
System.setProperty("net.fortuna.ical4j.timezone.date.floating", "true");
}
public static LocalDateTime getNextRecurringDate(final String recurringRule, final LocalDateTime seedDate,
final LocalDateTime startDate) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return null;
}
LocalDateTime nextDate = getNextRecurringDate(recur, seedDate, startDate);
nextDate = (LocalDateTime) adjustDate(nextDate, seedDate, getMeetingPeriodFrequencyType(recurringRule));
return nextDate;
}
public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return null;
}
LocalDate nextDate = getNextRecurringDate(recur, seedDate, startDate);
nextDate = (LocalDate) adjustDate(nextDate, seedDate, getMeetingPeriodFrequencyType(recurringRule));
return nextDate;
}
public static Temporal adjustDate(final Temporal date, final Temporal seedDate, final PeriodFrequencyType frequencyType) {
if (frequencyType.isMonthly() && seedDate.get(ChronoField.DAY_OF_MONTH) > 28 && date.get(ChronoField.DAY_OF_MONTH) >= 28) {
int noOfDaysInCurrentMonth = YearMonth.from(date).lengthOfMonth();
int seedDay = seedDate.get(ChronoField.DAY_OF_MONTH);
int adjustedDay = Math.min(noOfDaysInCurrentMonth, seedDay);
return date.with(ChronoField.DAY_OF_MONTH, adjustedDay);
}
return date;
}
private static LocalDateTime getNextRecurringDate(final Recur recur, final LocalDateTime seedDate, final LocalDateTime startDate) {
final DateTime periodStart = new DateTime(java.util.Date.from(startDate.atZone(ZoneId.systemDefault()).toInstant()));
final Date seed = convertToiCal4JCompatibleDate(seedDate);
final Date nextRecDate = recur.getNextDate(seed, periodStart);
return nextRecDate == null ? null : LocalDateTime.ofInstant(nextRecDate.toInstant(), DateUtils.getSystemZoneId());
}
private static LocalDate getNextRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate startDate) {
final DateTime periodStart = new DateTime(java.util.Date.from(startDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
final Date seed = convertToiCal4JCompatibleDate(seedDate.atStartOfDay());
final Date nextRecDate = recur.getNextDate(seed, periodStart);
return nextRecDate == null ? null : LocalDate.ofInstant(nextRecDate.toInstant(), DateUtils.getSystemZoneId());
}
private static Date convertToiCal4JCompatibleDate(final LocalDateTime inputDate) {
Date formattedDate = null;
final String seedDateStr = DateUtils.DEFAULT_DATETIME_FORMATTER.format(inputDate);
try {
formattedDate = new Date(seedDateStr, DateUtils.DEFAULT_DATETIME_FORMAT);
} catch (final ParseException e) {
LOG.error("Invalid date: {}", seedDateStr, e);
}
return formattedDate;
}
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate endDate) {
final LocalDate periodStartDate = DateUtils.getLocalDateOfTenant();
final LocalDate periodEndDate = (endDate == null) ? DateUtils.getLocalDateOfTenant().plusYears(5) : endDate;
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
}
public static Collection<LocalDate> getRecurringDatesFrom(final String recurringRule, final LocalDate seedDate,
final LocalDate startDate) {
final LocalDate periodStartDate = (startDate == null) ? DateUtils.getLocalDateOfTenant() : startDate;
final LocalDate periodEndDate = DateUtils.getLocalDateOfTenant().plusYears(5);
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
}
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
final LocalDate periodStartDate, final LocalDate periodEndDate) {
final int maxCount = 10;// Default number of recurring dates
boolean isSkipRepaymentOnFirstdayofMonth = false;
final Integer numberofDays = 0;
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount, isSkipRepaymentOnFirstdayofMonth,
numberofDays);
}
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount, boolean isSkippMeetingOnFirstDay,
final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return getRecurringDates(recur, seedDate, periodStartDate, periodEndDate, maxCount, isSkippMeetingOnFirstDay, numberOfDays);
}
private static Collection<LocalDate> getRecurringDates(final Recur recur, final LocalDate seedDate, final LocalDate periodStartDate,
final LocalDate periodEndDate, final int maxCount, boolean isSkippMeetingOnFirstDay, final Integer numberOfDays) {
if (recur == null) {
return null;
}
final Date seed = convertToiCal4JCompatibleDate(seedDate.atStartOfDay());
final DateTime periodStart = new DateTime(java.util.Date.from(periodStartDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
final DateTime periodEnd = new DateTime(java.util.Date.from(periodEndDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
final Value value = new Value(Value.DATE.getValue());
final DateList recurringDates = recur.getDates(seed, periodStart, periodEnd, value, maxCount);
return convertToLocalDateList(recurringDates, seedDate, getMeetingPeriodFrequencyType(recur), isSkippMeetingOnFirstDay,
numberOfDays);
}
private static Collection<LocalDate> convertToLocalDateList(final DateList dates, final LocalDate seedDate,
final PeriodFrequencyType frequencyType, boolean isSkippMeetingOnFirstDay, final Integer numberOfDays) {
final Collection<LocalDate> recurringDates = new ArrayList<>();
for (@SuppressWarnings("rawtypes")
final Iterator iterator = dates.iterator(); iterator.hasNext();) {
final Date date = (Date) iterator.next();
recurringDates.add((LocalDate) adjustDate(LocalDate.ofInstant(date.toInstant(), DateUtils.getDateTimeZoneOfTenant()), seedDate,
frequencyType));
}
if (isSkippMeetingOnFirstDay) {
return skipMeetingOnFirstdayOfMonth(recurringDates, numberOfDays);
}
return recurringDates;
}
private static Collection<LocalDate> skipMeetingOnFirstdayOfMonth(final Collection<LocalDate> recurringDates,
final Integer numberOfDays) {
final Collection<LocalDate> adjustedRecurringDates = new ArrayList<>();
for (@SuppressWarnings("rawtypes")
final Iterator iterator = recurringDates.iterator(); iterator.hasNext();) {
LocalDate recuringDate = (LocalDate) iterator.next();
adjustedRecurringDates.add(adjustRecurringDate(recuringDate, numberOfDays));
}
return adjustedRecurringDates;
}
public static LocalDate adjustRecurringDate(final LocalDate recuringDate, final Integer numberOfDays) {
if (recuringDate.getDayOfMonth() == 1) {
LocalDate adjustedRecurringDate = recuringDate.plusDays(numberOfDays);
return adjustedRecurringDate;
}
return recuringDate;
}
public static Recur getICalRecur(final String recurringRule) {
// Construct RRule
try {
final RRule rrule = new RRule(recurringRule);
rrule.validate();
final Recur recur = rrule.getRecur();
return recur;
} catch (final ParseException e) {
// TODO Auto-generated catch block
LOG.error("Problem occurred in getICalRecur function", e);
} catch (final ValidationException e) {
// TODO Auto-generated catch block
LOG.error("Problem occurred in getICalRecur function", e);
}
return null;
}
public static String getRRuleReadable(final LocalDate startDate, final String recurringRule) {
String humanReadable = "";
RRule rrule;
Recur recur = null;
try {
rrule = new RRule(recurringRule);
rrule.validate();
recur = rrule.getRecur();
} catch (final ValidationException e) {
throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule",
"The Recurring Rule value: " + recurringRule + " is not valid.", "recurrence", recurringRule, e);
} catch (final ParseException e) {
throw new PlatformDataIntegrityException("error.msg.recurring.rule.parsing.error",
"Error in pasring the Recurring Rule value: " + recurringRule, "recurrence", recurringRule, e);
}
if (recur == null) {
return humanReadable;
}
if (recur.getFrequency().equals(Recur.Frequency.DAILY)) {
if (recur.getInterval() == 1) {
humanReadable = "Daily";
} else {
humanReadable = "Every " + recur.getInterval() + " days";
}
} else if (recur.getFrequency().equals(Recur.Frequency.WEEKLY)) {
if (recur.getInterval() == 1 || recur.getInterval() == -1) {
humanReadable = "Weekly";
} else {
humanReadable = "Every " + recur.getInterval() + " weeks";
}
humanReadable += " on ";
final WeekDayList weekDayList = recur.getDayList();
for (@SuppressWarnings("rawtypes")
final Iterator iterator = weekDayList.iterator(); iterator.hasNext();) {
final WeekDay weekDay = (WeekDay) iterator.next();
humanReadable += DayNameEnum.from(weekDay.getDay().name()).getCode();
}
} else if (recur.getFrequency().equals(Recur.Frequency.MONTHLY)) {
NumberList nthDays = recur.getSetPosList();
Integer nthDay = null;
if (!nthDays.isEmpty()) {
nthDay = nthDays.get(0);
}
NumberList monthDays = recur.getMonthDayList();
Integer monthDay = null;
if (!monthDays.isEmpty()) {
monthDay = monthDays.get(0);
}
WeekDayList weekdays = recur.getDayList();
WeekDay weekDay = null;
if (!weekdays.isEmpty()) {
weekDay = weekdays.get(0);
}
if (nthDay != null && weekDay != null) {
NthDayType nthDayType = NthDayType.fromInt(nthDay);
NthDayNameEnum nthDayName = NthDayNameEnum.from(nthDayType.toString());
DayNameEnum weekdayType = DayNameEnum.from(weekDay.getDay().name());
if (recur.getInterval() == 1 || recur.getInterval() == -1) {
humanReadable = "Monthly on " + nthDayName.getCode().toLowerCase() + " " + weekdayType.getCode().toLowerCase();
} else {
humanReadable = "Every " + recur.getInterval() + " months on " + nthDayName.getCode().toLowerCase() + " "
+ weekdayType.getCode().toLowerCase();
}
} else if (monthDay != null) {
if (monthDay == -1) {
if (recur.getInterval() == 1 || recur.getInterval() == -1) {
humanReadable = "Monthly on last day";
} else {
humanReadable = "Every " + recur.getInterval() + " months on last day";
}
} else {
if (recur.getInterval() == 1 || recur.getInterval() == -1) {
humanReadable = "Monthly on day " + monthDay;
} else {
humanReadable = "Every " + recur.getInterval() + " months on day " + monthDay;
}
}
} else {
if (recur.getInterval() == 1 || recur.getInterval() == -1) {
humanReadable = "Monthly on day " + startDate.getDayOfMonth();
} else {
humanReadable = "Every " + recur.getInterval() + " months on day " + startDate.getDayOfMonth();
}
}
} else if (recur.getFrequency().equals(Recur.Frequency.YEARLY)) {
if (recur.getInterval() == 1) {
humanReadable = "Annually on " + startDate.format(DateTimeFormatter.ofPattern("MMM")) + " " + startDate.getDayOfMonth();
} else {
humanReadable = "Every " + recur.getInterval() + " years on " + startDate.format(DateTimeFormatter.ofPattern("MMM")) + " "
+ startDate.getDayOfMonth();
}
}
if (recur.getCount() > 0) {
if (recur.getCount() == 1) {
humanReadable = "Once";
}
humanReadable += ", " + recur.getCount() + " times";
}
final Date endDate = recur.getUntil();
if (endDate != null) {
final LocalDate date = LocalDate.ofInstant(endDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MMMM yy");
final String formattedDate = date.format(fmt);
humanReadable += ", until " + formattedDate;
}
return humanReadable;
}
public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return false;
}
final boolean isSkipRepaymentonFirstDayOfMonth = false;
final int numberOfDays = 0;
return isValidRecurringDate(recur, seedDate, date, isSkipRepaymentonFirstDayOfMonth, numberOfDays);
}
public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date,
boolean isSkipRepaymentonFirstDayOfMonth, final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return false;
}
return isValidRecurringDate(recur, seedDate, date, isSkipRepaymentonFirstDayOfMonth, numberOfDays);
}
public static boolean isValidRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate date,
boolean isSkipRepaymentonFirstDayOfMonth, final int numberOfDays) {
LocalDate startDate = date;
if (isSkipRepaymentonFirstDayOfMonth && date.getDayOfMonth() == (numberOfDays + 1)) {
startDate = startDate.minusDays(numberOfDays);
}
final Collection<LocalDate> recurDate = getRecurringDates(recur, seedDate, startDate, date.plusDays(1), 1,
isSkipRepaymentonFirstDayOfMonth, numberOfDays);
return (recurDate == null || recurDate.isEmpty()) ? false : recurDate.contains(date);
}
public enum DayNameEnum {
MO(1, "Monday"), TU(2, "Tuesday"), WE(3, "Wednesday"), TH(4, "Thursday"), FR(5, "Friday"), SA(6, "Saturday"), SU(7, "Sunday");
private final String code;
private final Integer value;
DayNameEnum(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public String getCode() {
return this.code;
}
public int getValue() {
return this.value;
}
public static DayNameEnum from(final String name) {
for (final DayNameEnum dayName : DayNameEnum.values()) {
if (dayName.toString().equals(name)) {
return dayName;
}
}
return DayNameEnum.MO;// Default it to Monday
}
}
public enum NthDayNameEnum {
ONE(1, "First"), TWO(2, "Second"), THREE(3, "Third"), FOUR(4, "Fourth"), FIVE(5, "Fifth"), LAST(-1, "Last"), INVALID(0, "Invalid");
private final String code;
private final Integer value;
NthDayNameEnum(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public String getCode() {
return this.code;
}
public int getValue() {
return this.value;
}
public static NthDayNameEnum from(final String name) {
for (final NthDayNameEnum nthDayName : NthDayNameEnum.values()) {
if (nthDayName.toString().equals(name)) {
return nthDayName;
}
}
return NthDayNameEnum.INVALID;
}
}
public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return getMeetingPeriodFrequencyType(recur);
}
private static PeriodFrequencyType getMeetingPeriodFrequencyType(final Recur recur) {
PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID;
if (recur.getFrequency().equals(Recur.Frequency.DAILY)) {
meetingFrequencyType = PeriodFrequencyType.DAYS;
} else if (recur.getFrequency().equals(Recur.Frequency.WEEKLY)) {
meetingFrequencyType = PeriodFrequencyType.WEEKS;
} else if (recur.getFrequency().equals(Recur.Frequency.MONTHLY)) {
meetingFrequencyType = PeriodFrequencyType.MONTHS;
} else if (recur.getFrequency().equals(Recur.Frequency.YEARLY)) {
meetingFrequencyType = PeriodFrequencyType.YEARS;
}
return meetingFrequencyType;
}
public static String getMeetingFrequencyFromPeriodFrequencyType(final PeriodFrequencyType periodFrequency) {
String frequency = null;
if (periodFrequency.equals(PeriodFrequencyType.DAYS)) {
frequency = Recur.Frequency.DAILY.name();
} else if (periodFrequency.equals(PeriodFrequencyType.WEEKS)) {
frequency = Recur.Frequency.WEEKLY.name();
} else if (periodFrequency.equals(PeriodFrequencyType.MONTHS)) {
frequency = Recur.Frequency.MONTHLY.name();
} else if (periodFrequency.equals(PeriodFrequencyType.YEARS)) {
frequency = Recur.Frequency.YEARLY.name();
}
return frequency;
}
public static int getInterval(final String recurringRule) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return recur.getInterval();
}
public static CalendarFrequencyType getFrequency(final String recurringRule) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return CalendarFrequencyType.fromString(recur.getFrequency().name());
}
public static CalendarWeekDaysType getRepeatsOnDay(final String recurringRule) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
final WeekDayList weekDays = recur.getDayList();
if (weekDays.isEmpty()) {
return CalendarWeekDaysType.INVALID;
}
// supports only one day
WeekDay weekDay = weekDays.get(0);
return CalendarWeekDaysType.fromString(weekDay.getDay().name());
}
public static NthDayType getRepeatsOnNthDayOfMonth(final String recurringRule) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
NumberList monthDays = null;
if (recur.getDayList().isEmpty()) {
monthDays = recur.getMonthDayList();
} else {
monthDays = recur.getSetPosList();
}
if (monthDays.isEmpty()) {
return NthDayType.INVALID;
}
if (!recur.getMonthDayList().isEmpty() && recur.getSetPosList().isEmpty()) {
return NthDayType.ONDAY;
}
Integer monthDay = monthDays.get(0);
return NthDayType.fromInt(monthDay);
}
public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate,
final Integer loanRepaymentInterval, final String frequency, boolean isSkipRepaymentOnFirstDayOfMonth,
final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
if (recur == null) {
return null;
}
LocalDate startDate = disbursementDate;
final LocalDate seedDate = calendar.getStartDateLocalDate();
if (isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)
&& !frequency.equals(Recur.Frequency.DAILY.name())) {
startDate = startDate.plusDays(1);
}
// Recurring dates should follow loanRepaymentInterval.
// e.g.
// for weekly meeting interval is 1
// where as for loan product with fortnightly frequency interval is 2
// to generate currect set of meeting dates reset interval same as loan
// repayment interval.
Recur.Builder recurBuilder = getRecurBuilder(recur);
recurBuilder = recurBuilder.interval(loanRepaymentInterval);
// Recurring dates should follow loanRepayment frequency.
// e.g.
// daily meeting frequency should support all loan products with any
// frequency type.
// to generate currect set of meeting dates reset frequency same as loan
// repayment frequency.
if (recur.getFrequency().equals(Recur.Frequency.DAILY)) {
recurBuilder = recurBuilder.frequency(Recur.Frequency.valueOf(frequency));
}
Recur modifiedRecur = recurBuilder.build();
final LocalDate firstRepaymentDate = getNextRecurringDate(modifiedRecur, seedDate, startDate);
if (isSkipRepaymentOnFirstDayOfMonth && firstRepaymentDate.getDayOfMonth() == 1) {
return adjustRecurringDate(firstRepaymentDate, numberOfDays);
}
return firstRepaymentDate;
}
public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
final boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return null;
}
if (isValidRecurringDate(recur, seedDate, oldRepaymentDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) {
return oldRepaymentDate;
}
LocalDate nextRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, oldRepaymentDate, loanRepaymentInterval,
frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
return nextRepaymentDate;
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate repaymentDate,
final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return null;
}
LocalDate tmpDate = repaymentDate;
if (isValidRecurringDate(recur, seedDate, repaymentDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) {
tmpDate = repaymentDate.plusDays(1);
}
/*
* Recurring dates should follow loanRepaymentInterval.
*
* e.g. The weekly meeting will have interval of 1, if the loan product with fortnightly frequency will have
* interval of 2, to generate right set of meeting dates reset interval same as loan repayment interval.
*/
Recur.Builder recurBuilder = getRecurBuilder(recur);
recurBuilder = recurBuilder.interval(loanRepaymentInterval);
/*
* Recurring dates should follow loanRepayment frequency. //e.g. daily meeting frequency should support all loan
* products with any type of frequency. to generate right set of meeting dates reset frequency same as loan
* repayment frequency.
*/
if (recur.getFrequency().equals(Recur.Frequency.DAILY)) {
recurBuilder = recurBuilder.frequency(Recur.Frequency.valueOf(frequency));
}
Recur modifiedRecur = recurBuilder.build();
LocalDate newRepaymentDate = getNextRecurringDate(modifiedRecur, seedDate, tmpDate);
final LocalDate nextRepaymentDate = getNextRecurringDate(modifiedRecur, seedDate, newRepaymentDate);
newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
if (isSkipRepaymentOnFirstDayOfMonth) {
LocalDate newRepaymentDateTemp = adjustRecurringDate(newRepaymentDate, numberOfDays);
return WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDateTemp, nextRepaymentDate, workingDays);
}
return newRepaymentDate;
}
public static boolean isFrequencySame(final String oldRRule, final String newRRule) {
final Recur oldRecur = getICalRecur(oldRRule);
final Recur newRecur = getICalRecur(newRRule);
if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) {
return false;
}
return oldRecur.getFrequency().equals(newRecur.getFrequency());
}
public static boolean isIntervalSame(final String oldRRule, final String newRRule) {
final Recur oldRecur = getICalRecur(oldRRule);
final Recur newRecur = getICalRecur(newRRule);
if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) {
return false;
}
return (oldRecur.getInterval() == newRecur.getInterval());
}
public static List<Integer> createIntegerListFromQueryParameter(final String calendarTypeQuery) {
final List<Integer> calendarTypeOptions = new ArrayList<>();
// adding all calendar Types if query parameter is "all"
if (calendarTypeQuery.equalsIgnoreCase("all")) {
calendarTypeOptions.add(1);
calendarTypeOptions.add(2);
calendarTypeOptions.add(3);
calendarTypeOptions.add(4);
return calendarTypeOptions;
}
// creating a list of calendar type options from the comma separated
// query parameter.
final List<String> calendarTypeOptionsInQuery = new ArrayList<>();
final StringTokenizer st = new StringTokenizer(calendarTypeQuery, ",");
while (st.hasMoreElements()) {
calendarTypeOptionsInQuery.add(st.nextElement().toString());
}
for (final String calType : calendarTypeOptionsInQuery) {
if (calType.equalsIgnoreCase("collection")) {
calendarTypeOptions.add(1);
} else if (calType.equalsIgnoreCase("training")) {
calendarTypeOptions.add(2);
} else if (calType.equalsIgnoreCase("audit")) {
calendarTypeOptions.add(3);
} else if (calType.equalsIgnoreCase("general")) {
calendarTypeOptions.add(4);
}
}
return calendarTypeOptions;
}
/**
* function returns a comma separated list of calendar_type_enum values ex. 1,2,3,4
*
* @param calendarTypeOptions
* @return
*/
public static String getSqlCalendarTypeOptionsInString(final List<Integer> calendarTypeOptions) {
String sqlCalendarTypeOptions = "";
final int size = calendarTypeOptions.size();
for (int i = 0; i < size - 1; i++) {
sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ",";
}
sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString();
return sqlCalendarTypeOptions;
}
public static LocalDate getRecentEligibleMeetingDate(final String recurringRule, final LocalDate seedDate,
final boolean isSkipMeetingOnFirstDay, final Integer numberOfDays) {
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return null;
}
if (isValidRecurringDate(recur, seedDate, currentDate, isSkipMeetingOnFirstDay, numberOfDays)) {
return currentDate;
}
if (recur.getFrequency().equals(Recur.Frequency.DAILY)) {
currentDate = currentDate.plusDays(recur.getInterval());
} else if (recur.getFrequency().equals(Recur.Frequency.WEEKLY)) {
currentDate = currentDate.plusWeeks(recur.getInterval());
} else if (recur.getFrequency().equals(Recur.Frequency.MONTHLY)) {
currentDate = currentDate.plusMonths(recur.getInterval());
} else if (recur.getFrequency().equals(Recur.Frequency.YEARLY)) {
currentDate = currentDate.plusYears(recur.getInterval());
}
return getNextRecurringDate(recur, seedDate, currentDate);
}
public static LocalDate getNextScheduleDate(final Calendar calendar, final LocalDate startDate) {
final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
if (recur == null) {
return null;
}
LocalDate date = startDate;
final LocalDate seedDate = calendar.getStartDateLocalDate();
/**
* if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) { date = date.plusDays(1); }
**/
final LocalDate scheduleDate = getNextRecurringDate(recur, seedDate, date);
return scheduleDate;
}
public static void validateNthDayOfMonthFrequency(DataValidatorBuilder baseDataValidator, final String repeatsOnNthDayOfMonthParamName,
final String repeatsOnDayParamName, final JsonElement element, final FromJsonHelper fromApiJsonHelper) {
final Integer repeatsOnNthDayOfMonth = fromApiJsonHelper.extractIntegerSansLocaleNamed(repeatsOnNthDayOfMonthParamName, element);
baseDataValidator.reset().parameter(repeatsOnNthDayOfMonthParamName).value(repeatsOnNthDayOfMonth).ignoreIfNull()
.isOneOfTheseValues(NthDayType.ONE.getValue(), NthDayType.TWO.getValue(), NthDayType.THREE.getValue(),
NthDayType.FOUR.getValue(), NthDayType.LAST.getValue(), NthDayType.ONDAY.getValue());
final Integer repeatsOnDay = fromApiJsonHelper.extractIntegerSansLocaleNamed(repeatsOnDayParamName, element);
baseDataValidator.reset().parameter(repeatsOnDayParamName).value(repeatsOnDay).ignoreIfNull()
.inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue());
NthDayType nthDayType = null;
if (repeatsOnNthDayOfMonth != null) {
nthDayType = NthDayType.fromInt(repeatsOnNthDayOfMonth);
}
if (nthDayType != null && nthDayType != NthDayType.INVALID) {
if (nthDayType == NthDayType.ONE || nthDayType == NthDayType.TWO || nthDayType == NthDayType.THREE
|| nthDayType == NthDayType.FOUR) {
baseDataValidator.reset().parameter(repeatsOnDayParamName).value(repeatsOnDay).cantBeBlankWhenParameterProvidedIs(
repeatsOnNthDayOfMonthParamName, NthDayNameEnum.from(nthDayType.toString()).getCode().toLowerCase());
}
}
}
public static Integer getMonthOnDay(String recurringRule) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
NumberList monthDayList = null;
Integer monthOnDay = null;
if (getMeetingPeriodFrequencyType(recur).isMonthly()) {
monthDayList = recur.getMonthDayList();
if (!monthDayList.isEmpty()) {
monthOnDay = monthDayList.get(0);
}
}
return monthOnDay;
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate repaymentDate,
final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean applyWorkingDays) {
boolean isCalledFirstTime = true;
return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency, workingDays,
isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate repaymentDate,
final Integer loanRepaymentInterval, final String frequency, boolean isSkipRepaymentOnFirstDayOfMonth,
final Integer numberOfDays) {
boolean isCalledFirstTime = true;
final WorkingDays workingDays = null;
boolean applyWorkingDays = false;
return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency, workingDays,
isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate repaymentDate,
final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean isCalledFirstTime, boolean applyWorkingDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return null;
}
LocalDate tmpDate = repaymentDate;
final Integer repaymentInterval = getMeetingIntervalFromFrequency(loanRepaymentInterval, frequency, recur);
/*
* Recurring dates should follow loanRepaymentInterval.
*
* e.g. The weekly meeting will have interval of 1, if the loan product with fortnightly frequency will have
* interval of 2, to generate right set of meeting dates reset interval same as loan repayment interval.
*/
int meetingInterval = recur.getInterval();
if (meetingInterval < 1) {
meetingInterval = 1;
}
int rep = repaymentInterval < meetingInterval ? 1 : repaymentInterval / meetingInterval;
/*
* Recurring dates should follow loanRepayment frequency. //e.g. daily meeting frequency should support all loan
* products with any type of frequency. to generate right set of meeting dates reset frequency same as loan
* repayment frequency.
*/
Recur.Builder recurBuilder = getRecurBuilder(recur);
if (recur.getFrequency().equals(Recur.Frequency.DAILY)) {
recurBuilder = recurBuilder.frequency(Recur.Frequency.valueOf(frequency));
}
Recur modifiedRecur = recurBuilder.build();
/**
* Below code modified as discussed with Pramod N
*/
LocalDate newRepaymentDate = tmpDate;
int newRepayment = rep;
while (newRepayment > 0) {
newRepaymentDate = getNextRecurringDate(modifiedRecur, seedDate, newRepaymentDate);
newRepayment--;
}
LocalDate nextRepaymentDate = null;
if (applyWorkingDays) {
if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDate)
&& WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDate).isMoveToNextRepaymentDay()) {
newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1), loanRepaymentInterval,
frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
} else {
newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
}
}
if (isCalledFirstTime && newRepaymentDate.equals(repaymentDate)) {
isCalledFirstTime = false;
newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate.plusDays(1), loanRepaymentInterval,
frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
}
if (isSkipRepaymentOnFirstDayOfMonth) {
final LocalDate newRepaymentDateTemp = adjustRecurringDate(newRepaymentDate, numberOfDays);
if (applyWorkingDays) {
if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDateTemp)
&& WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDateTemp).isMoveToNextRepaymentDay()) {
newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1),
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays,
isCalledFirstTime, applyWorkingDays);
} else {
newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDateTemp, nextRepaymentDate, workingDays);
}
}
}
return newRepaymentDate;
}
public static Integer getMeetingIntervalFromFrequency(final Integer loanRepaymentInterval, final String frequency, final Recur recur) {
final Integer interval = 4;
Integer repaymentInterval = loanRepaymentInterval;
/*
* check loanRepaymentInterval equal to 1, if repayments frequency is monthly and meeting frequency is weekly,
* then generate repayments schedule as every 4 weeks
*/
if (frequency.equals(Recur.Frequency.MONTHLY.name()) && recur.getFrequency().equals(Recur.Frequency.WEEKLY)) {
repaymentInterval = loanRepaymentInterval * interval;
}
return repaymentInterval;
}
private static Recur.Builder getRecurBuilder(Recur recur) {
Recur.Builder recurBuilder = new Recur.Builder();
recurBuilder = recurBuilder.frequency(recur.getFrequency()).until(recur.getUntil()).count(recur.getCount())
.interval(recur.getInterval()).secondList(recur.getSecondList()).minuteList(recur.getMinuteList())
.hourList(recur.getHourList()).dayList(recur.getDayList()).monthDayList(recur.getMonthDayList())
.yearDayList(recur.getYearDayList()).weekNoList(recur.getWeekNoList()).monthList(recur.getMonthList())
.setPosList(recur.getSetPosList()).weekStartDay(recur.getWeekStartDay());
return recurBuilder;
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.api;
public final class ChargesApiConstants {
private ChargesApiConstants() {
}
public static final String glAccountIdParamName = "incomeAccountId";
public static final String taxGroupIdParamName = "taxGroupId";
}
@@ -0,0 +1,310 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.MonthDay;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
/**
* Immutable data object for charge data.
*/
@Getter
public final class ChargeData implements Comparable<ChargeData>, Serializable {
private final Long id;
private final String name;
private final boolean active;
private final boolean penalty;
private final boolean freeWithdrawal;
private final Integer freeWithdrawalChargeFrequency;
private final Integer restartFrequency;
private final Integer restartFrequencyEnum;
private final boolean isPaymentType;
private final PaymentTypeData paymentTypeOptions;
private final CurrencyData currency;
private final BigDecimal amount;
private final EnumOptionData chargeTimeType;
private final EnumOptionData chargeAppliesTo;
private final EnumOptionData chargeCalculationType;
private final EnumOptionData chargePaymentMode;
private final MonthDay feeOnMonthDay;
private final Integer feeInterval;
private final BigDecimal minCap;
private final BigDecimal maxCap;
private final EnumOptionData feeFrequency;
private final GLAccountData incomeOrLiabilityAccount;
private final TaxGroupData taxGroup;
// template attributes
private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;//
private final List<EnumOptionData> chargeAppliesToOptions;//
private final List<EnumOptionData> chargeTimeTypeOptions;//
private final List<EnumOptionData> chargePaymetModeOptions;//
private final List<EnumOptionData> loanChargeCalculationTypeOptions;
private final List<EnumOptionData> loanChargeTimeTypeOptions;
private final List<EnumOptionData> savingsChargeCalculationTypeOptions;
private final List<EnumOptionData> savingsChargeTimeTypeOptions;
private final List<EnumOptionData> clientChargeCalculationTypeOptions;
private final List<EnumOptionData> clientChargeTimeTypeOptions;
private final List<EnumOptionData> shareChargeCalculationTypeOptions;
private final List<EnumOptionData> shareChargeTimeTypeOptions;
private final List<EnumOptionData> feeFrequencyOptions;
private final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions;
private final Collection<TaxGroupData> taxGroupOptions;
private final String accountMappingForChargeConfig;
private final List<GLAccountData> expenseAccountOptions;
private final List<GLAccountData> assetAccountOptions;
public static ChargeData template(final Collection<CurrencyData> currencyOptions,
final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
final List<EnumOptionData> chargeTimeTypeOptions, final List<EnumOptionData> chargePaymentModeOptions,
final List<EnumOptionData> loansChargeCalculationTypeOptions, final List<EnumOptionData> loansChargeTimeTypeOptions,
final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> savingsChargeTimeTypeOptions,
final List<EnumOptionData> clientChargeCalculationTypeOptions, final List<EnumOptionData> clientChargeTimeTypeOptions,
final List<EnumOptionData> feeFrequencyOptions, final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions,
final Collection<TaxGroupData> taxGroupOptions, final List<EnumOptionData> shareChargeCalculationTypeOptions,
final List<EnumOptionData> shareChargeTimeTypeOptions, String accountMappingForChargeConfig,
List<GLAccountData> expenseAccountOptions, List<GLAccountData> assetAccountOptions) {
final GLAccountData account = null;
final TaxGroupData taxGroupData = null;
return new ChargeData(null, null, null, null, null, null, null, null, false, false, false, null, null, null, false, null,
taxGroupData, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
clientChargeTimeTypeOptions, null, null, null, null, null, feeFrequencyOptions, account, incomeOrLiabilityAccountOptions,
taxGroupOptions, shareChargeCalculationTypeOptions, shareChargeTimeTypeOptions, accountMappingForChargeConfig,
expenseAccountOptions, assetAccountOptions);
}
public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) {
return new ChargeData(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, charge.chargeAppliesTo,
charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, charge.freeWithdrawal,
charge.freeWithdrawalChargeFrequency, charge.restartFrequency, charge.restartFrequencyEnum, charge.isPaymentType,
charge.paymentTypeOptions, charge.taxGroup, template.currencyOptions, template.chargeCalculationTypeOptions,
template.chargeAppliesToOptions, template.chargeTimeTypeOptions, template.chargePaymetModeOptions,
template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions, template.savingsChargeCalculationTypeOptions,
template.savingsChargeTimeTypeOptions, template.clientChargeCalculationTypeOptions, template.clientChargeTimeTypeOptions,
charge.feeOnMonthDay, charge.feeInterval, charge.minCap, charge.maxCap, charge.feeFrequency, template.feeFrequencyOptions,
charge.incomeOrLiabilityAccount, template.incomeOrLiabilityAccountOptions, template.taxGroupOptions,
template.shareChargeCalculationTypeOptions, template.shareChargeTimeTypeOptions, template.accountMappingForChargeConfig,
template.expenseAccountOptions, template.assetAccountOptions);
}
public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final EnumOptionData chargePaymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final boolean penalty,
final boolean active, final boolean freeWithdrawal, final Integer freeWithdrawalChargeFrequency, final Integer restartFrequency,
final Integer restartFrequencyEnum, final boolean isPaymentType, final PaymentTypeData paymentTypeOptions,
final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency, final GLAccountData accountData,
TaxGroupData taxGroupData) {
final Collection<CurrencyData> currencyOptions = null;
final List<EnumOptionData> chargeCalculationTypeOptions = null;
final List<EnumOptionData> chargeAppliesToOptions = null;
final List<EnumOptionData> chargeTimeTypeOptions = null;
final List<EnumOptionData> chargePaymentModeOptions = null;
final List<EnumOptionData> loansChargeCalculationTypeOptions = null;
final List<EnumOptionData> loansChargeTimeTypeOptions = null;
final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
final List<EnumOptionData> savingsChargeTimeTypeOptions = null;
final List<EnumOptionData> feeFrequencyOptions = null;
final List<EnumOptionData> clientChargeCalculationTypeOptions = null;
final List<EnumOptionData> clientChargeTimeTypeOptions = null;
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
final List<EnumOptionData> shareChargeCalculationTypeOptions = null;
final List<EnumOptionData> shareChargeTimeTypeOptions = null;
final Collection<TaxGroupData> taxGroupOptions = null;
final String accountMappingForChargeConfig = null;
final List<GLAccountData> expenseAccountOptions = null;
final List<GLAccountData> assetAccountOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, freeWithdrawal, freeWithdrawalChargeFrequency, restartFrequency, restartFrequencyEnum, isPaymentType,
paymentTypeOptions, taxGroupData, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions,
chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, accountData,
incomeOrLiabilityAccountOptions, taxGroupOptions, shareChargeCalculationTypeOptions, shareChargeTimeTypeOptions,
accountMappingForChargeConfig, expenseAccountOptions, assetAccountOptions);
}
public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) {
final BigDecimal amount = null;
final CurrencyData currency = null;
final EnumOptionData chargeTimeType = null;
final EnumOptionData chargeAppliesTo = null;
final EnumOptionData chargeCalculationType = null;
final EnumOptionData chargePaymentMode = null;
final MonthDay feeOnMonthDay = null;
final Integer feeInterval = null;
final Boolean penalty = isPenalty;
final Boolean active = false;
final Boolean freeWithdrawal = false;
final Integer freeWithdrawalChargeFrequency = null;
final Integer restartFrequency = null;
final Integer restartFrequencyEnum = null;
final Boolean isPaymentType = false;
final PaymentTypeData paymentTypeOptions = null;
final BigDecimal minCap = null;
final BigDecimal maxCap = null;
final Collection<CurrencyData> currencyOptions = null;
final List<EnumOptionData> chargeCalculationTypeOptions = null;
final List<EnumOptionData> chargeAppliesToOptions = null;
final List<EnumOptionData> chargeTimeTypeOptions = null;
final List<EnumOptionData> chargePaymentModeOptions = null;
final List<EnumOptionData> loansChargeCalculationTypeOptions = null;
final List<EnumOptionData> loansChargeTimeTypeOptions = null;
final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
final List<EnumOptionData> savingsChargeTimeTypeOptions = null;
final List<EnumOptionData> clientChargeCalculationTypeOptions = null;
final List<EnumOptionData> clientChargeTimeTypeOptions = null;
final EnumOptionData feeFrequency = null;
final List<EnumOptionData> feeFrequencyOptions = null;
final GLAccountData account = null;
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
final List<EnumOptionData> shareChargeCalculationTypeOptions = null;
final List<EnumOptionData> shareChargeTimeTypeOptions = null;
final TaxGroupData taxGroupData = null;
final Collection<TaxGroupData> taxGroupOptions = null;
final String accountMappingForChargeConfig = null;
final List<GLAccountData> expenseAccountOptions = null;
final List<GLAccountData> assetAccountOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, freeWithdrawal, freeWithdrawalChargeFrequency, restartFrequency, restartFrequencyEnum, isPaymentType,
paymentTypeOptions, taxGroupData, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions,
chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, account,
incomeOrLiabilityAccountOptions, taxGroupOptions, shareChargeCalculationTypeOptions, shareChargeTimeTypeOptions,
accountMappingForChargeConfig, expenseAccountOptions, assetAccountOptions);
}
private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active, final boolean freeWithdrawal,
final Integer freeWithdrawalChargeFrequency, final Integer restartFrequency, final Integer restartFrequencyEnum,
final boolean isPaymentType, final PaymentTypeData paymentTypeOptions, final TaxGroupData taxGroupData,
final Collection<CurrencyData> currencyOptions, final List<EnumOptionData> chargeCalculationTypeOptions,
final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions,
final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
final List<EnumOptionData> loansChargeTimeTypeOptions, final List<EnumOptionData> savingsChargeCalculationTypeOptions,
final List<EnumOptionData> savingsChargeTimeTypeOptions, final List<EnumOptionData> clientChargeCalculationTypeOptions,
final List<EnumOptionData> clientChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval,
final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
final List<EnumOptionData> feeFrequencyOptions, final GLAccountData account,
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions, final Collection<TaxGroupData> taxGroupOptions,
final List<EnumOptionData> shareChargeCalculationTypeOptions, final List<EnumOptionData> shareChargeTimeTypeOptions,
final String accountMappingForChargeConfig, final List<GLAccountData> expenseAccountOptions,
final List<GLAccountData> assetAccountOptions) {
this.id = id;
this.name = name;
this.amount = amount;
this.currency = currency;
this.chargeTimeType = chargeTimeType;
this.chargeAppliesTo = chargeAppliesTo;
this.chargeCalculationType = chargeCalculationType;
this.chargePaymentMode = chargePaymentMode;
this.feeInterval = feeInterval;
this.feeOnMonthDay = feeOnMonthDay;
this.penalty = penalty;
this.active = active;
this.freeWithdrawal = freeWithdrawal;
this.freeWithdrawalChargeFrequency = freeWithdrawalChargeFrequency;
this.restartFrequency = restartFrequency;
this.restartFrequencyEnum = restartFrequencyEnum;
this.isPaymentType = isPaymentType;
this.paymentTypeOptions = paymentTypeOptions;
this.minCap = minCap;
this.maxCap = maxCap;
this.currencyOptions = currencyOptions;
this.chargeCalculationTypeOptions = chargeCalculationTypeOptions;
this.chargeAppliesToOptions = chargeAppliesToOptions;
this.chargeTimeTypeOptions = chargeTimeTypeOptions;
this.chargePaymetModeOptions = chargePaymentModeOptions;
this.savingsChargeCalculationTypeOptions = savingsChargeCalculationTypeOptions;
this.savingsChargeTimeTypeOptions = savingsChargeTimeTypeOptions;
this.clientChargeCalculationTypeOptions = clientChargeCalculationTypeOptions;
this.clientChargeTimeTypeOptions = clientChargeTimeTypeOptions;
this.loanChargeCalculationTypeOptions = loansChargeCalculationTypeOptions;
this.loanChargeTimeTypeOptions = loansChargeTimeTypeOptions;
this.feeFrequency = feeFrequency;
this.feeFrequencyOptions = feeFrequencyOptions;
this.incomeOrLiabilityAccount = account;
this.incomeOrLiabilityAccountOptions = incomeOrLiabilityAccountOptions;
this.taxGroup = taxGroupData;
this.taxGroupOptions = taxGroupOptions;
this.shareChargeCalculationTypeOptions = shareChargeCalculationTypeOptions;
this.shareChargeTimeTypeOptions = shareChargeTimeTypeOptions;
this.accountMappingForChargeConfig = accountMappingForChargeConfig;
this.assetAccountOptions = assetAccountOptions;
this.expenseAccountOptions = expenseAccountOptions;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof ChargeData)) {
return false;
}
final ChargeData chargeData = (ChargeData) obj;
return this.id.equals(chargeData.id);
}
@Override
public int hashCode() {
return this.id.hashCode();
}
@Override
public int compareTo(final ChargeData obj) {
if (obj == null) {
return -1;
}
return obj.id.compareTo(this.id);
}
public boolean isOverdueInstallmentCharge() {
boolean isOverdueInstallmentCharge = false;
if (this.chargeTimeType != null) {
isOverdueInstallmentCharge = ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isOverdueInstallment();
}
return isOverdueInstallmentCharge;
}
public boolean isIsPaymentType() {
return this.isPaymentType;
}
}
@@ -0,0 +1,783 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.domain;
import java.math.BigDecimal;
import java.time.MonthDay;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.api.ChargesApiConstants;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.exception.ChargeDueAtDisbursementCannotBePenaltyException;
import org.apache.fineract.portfolio.charge.exception.ChargeMustBePenaltyException;
import org.apache.fineract.portfolio.charge.exception.ChargeParameterUpdateNotSupportedException;
import org.apache.fineract.portfolio.charge.service.ChargeEnumerations;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
@Entity
@Table(name = "m_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "name") })
public class Charge extends AbstractPersistableCustom {
@Column(name = "name", length = 100)
private String name;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@Column(name = "currency_code", length = 3)
private String currencyCode;
@Column(name = "charge_applies_to_enum", nullable = false)
private Integer chargeAppliesTo;
@Column(name = "charge_time_enum", nullable = false)
private Integer chargeTimeType;
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@Column(name = "charge_payment_mode_enum", nullable = true)
private Integer chargePaymentMode;
@Column(name = "fee_on_day", nullable = true)
private Integer feeOnDay;
@Column(name = "fee_interval", nullable = true)
private Integer feeInterval;
@Column(name = "fee_on_month", nullable = true)
private Integer feeOnMonth;
@Column(name = "is_penalty", nullable = false)
private boolean penalty;
@Column(name = "is_active", nullable = false)
private boolean active;
@Column(name = "is_deleted", nullable = false)
private boolean deleted = false;
@Column(name = "min_cap", scale = 6, precision = 19, nullable = true)
private BigDecimal minCap;
@Column(name = "max_cap", scale = 6, precision = 19, nullable = true)
private BigDecimal maxCap;
@Column(name = "fee_frequency", nullable = true)
private Integer feeFrequency;
@Column(name = "is_free_withdrawal", nullable = false)
private boolean enableFreeWithdrawal;
@Column(name = "free_withdrawal_charge_frequency", nullable = true)
private Integer freeWithdrawalFrequency;
@Column(name = "restart_frequency", nullable = true)
private Integer restartFrequency;
@Column(name = "restart_frequency_enum", nullable = true)
private Integer restartFrequencyEnum;
@Column(name = "is_payment_type", nullable = false)
private boolean enablePaymentType;
@ManyToOne
@JoinColumn(name = "payment_type_id", nullable = false)
private PaymentType paymentType;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "income_or_liability_account_id")
private GLAccount account;
@ManyToOne
@JoinColumn(name = "tax_group_id")
private TaxGroup taxGroup;
public static Charge fromJson(final JsonCommand command, final GLAccount account, final TaxGroup taxGroup,
final PaymentType paymentType) {
final String name = command.stringValueOfParameterNamed("name");
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
final ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.integerValueOfParameterNamed("chargeAppliesTo"));
final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.integerValueOfParameterNamed("chargeTimeType"));
final ChargeCalculationType chargeCalculationType = ChargeCalculationType
.fromInt(command.integerValueOfParameterNamed("chargeCalculationType"));
final Integer chargePaymentMode = command.integerValueOfParameterNamed("chargePaymentMode");
final ChargePaymentMode paymentMode = chargePaymentMode == null ? null : ChargePaymentMode.fromInt(chargePaymentMode);
final boolean penalty = command.booleanPrimitiveValueOfParameterNamed("penalty");
final boolean active = command.booleanPrimitiveValueOfParameterNamed("active");
final MonthDay feeOnMonthDay = command.extractMonthDayNamed("feeOnMonthDay");
final Integer feeInterval = command.integerValueOfParameterNamed("feeInterval");
final BigDecimal minCap = command.bigDecimalValueOfParameterNamed("minCap");
final BigDecimal maxCap = command.bigDecimalValueOfParameterNamed("maxCap");
final Integer feeFrequency = command.integerValueOfParameterNamed("feeFrequency");
boolean enableFreeWithdrawalCharge = false;
enableFreeWithdrawalCharge = command.booleanPrimitiveValueOfParameterNamed("enableFreeWithdrawalCharge");
boolean enablePaymentType = false;
enablePaymentType = command.booleanPrimitiveValueOfParameterNamed("enablePaymentType");
Integer freeWithdrawalFrequency = null;
Integer restartCountFrequency = null;
PeriodFrequencyType countFrequencyType = null;
if (enableFreeWithdrawalCharge) {
freeWithdrawalFrequency = command.integerValueOfParameterNamed("freeWithdrawalFrequency");
restartCountFrequency = command.integerValueOfParameterNamed("restartCountFrequency");
countFrequencyType = PeriodFrequencyType.fromInt(command.integerValueOfParameterNamed("countFrequencyType"));
}
return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode,
feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, enableFreeWithdrawalCharge, freeWithdrawalFrequency,
restartCountFrequency, countFrequencyType, account, taxGroup, enablePaymentType, paymentType);
}
protected Charge() {}
private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active,
final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap,
final BigDecimal maxCap, final Integer feeFrequency, final boolean enableFreeWithdrawalCharge,
final Integer freeWithdrawalFrequency, final Integer restartFrequency, final PeriodFrequencyType restartFrequencyEnum,
final GLAccount account, final TaxGroup taxGroup, final boolean enablePaymentType, final PaymentType paymentType) {
this.name = name;
this.amount = amount;
this.currencyCode = currencyCode;
this.chargeAppliesTo = chargeAppliesTo.getValue();
this.chargeTimeType = chargeTime.getValue();
this.chargeCalculation = chargeCalculationType.getValue();
this.penalty = penalty;
this.active = active;
this.account = account;
this.taxGroup = taxGroup;
this.chargePaymentMode = paymentMode == null ? null : paymentMode.getValue();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
if (isMonthlyFee() || isAnnualFee()) {
this.feeOnMonth = feeOnMonthDay.getMonthValue();
this.feeOnDay = feeOnMonthDay.getDayOfMonth();
}
this.feeInterval = feeInterval;
this.feeFrequency = feeFrequency;
if (isSavingsCharge()) {
// TODO vishwas, this validation seems unnecessary as identical
// validation is performed in the write service
if (!isAllowedSavingsChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
}
// TODO vishwas, this validation seems unnecessary as identical
// validation is performed in the writeservice
if (!isAllowedSavingsChargeCalculationType()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
}
if (!(ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee()
|| ChargeTimeType.fromInt(getChargeTimeType()).isSavingsNoActivityFee())
&& ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode(
"savings.charge.calculation.type.percentage.allowed.only.for.withdrawal.or.NoActivity");
}
if (enableFreeWithdrawalCharge) {
this.enableFreeWithdrawal = true;
this.freeWithdrawalFrequency = freeWithdrawalFrequency;
this.restartFrequency = restartFrequency;
this.restartFrequencyEnum = restartFrequencyEnum.getValue();
}
if (enablePaymentType) {
if (paymentType != null) {
this.enablePaymentType = true;
this.paymentType = paymentType;
}
}
} else if (isLoanCharge()) {
if (penalty && (chargeTime.isTimeOfDisbursement() || chargeTime.isTrancheDisbursement())) {
throw new ChargeDueAtDisbursementCannotBePenaltyException(name);
}
if (!penalty && chargeTime.isOverdueInstallment()) {
throw new ChargeMustBePenaltyException(name);
}
// TODO vishwas, this validation seems unnecessary as identical
// validation is performed in the write service
if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
}
}
if (isPercentageOfApprovedAmount()) {
this.minCap = minCap;
this.maxCap = maxCap;
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public String getName() {
return this.name;
}
public BigDecimal getAmount() {
return this.amount;
}
public String getCurrencyCode() {
return this.currencyCode;
}
public Integer getChargeTimeType() {
return this.chargeTimeType;
}
public Integer getChargeCalculation() {
return this.chargeCalculation;
}
public boolean isActive() {
return this.active;
}
public boolean isPenalty() {
return this.penalty;
}
public boolean isDeleted() {
return this.deleted;
}
public boolean isLoanCharge() {
return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isLoanCharge();
}
public boolean isAllowedLoanChargeTime() {
return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedLoanChargeTime();
}
public boolean isAllowedClientChargeTime() {
return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedClientChargeTime();
}
public boolean isSavingsCharge() {
return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isSavingsCharge();
}
public boolean isClientCharge() {
return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isClientCharge();
}
public boolean isAllowedSavingsChargeTime() {
return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedSavingsChargeTime();
}
public boolean isAllowedSavingsChargeCalculationType() {
return ChargeCalculationType.fromInt(this.chargeCalculation).isAllowedSavingsChargeCalculationType();
}
public boolean isAllowedClientChargeCalculationType() {
return ChargeCalculationType.fromInt(this.chargeCalculation).isAllowedClientChargeCalculationType();
}
public boolean isPercentageOfApprovedAmount() {
return ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount();
}
public boolean isPercentageOfDisbursementAmount() {
return ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfDisbursementAmount();
}
public BigDecimal getMinCap() {
return this.minCap;
}
public BigDecimal getMaxCap() {
return this.maxCap;
}
public boolean isEnableFreeWithdrawal() {
return this.enableFreeWithdrawal;
}
public boolean isEnablePaymentType() {
return this.enablePaymentType;
}
public Integer getFrequencyFreeWithdrawalCharge() {
return this.freeWithdrawalFrequency;
}
public Integer getRestartFrequency() {
return this.restartFrequency;
}
public Integer getRestartFrequencyEnum() {
return this.restartFrequencyEnum;
}
public PaymentType getPaymentType() {
return this.paymentType;
}
public void setPaymentType(PaymentType paymentType) {
this.paymentType = paymentType;
}
private Long getPaymentTypeId() {
Long paymentTypeId = null;
if (this.paymentType != null) {
paymentTypeId = this.paymentType.getId();
}
return paymentTypeId;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
final Locale locale = command.extractLocale();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
final String nameParamName = "name";
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(nameParamName);
actualChanges.put(nameParamName, newValue);
this.name = newValue;
}
final String currencyCodeParamName = "currencyCode";
if (command.isChangeInStringParameterNamed(currencyCodeParamName, this.currencyCode)) {
final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
actualChanges.put(currencyCodeParamName, newValue);
this.currencyCode = newValue;
}
final String amountParamName = "amount";
if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName, locale);
actualChanges.put(amountParamName, newValue);
actualChanges.put("locale", locale.getLanguage());
this.amount = newValue;
}
final String chargeTimeParamName = "chargeTimeType";
if (command.isChangeInIntegerParameterNamed(chargeTimeParamName, this.chargeTimeType)) {
final Integer newValue = command.integerValueOfParameterNamed(chargeTimeParamName);
actualChanges.put(chargeTimeParamName, newValue);
actualChanges.put("locale", locale.getLanguage());
this.chargeTimeType = ChargeTimeType.fromInt(newValue).getValue();
if (isSavingsCharge()) {
if (!isAllowedSavingsChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
}
// if charge time is changed to monthly then validate for
// feeOnMonthDay and feeInterval
if (isMonthlyFee()) {
final MonthDay monthDay = command.extractMonthDayNamed("feeOnMonthDay");
baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
final Integer feeInterval = command.integerValueOfParameterNamed("feeInterval");
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull().inMinMaxRange(1, 12);
}
} else if (isLoanCharge()) {
if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
}
} else if (isClientCharge()) {
if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.client");
}
}
}
final String freeWithdrawalFrequencyParamName = "freeWithdrawalFrequency";
if (command.isChangeInIntegerParameterNamed(freeWithdrawalFrequencyParamName, this.freeWithdrawalFrequency)) {
final Integer enableFreeWithdrawalChargeNewValue = command.integerValueOfParameterNamed(freeWithdrawalFrequencyParamName);
actualChanges.put(freeWithdrawalFrequencyParamName, enableFreeWithdrawalChargeNewValue);
this.freeWithdrawalFrequency = enableFreeWithdrawalChargeNewValue;
}
final String restartCountFrequencyParamName = "restartCountFrequency";
if (command.isChangeInIntegerParameterNamed(restartCountFrequencyParamName, this.restartFrequency)) {
final Integer restartCountFrequencyNewValue = command.integerValueOfParameterNamed(restartCountFrequencyParamName);
actualChanges.put(restartCountFrequencyParamName, restartCountFrequencyNewValue);
this.restartFrequency = restartCountFrequencyNewValue;
}
final String countFrequencyTypeParamName = "countFrequencyType";
if (command.isChangeInIntegerParameterNamed(countFrequencyTypeParamName, this.restartFrequencyEnum)) {
final Integer countFrequencyTypeNewValue = command.integerValueOfParameterNamed(countFrequencyTypeParamName);
actualChanges.put(countFrequencyTypeParamName, countFrequencyTypeNewValue);
this.restartFrequencyEnum = ChargeTimeType.fromInt(countFrequencyTypeNewValue).getValue();
}
final String enableFreeWithdrawalChargeParamName = "enableFreeWithdrawalCharge";
if (command.isChangeInBooleanParameterNamed(enableFreeWithdrawalChargeParamName, this.enableFreeWithdrawal)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enableFreeWithdrawalChargeParamName);
actualChanges.put(enableFreeWithdrawalChargeParamName, newValue);
this.enableFreeWithdrawal = newValue;
}
final String enablePaymentTypeParamName = "enablePaymentType";
if (command.isChangeInBooleanParameterNamed(enablePaymentTypeParamName, this.enablePaymentType)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enablePaymentTypeParamName);
actualChanges.put(enablePaymentTypeParamName, newValue);
this.enablePaymentType = newValue;
}
final String paymentTypeParamName = "paymentTypeId";
if (command.isChangeInLongParameterNamed(paymentTypeParamName, getPaymentTypeId())) {
final Long newValue = command.longValueOfParameterNamed(paymentTypeParamName);
actualChanges.put(paymentTypeParamName, newValue);
}
final String chargeAppliesToParamName = "chargeAppliesTo";
if (command.isChangeInIntegerParameterNamed(chargeAppliesToParamName, this.chargeAppliesTo)) {
/*
* final Integer newValue = command.integerValueOfParameterNamed(chargeAppliesToParamName);
* actualChanges.put(chargeAppliesToParamName, newValue); actualChanges.put("locale", localeAsInput);
* this.chargeAppliesTo = ChargeAppliesTo.fromInt(newValue).getValue();
*/
// AA: Do not allow to change chargeAppliesTo.
final String errorMessage = "Update of Charge applies to is not supported";
throw new ChargeParameterUpdateNotSupportedException("charge.applies.to", errorMessage);
}
final String chargeCalculationParamName = "chargeCalculationType";
if (command.isChangeInIntegerParameterNamed(chargeCalculationParamName, this.chargeCalculation)) {
final Integer newValue = command.integerValueOfParameterNamed(chargeCalculationParamName);
actualChanges.put(chargeCalculationParamName, newValue);
actualChanges.put("locale", locale.getLanguage());
this.chargeCalculation = ChargeCalculationType.fromInt(newValue).getValue();
if (isSavingsCharge()) {
if (!isAllowedSavingsChargeCalculationType()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
}
if (!(ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee()
|| ChargeTimeType.fromInt(getChargeTimeType()).isSavingsNoActivityFee())
&& ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode(
"charge.calculation.type.percentage.allowed.only.for.withdrawal.or.noactivity");
}
} else if (isClientCharge()) {
if (!isAllowedClientChargeCalculationType()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.client");
}
}
}
// validate only for loan charge
if (isLoanCharge()) {
final String paymentModeParamName = "chargePaymentMode";
if (command.isChangeInIntegerParameterNamed(paymentModeParamName, this.chargePaymentMode)) {
final Integer newValue = command.integerValueOfParameterNamed(paymentModeParamName);
actualChanges.put(paymentModeParamName, newValue);
actualChanges.put("locale", locale.getLanguage());
this.chargePaymentMode = ChargePaymentMode.fromInt(newValue).getValue();
}
}
if (command.hasParameter("feeOnMonthDay")) {
final MonthDay monthDay = command.extractMonthDayNamed("feeOnMonthDay");
final String actualValueEntered = command.stringValueOfParameterNamed("feeOnMonthDay");
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
if (!this.feeOnDay.equals(dayOfMonthValue)) {
actualChanges.put("feeOnMonthDay", actualValueEntered);
actualChanges.put("locale", locale.getLanguage());
this.feeOnDay = dayOfMonthValue;
}
final Integer monthOfYear = monthDay.getMonthValue();
if (!this.feeOnMonth.equals(monthOfYear)) {
actualChanges.put("feeOnMonthDay", actualValueEntered);
actualChanges.put("locale", locale.getLanguage());
this.feeOnMonth = monthOfYear;
}
}
final String feeInterval = "feeInterval";
if (command.isChangeInIntegerParameterNamed(feeInterval, this.feeInterval)) {
final Integer newValue = command.integerValueOfParameterNamed(feeInterval);
actualChanges.put(feeInterval, newValue);
actualChanges.put("locale", locale.getLanguage());
this.feeInterval = newValue;
}
final String feeFrequency = "feeFrequency";
if (command.isChangeInIntegerParameterNamed(feeFrequency, this.feeFrequency)) {
final Integer newValue = command.integerValueOfParameterNamed(feeFrequency);
actualChanges.put(feeFrequency, newValue);
actualChanges.put("locale", locale.getLanguage());
this.feeFrequency = newValue;
}
if (this.feeFrequency != null) {
baseDataValidator.reset().parameter("feeInterval").value(this.feeInterval).notNull();
}
final String penaltyParamName = "penalty";
if (command.isChangeInBooleanParameterNamed(penaltyParamName, this.penalty)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(penaltyParamName);
actualChanges.put(penaltyParamName, newValue);
this.penalty = newValue;
}
final String activeParamName = "active";
if (command.isChangeInBooleanParameterNamed(activeParamName, this.active)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(activeParamName);
actualChanges.put(activeParamName, newValue);
this.active = newValue;
}
// allow min and max cap to be only added to PERCENT_OF_AMOUNT for now
if (isPercentageOfApprovedAmount()) {
final String minCapParamName = "minCap";
if (command.isChangeInBigDecimalParameterNamed(minCapParamName, this.minCap)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minCapParamName);
actualChanges.put(minCapParamName, newValue);
actualChanges.put("locale", locale.getLanguage());
this.minCap = newValue;
}
final String maxCapParamName = "maxCap";
if (command.isChangeInBigDecimalParameterNamed(maxCapParamName, this.maxCap)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxCapParamName);
actualChanges.put(maxCapParamName, newValue);
actualChanges.put("locale", locale.getLanguage());
this.maxCap = newValue;
}
}
if (this.penalty && ChargeTimeType.fromInt(this.chargeTimeType).isTimeOfDisbursement()) {
throw new ChargeDueAtDisbursementCannotBePenaltyException(this.name);
}
if (!penalty && ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment()) {
throw new ChargeMustBePenaltyException(name);
}
if (command.isChangeInLongParameterNamed(ChargesApiConstants.glAccountIdParamName, getIncomeAccountId())) {
final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName);
actualChanges.put(ChargesApiConstants.glAccountIdParamName, newValue);
}
if (command.isChangeInLongParameterNamed(ChargesApiConstants.taxGroupIdParamName, getTaxGroupId())) {
final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.taxGroupIdParamName);
actualChanges.put(ChargesApiConstants.taxGroupIdParamName, newValue);
if (taxGroup != null) {
baseDataValidator.reset().parameter(ChargesApiConstants.taxGroupIdParamName).failWithCode("modification.not.supported");
}
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
return actualChanges;
}
/**
* Delete is a <i>soft delete</i>. Updates flag on charge so it wont appear in query/report results.
*
* Any fields with unique constraints and prepended with id of record.
*/
public void delete() {
this.deleted = true;
this.name = getId() + "_" + this.name;
}
public ChargeData toData() {
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(this.chargeTimeType);
final EnumOptionData chargeAppliesTo = ChargeEnumerations.chargeAppliesTo(this.chargeAppliesTo);
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation);
final EnumOptionData chargePaymentMode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
EnumOptionData feeFrequencyType = null;
if (this.feeFrequency != null) {
feeFrequencyType = ChargeEnumerations.feeFrequencyType(this.feeFrequency);
}
GLAccountData accountData = null;
if (account != null) {
accountData = new GLAccountData().setId(account.getId()).setName(account.getName()).setGlCode(account.getGlCode());
}
TaxGroupData taxGroupData = null;
if (this.taxGroup != null) {
taxGroupData = TaxGroupData.lookup(taxGroup.getId(), taxGroup.getName());
}
PaymentTypeData paymentTypeData = null;
if (this.paymentType != null) {
paymentTypeData = PaymentTypeData.instance(paymentType.getId(), paymentType.getName());
}
final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType,
chargePaymentMode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.enableFreeWithdrawal,
this.freeWithdrawalFrequency, this.restartFrequency, this.restartFrequencyEnum, this.enablePaymentType, paymentTypeData,
this.minCap, this.maxCap, feeFrequencyType, accountData, taxGroupData);
}
public Integer getChargePaymentMode() {
return this.chargePaymentMode;
}
public Integer getFeeInterval() {
return this.feeInterval;
}
public boolean isMonthlyFee() {
return ChargeTimeType.fromInt(this.chargeTimeType).isMonthlyFee();
}
public boolean isAnnualFee() {
return ChargeTimeType.fromInt(this.chargeTimeType).isAnnualFee();
}
public boolean isOverdueInstallment() {
return ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment();
}
public MonthDay getFeeOnMonthDay() {
MonthDay feeOnMonthDay = null;
if (this.feeOnDay != null && this.feeOnMonth != null) {
feeOnMonthDay = MonthDay.now(DateUtils.getDateTimeZoneOfTenant()).withMonth(this.feeOnMonth).withDayOfMonth(this.feeOnDay);
}
return feeOnMonthDay;
}
public Integer feeInterval() {
return this.feeInterval;
}
public Integer feeFrequency() {
return this.feeFrequency;
}
public GLAccount getAccount() {
return this.account;
}
public void setAccount(GLAccount account) {
this.account = account;
}
public Long getIncomeAccountId() {
Long incomeAccountId = null;
if (this.account != null) {
incomeAccountId = this.account.getId();
}
return incomeAccountId;
}
private Long getTaxGroupId() {
Long taxGroupId = null;
if (this.taxGroup != null) {
taxGroupId = this.taxGroup.getId();
}
return taxGroupId;
}
public boolean isDisbursementCharge() {
return ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.DISBURSEMENT)
|| ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
}
public TaxGroup getTaxGroup() {
return this.taxGroup;
}
public void setTaxGroup(TaxGroup taxGroup) {
this.taxGroup = taxGroup;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Charge)) {
return false;
}
Charge other = (Charge) o;
return Objects.equals(name, other.name) && Objects.equals(amount, other.amount) && Objects.equals(currencyCode, other.currencyCode)
&& Objects.equals(chargeAppliesTo, other.chargeAppliesTo) && Objects.equals(chargeTimeType, other.chargeTimeType)
&& Objects.equals(chargeCalculation, other.chargeCalculation) && Objects.equals(chargePaymentMode, other.chargePaymentMode)
&& Objects.equals(feeOnDay, other.feeOnDay) && Objects.equals(feeInterval, other.feeInterval)
&& Objects.equals(feeOnMonth, other.feeOnMonth) && penalty == other.penalty && active == other.active
&& deleted == other.deleted && Objects.equals(minCap, other.minCap) && Objects.equals(maxCap, other.maxCap)
&& Objects.equals(feeFrequency, other.feeFrequency) && Objects.equals(account, other.account)
&& Objects.equals(taxGroup, other.taxGroup);
}
@Override
public int hashCode() {
return Objects.hash(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculation, chargePaymentMode, feeOnDay,
feeInterval, feeOnMonth, penalty, active, deleted, minCap, maxCap, feeFrequency, account, taxGroup);
}
}
@@ -0,0 +1,90 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.domain;
public enum ChargeAppliesTo {
INVALID(0, "chargeAppliesTo.invalid"), //
LOAN(1, "chargeAppliesTo.loan"), //
SAVINGS(2, "chargeAppliesTo.savings"), //
CLIENT(3, "chargeAppliesTo.client"), SHARES(4, "chargeAppliesTo.shares");
private final Integer value;
private final String code;
ChargeAppliesTo(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static ChargeAppliesTo fromInt(final Integer chargeAppliesTo) {
ChargeAppliesTo chargeAppliesToType = ChargeAppliesTo.INVALID;
if (chargeAppliesTo != null) {
switch (chargeAppliesTo) {
case 1:
chargeAppliesToType = LOAN;
break;
case 2:
chargeAppliesToType = SAVINGS;
break;
case 3:
chargeAppliesToType = CLIENT;
break;
case 4:
chargeAppliesToType = SHARES;
break;
default:
chargeAppliesToType = INVALID;
break;
}
}
return chargeAppliesToType;
}
public boolean isLoanCharge() {
return this.value.equals(ChargeAppliesTo.LOAN.getValue());
}
public boolean isSavingsCharge() {
return this.value.equals(ChargeAppliesTo.SAVINGS.getValue());
}
public boolean isClientCharge() {
return this.value.equals(ChargeAppliesTo.CLIENT.getValue());
}
public boolean isSharesCharge() {
return this.value.equals(SHARES.getValue());
}
public static Object[] validValues() {
return new Object[] { ChargeAppliesTo.LOAN.getValue(), ChargeAppliesTo.SAVINGS.getValue(), ChargeAppliesTo.CLIENT.getValue(),
ChargeAppliesTo.SHARES.getValue() };
}
}
@@ -0,0 +1,126 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.domain;
public enum ChargeCalculationType {
INVALID(0, "chargeCalculationType.invalid"), //
FLAT(1, "chargeCalculationType.flat"), //
PERCENT_OF_AMOUNT(2, "chargeCalculationType.percent.of.amount"), //
PERCENT_OF_AMOUNT_AND_INTEREST(3, "chargeCalculationType.percent.of.amount.and.interest"), //
PERCENT_OF_INTEREST(4, "chargeCalculationType.percent.of.interest"), PERCENT_OF_DISBURSEMENT_AMOUNT(5,
"chargeCalculationType.percent.of.disbursement.amount");
private final Integer value;
private final String code;
ChargeCalculationType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static Object[] validValuesForLoan() {
return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue(),
ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue(), ChargeCalculationType.PERCENT_OF_INTEREST.getValue(),
ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue() };
}
public static Object[] validValuesForSavings() {
return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue() };
}
public static Object[] validValuesForShares() {
return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue() };
}
public static Object[] validValuesForClients() {
return new Integer[] { ChargeCalculationType.FLAT.getValue() };
}
public static Object[] validValuesForShareAccountActivation() {
return new Integer[] { ChargeCalculationType.FLAT.getValue() };
}
public static Object[] validValuesForTrancheDisbursement() {
return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue() };
}
public static ChargeCalculationType fromInt(final Integer chargeCalculation) {
ChargeCalculationType chargeCalculationType = ChargeCalculationType.INVALID;
switch (chargeCalculation) {
case 1:
chargeCalculationType = FLAT;
break;
case 2:
chargeCalculationType = PERCENT_OF_AMOUNT;
break;
case 3:
chargeCalculationType = PERCENT_OF_AMOUNT_AND_INTEREST;
break;
case 4:
chargeCalculationType = PERCENT_OF_INTEREST;
break;
case 5:
chargeCalculationType = PERCENT_OF_DISBURSEMENT_AMOUNT;
break;
}
return chargeCalculationType;
}
public boolean isPercentageOfAmount() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue());
}
public boolean isPercentageOfAmountAndInterest() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue());
}
public boolean isPercentageOfInterest() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_INTEREST.getValue());
}
public boolean isFlat() {
return this.value.equals(ChargeCalculationType.FLAT.getValue());
}
public boolean isAllowedSavingsChargeCalculationType() {
return isFlat() || isPercentageOfAmount();
}
public boolean isAllowedClientChargeCalculationType() {
return isFlat();
}
public boolean isPercentageBased() {
return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest()
|| isPercentageOfDisbursementAmount();
}
public boolean isPercentageOfDisbursementAmount() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue());
}
}
@@ -0,0 +1,62 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.domain;
public enum ChargePaymentMode {
REGULAR(0, "chargepaymentmode.regular"), //
ACCOUNT_TRANSFER(1, "chargepaymentmode.accounttransfer");
private final Integer value;
private final String code;
ChargePaymentMode(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static Object[] validValues() {
return new Integer[] { ChargePaymentMode.REGULAR.getValue(), ChargePaymentMode.ACCOUNT_TRANSFER.getValue() };
}
public static ChargePaymentMode fromInt(final Integer paymentMode) {
ChargePaymentMode chargeAppliesToType = ChargePaymentMode.REGULAR;
switch (paymentMode) {
case 1:
chargeAppliesToType = ACCOUNT_TRANSFER;
break;
default:
chargeAppliesToType = REGULAR;
break;
}
return chargeAppliesToType;
}
public boolean isPaymentModeAccountTransfer() {
return this.value.equals(ChargePaymentMode.ACCOUNT_TRANSFER.getValue());
}
}
@@ -0,0 +1,221 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.domain;
public enum ChargeTimeType {
INVALID(0, "chargeTimeType.invalid"), //
DISBURSEMENT(1, "chargeTimeType.disbursement"), // only for loan charges
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), // for loan and
SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for
SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings
WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings
ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings
MONTHLY_FEE(7, "chargeTimeType.monthlyFee"), // only for savings
INSTALMENT_FEE(8, "chargeTimeType.instalmentFee"), // only for loan charges
OVERDUE_INSTALLMENT(9, "chargeTimeType.overdueInstallment"), // only for
OVERDRAFT_FEE(10, "chargeTimeType.overdraftFee"), // only for savings
WEEKLY_FEE(11, "chargeTimeType.weeklyFee"), // only for savings
TRANCHE_DISBURSEMENT(12, "chargeTimeType.tranchedisbursement"), // only for
// loan
SHAREACCOUNT_ACTIVATION(13, "chargeTimeType.activation"), // only for loan
SHARE_PURCHASE(14, "chargeTimeType.sharespurchase"), SHARE_REDEEM(15, "chargeTimeType.sharesredeem"),
SAVINGS_NOACTIVITY_FEE(16, "chargeTimeType.savingsNoActivityFee");
private final Integer value;
private final String code;
ChargeTimeType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static Object[] validLoanValues() {
return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
ChargeTimeType.INSTALMENT_FEE.getValue(), ChargeTimeType.OVERDUE_INSTALLMENT.getValue(),
ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() };
}
public static Object[] validLoanChargeValues() {
return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
ChargeTimeType.INSTALMENT_FEE.getValue() };
}
public static Object[] validSavingsValues() {
return new Integer[] { ChargeTimeType.SPECIFIED_DUE_DATE.getValue(), ChargeTimeType.SAVINGS_ACTIVATION.getValue(),
ChargeTimeType.SAVINGS_CLOSURE.getValue(), ChargeTimeType.WITHDRAWAL_FEE.getValue(), ChargeTimeType.ANNUAL_FEE.getValue(),
ChargeTimeType.MONTHLY_FEE.getValue(), ChargeTimeType.OVERDRAFT_FEE.getValue(), ChargeTimeType.WEEKLY_FEE.getValue(),
ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue() };
}
public static Object[] validClientValues() {
return new Integer[] { ChargeTimeType.SPECIFIED_DUE_DATE.getValue() };
}
public static Object[] validShareValues() {
return new Integer[] { ChargeTimeType.SHAREACCOUNT_ACTIVATION.getValue(), ChargeTimeType.SHARE_PURCHASE.getValue(),
ChargeTimeType.SHARE_REDEEM.getValue() };
}
public static ChargeTimeType fromInt(final Integer chargeTime) {
ChargeTimeType chargeTimeType = ChargeTimeType.INVALID;
if (chargeTime != null) {
switch (chargeTime) {
case 1:
chargeTimeType = DISBURSEMENT;
break;
case 2:
chargeTimeType = SPECIFIED_DUE_DATE;
break;
case 3:
chargeTimeType = SAVINGS_ACTIVATION;
break;
case 4:
chargeTimeType = SAVINGS_CLOSURE;
break;
case 5:
chargeTimeType = WITHDRAWAL_FEE;
break;
case 6:
chargeTimeType = ANNUAL_FEE;
break;
case 7:
chargeTimeType = MONTHLY_FEE;
break;
case 8:
chargeTimeType = INSTALMENT_FEE;
break;
case 9:
chargeTimeType = OVERDUE_INSTALLMENT;
break;
case 10:
chargeTimeType = OVERDRAFT_FEE;
break;
case 11:
chargeTimeType = WEEKLY_FEE;
break;
case 12:
chargeTimeType = TRANCHE_DISBURSEMENT;
break;
case 13:
chargeTimeType = SHAREACCOUNT_ACTIVATION;
break;
case 14:
chargeTimeType = SHARE_PURCHASE;
break;
case 15:
chargeTimeType = SHARE_REDEEM;
break;
case 16:
chargeTimeType = SAVINGS_NOACTIVITY_FEE;
break;
default:
chargeTimeType = INVALID;
break;
}
}
return chargeTimeType;
}
public boolean isTimeOfDisbursement() {
return ChargeTimeType.DISBURSEMENT.getValue().equals(this.value);
}
public boolean isOnSpecifiedDueDate() {
return this.value.equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue());
}
public boolean isSavingsActivation() {
return this.value.equals(ChargeTimeType.SAVINGS_ACTIVATION.getValue());
}
public boolean isSavingsClosure() {
return this.value.equals(ChargeTimeType.SAVINGS_CLOSURE.getValue());
}
public boolean isWithdrawalFee() {
return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue());
}
public boolean isSavingsNoActivityFee() {
return this.value.equals(ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue());
}
public boolean isAnnualFee() {
return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue());
}
public boolean isMonthlyFee() {
return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue());
}
public boolean isWeeklyFee() {
return this.value.equals(ChargeTimeType.WEEKLY_FEE.getValue());
}
public boolean isInstalmentFee() {
return this.value.equals(ChargeTimeType.INSTALMENT_FEE.getValue());
}
public boolean isOverdueInstallment() {
return this.value.equals(ChargeTimeType.OVERDUE_INSTALLMENT.getValue());
}
public boolean isAllowedLoanChargeTime() {
return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee() || isOverdueInstallment() || isTrancheDisbursement();
}
public boolean isAllowedClientChargeTime() {
return isOnSpecifiedDueDate();
}
public boolean isAllowedSavingsChargeTime() {
return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee() || isMonthlyFee()
|| isWeeklyFee() || isOverdraftFee() || isSavingsNoActivityFee();
}
public boolean isOverdraftFee() {
return this.value.equals(ChargeTimeType.OVERDRAFT_FEE.getValue());
}
public boolean isTrancheDisbursement() {
return this.value.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue());
}
public boolean isShareAccountActivation() {
return this.value.equals(ChargeTimeType.SHAREACCOUNT_ACTIVATION.getValue());
}
public boolean isSharesPurchase() {
return this.value.equals(ChargeTimeType.SHARE_PURCHASE.getValue());
}
public boolean isSharesRedeem() {
return this.value.equals(ChargeTimeType.SHARE_REDEEM.getValue());
}
}
@@ -0,0 +1,32 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when attempting to crate charge of type penalty due at
* disbursement .
*/
public class ChargeDueAtDisbursementCannotBePenaltyException extends AbstractPlatformDomainRuleException {
public ChargeDueAtDisbursementCannotBePenaltyException(final String name) {
super("error.msg.charge.due.at.disbursement.cannot.be.penalty", "Charge '" + name + "' is invalid.", name, name);
}
}
@@ -0,0 +1,32 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when attempting to crate charge of type penalty due at
* disbursement .
*/
public class ChargeMustBePenaltyException extends AbstractPlatformDomainRuleException {
public ChargeMustBePenaltyException(final String name) {
super("error.msg.charge.must.be.penalty", "Charge '" + name + "' is invalid.", name, name);
}
}
@@ -0,0 +1,32 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when Calendar resources are not found.
*/
public class ChargeParameterUpdateNotSupportedException extends AbstractPlatformDomainRuleException {
public ChargeParameterUpdateNotSupportedException(final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg.charge.update.of." + postFix + ".is.not.supported", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,33 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when loan charge is not allowed to be added to the loan.
*/
public class LoanChargeCannotBeAddedException extends AbstractPlatformDomainRuleException {
public LoanChargeCannotBeAddedException(final String entity, final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".cannot.be.added.as." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,217 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.service;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.charge.domain.ChargeAppliesTo;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
public final class ChargeEnumerations {
private ChargeEnumerations() {
}
public static EnumOptionData chargeTimeType(final int id) {
return chargeTimeType(ChargeTimeType.fromInt(id));
}
public static EnumOptionData chargeTimeType(final ChargeTimeType type) {
EnumOptionData optionData = null;
switch (type) {
case DISBURSEMENT:
optionData = new EnumOptionData(ChargeTimeType.DISBURSEMENT.getValue().longValue(), ChargeTimeType.DISBURSEMENT.getCode(),
"Disbursement");
break;
case SPECIFIED_DUE_DATE:
optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(),
ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date");
break;
case SAVINGS_ACTIVATION:
optionData = new EnumOptionData(ChargeTimeType.SAVINGS_ACTIVATION.getValue().longValue(),
ChargeTimeType.SAVINGS_ACTIVATION.getCode(), "Savings Activation");
break;
case SAVINGS_CLOSURE:
optionData = new EnumOptionData(ChargeTimeType.SAVINGS_CLOSURE.getValue().longValue(),
ChargeTimeType.SAVINGS_CLOSURE.getCode(), "Savings Closure");
break;
case WITHDRAWAL_FEE:
optionData = new EnumOptionData(ChargeTimeType.WITHDRAWAL_FEE.getValue().longValue(),
ChargeTimeType.WITHDRAWAL_FEE.getCode(), "Withdrawal Fee");
break;
case ANNUAL_FEE:
optionData = new EnumOptionData(ChargeTimeType.ANNUAL_FEE.getValue().longValue(), ChargeTimeType.ANNUAL_FEE.getCode(),
"Annual Fee");
break;
case MONTHLY_FEE:
optionData = new EnumOptionData(ChargeTimeType.MONTHLY_FEE.getValue().longValue(), ChargeTimeType.MONTHLY_FEE.getCode(),
"Monthly Fee");
break;
case WEEKLY_FEE:
optionData = new EnumOptionData(ChargeTimeType.WEEKLY_FEE.getValue().longValue(), ChargeTimeType.WEEKLY_FEE.getCode(),
"Weekly Fee");
break;
case INSTALMENT_FEE:
optionData = new EnumOptionData(ChargeTimeType.INSTALMENT_FEE.getValue().longValue(),
ChargeTimeType.INSTALMENT_FEE.getCode(), "Installment Fee");
break;
case OVERDUE_INSTALLMENT:
optionData = new EnumOptionData(ChargeTimeType.OVERDUE_INSTALLMENT.getValue().longValue(),
ChargeTimeType.OVERDUE_INSTALLMENT.getCode(), "Overdue Fees");
break;
case OVERDRAFT_FEE:
optionData = new EnumOptionData(ChargeTimeType.OVERDRAFT_FEE.getValue().longValue(), ChargeTimeType.OVERDRAFT_FEE.getCode(),
"Overdraft Fee");
break;
case TRANCHE_DISBURSEMENT:
optionData = new EnumOptionData(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().longValue(),
ChargeTimeType.TRANCHE_DISBURSEMENT.getCode(), "Tranche Disbursement");
break;
case SHAREACCOUNT_ACTIVATION:
optionData = new EnumOptionData(ChargeTimeType.SHAREACCOUNT_ACTIVATION.getValue().longValue(),
ChargeTimeType.SHAREACCOUNT_ACTIVATION.getCode(), "Share Account Activate");
break;
case SHARE_PURCHASE:
optionData = new EnumOptionData(ChargeTimeType.SHARE_PURCHASE.getValue().longValue(),
ChargeTimeType.SHARE_PURCHASE.getCode(), "Share Purchase");
break;
case SHARE_REDEEM:
optionData = new EnumOptionData(ChargeTimeType.SHARE_REDEEM.getValue().longValue(), ChargeTimeType.SHARE_REDEEM.getCode(),
"Share Redeem");
break;
case SAVINGS_NOACTIVITY_FEE:
optionData = new EnumOptionData(ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue().longValue(),
ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getCode(), "Saving No Activity Fee");
break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break;
}
return optionData;
}
public static EnumOptionData chargeAppliesTo(final int id) {
return chargeAppliesTo(ChargeAppliesTo.fromInt(id));
}
public static EnumOptionData chargeAppliesTo(final ChargeAppliesTo type) {
EnumOptionData optionData = null;
switch (type) {
case LOAN:
optionData = new EnumOptionData(ChargeAppliesTo.LOAN.getValue().longValue(), ChargeAppliesTo.LOAN.getCode(), "Loan");
break;
case SAVINGS:
optionData = new EnumOptionData(ChargeAppliesTo.SAVINGS.getValue().longValue(), ChargeAppliesTo.SAVINGS.getCode(),
"Savings");
break;
case CLIENT:
optionData = new EnumOptionData(ChargeAppliesTo.CLIENT.getValue().longValue(), ChargeAppliesTo.CLIENT.getCode(), "Client");
break;
case SHARES:
optionData = new EnumOptionData(ChargeAppliesTo.SHARES.getValue().longValue(), ChargeAppliesTo.SHARES.getCode(), "Shares");
break;
default:
optionData = new EnumOptionData(ChargeAppliesTo.INVALID.getValue().longValue(), ChargeAppliesTo.INVALID.getCode(),
"Invalid");
break;
}
return optionData;
}
public static EnumOptionData chargeCalculationType(final int id) {
return chargeCalculationType(ChargeCalculationType.fromInt(id));
}
public static EnumOptionData chargeCalculationType(final ChargeCalculationType type) {
EnumOptionData optionData = null;
switch (type) {
case FLAT:
optionData = new EnumOptionData(ChargeCalculationType.FLAT.getValue().longValue(), ChargeCalculationType.FLAT.getCode(),
"Flat");
break;
case PERCENT_OF_AMOUNT:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Amount");
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getCode(), "% Loan Amount + Interest");
break;
case PERCENT_OF_INTEREST:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_INTEREST.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_INTEREST.getCode(), "% Interest");
break;
case PERCENT_OF_DISBURSEMENT_AMOUNT:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getCode(), "% Disbursement Amount");
break;
default:
optionData = new EnumOptionData(ChargeCalculationType.INVALID.getValue().longValue(),
ChargeCalculationType.INVALID.getCode(), "Invalid");
break;
}
return optionData;
}
public static EnumOptionData chargePaymentMode(final int id) {
return chargePaymentMode(ChargePaymentMode.fromInt(id));
}
public static EnumOptionData chargePaymentMode(final ChargePaymentMode type) {
EnumOptionData optionData = null;
switch (type) {
case ACCOUNT_TRANSFER:
optionData = new EnumOptionData(ChargePaymentMode.ACCOUNT_TRANSFER.getValue().longValue(),
ChargePaymentMode.ACCOUNT_TRANSFER.getCode(), "Account transfer");
break;
default:
optionData = new EnumOptionData(ChargePaymentMode.REGULAR.getValue().longValue(), ChargePaymentMode.REGULAR.getCode(),
"Regular");
break;
}
return optionData;
}
public static EnumOptionData feeFrequencyType(final int id) {
return feeFrequencyType(PeriodFrequencyType.fromInt(id));
}
public static EnumOptionData feeFrequencyType(final PeriodFrequencyType frequencyType) {
EnumOptionData optionData;
switch (frequencyType) {
case DAYS -> optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(),
PeriodFrequencyType.DAYS.getCode(), "Daily");
case WEEKS -> optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(),
PeriodFrequencyType.WEEKS.getCode(), "Weekly");
case MONTHS -> optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(),
PeriodFrequencyType.MONTHS.getCode(), "Monthly");
case YEARS -> optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(),
PeriodFrequencyType.YEARS.getCode(), "Yearly");
case WHOLE_TERM -> optionData = new EnumOptionData(PeriodFrequencyType.WHOLE_TERM.getValue().longValue(),
PeriodFrequencyType.WHOLE_TERM.getCode(), "Whole term");
default -> throw new UnsupportedOperationException(frequencyType + " is not supported");
}
return optionData;
}
}
@@ -0,0 +1,38 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.data;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public final class ClientCollateralManagementData implements Serializable {
private final Long id;
private final String name;
private final BigDecimal quantity;
private final BigDecimal pctToBase;
private final BigDecimal unitPrice;
private final BigDecimal total;
private final BigDecimal totalCollateral;
}
@@ -0,0 +1,645 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.data;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.organisation.office.data.OfficeData;
import org.apache.fineract.organisation.staff.data.StaffData;
import org.apache.fineract.portfolio.address.data.AddressData;
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
/**
* Immutable data object representing client data.
*/
@NoArgsConstructor
@Getter
@Setter
public final class ClientData implements Comparable<ClientData>, Serializable {
private Long id;
private String accountNo;
private ExternalId externalId;
private EnumOptionData status;
private CodeValueData subStatus;
private Boolean active;
private LocalDate activationDate;
private String firstname;
private String middlename;
private String lastname;
private String fullname;
private String displayName;
private String mobileNo;
private String emailAddress;
private LocalDate dateOfBirth;
private CodeValueData gender;
private CodeValueData clientType;
private CodeValueData clientClassification;
private Boolean isStaff;
private Long officeId;
private String officeName;
private Long transferToOfficeId;
private String transferToOfficeName;
private Long imageId;
private Boolean imagePresent;
private Long staffId;
private String staffName;
private ClientTimelineData timeline;
private Long savingsProductId;
private String savingsProductName;
private Long savingsAccountId;
private EnumOptionData legalForm;
private Set<ClientCollateralManagementData> clientCollateralManagements;
// associations
private Collection<GroupGeneralData> groups;
// template
private Collection<OfficeData> officeOptions;
private Collection<StaffData> staffOptions;
private Collection<CodeValueData> narrations;
private Collection<SavingsProductData> savingProductOptions;
private Collection<SavingsAccountData> savingAccountOptions;
private Collection<CodeValueData> genderOptions;
private Collection<CodeValueData> clientTypeOptions;
private Collection<CodeValueData> clientClassificationOptions;
private Collection<CodeValueData> clientNonPersonConstitutionOptions;
private Collection<CodeValueData> clientNonPersonMainBusinessLineOptions;
private List<EnumOptionData> clientLegalFormOptions;
private ClientFamilyMembersData familyMemberOptions;
private ClientNonPersonData clientNonPersonDetails;
private Collection<AddressData> address;
private Boolean isAddressEnabled;
private List<DatatableData> datatables;
// import fields
private transient Integer rowIndex;
private String dateFormat;
private String locale;
private Long clientTypeId;
private Long genderId;
private Long clientClassificationId;
private Long legalFormId;
private LocalDate submittedOnDate;
public static ClientData importClientEntityInstance(Long legalFormId, Integer rowIndex, String fullname, Long officeId,
Long clientTypeId, Long clientClassificationId, Long staffId, Boolean active, LocalDate activationDate,
LocalDate submittedOnDate, ExternalId externalId, LocalDate dateOfBirth, String mobileNo,
ClientNonPersonData clientNonPersonDetails, Collection<AddressData> address, String locale, String dateFormat) {
return new ClientData(legalFormId, rowIndex, fullname, null, null, null, submittedOnDate, activationDate, active, externalId,
officeId, staffId, mobileNo, dateOfBirth, clientTypeId, null, clientClassificationId, null, address, clientNonPersonDetails,
locale, dateFormat);
}
public static ClientData createClientForInterestPosting(final Long id, final Long officeId) {
return new ClientData(id, officeId);
}
private ClientData(final Long clientId, final Long officeId) {
this.rowIndex = null;
this.dateFormat = null;
this.locale = null;
this.firstname = null;
this.lastname = null;
this.middlename = null;
this.fullname = null;
this.activationDate = null;
this.submittedOnDate = null;
this.active = null;
this.externalId = null;
this.officeId = officeId;
this.staffId = null;
this.legalFormId = null;
this.mobileNo = null;
this.dateOfBirth = null;
this.clientTypeId = null;
this.genderId = null;
this.clientClassificationId = null;
this.isStaff = false;
this.address = null;
this.accountNo = null;
this.status = null;
this.subStatus = null;
this.displayName = null;
this.gender = null;
this.clientType = null;
this.clientClassification = null;
this.officeName = null;
this.transferToOfficeId = null;
this.transferToOfficeName = null;
this.imageId = null;
this.imagePresent = null;
this.staffName = null;
this.timeline = null;
this.savingsProductId = null;
this.savingsProductName = null;
this.savingsAccountId = null;
this.legalForm = null;
this.groups = null;
this.officeOptions = null;
this.staffOptions = null;
this.narrations = null;
this.savingProductOptions = null;
this.savingAccountOptions = null;
this.genderOptions = null;
this.clientTypeOptions = null;
this.clientClassificationOptions = null;
this.clientNonPersonConstitutionOptions = null;
this.clientNonPersonMainBusinessLineOptions = null;
this.clientLegalFormOptions = null;
this.clientNonPersonDetails = null;
this.isAddressEnabled = null;
this.datatables = null;
this.familyMemberOptions = null;
this.emailAddress = null;
this.clientCollateralManagements = null;
this.id = clientId;
}
public static ClientData importClientPersonInstance(Long legalFormId, Integer rowIndex, String firstname, String lastname,
String middlename, LocalDate submittedOn, LocalDate activationDate, Boolean active, ExternalId externalId, Long officeId,
Long staffId, String mobileNo, LocalDate dob, Long clientTypeId, Long genderId, Long clientClassificationId, Boolean isStaff,
Collection<AddressData> address, String locale, String dateFormat) {
return new ClientData(legalFormId, rowIndex, null, firstname, lastname, middlename, submittedOn, activationDate, active, externalId,
officeId, staffId, mobileNo, dob, clientTypeId, genderId, clientClassificationId, isStaff, address, null, locale,
dateFormat);
}
public static ClientData emptyInstance(Long clientId) {
return lookup(clientId, null, null, null);
}
private ClientData(Long legalFormId, Integer rowIndex, String fullname, String firstname, String lastname, String middlename,
LocalDate submittedOn, LocalDate activationDate, Boolean active, ExternalId externalId, Long officeId, Long staffId,
String mobileNo, LocalDate dob, Long clientTypeId, Long genderId, Long clientClassificationId, Boolean isStaff,
Collection<AddressData> address, ClientNonPersonData clientNonPersonDetails, String locale, String dateFormat) {
this.rowIndex = rowIndex;
this.dateFormat = dateFormat;
this.locale = locale;
this.firstname = firstname;
this.lastname = lastname;
this.middlename = middlename;
this.fullname = fullname;
this.activationDate = activationDate;
this.submittedOnDate = submittedOn;
this.active = active;
this.externalId = externalId;
this.officeId = officeId;
this.staffId = staffId;
this.legalFormId = legalFormId;
this.mobileNo = mobileNo;
this.dateOfBirth = dob;
this.clientTypeId = clientTypeId;
this.genderId = genderId;
this.clientClassificationId = clientClassificationId;
this.isStaff = isStaff;
this.address = address;
this.id = null;
this.accountNo = null;
this.status = null;
this.subStatus = null;
this.displayName = null;
this.gender = null;
this.clientType = null;
this.clientClassification = null;
this.officeName = null;
this.transferToOfficeId = null;
this.transferToOfficeName = null;
this.imageId = null;
this.imagePresent = null;
this.staffName = null;
this.timeline = null;
this.savingsProductId = null;
this.savingsProductName = null;
this.savingsAccountId = null;
this.legalForm = null;
this.groups = null;
this.officeOptions = null;
this.staffOptions = null;
this.narrations = null;
this.savingProductOptions = null;
this.savingAccountOptions = null;
this.genderOptions = null;
this.clientTypeOptions = null;
this.clientClassificationOptions = null;
this.clientNonPersonConstitutionOptions = null;
this.clientNonPersonMainBusinessLineOptions = null;
this.clientLegalFormOptions = null;
this.clientNonPersonDetails = null;
this.isAddressEnabled = null;
this.datatables = null;
this.familyMemberOptions = null;
this.emailAddress = null;
this.clientCollateralManagements = null;
}
public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection<OfficeData> officeOptions,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> narrations,
final Collection<CodeValueData> genderOptions, final Collection<SavingsProductData> savingProductOptions,
final Collection<CodeValueData> clientTypeOptions, final Collection<CodeValueData> clientClassificationOptions,
final Collection<CodeValueData> clientNonPersonConstitutionOptions,
final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions, final List<EnumOptionData> clientLegalFormOptions,
final ClientFamilyMembersData familyMemberOptions, final Collection<AddressData> address, final Boolean isAddressEnabled,
final List<DatatableData> datatables) {
final String accountNo = null;
final EnumOptionData status = null;
final CodeValueData subStatus = null;
final String officeName = null;
final Long transferToOfficeId = null;
final String transferToOfficeName = null;
final Long id = null;
final String firstname = null;
final String middlename = null;
final String lastname = null;
final String fullname = null;
final String displayName = null;
final ExternalId externalId = ExternalId.empty();
final String mobileNo = null;
final String emailAddress = null;
final LocalDate dateOfBirth = null;
final CodeValueData gender = null;
final Long imageId = null;
final Long staffId = null;
final String staffName = null;
final Collection<GroupGeneralData> groups = null;
final ClientTimelineData timeline = null;
final Long savingsProductId = null;
final String savingsProductName = null;
final Long savingsAccountId = null;
final Collection<SavingsAccountData> savingAccountOptions = null;
final CodeValueData clientType = null;
final CodeValueData clientClassification = null;
final EnumOptionData legalForm = null;
final Boolean isStaff = false;
final ClientNonPersonData clientNonPersonDetails = null;
final Set<ClientCollateralManagementData> clientCollateralManagements = null;
return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, joinedDate, imageId,
staffId, staffName, officeOptions, groups, staffOptions, narrations, genderOptions, timeline, savingProductOptions,
savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType, clientClassification,
clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions,
clientNonPersonDetails, clientLegalFormOptions, familyMemberOptions, legalForm, address, isAddressEnabled, datatables,
isStaff, clientCollateralManagements);
}
public static ClientData templateOnTop(final ClientData clientData, final ClientData templateData) {
final Set<ClientCollateralManagementData> clientCollateralManagements = null;
return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
clientData.emailAddress, clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId,
clientData.staffId, clientData.staffName, templateData.officeOptions, clientData.groups, templateData.staffOptions,
templateData.narrations, templateData.genderOptions, clientData.timeline, templateData.savingProductOptions,
clientData.savingsProductId, clientData.savingsProductName, clientData.savingsAccountId, clientData.savingAccountOptions,
clientData.clientType, clientData.clientClassification, templateData.clientTypeOptions,
templateData.clientClassificationOptions, templateData.clientNonPersonConstitutionOptions,
templateData.clientNonPersonMainBusinessLineOptions, clientData.clientNonPersonDetails, templateData.clientLegalFormOptions,
templateData.familyMemberOptions, clientData.legalForm, clientData.address, clientData.isAddressEnabled, null,
clientData.isStaff, clientCollateralManagements);
}
public static ClientData templateWithSavingAccountOptions(final ClientData clientData,
final Collection<SavingsAccountData> savingAccountOptions) {
final Set<ClientCollateralManagementData> clientCollateralManagements = null;
return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
clientData.emailAddress, clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId,
clientData.staffId, clientData.staffName, clientData.officeOptions, clientData.groups, clientData.staffOptions,
clientData.narrations, clientData.genderOptions, clientData.timeline, clientData.savingProductOptions,
clientData.savingsProductId, clientData.savingsProductName, clientData.savingsAccountId, savingAccountOptions,
clientData.clientType, clientData.clientClassification, clientData.clientTypeOptions,
clientData.clientClassificationOptions, clientData.clientNonPersonConstitutionOptions,
clientData.clientNonPersonMainBusinessLineOptions, clientData.clientNonPersonDetails, clientData.clientLegalFormOptions,
clientData.familyMemberOptions, clientData.legalForm, clientData.address, clientData.isAddressEnabled, null,
clientData.isStaff, clientCollateralManagements);
}
public static ClientData setParentGroups(final ClientData clientData, final Collection<GroupGeneralData> parentGroups,
final Set<ClientCollateralManagementData> clientCollateralManagements) {
return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
clientData.emailAddress, clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId,
clientData.staffId, clientData.staffName, clientData.officeOptions, parentGroups, clientData.staffOptions, null, null,
clientData.timeline, clientData.savingProductOptions, clientData.savingsProductId, clientData.savingsProductName,
clientData.savingsAccountId, clientData.savingAccountOptions, clientData.clientType, clientData.clientClassification,
clientData.clientTypeOptions, clientData.clientClassificationOptions, clientData.clientNonPersonConstitutionOptions,
clientData.clientNonPersonMainBusinessLineOptions, clientData.clientNonPersonDetails, clientData.clientLegalFormOptions,
clientData.familyMemberOptions, clientData.legalForm, clientData.address, clientData.isAddressEnabled, null,
clientData.isStaff, clientCollateralManagements);
}
public static ClientData clientIdentifier(final Long id, final String accountNo, final String firstname, final String middlename,
final String lastname, final String fullname, final String displayName, final Long officeId, final String officeName) {
final Long transferToOfficeId = null;
final String transferToOfficeName = null;
final ExternalId externalId = ExternalId.empty();
final String mobileNo = null;
final String emailAddress = null;
final LocalDate dateOfBirth = null;
final CodeValueData gender = null;
final LocalDate activationDate = null;
final Long imageId = null;
final Long staffId = null;
final String staffName = null;
final Collection<OfficeData> allowedOffices = null;
final Collection<GroupGeneralData> groups = null;
final Collection<StaffData> staffOptions = null;
final Collection<CodeValueData> closureReasons = null;
final Collection<CodeValueData> genderOptions = null;
final ClientTimelineData timeline = null;
final Collection<SavingsProductData> savingProductOptions = null;
final Long savingsProductId = null;
final String savingsProductName = null;
final Long savingsAccountId = null;
final Collection<SavingsAccountData> savingAccountOptions = null;
final CodeValueData clientType = null;
final CodeValueData clientClassification = null;
final Collection<CodeValueData> clientTypeOptions = null;
final Collection<CodeValueData> clientClassificationOptions = null;
final Collection<CodeValueData> clientNonPersonConstitutionOptions = null;
final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions = null;
final List<EnumOptionData> clientLegalFormOptions = null;
final ClientFamilyMembersData familyMemberOptions = null;
final EnumOptionData status = null;
final CodeValueData subStatus = null;
final EnumOptionData legalForm = null;
final Boolean isStaff = false;
final ClientNonPersonData clientNonPerson = null;
final Set<ClientCollateralManagementData> clientCollateralManagements = null;
return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate,
imageId, staffId, staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline,
savingProductOptions, savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType,
clientClassification, clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions,
clientNonPersonMainBusinessLineOptions, clientNonPerson, clientLegalFormOptions, familyMemberOptions, legalForm, null, null,
null, isStaff, clientCollateralManagements);
}
public static ClientData lookup(final Long id, final String displayName, final Long officeId, final String officeName) {
final String accountNo = null;
final EnumOptionData status = null;
final CodeValueData subStatus = null;
final Long transferToOfficeId = null;
final String transferToOfficeName = null;
final String firstname = null;
final String middlename = null;
final String lastname = null;
final String fullname = null;
final ExternalId externalId = ExternalId.empty();
final String mobileNo = null;
final String emailAddress = null;
final LocalDate dateOfBirth = null;
final CodeValueData gender = null;
final LocalDate activationDate = null;
final Long imageId = null;
final Long staffId = null;
final String staffName = null;
final Collection<OfficeData> allowedOffices = null;
final Collection<GroupGeneralData> groups = null;
final Collection<StaffData> staffOptions = null;
final Collection<CodeValueData> closureReasons = null;
final Collection<CodeValueData> genderOptions = null;
final ClientTimelineData timeline = null;
final Collection<SavingsProductData> savingProductOptions = null;
final Long savingsProductId = null;
final String savingsProductName = null;
final Long savingsAccountId = null;
final Collection<SavingsAccountData> savingAccountOptions = null;
final CodeValueData clientType = null;
final CodeValueData clientClassification = null;
final Collection<CodeValueData> clientTypeOptions = null;
final Collection<CodeValueData> clientClassificationOptions = null;
final Collection<CodeValueData> clientNonPersonConstitutionOptions = null;
final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions = null;
final List<EnumOptionData> clientLegalFormOptions = null;
final ClientFamilyMembersData familyMemberOptions = null;
final EnumOptionData legalForm = null;
final Boolean isStaff = false;
final ClientNonPersonData clientNonPerson = null;
final Set<ClientCollateralManagementData> clientCollateralManagements = null;
return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate,
imageId, staffId, staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline,
savingProductOptions, savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType,
clientClassification, clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions,
clientNonPersonMainBusinessLineOptions, clientNonPerson, clientLegalFormOptions, familyMemberOptions, legalForm, null, null,
null, isStaff, clientCollateralManagements);
}
public static ClientData instance(final Long id, final String displayName) {
final Long officeId = null;
final String officeName = null;
return lookup(id, displayName, officeId, officeName);
}
public static ClientData instance(final String accountNo, final EnumOptionData status, final CodeValueData subStatus,
final Long officeId, final String officeName, final Long transferToOfficeId, final String transferToOfficeName, final Long id,
final String firstname, final String middlename, final String lastname, final String fullname, final String displayName,
final ExternalId externalId, final String mobileNo, final String emailAddress, final LocalDate dateOfBirth,
final CodeValueData gender, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
final ClientTimelineData timeline, final Long savingsProductId, final String savingsProductName, final Long savingsAccountId,
final CodeValueData clientType, final CodeValueData clientClassification, final EnumOptionData legalForm,
final ClientNonPersonData clientNonPerson, final Boolean isStaff) {
final Collection<OfficeData> allowedOffices = null;
final Collection<GroupGeneralData> groups = null;
final Collection<StaffData> staffOptions = null;
final Collection<CodeValueData> closureReasons = null;
final Collection<CodeValueData> genderOptions = null;
final Collection<SavingsProductData> savingProductOptions = null;
final Collection<CodeValueData> clientTypeOptions = null;
final Collection<CodeValueData> clientClassificationOptions = null;
final Collection<CodeValueData> clientNonPersonConstitutionOptions = null;
final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions = null;
final List<EnumOptionData> clientLegalFormOptions = null;
final ClientFamilyMembersData familyMemberOptions = null;
return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate,
imageId, staffId, staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline,
savingProductOptions, savingsProductId, savingsProductName, savingsAccountId, null, clientType, clientClassification,
clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions,
clientNonPerson, clientLegalFormOptions, familyMemberOptions, legalForm, null, null, null, isStaff, null);
}
private ClientData(final String accountNo, final EnumOptionData status, final CodeValueData subStatus, final Long officeId,
final String officeName, final Long transferToOfficeId, final String transferToOfficeName, final Long id,
final String firstname, final String middlename, final String lastname, final String fullname, final String displayName,
final ExternalId externalId, final String mobileNo, final String emailAddress, final LocalDate dateOfBirth,
final CodeValueData gender, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
final Collection<OfficeData> allowedOffices, final Collection<GroupGeneralData> groups,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> narrations,
final Collection<CodeValueData> genderOptions, final ClientTimelineData timeline,
final Collection<SavingsProductData> savingProductOptions, final Long savingsProductId, final String savingsProductName,
final Long savingsAccountId, final Collection<SavingsAccountData> savingAccountOptions, final CodeValueData clientType,
final CodeValueData clientClassification, final Collection<CodeValueData> clientTypeOptions,
final Collection<CodeValueData> clientClassificationOptions, final Collection<CodeValueData> clientNonPersonConstitutionOptions,
final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions, final ClientNonPersonData clientNonPerson,
final List<EnumOptionData> clientLegalFormOptions, final ClientFamilyMembersData familyMemberOptions,
final EnumOptionData legalForm, final Collection<AddressData> address, final Boolean isAddressEnabled,
final List<DatatableData> datatables, final Boolean isStaff,
final Set<ClientCollateralManagementData> clientCollateralManagements) {
this.accountNo = accountNo;
this.status = status;
if (status != null) {
this.active = status.getId().equals(300L);
} else {
this.active = null;
}
this.subStatus = subStatus;
this.officeId = officeId;
this.officeName = officeName;
this.transferToOfficeId = transferToOfficeId;
this.transferToOfficeName = transferToOfficeName;
this.id = id;
this.firstname = StringUtils.defaultIfEmpty(firstname, null);
this.middlename = StringUtils.defaultIfEmpty(middlename, null);
this.lastname = StringUtils.defaultIfEmpty(lastname, null);
this.fullname = StringUtils.defaultIfEmpty(fullname, null);
this.displayName = StringUtils.defaultIfEmpty(displayName, null);
this.externalId = externalId;
this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
this.emailAddress = StringUtils.defaultIfEmpty(emailAddress, null);
this.activationDate = activationDate;
this.dateOfBirth = dateOfBirth;
this.gender = gender;
this.clientClassification = clientClassification;
this.clientType = clientType;
this.imageId = imageId;
if (imageId != null) {
this.imagePresent = Boolean.TRUE;
} else {
this.imagePresent = null;
}
this.staffId = staffId;
this.staffName = staffName;
// associations
this.groups = groups;
// template
this.officeOptions = allowedOffices;
this.staffOptions = staffOptions;
this.narrations = narrations;
this.genderOptions = genderOptions;
this.clientClassificationOptions = clientClassificationOptions;
this.clientTypeOptions = clientTypeOptions;
this.clientNonPersonConstitutionOptions = clientNonPersonConstitutionOptions;
this.clientNonPersonMainBusinessLineOptions = clientNonPersonMainBusinessLineOptions;
this.clientLegalFormOptions = clientLegalFormOptions;
this.familyMemberOptions = familyMemberOptions;
this.timeline = timeline;
this.savingProductOptions = savingProductOptions;
this.savingsProductId = savingsProductId;
this.savingsProductName = savingsProductName;
this.savingsAccountId = savingsAccountId;
this.savingAccountOptions = savingAccountOptions;
this.legalForm = legalForm;
this.isStaff = isStaff;
this.clientNonPersonDetails = clientNonPerson;
this.address = address;
this.isAddressEnabled = isAddressEnabled;
this.datatables = datatables;
this.clientCollateralManagements = clientCollateralManagements;
}
public ExternalId getExternalId() {
if (this.externalId == null) {
return ExternalId.empty();
}
return this.externalId;
}
@Override
public int compareTo(final ClientData obj) {
if (obj == null) {
return -1;
}
return new CompareToBuilder() //
.append(this.id, obj.id) //
.append(this.displayName, obj.displayName) //
.toComparison();
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj.getClass() != getClass()) {
return false;
}
final ClientData rhs = (ClientData) obj;
return new EqualsBuilder() //
.append(this.id, rhs.id) //
.append(this.displayName, rhs.displayName) //
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37) //
.append(this.id) //
.append(this.displayName) //
.toHashCode();
}
}
@@ -0,0 +1,191 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.data;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.Collection;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
public final class ClientFamilyMembersData implements Serializable {
private final Long id;
private final Long clientId;
private final String firstName;
private final String middleName;
private final String lastName;
private final String qualification;
private final Long relationshipId;
private final String relationship;
private final Long maritalStatusId;
private final String maritalStatus;
private final Long genderId;
private final String gender;
private final LocalDate dateOfBirth;
private final Long professionId;
private final String profession;
private final String mobileNumber;
private final Long age;
private final Boolean isDependent;
// template holder
private final Collection<CodeValueData> relationshipIdOptions;
private final Collection<CodeValueData> genderIdOptions;
private final Collection<CodeValueData> maritalStatusIdOptions;
private final Collection<CodeValueData> professionIdOptions;
private ClientFamilyMembersData(final Long id, final Long clientId, final String firstName, final String middleName,
final String lastName, final String qualification, final String mobileNumber, final Long age, final Boolean isDependent,
final String relationship, final Long relationshipId, final String maritalStatus, final Long maritalStatusId,
final String gender, final Long genderId, final LocalDate dateOfBirth, final String profession, final Long professionId,
final Collection<CodeValueData> relationshipIdOptions, final Collection<CodeValueData> genderIdOptions,
final Collection<CodeValueData> maritalStatusIdOptions, final Collection<CodeValueData> professionIdOptions) {
this.id = id;
this.clientId = clientId;
this.firstName = firstName;
this.middleName = middleName;
this.lastName = lastName;
this.qualification = qualification;
this.relationship = relationship;
this.relationshipId = relationshipId;
this.maritalStatus = maritalStatus;
this.maritalStatusId = maritalStatusId;
this.gender = gender;
this.genderId = genderId;
this.dateOfBirth = dateOfBirth;
this.profession = profession;
this.professionId = professionId;
this.mobileNumber = mobileNumber;
this.age = age;
this.isDependent = isDependent;
this.relationshipIdOptions = relationshipIdOptions;
this.genderIdOptions = genderIdOptions;
this.maritalStatusIdOptions = maritalStatusIdOptions;
this.professionIdOptions = professionIdOptions;
}
public static ClientFamilyMembersData instance(final Long id, final Long clientId, final String firstName, final String middleName,
final String lastName, final String qualification, final String mobileNumber, final Long age, final Boolean isDependent,
final String relationship, final Long relationshipId, final String maritalStatus, final Long maritalStatusId,
final String gender, final Long genderId, final LocalDate dateOfBirth, final String profession, final Long professionId) {
return new ClientFamilyMembersData(id, clientId, firstName, middleName, lastName, qualification, mobileNumber, age, isDependent,
relationship, relationshipId, maritalStatus, maritalStatusId, gender, genderId, dateOfBirth, profession, professionId, null,
null, null, null);
}
public static ClientFamilyMembersData templateInstance(final Collection<CodeValueData> relationshipIdOptions,
final Collection<CodeValueData> genderIdOptions, final Collection<CodeValueData> maritalStatusIdOptions,
final Collection<CodeValueData> professionIdOptions) {
return new ClientFamilyMembersData(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, relationshipIdOptions, genderIdOptions, maritalStatusIdOptions, professionIdOptions);
}
public Long getId() {
return this.id;
}
public Long getClientId() {
return this.clientId;
}
public String getFirstName() {
return this.firstName;
}
public String getMiddleName() {
return this.middleName;
}
public String getLastName() {
return this.lastName;
}
public String getQualification() {
return this.qualification;
}
public Long getRelationshipId() {
return this.relationshipId;
}
public String getRelationship() {
return this.relationship;
}
public Long getMaritalStatusId() {
return this.maritalStatusId;
}
public String getMaritalStatus() {
return this.maritalStatus;
}
public Long getGenderId() {
return this.genderId;
}
public String getGender() {
return this.gender;
}
public LocalDate getDateOfBirth() {
return this.dateOfBirth;
}
public Long getProfessionId() {
return this.professionId;
}
public String getProfession() {
return this.profession;
}
public String getMobileNumber() {
return this.mobileNumber;
}
public Long getAge() {
return this.age;
}
public Boolean getIsDependent() {
return this.isDependent;
}
}
@@ -0,0 +1,73 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.data;
import java.io.Serializable;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
/**
* Immutable data object representing the ClientNonPerson
*/
@SuppressWarnings("unused")
public class ClientNonPersonData implements Serializable {
private final CodeValueData constitution;
private final String incorpNumber;
private final LocalDate incorpValidityTillDate;
private final CodeValueData mainBusinessLine;
private final String remarks;
// import fields
private Long mainBusinessLineId;
private Long constitutionId;
private String locale;
private String dateFormat;
public static ClientNonPersonData importInstance(String incorporationNo, LocalDate incorpValidityTillDate, String remarks,
Long mainBusinessLineId, Long constitutionId, String locale, String dateFormat) {
return new ClientNonPersonData(incorporationNo, incorpValidityTillDate, remarks, mainBusinessLineId, constitutionId, locale,
dateFormat);
}
private ClientNonPersonData(String incorpNumber, LocalDate incorpValidityTillDate, String remarks, Long mainBusinessLineId,
Long constitutionId, String locale, String dateFormat) {
this.incorpNumber = incorpNumber;
this.incorpValidityTillDate = incorpValidityTillDate;
this.remarks = remarks;
this.mainBusinessLineId = mainBusinessLineId;
this.constitutionId = constitutionId;
this.dateFormat = dateFormat;
this.locale = locale;
this.constitution = null;
this.mainBusinessLine = null;
}
public ClientNonPersonData(CodeValueData constitution, String incorpNo, LocalDate incorpValidityTillDate,
CodeValueData mainBusinessLine, String remarks) {
this.constitution = constitution;
this.incorpNumber = incorpNo;
this.incorpValidityTillDate = incorpValidityTillDate;
this.mainBusinessLine = mainBusinessLine;
this.remarks = remarks;
}
}
@@ -0,0 +1,68 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.data;
import java.io.Serializable;
import java.time.LocalDate;
import lombok.Getter;
/**
* Immutable data object represent the important time-line events of a loan application and loan.
*/
@SuppressWarnings("unused")
@Getter
public class ClientTimelineData implements Serializable {
private final LocalDate submittedOnDate;
private final String submittedByUsername;
private final String submittedByFirstname;
private final String submittedByLastname;
private final LocalDate activatedOnDate;
private final String activatedByUsername;
private final String activatedByFirstname;
private final String activatedByLastname;
private final LocalDate closedOnDate;
private final String closedByUsername;
private final String closedByFirstname;
private final String closedByLastname;
public ClientTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, final String submittedByFirstname,
final String submittedByLastname, final LocalDate activatedOnDate, final String activatedByUsername,
final String activatedByFirstname, final String activatedByLastname, final LocalDate closedOnDate,
final String closedByUsername, final String closedByFirstname, final String closedByLastname) {
this.submittedOnDate = submittedOnDate;
this.submittedByUsername = submittedByUsername;
this.submittedByFirstname = submittedByFirstname;
this.submittedByLastname = submittedByLastname;
this.activatedOnDate = activatedOnDate;
this.activatedByUsername = activatedByUsername;
this.activatedByFirstname = activatedByFirstname;
this.activatedByLastname = activatedByLastname;
this.closedOnDate = closedOnDate;
this.closedByUsername = closedByUsername;
this.closedByFirstname = closedByFirstname;
this.closedByLastname = closedByLastname;
}
}
@@ -0,0 +1,774 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.domain;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.useradministration.domain.AppUser;
@Entity
@Getter
@Setter
@Table(name = "m_client", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "account_no_UNIQUE"), //
@UniqueConstraint(columnNames = { "mobile_no" }, name = "mobile_no_UNIQUE") })
public class Client extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "account_no", length = 20, unique = true, nullable = false)
private String accountNumber;
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@ManyToOne
@JoinColumn(name = "transfer_to_office_id")
private Office transferToOffice;
@OneToOne(optional = true)
@JoinColumn(name = "image_id")
private Image image;
/**
* A value from {@link ClientStatus}.
*/
@Column(name = "status_enum", nullable = false)
private Integer status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "sub_status")
private CodeValue subStatus;
@Column(name = "activation_date")
private LocalDate activationDate;
@Column(name = "office_joining_date")
private LocalDate officeJoiningDate;
@Column(name = "firstname", length = 50)
private String firstname;
@Column(name = "middlename", length = 50)
private String middlename;
@Column(name = "lastname", length = 50)
private String lastname;
@Column(name = "fullname", length = 100)
private String fullname;
@Column(name = "display_name", length = 100, nullable = false)
private String displayName;
@Column(name = "mobile_no", length = 50, unique = true)
private String mobileNo;
@Column(name = "email_address", length = 50, unique = true)
private String emailAddress;
@Column(name = "is_staff", nullable = false)
private boolean isStaff;
@Column(name = "external_id", length = 100, unique = true)
private ExternalId externalId;
@Column(name = "date_of_birth")
private LocalDate dateOfBirth;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "gender_cv_id")
private CodeValue gender;
@ManyToOne
@JoinColumn(name = "staff_id")
private Staff staff;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "m_group_client", joinColumns = @JoinColumn(name = "client_id"), inverseJoinColumns = @JoinColumn(name = "group_id"))
private Set<Group> groups;
@Transient
private boolean accountNumberRequiresAutoGeneration = false;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "closure_reason_cv_id")
private CodeValue closureReason;
@Column(name = "closedon_date")
private LocalDate closureDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reject_reason_cv_id")
private CodeValue rejectionReason;
@Column(name = "rejectedon_date")
private LocalDate rejectionDate;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "rejectedon_userid")
private AppUser rejectedBy;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "withdraw_reason_cv_id")
private CodeValue withdrawalReason;
@Column(name = "withdrawn_on_date")
private LocalDate withdrawalDate;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "withdraw_on_userid")
private AppUser withdrawnBy;
@Column(name = "reactivated_on_date")
private LocalDate reactivateDate;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "reactivated_on_userid")
private AppUser reactivatedBy;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "closedon_userid")
private AppUser closedBy;
@Column(name = "submittedon_date")
private LocalDate submittedOnDate;
/*
* Deprecated since common Auditable fields were introduced. Columns and data left untouched to help migration.
*
* @Column(name = "updated_on") private LocalDate updatedOnDate;
*
* @ManyToOne(optional = true, fetch = FetchType.LAZY)
*
* @JoinColumn(name = "updated_by") private AppUser updatedBy;
*/
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "activatedon_userid")
private AppUser activatedBy;
@Column(name = "default_savings_product")
private Long savingsProductId;
@Column(name = "default_savings_account")
private Long savingsAccountId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_type_cv_id")
private CodeValue clientType;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_classification_cv_id")
private CodeValue clientClassification;
@Column(name = "legal_form_enum")
private Integer legalForm;
@Column(name = "reopened_on_date")
private LocalDate reopenedDate;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "reopened_by_userid")
private AppUser reopenedBy;
@Column(name = "proposed_transfer_date")
private LocalDate proposedTransferDate;
public static Client instance(final AppUser currentUser, final ClientStatus status, final Office office, final Group clientParentGroup,
final String accountNo, final String firstname, final String middlename, final String lastname, final String fullname,
final LocalDate activationDate, final LocalDate officeJoiningDate, final ExternalId externalId, final String mobileNo,
final String emailAddress, final Staff staff, final LocalDate submittedOnDate, final Long savingsProductId,
final Long savingsAccountId, final LocalDate dateOfBirth, final CodeValue gender, final CodeValue clientType,
final CodeValue clientClassification, final Integer legalForm, final Boolean isStaff) {
return new Client(currentUser, status, office, clientParentGroup, accountNo, firstname, middlename, lastname, fullname,
activationDate, officeJoiningDate, externalId, mobileNo, emailAddress, staff, submittedOnDate, savingsProductId,
savingsAccountId, dateOfBirth, gender, clientType, clientClassification, legalForm, isStaff);
}
protected Client() {}
private Client(final AppUser currentUser, final ClientStatus status, final Office office, final Group clientParentGroup,
final String accountNo, final String firstname, final String middlename, final String lastname, final String fullname,
final LocalDate activationDate, final LocalDate officeJoiningDate, final ExternalId externalId, final String mobileNo,
final String emailAddress, final Staff staff, final LocalDate submittedOnDate, final Long savingsProductId,
final Long savingsAccountId, final LocalDate dateOfBirth, final CodeValue gender, final CodeValue clientType,
final CodeValue clientClassification, final Integer legalForm, final Boolean isStaff) {
if (StringUtils.isBlank(accountNo)) {
this.accountNumber = new RandomPasswordGenerator(19).generate();
this.accountNumberRequiresAutoGeneration = true;
} else {
this.accountNumber = accountNo;
}
this.submittedOnDate = submittedOnDate;
this.status = status.getValue();
this.office = office;
this.externalId = externalId;
if (StringUtils.isNotBlank(mobileNo)) {
this.mobileNo = mobileNo.trim();
}
if (StringUtils.isNotBlank(emailAddress)) {
this.emailAddress = emailAddress.trim();
}
if (activationDate != null) {
this.activationDate = activationDate;
this.activatedBy = currentUser;
}
this.officeJoiningDate = officeJoiningDate;
if (StringUtils.isNotBlank(firstname)) {
this.firstname = firstname.trim();
}
if (StringUtils.isNotBlank(middlename)) {
this.middlename = middlename.trim();
}
if (StringUtils.isNotBlank(lastname)) {
this.lastname = lastname.trim();
}
if (StringUtils.isNotBlank(fullname)) {
this.fullname = fullname.trim();
}
if (clientParentGroup != null) {
this.groups = new HashSet<>();
this.groups.add(clientParentGroup);
}
this.staff = staff;
this.savingsProductId = savingsProductId;
this.savingsAccountId = savingsAccountId;
if (gender != null) {
this.gender = gender;
}
this.dateOfBirth = dateOfBirth;
this.clientType = clientType;
this.clientClassification = clientClassification;
this.setLegalForm(legalForm);
deriveDisplayName();
validate();
}
private void validate() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
validateNameParts(dataValidationErrors);
validateActivationDate(dataValidationErrors);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public void validateUpdate() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
// Not validating name parts while update request as firstname/lastname
// can be along with fullname
// when we change clientType from Individual to Organisation or
// vice-cersa
validateActivationDate(dataValidationErrors);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public boolean isAccountNumberRequiresAutoGeneration() {
return this.accountNumberRequiresAutoGeneration;
}
public void setAccountNumberRequiresAutoGeneration(final boolean accountNumberRequiresAutoGeneration) {
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
}
public boolean identifiedBy(final Long clientId) {
return getId().equals(clientId);
}
public void updateAccountNo(final String accountIdentifier) {
this.accountNumber = accountIdentifier;
this.accountNumberRequiresAutoGeneration = false;
}
public void activate(final AppUser currentUser, final DateTimeFormatter formatter, final LocalDate activationLocalDate) {
if (isActive()) {
final String defaultUserMessage = "Cannot activate client. Client is already active.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.already.active", defaultUserMessage,
ClientApiConstants.activationDateParamName, activationLocalDate.format(formatter));
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(dataValidationErrors);
}
this.activationDate = activationLocalDate;
this.activatedBy = currentUser;
this.officeJoiningDate = this.activationDate;
this.status = ClientStatus.ACTIVE.getValue();
// in case a closed client is being re open
this.closureDate = null;
this.closureReason = null;
this.closedBy = null;
validate();
}
public boolean isNotActive() {
return !isActive();
}
public boolean isActive() {
return ClientStatus.fromInt(this.status).isActive();
}
public boolean isClosed() {
return ClientStatus.fromInt(this.status).isClosed();
}
public boolean isTransferInProgress() {
return ClientStatus.fromInt(this.status).isTransferInProgress();
}
public boolean isTransferOnHold() {
return ClientStatus.fromInt(this.status).isTransferOnHold();
}
public boolean isTransferInProgressOrOnHold() {
return isTransferInProgress() || isTransferOnHold();
}
public boolean isNotPending() {
return !isPending();
}
public boolean isPending() {
return ClientStatus.fromInt(this.status).isPending();
}
private boolean isDateInTheFuture(final LocalDate localDate) {
return localDate.isAfter(DateUtils.getBusinessLocalDate());
}
public boolean isRejected() {
return ClientStatus.fromInt(this.status).isRejected();
}
public boolean isWithdrawn() {
return ClientStatus.fromInt(this.status).isWithdrawn();
}
private void validateNameParts(final List<ApiParameterError> dataValidationErrors) {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("client");
if (StringUtils.isNotBlank(this.fullname)) {
baseDataValidator.reset().parameter(ClientApiConstants.firstnameParamName).value(this.firstname)
.mustBeBlankWhenParameterProvided(ClientApiConstants.fullnameParamName, this.fullname);
baseDataValidator.reset().parameter(ClientApiConstants.middlenameParamName).value(this.middlename)
.mustBeBlankWhenParameterProvided(ClientApiConstants.fullnameParamName, this.fullname);
baseDataValidator.reset().parameter(ClientApiConstants.lastnameParamName).value(this.lastname)
.mustBeBlankWhenParameterProvided(ClientApiConstants.fullnameParamName, this.fullname);
} else {
baseDataValidator.reset().parameter(ClientApiConstants.firstnameParamName).value(this.firstname).notBlank()
.notExceedingLengthOf(50);
baseDataValidator.reset().parameter(ClientApiConstants.middlenameParamName).value(this.middlename).ignoreIfNull()
.notExceedingLengthOf(50);
baseDataValidator.reset().parameter(ClientApiConstants.lastnameParamName).value(this.lastname).notBlank()
.notExceedingLengthOf(50);
}
}
private void validateActivationDate(final List<ApiParameterError> dataValidationErrors) {
if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) {
final String defaultUserMessage = "submitted date cannot be in the future.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.in.the.future",
defaultUserMessage, ClientApiConstants.submittedOnDateParamName, this.submittedOnDate);
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) {
final String defaultUserMessage = "submitted date cannot be after the activation date";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.after.activation.date",
defaultUserMessage, ClientApiConstants.submittedOnDateParamName, this.submittedOnDate);
dataValidationErrors.add(error);
}
if (getReopenedDate() != null && getActivationLocalDate() != null && getReopenedDate().isAfter(getActivationLocalDate())) {
final String defaultUserMessage = "reopened date cannot be after the submittedon date";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.after.reopened.date",
defaultUserMessage, ClientApiConstants.reopenedDateParamName, this.reopenedDate);
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) {
final String defaultUserMessage = "Activation date cannot be in the future.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.activationDate.in.the.future",
defaultUserMessage, ClientApiConstants.activationDateParamName, getActivationLocalDate());
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null) {
if (this.office.isOpeningDateAfter(getActivationLocalDate())) {
final String defaultUserMessage = "Client activation date cannot be a date before the office opening date.";
final ApiParameterError error = ApiParameterError.parameterError(
"error.msg.clients.activationDate.cannot.be.before.office.activation.date", defaultUserMessage,
ClientApiConstants.activationDateParamName, getActivationLocalDate());
dataValidationErrors.add(error);
}
}
}
public void deriveDisplayName() {
StringBuilder nameBuilder = new StringBuilder();
Integer legalForm = this.getLegalForm();
if (legalForm == null || LegalForm.fromInt(legalForm).isPerson()) {
if (StringUtils.isNotBlank(this.firstname)) {
nameBuilder.append(this.firstname).append(' ');
}
if (StringUtils.isNotBlank(this.middlename)) {
nameBuilder.append(this.middlename).append(' ');
}
if (StringUtils.isNotBlank(this.lastname)) {
nameBuilder.append(this.lastname);
}
if (StringUtils.isNotBlank(this.fullname)) {
nameBuilder = new StringBuilder(this.fullname);
}
} else if (LegalForm.fromInt(legalForm).isEntity()) {
if (StringUtils.isNotBlank(this.fullname)) {
nameBuilder = new StringBuilder(this.fullname);
}
}
this.displayName = nameBuilder.toString();
}
public LocalDate getActivationLocalDate() {
return this.activationDate;
}
public LocalDate getOfficeJoiningLocalDate() {
return this.officeJoiningDate;
}
public boolean isOfficeIdentifiedBy(final Long officeId) {
return this.office.identifiedBy(officeId);
}
public Long officeId() {
return this.office.getId();
}
public void setImage(final Image image) {
this.image = image;
}
public String mobileNo() {
return this.mobileNo;
}
public String emailAddress() {
return this.emailAddress;
}
public void setMobileNo(final String mobileNo) {
this.mobileNo = mobileNo;
}
public boolean isNotStaff() {
return !isStaff();
}
public boolean isStaff() {
return this.isStaff;
}
public void setEmailAddress(final String emailAddress) {
this.emailAddress = emailAddress;
}
public void setDisplayName(final String displayName) {
this.displayName = displayName;
}
public void updateOffice(final Office office) {
this.office = office;
}
public void updateTransferToOffice(final Office office) {
this.transferToOffice = office;
}
public void updateOfficeJoiningDate(final LocalDate date) {
this.officeJoiningDate = date;
}
public Long staffId() {
Long staffId = null;
if (this.staff != null) {
staffId = this.staff.getId();
}
return staffId;
}
public void updateStaff(final Staff staff) {
this.staff = staff;
}
public void unassignStaff() {
this.staff = null;
}
public void assignStaff(final Staff staff) {
this.staff = staff;
}
public void close(final AppUser currentUser, final CodeValue closureReason, final LocalDate closureDate) {
this.closureReason = closureReason;
this.closureDate = closureDate;
this.closedBy = currentUser;
this.status = ClientStatus.CLOSED.getValue();
}
public CodeValue subStatus() {
return this.subStatus;
}
public Long subStatusId() {
Long subStatusId = null;
if (this.subStatus != null) {
subStatusId = this.subStatus.getId();
}
return subStatusId;
}
public boolean isActivatedAfter(final LocalDate submittedOn) {
return getActivationLocalDate().isAfter(submittedOn);
}
public boolean isChildOfGroup(final Long groupId) {
if (groupId != null && this.groups != null && !this.groups.isEmpty()) {
for (final Group group : this.groups) {
if (group.getId().equals(groupId)) {
return true;
}
}
}
return false;
}
public Long savingsProductId() {
return this.savingsProductId;
}
public void updateSavingsProduct(final Long savingsProductId) {
this.savingsProductId = savingsProductId;
}
public AppUser activatedBy() {
return this.activatedBy;
}
public Long savingsAccountId() {
return this.savingsAccountId;
}
public void updateSavingsAccount(Long savingsAccountId) {
this.savingsAccountId = savingsAccountId;
}
public Long genderId() {
Long genderId = null;
if (this.gender != null) {
genderId = this.gender.getId();
}
return genderId;
}
public Long clientTypeId() {
Long clientTypeId = null;
if (this.clientType != null) {
clientTypeId = this.clientType.getId();
}
return clientTypeId;
}
public Long clientClassificationId() {
Long clientClassificationId = null;
if (this.clientClassification != null) {
clientClassificationId = this.clientClassification.getId();
}
return clientClassificationId;
}
public LocalDate getRejectedDate() {
return this.rejectionDate;
}
public CodeValue gender() {
return this.gender;
}
public CodeValue clientType() {
return this.clientType;
}
public void updateClientType(CodeValue clientType) {
this.clientType = clientType;
}
public CodeValue clientClassification() {
return this.clientClassification;
}
public void updateClientClassification(CodeValue clientClassification) {
this.clientClassification = clientClassification;
}
public void updateGender(CodeValue gender) {
this.gender = gender;
}
public LocalDate dateOfBirth() {
return this.dateOfBirth;
}
public LocalDate dateOfBirthLocalDate() {
return this.dateOfBirth;
}
public void reject(AppUser currentUser, CodeValue rejectionReason, LocalDate rejectionDate) {
this.rejectionReason = rejectionReason;
this.rejectionDate = rejectionDate;
this.rejectedBy = currentUser;
this.status = ClientStatus.REJECTED.getValue();
}
public void withdraw(AppUser currentUser, CodeValue withdrawalReason, LocalDate withdrawalDate) {
this.withdrawalReason = withdrawalReason;
this.withdrawalDate = withdrawalDate;
this.withdrawnBy = currentUser;
this.status = ClientStatus.WITHDRAWN.getValue();
}
public void reActivate(AppUser currentUser, LocalDate reactivateDate) {
this.closureDate = null;
this.closureReason = null;
this.reactivateDate = reactivateDate;
this.reactivatedBy = currentUser;
this.status = ClientStatus.PENDING.getValue();
}
public void reOpened(AppUser currentUser, LocalDate reopenedDate) {
this.reopenedDate = reopenedDate;
this.reopenedBy = currentUser;
this.status = ClientStatus.PENDING.getValue();
}
public void setLegalForm(Integer legalForm) {
this.legalForm = legalForm;
}
public void loadLazyCollections() {
this.groups.size();
}
public LocalDate getProposedTransferDate() {
return proposedTransferDate;
}
public void updateProposedTransferDate(LocalDate proposedTransferDate) {
this.proposedTransferDate = proposedTransferDate;
}
public void resetDerivedNames(final LegalForm legalForm) {
if (legalForm.isPerson()) {
setFullname(null);
} else if (legalForm.isEntity()) {
setFirstname(null);
setLastname(null);
setDisplayName(null);
}
}
}
@@ -0,0 +1,143 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.domain;
import org.springframework.util.StringUtils;
/**
* Enum representation of client status states.
*/
public enum ClientStatus {
INVALID(0, "clientStatusType.invalid"), //
PENDING(100, "clientStatusType.pending"), //
ACTIVE(300, "clientStatusType.active"), //
TRANSFER_IN_PROGRESS(303, "clientStatusType.transfer.in.progress"), //
TRANSFER_ON_HOLD(304, "clientStatusType.transfer.on.hold"), //
CLOSED(600, "clientStatusType.closed"), REJECTED(700, "clientStatusType.rejected"), WITHDRAWN(800, "clientStatusType.withdraw");
private final Integer value;
private final String code;
public static ClientStatus fromInt(final Integer statusValue) {
ClientStatus enumeration = ClientStatus.INVALID;
switch (statusValue) {
case 100:
enumeration = ClientStatus.PENDING;
break;
case 300:
enumeration = ClientStatus.ACTIVE;
break;
case 303:
enumeration = ClientStatus.TRANSFER_IN_PROGRESS;
break;
case 304:
enumeration = ClientStatus.TRANSFER_ON_HOLD;
break;
case 600:
enumeration = ClientStatus.CLOSED;
break;
case 700:
enumeration = ClientStatus.REJECTED;
break;
case 800:
enumeration = ClientStatus.WITHDRAWN;
break;
}
return enumeration;
}
public static ClientStatus fromString(final String clientString) {
ClientStatus clientStatus = ClientStatus.INVALID;
if (!StringUtils.hasLength(clientString)) {
return clientStatus;
}
if (clientString.equalsIgnoreCase(ClientStatus.PENDING.toString())) {
clientStatus = ClientStatus.PENDING;
} else if (clientString.equalsIgnoreCase(ClientStatus.ACTIVE.toString())) {
clientStatus = ClientStatus.ACTIVE;
} else if (clientString.equalsIgnoreCase(ClientStatus.TRANSFER_IN_PROGRESS.toString())) {
clientStatus = ClientStatus.TRANSFER_IN_PROGRESS;
} else if (clientString.equalsIgnoreCase(ClientStatus.WITHDRAWN.toString())) {
clientStatus = ClientStatus.WITHDRAWN;
} else if (clientString.equalsIgnoreCase(ClientStatus.CLOSED.toString())) {
clientStatus = ClientStatus.CLOSED;
} else if (clientString.equalsIgnoreCase(ClientStatus.TRANSFER_ON_HOLD.toString())) {
clientStatus = ClientStatus.TRANSFER_ON_HOLD;
} else if (clientString.equalsIgnoreCase(ClientStatus.REJECTED.toString())) {
clientStatus = ClientStatus.REJECTED;
}
return clientStatus;
}
ClientStatus(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final ClientStatus state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isPending() {
return this.value.equals(ClientStatus.PENDING.getValue());
}
public boolean isActive() {
return this.value.equals(ClientStatus.ACTIVE.getValue());
}
public boolean isClosed() {
return this.value.equals(ClientStatus.CLOSED.getValue());
}
public boolean isRejected() {
return this.value.equals(ClientStatus.REJECTED.getValue());
}
public boolean isWithdrawn() {
return this.value.equals(ClientStatus.WITHDRAWN.getValue());
}
public boolean isTransferInProgress() {
return this.value.equals(ClientStatus.TRANSFER_IN_PROGRESS.getValue());
}
public boolean isTransferOnHold() {
return this.value.equals(ClientStatus.TRANSFER_ON_HOLD.getValue());
}
public boolean isUnderTransfer() {
return isTransferInProgress() || isTransferOnHold();
}
}

Some files were not shown because too many files have changed in this diff Show More