Fix inconsistenant Date and Date time handling

This commit is contained in:
Adam Saghy
2022-08-01 15:47:47 +02:00
committed by Arnold Gálovics
parent 7fb61d028e
commit 65c6b18ed1
20 changed files with 595 additions and 364 deletions
@@ -46,9 +46,9 @@ public class AccrualAccountingWritePlatformServiceImpl implements AccrualAccount
@Override
public CommandProcessingResult executeLoansPeriodicAccrual(JsonCommand command) {
this.accountingDataValidator.validateLoanPeriodicAccrualData(command.json());
LocalDate tilldate = command.localDateValueOfParameterNamed(accrueTillParamName);
LocalDate tillDate = command.localDateValueOfParameterNamed(accrueTillParamName);
try {
this.loanAccrualPlatformService.addPeriodicAccruals(tilldate);
this.loanAccrualPlatformService.addPeriodicAccruals(tillDate);
} catch (MultiException e) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
@@ -26,6 +26,8 @@ import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.springframework.jdbc.support.JdbcUtils;
@@ -133,4 +135,14 @@ public final class JdbcSupport {
}
return result;
}
public static OffsetDateTime getOffsetDateTime(ResultSet rs, String columnName) throws SQLException {
final Timestamp timestamp = rs.getTimestamp(columnName);
if (timestamp != null) {
OffsetDateTime offsetDateTimeAtUTC = OffsetDateTime.of(timestamp.toLocalDateTime(), ZoneOffset.UTC);
return offsetDateTimeAtUTC
.withOffsetSameInstant(DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(offsetDateTimeAtUTC.toInstant()));
}
return null;
}
}
@@ -22,6 +22,7 @@ import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceF
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
@@ -46,6 +47,9 @@ public class UpdateNpaConfig {
@Autowired
private DatabaseSpecificSQLGenerator sqlGenerator;
@Autowired
private PlatformSecurityContext platformSecurityContext;
@Bean
protected Step updateNpaStep() {
return steps.get(JobName.UPDATE_NPA.name()).tasklet(updateNpaTasklet()).build();
@@ -58,6 +62,6 @@ public class UpdateNpaConfig {
@Bean
public UpdateNpaTasklet updateNpaTasklet() {
return new UpdateNpaTasklet(dataSourceServiceFactory, databaseTypeResolver, sqlGenerator);
return new UpdateNpaTasklet(dataSourceServiceFactory, databaseTypeResolver, sqlGenerator, platformSecurityContext);
}
}
@@ -20,10 +20,13 @@ package org.apache.fineract.infrastructure.jobs.service.updatenpa;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
@@ -37,9 +40,11 @@ public class UpdateNpaTasklet implements Tasklet {
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
private final DatabaseTypeResolver databaseTypeResolver;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final PlatformSecurityContext context;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
AppUser user = context.getAuthenticatedUserIfPresent();
final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
final StringBuilder resetNPASqlBuilder = new StringBuilder();
@@ -52,11 +57,13 @@ public class UpdateNpaTasklet implements Tasklet {
String wherePart = " where loan.id = sl.id ";
if (databaseTypeResolver.isMySQL()) {
resetNPASqlBuilder.append(", ").append(fromPart).append(" set loan.is_npa = false").append(wherePart);
resetNPASqlBuilder.append(", ").append(fromPart).append(" set loan.is_npa = false")
.append(", loan.last_modified_by = ?, loan.last_modified_on_utc = ? ").append(wherePart);
} else {
resetNPASqlBuilder.append("set is_npa = false").append(" FROM ").append(fromPart).append(wherePart);
resetNPASqlBuilder.append("set is_npa = false").append(", last_modified_by = ?, last_modified_on_utc = ? ").append(" FROM ")
.append(fromPart).append(wherePart);
}
jdbcTemplate.update(resetNPASqlBuilder.toString());
jdbcTemplate.update(resetNPASqlBuilder.toString(), user.getId(), DateUtils.getOffsetDateTimeOfTenant());
final StringBuilder updateSqlBuilder = new StringBuilder(900);
@@ -68,12 +75,14 @@ public class UpdateNpaTasklet implements Tasklet {
wherePart = " where ml.id=sl.id ";
updateSqlBuilder.append("UPDATE m_loan as ml ");
if (databaseTypeResolver.isMySQL()) {
updateSqlBuilder.append(", ").append(fromPart).append(" SET ml.is_npa = true").append(wherePart);
updateSqlBuilder.append(", ").append(fromPart).append(" SET ml.is_npa = true")
.append(", ml.last_modified_by = ?, ml.last_modified_on_utc = ? ").append(wherePart);
} else {
updateSqlBuilder.append(" SET is_npa = true").append(" FROM ").append(fromPart).append(wherePart);
updateSqlBuilder.append(" SET is_npa = true").append(", last_modified_by = ?, last_modified_on_utc = ? ").append(" FROM ")
.append(fromPart).append(wherePart);
}
final int result = jdbcTemplate.update(updateSqlBuilder.toString());
final int result = jdbcTemplate.update(updateSqlBuilder.toString(), user.getId(), DateUtils.getOffsetDateTimeOfTenant());
log.info("{}: Records affected by updateNPA: {}", ThreadLocalContextUtil.getTenant().getName(), result);
return RepeatStatus.FINISHED;
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.client.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -39,18 +38,17 @@ import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.office.domain.OrganisationCurrency;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.useradministration.domain.AppUser;
@Entity
@Table(name = "m_client_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
public class ClientTransaction extends AbstractPersistableCustom {
public class ClientTransaction extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "client_id", nullable = false)
@@ -73,6 +71,9 @@ public class ClientTransaction extends AbstractPersistableCustom {
@Column(name = "transaction_date", nullable = false)
private LocalDate dateOf;
@Column(name = "submitted_on_date", nullable = false)
private LocalDate submittedOnDate;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@@ -82,12 +83,11 @@ public class ClientTransaction extends AbstractPersistableCustom {
@Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
@ManyToOne
@JoinColumn(name = "appuser_id", nullable = true)
private AppUser appUser;
/*
* Deprecated since common Auditable fields were introduced. Columns and data left untouched to help migration.
*
* @Column(name = "created_date", nullable = false) private LocalDateTime createdDate;
*/
@OneToMany(cascade = CascadeType.ALL, mappedBy = "clientTransaction", orphanRemoval = true, fetch = FetchType.EAGER)
private Set<ClientChargePaidBy> clientChargePaidByCollection = new HashSet<>();
@@ -97,37 +97,36 @@ public class ClientTransaction extends AbstractPersistableCustom {
protected ClientTransaction() {}
public static ClientTransaction payCharge(final Client client, final Office office, PaymentDetail paymentDetail, final LocalDate date,
final Money amount, final String currencyCode, final AppUser appUser) {
public static ClientTransaction payCharge(final Client client, final Office office, PaymentDetail paymentDetail,
final LocalDate transactionDate, final Money amount, final String currencyCode) {
final boolean isReversed = false;
final String externalId = null;
return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.PAY_CHARGE.getValue(), date, amount, isReversed,
externalId, DateUtils.getLocalDateTimeOfTenant(), currencyCode, appUser);
return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.PAY_CHARGE.getValue(), transactionDate, amount,
isReversed, externalId, currencyCode);
}
public static ClientTransaction waiver(final Client client, final Office office, final LocalDate date, final Money amount,
final String currencyCode, final AppUser appUser) {
public static ClientTransaction waiver(final Client client, final Office office, final LocalDate transactionDate, final Money amount,
final String currencyCode) {
final boolean isReversed = false;
final String externalId = null;
final PaymentDetail paymentDetail = null;
return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.WAIVE_CHARGE.getValue(), date, amount, isReversed,
externalId, DateUtils.getLocalDateTimeOfTenant(), currencyCode, appUser);
return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.WAIVE_CHARGE.getValue(), transactionDate, amount,
isReversed, externalId, currencyCode);
}
public ClientTransaction(Client client, Office office, PaymentDetail paymentDetail, Integer typeOf, LocalDate transactionLocalDate,
Money amount, boolean reversed, String externalId, LocalDateTime createdDate, String currencyCode, AppUser appUser) {
public ClientTransaction(Client client, Office office, PaymentDetail paymentDetail, Integer typeOf, LocalDate transactionDate,
Money amount, boolean reversed, String externalId, String currencyCode) {
this.client = client;
this.office = office;
this.paymentDetail = paymentDetail;
this.typeOf = typeOf;
this.dateOf = transactionLocalDate;
this.dateOf = transactionDate;
this.amount = amount.getAmount();
this.reversed = reversed;
this.externalId = externalId;
this.createdDate = createdDate;
this.currencyCode = currencyCode;
this.appUser = appUser;
this.submittedOnDate = DateUtils.getBusinessLocalDate();
}
public void reverse() {
@@ -226,4 +225,8 @@ public class ClientTransaction extends AbstractPersistableCustom {
return this.dateOf;
}
public LocalDate getSubmittedOnDate() {
return this.submittedOnDate;
}
}
@@ -54,7 +54,6 @@ import org.apache.fineract.portfolio.client.domain.ClientTransaction;
import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -174,7 +173,7 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
ClientTransaction clientTransaction = ClientTransaction.payCharge(client, client.getOffice(), paymentDetail, transactionDate,
chargePaid, clientCharge.getCurrency().getCode(), getAppUserIfPresent());
chargePaid, clientCharge.getCurrency().getCode());
this.clientTransactionRepository.saveAndFlush(clientTransaction);
// update charge paid by associations
@@ -217,7 +216,7 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client
// create Waiver Transaction
ClientTransaction clientTransaction = ClientTransaction.waiver(client, client.getOffice(), transactionDate, waivedAmount,
clientCharge.getCurrency().getCode(), getAppUserIfPresent());
clientCharge.getCurrency().getCode());
this.clientTransactionRepository.saveAndFlush(clientTransaction);
// update charge paid by associations
@@ -392,7 +391,8 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client
/**
* Ensures that the charge transaction date (for payments) is not on a holiday or a non working day
*
* @param savingsAccountCharge
* @param transactionDate
* @param clientCharge
* @param fmt
*/
private void validateTransactionDateOnWorkingDay(final LocalDate transactionDate, final ClientCharge clientCharge,
@@ -406,7 +406,8 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client
* @param date
* @param officeId
* @param jsonPropertyName
* @param errorMessageFragment
* @param errorMessageFragmentForActivityOnHoliday
* @param errorMessageFragmentForActivityOnNonWorkingDay
* @param fmt
*/
private void validateActivityDateFallOnAWorkingDay(final LocalDate date, final Long officeId, final String jsonPropertyName,
@@ -436,14 +437,6 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client
}
}
private AppUser getAppUserIfPresent() {
AppUser user = null;
if (this.context != null) {
user = this.context.getAuthenticatedUserIfPresent();
}
return user;
}
private void handleDataIntegrityIssues(@SuppressWarnings("unused") final Long clientId, final Long clientChargeId,
final Throwable realCause, final NonTransientDataAccessException dve) {
@@ -68,7 +68,7 @@ public class ClientTransactionReadPlatformServiceImpl implements ClientTransacti
final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount, ");
sqlBuilder.append("tr.created_date as submittedOnDate, tr.is_reversed as reversed, ");
sqlBuilder.append("tr.submitted_on_date as submittedOnDate, tr.is_reversed as reversed, ");
sqlBuilder.append("tr.external_id as externalId, o.name as officeName, o.id as officeId, ");
sqlBuilder.append("c.id as clientId, c.account_no as accountNo, ccpb.client_charge_id as clientChargeId, ");
sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, ");
@@ -143,7 +143,7 @@ public class ClientTransactionReadPlatformServiceImpl implements ClientTransacti
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ").append(this.clientTransactionMapper.schema())
.append(" where c.id = ? ");
parameters[0] = clientId;
sqlBuilder.append(" order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC ");
sqlBuilder.append(" order by tr.transaction_date DESC, tr.submitted_on_date DESC, tr.id DESC ");
// apply limit and offsets
@@ -169,7 +169,7 @@ public class ClientTransactionReadPlatformServiceImpl implements ClientTransacti
sql = sql + " and ccpb.client_charge_id = ?";
}
parameters[0] = clientId;
sql = sql + " order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC";
sql = sql + " order by tr.transaction_date DESC, tr.submitted_on_date DESC, tr.id DESC";
return this.jdbcTemplate.query(sql, this.clientTransactionMapper, parameters); // NOSONAR
}
@@ -19,7 +19,7 @@
package org.apache.fineract.portfolio.floatingrates.data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.List;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
@@ -33,15 +33,15 @@ public class FloatingRateData implements Comparable<FloatingRateData>, Serializa
private final boolean isBaseLendingRate;
private final boolean isActive;
private final String createdBy;
private final LocalDate createdOn;
private final OffsetDateTime createdOn;
private final String modifiedBy;
private final LocalDate modifiedOn;
private final OffsetDateTime modifiedOn;
private final List<FloatingRatePeriodData> ratePeriods;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRateFrequencyTypeOptions;
public FloatingRateData(Long id, String name, boolean isBaseLendingRate, boolean isActive, String createdBy, LocalDate createdOn,
String modifiedBy, LocalDate modifiedOn, List<FloatingRatePeriodData> ratePeriods,
public FloatingRateData(Long id, String name, boolean isBaseLendingRate, boolean isActive, String createdBy, OffsetDateTime createdOn,
String modifiedBy, OffsetDateTime modifiedOn, List<FloatingRatePeriodData> ratePeriods,
List<EnumOptionData> interestRateFrequencyTypeOptions) {
this.id = id;
this.name = name;
@@ -75,7 +75,7 @@ public class FloatingRateData implements Comparable<FloatingRateData>, Serializa
return this.createdBy;
}
public LocalDate getCreatedOn() {
public OffsetDateTime getCreatedOn() {
return this.createdOn;
}
@@ -83,7 +83,7 @@ public class FloatingRateData implements Comparable<FloatingRateData>, Serializa
return this.modifiedBy;
}
public LocalDate getModifiedOn() {
public OffsetDateTime getModifiedOn() {
return this.modifiedOn;
}
@@ -21,7 +21,7 @@ package org.apache.fineract.portfolio.floatingrates.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
@@ -34,12 +34,12 @@ public class FloatingRatePeriodData implements Comparable<FloatingRatePeriodData
private boolean isDifferentialToBaseLendingRate;
private boolean isActive;
private String createdBy;
private LocalDateTime createdOn;
private OffsetDateTime createdOn;
private String modifiedBy;
private LocalDateTime modifiedOn;
private OffsetDateTime modifiedOn;
public FloatingRatePeriodData(Long id, LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBaseLendingRate,
boolean isActive, String createdBy, LocalDateTime createdOn, String modifiedBy, LocalDateTime modifiedOn) {
boolean isActive, String createdBy, OffsetDateTime createdOn, String modifiedBy, OffsetDateTime modifiedOn) {
this.id = id;
this.fromDate = fromDate;
this.interestRate = interestRate;
@@ -51,6 +51,15 @@ public class FloatingRatePeriodData implements Comparable<FloatingRatePeriodData
this.modifiedOn = modifiedOn;
}
public FloatingRatePeriodData(Long id, LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBaseLendingRate,
boolean isActive) {
this.id = id;
this.fromDate = fromDate;
this.interestRate = interestRate;
this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate;
this.isActive = isActive;
}
public Long getId() {
return this.id;
}
@@ -79,7 +88,7 @@ public class FloatingRatePeriodData implements Comparable<FloatingRatePeriodData
return this.createdBy;
}
public LocalDateTime getCreatedOn() {
public OffsetDateTime getCreatedOn() {
return this.createdOn;
}
@@ -87,7 +96,7 @@ public class FloatingRatePeriodData implements Comparable<FloatingRatePeriodData
return this.modifiedBy;
}
public LocalDateTime getModifiedOn() {
public OffsetDateTime getModifiedOn() {
return this.modifiedOn;
}
@@ -23,7 +23,6 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -34,24 +33,20 @@ import javax.persistence.CascadeType;
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.OrderBy;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.useradministration.domain.AppUser;
//TODO: refactor to use AbstractAuditableCustom!
@Entity
@Table(name = "m_floating_rates", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") })
public class FloatingRate extends AbstractPersistableCustom {
public class FloatingRate extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "name", length = 200, unique = true, nullable = false)
private String name;
@@ -66,34 +61,23 @@ public class FloatingRate extends AbstractPersistableCustom {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "floatingRate", orphanRemoval = true, fetch = FetchType.EAGER)
private List<FloatingRatePeriod> floatingRatePeriods;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "createdby_id", nullable = false)
private AppUser createdBy;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "lastmodifiedby_id", nullable = false)
private AppUser modifiedBy;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdOn;
@Column(name = "lastmodified_date", nullable = false)
private LocalDateTime modifiedOn;
/*
* Deprecated since common Auditable fields were introduced. Columns and data left untouched to help migration.
*
* @Column(name = "created_date", nullable = false) private LocalDateTime createdOn;
*
* @Column(name = "lastmodified_date", nullable = false) private LocalDateTime modifiedOn;
*/
public FloatingRate() {
}
public FloatingRate(String name, boolean isBaseLendingRate, boolean isActive, List<FloatingRatePeriod> floatingRatePeriods,
AppUser createdBy, AppUser modifiedBy, LocalDateTime createdOn, LocalDateTime modifiedOn) {
public FloatingRate(String name, boolean isBaseLendingRate, boolean isActive, List<FloatingRatePeriod> floatingRatePeriods) {
this.name = name;
this.isBaseLendingRate = isBaseLendingRate;
this.isActive = isActive;
this.floatingRatePeriods = floatingRatePeriods;
this.createdBy = createdBy;
this.createdOn = createdOn;
this.modifiedBy = modifiedBy;
this.modifiedOn = modifiedOn;
if (floatingRatePeriods != null) {
for (FloatingRatePeriod ratePeriod : floatingRatePeriods) {
ratePeriod.updateFloatingRate(this);
@@ -101,20 +85,19 @@ public class FloatingRate extends AbstractPersistableCustom {
}
}
public static FloatingRate createNew(AppUser currentUser, JsonCommand command) {
public static FloatingRate createNew(JsonCommand command) {
final String name = command.stringValueOfParameterNamed("name");
final boolean isBaseLendingRate = command.parameterExists("isBaseLendingRate")
? command.booleanPrimitiveValueOfParameterNamed("isBaseLendingRate")
: false;
final boolean isActive = command.parameterExists("isActive") ? command.booleanPrimitiveValueOfParameterNamed("isActive") : true;
final List<FloatingRatePeriod> floatingRatePeriods = getRatePeriods(currentUser, command);
final LocalDateTime currentDate = DateUtils.getLocalDateTimeOfSystem();
final List<FloatingRatePeriod> floatingRatePeriods = getRatePeriods(command);
return new FloatingRate(name, isBaseLendingRate, isActive, floatingRatePeriods, currentUser, currentUser, currentDate, currentDate);
return new FloatingRate(name, isBaseLendingRate, isActive, floatingRatePeriods);
}
private static List<FloatingRatePeriod> getRatePeriods(final AppUser currentUser, final JsonCommand command) {
private static List<FloatingRatePeriod> getRatePeriods(final JsonCommand command) {
if (!command.parameterExists("ratePeriods")) {
return null;
}
@@ -129,9 +112,7 @@ public class FloatingRate extends AbstractPersistableCustom {
? ratePeriodObject.get("isDifferentialToBaseLendingRate").getAsBoolean()
: false;
final boolean isActive = true;
final LocalDateTime currentDate = DateUtils.getLocalDateTimeOfSystem();
ratePeriods.add(new FloatingRatePeriod(fromDate, interestRate, isDifferentialToBaseLendingRate, isActive, currentUser,
currentUser, currentDate, currentDate));
ratePeriods.add(new FloatingRatePeriod(fromDate, interestRate, isDifferentialToBaseLendingRate, isActive));
}
return ratePeriods;
@@ -153,23 +134,7 @@ public class FloatingRate extends AbstractPersistableCustom {
return this.floatingRatePeriods;
}
public AppUser getCreatedBy() {
return this.createdBy;
}
public AppUser getModifiedBy() {
return this.modifiedBy;
}
public LocalDateTime getCreatedOn() {
return this.createdOn;
}
public LocalDateTime getModifiedOn() {
return this.modifiedOn;
}
public Map<String, Object> update(final JsonCommand command, final AppUser appUser) {
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
@@ -191,24 +156,22 @@ public class FloatingRate extends AbstractPersistableCustom {
this.isActive = newValue;
}
final List<FloatingRatePeriod> newRatePeriods = getRatePeriods(appUser, command);
final List<FloatingRatePeriod> newRatePeriods = getRatePeriods(command);
if (newRatePeriods != null && !newRatePeriods.isEmpty()) {
updateRatePeriods(newRatePeriods, appUser);
updateRatePeriods(newRatePeriods);
actualChanges.put("ratePeriods", command.jsonFragment("ratePeriods"));
}
return actualChanges;
}
private void updateRatePeriods(final List<FloatingRatePeriod> newRatePeriods, final AppUser appUser) {
private void updateRatePeriods(final List<FloatingRatePeriod> newRatePeriods) {
final LocalDate today = DateUtils.getBusinessLocalDate();
if (this.floatingRatePeriods != null) {
for (FloatingRatePeriod ratePeriod : this.floatingRatePeriods) {
LocalDate fromDate = ratePeriod.getFromDate();
if (fromDate.isAfter(today)) {
ratePeriod.setActive(false);
ratePeriod.setModifiedBy(appUser);
ratePeriod.setModifiedOn(DateUtils.getLocalDateTimeOfSystem());
}
}
}
@@ -20,22 +20,18 @@ package org.apache.fineract.portfolio.floatingrates.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
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 org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.useradministration.domain.AppUser;
//TODO: refactor to use AbstractAuditableCustom!
@Entity
@Table(name = "m_floating_rates_periods")
public class FloatingRatePeriod extends AbstractPersistableCustom {
public class FloatingRatePeriod extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne
@JoinColumn(name = "floating_rates_id", nullable = false)
@@ -53,41 +49,30 @@ public class FloatingRatePeriod extends AbstractPersistableCustom {
@Column(name = "is_active", nullable = false)
private boolean isActive;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "createdby_id", nullable = false)
private AppUser createdBy;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "lastmodifiedby_id", nullable = false)
private AppUser modifiedBy;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdOn;
@Column(name = "lastmodified_date", nullable = false)
private LocalDateTime modifiedOn;
/*
* Deprecated since common Auditable fields were introduced. Columns and data left untouched to help migration.
*
* @Column(name = "created_date", nullable = false) private LocalDateTime createdOn;
*
* @Column(name = "lastmodified_date", nullable = false) private LocalDateTime modifiedOn;
*/
public FloatingRatePeriod() {
}
public FloatingRatePeriod(LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBaseLendingRate, boolean isActive,
AppUser createdBy, AppUser modifiedBy, LocalDateTime createdOn, LocalDateTime modifiedOn) {
public FloatingRatePeriod(LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBaseLendingRate, boolean isActive) {
this.fromDate = fromDate;
this.interestRate = interestRate;
this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate;
this.isActive = isActive;
this.createdBy = createdBy;
this.modifiedBy = modifiedBy;
this.createdOn = createdOn;
this.modifiedOn = modifiedOn;
}
public void updateFloatingRate(FloatingRate floatingRate) {
this.floatingRate = floatingRate;
}
public FloatingRate getFloatingRatesId() {
public FloatingRate getFloatingRate() {
return this.floatingRate;
}
@@ -107,32 +92,8 @@ public class FloatingRatePeriod extends AbstractPersistableCustom {
return this.isActive;
}
public AppUser getCreatedBy() {
return this.createdBy;
}
public AppUser getModifiedBy() {
return this.modifiedBy;
}
public LocalDateTime getCreatedOn() {
return this.createdOn;
}
public LocalDateTime getModifiedOn() {
return this.modifiedOn;
}
public void setModifiedBy(AppUser modifiedBy) {
this.modifiedBy = modifiedBy;
}
public void setModifiedOn(LocalDateTime modifiedOn) {
this.modifiedOn = modifiedOn;
}
public void setActive(boolean b) {
this.isActive = b;
public void setActive(boolean isActive) {
this.isActive = isActive;
}
public LocalDate fetchFromDate() {
@@ -147,13 +108,7 @@ public class FloatingRatePeriod extends AbstractPersistableCustom {
}
final LocalDate fromDate = getFromDate();
final LocalDateTime createdOn = getCreatedOn();
final LocalDateTime modifiedOn = getModifiedOn();
String createdBy = getCreatedBy() != null ? getCreatedBy().getUsername() : null;
String modifiedBy = getModifiedBy() != null ? getModifiedBy().getUsername() : null;
return new FloatingRatePeriodData(getId(), fromDate, interest, isDifferentialToBaseLendingRate(), isActive(), createdBy, createdOn,
modifiedBy, modifiedOn);
return new FloatingRatePeriodData(getId(), fromDate, interest, isDifferentialToBaseLendingRate(), isActive());
}
}
@@ -25,11 +25,9 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
import org.apache.fineract.portfolio.floatingrates.serialization.FloatingRateDataValidator;
import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -42,14 +40,12 @@ import org.springframework.transaction.annotation.Transactional;
public class FloatingRateWritePlatformServiceImpl implements FloatingRateWritePlatformService {
private static final Logger LOG = LoggerFactory.getLogger(FloatingRateWritePlatformServiceImpl.class);
private final PlatformSecurityContext context;
private final FloatingRateDataValidator fromApiJsonDeserializer;
private final FloatingRateRepositoryWrapper floatingRateRepository;
@Autowired
public FloatingRateWritePlatformServiceImpl(final PlatformSecurityContext context,
final FloatingRateDataValidator fromApiJsonDeserializer, final FloatingRateRepositoryWrapper floatingRateRepository) {
this.context = context;
public FloatingRateWritePlatformServiceImpl(final FloatingRateDataValidator fromApiJsonDeserializer,
final FloatingRateRepositoryWrapper floatingRateRepository) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.floatingRateRepository = floatingRateRepository;
}
@@ -59,8 +55,7 @@ public class FloatingRateWritePlatformServiceImpl implements FloatingRateWritePl
public CommandProcessingResult createFloatingRate(final JsonCommand command) {
try {
this.fromApiJsonDeserializer.validateForCreate(command.json());
final AppUser currentUser = this.context.authenticatedUser();
final FloatingRate newFloatingRate = FloatingRate.createNew(currentUser, command);
final FloatingRate newFloatingRate = FloatingRate.createNew(command);
this.floatingRateRepository.saveAndFlush(newFloatingRate);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -82,8 +77,7 @@ public class FloatingRateWritePlatformServiceImpl implements FloatingRateWritePl
try {
final FloatingRate floatingRateForUpdate = this.floatingRateRepository.findOneWithNotFoundDetection(command.entityId());
this.fromApiJsonDeserializer.validateForUpdate(command.json(), floatingRateForUpdate);
final AppUser currentUser = this.context.authenticatedUser();
final Map<String, Object> changes = floatingRateForUpdate.update(command, currentUser);
final Map<String, Object> changes = floatingRateForUpdate.update(command);
if (!changes.isEmpty()) {
this.floatingRateRepository.save(floatingRateForUpdate);
@@ -24,8 +24,10 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.List;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
@@ -106,7 +108,8 @@ public class FloatingRatesReadPlatformServiceImpl implements FloatingRatesReadPl
private final StringBuilder sqlQuery = new StringBuilder().append("rate.id as id, ").append("rate.name as name, ")
.append("rate.is_base_lending_rate as isBaseLendingRate, ").append("rate.is_active as isActive, ")
.append("crappu.username as createdBy, ").append("rate.created_date as createdOn, ")
.append("moappu.username as modifiedBy, ").append("rate.lastmodified_date as modifiedOn ")
.append("rate.created_on_utc as createdOnUTC, ").append("moappu.username as modifiedBy, ")
.append("rate.lastmodified_date as modifiedOn, ").append("rate.last_modified_on_utc as modifiedOnUTC ")
.append("FROM m_floating_rates as rate ").append("LEFT JOIN m_appuser as crappu on rate.createdby_id = crappu.id ")
.append("LEFT JOIN m_appuser as moappu on rate.lastmodifiedby_id = moappu.id ");
@@ -121,10 +124,16 @@ public class FloatingRatesReadPlatformServiceImpl implements FloatingRatesReadPl
final boolean isBaseLendingRate = rs.getBoolean("isBaseLendingRate");
final boolean isActive = rs.getBoolean("isActive");
final String createdBy = rs.getString("createdBy");
final LocalDate createdOn = JdbcSupport.getLocalDate(rs, "createdOn");
final LocalDateTime createdOnLocal = JdbcSupport.getLocalDateTime(rs, "createdOn");
final OffsetDateTime createdOnUtc = JdbcSupport.getOffsetDateTime(rs, "createdOnUTC");
final String modifiedBy = rs.getString("modifiedBy");
final LocalDate modifiedOn = JdbcSupport.getLocalDate(rs, "modifiedOn");
final LocalDateTime modifiedOnLocal = JdbcSupport.getLocalDateTime(rs, "modifiedOn");
final OffsetDateTime modifiedOnUtc = JdbcSupport.getOffsetDateTime(rs, "modifiedOnUTC");
List<FloatingRatePeriodData> ratePeriods = null;
final OffsetDateTime createdOn = createdOnUtc != null ? createdOnUtc
: OffsetDateTime.of(createdOnLocal, DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(createdOnLocal));
final OffsetDateTime modifiedOn = modifiedOnUtc != null ? modifiedOnUtc
: OffsetDateTime.of(modifiedOnLocal, DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(modifiedOnLocal));
if (addRatePeriods) {
FloatingRatePeriodRowMapper ratePeriodMapper = new FloatingRatePeriodRowMapper();
final String sql = "select " + ratePeriodMapper.schema()
@@ -146,8 +155,9 @@ public class FloatingRatesReadPlatformServiceImpl implements FloatingRatesReadPl
.append("period.interest_rate as interestRate, ")
.append("period.is_differential_to_base_lending_rate as isDifferentialToBaseLendingRate, ")
.append("period.is_active as isActive, ").append("crappu.username as createdBy, ")
.append("period.created_date as createdOn, ").append("moappu.username as modifiedBy, ")
.append("period.lastmodified_date as modifiedOn ").append("FROM m_floating_rates_periods as period ")
.append("period.created_date as createdOn, ").append("rate.created_on_utc as createdOnUTC, ")
.append("moappu.username as modifiedBy, ").append("period.lastmodified_date as modifiedOn, ")
.append("rate.last_modified_on_utc as modifiedOnUTC ").append("FROM m_floating_rates_periods as period ")
.append("LEFT JOIN m_appuser as crappu on period.createdby_id = crappu.id ")
.append("LEFT JOIN m_appuser as moappu on period.lastmodifiedby_id = moappu.id ");
@@ -159,9 +169,15 @@ public class FloatingRatesReadPlatformServiceImpl implements FloatingRatesReadPl
final boolean isDifferentialToBaseLendingRate = rs.getBoolean("isDifferentialToBaseLendingRate");
final boolean isActive = rs.getBoolean("isActive");
final String createdBy = rs.getString("createdBy");
final LocalDateTime createdOn = JdbcSupport.getLocalDateTime(rs, "createdOn");
final LocalDateTime createdOnLocal = JdbcSupport.getLocalDateTime(rs, "createdOn");
final OffsetDateTime createdOnUtc = JdbcSupport.getOffsetDateTime(rs, "createdOnUTC");
final String modifiedBy = rs.getString("modifiedBy");
final LocalDateTime modifiedOn = JdbcSupport.getLocalDateTime(rs, "modifiedOn");
final LocalDateTime modifiedOnLocal = JdbcSupport.getLocalDateTime(rs, "modifiedOn");
final OffsetDateTime modifiedOnUtc = JdbcSupport.getOffsetDateTime(rs, "modifiedOnUTC");
final OffsetDateTime createdOn = createdOnUtc != null ? createdOnUtc
: OffsetDateTime.of(createdOnLocal, DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(createdOnLocal));
final OffsetDateTime modifiedOn = modifiedOnUtc != null ? modifiedOnUtc
: OffsetDateTime.of(modifiedOnLocal, DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(modifiedOnLocal));
return new FloatingRatePeriodData(id, fromDate, interestRate, isDifferentialToBaseLendingRate, isActive, createdBy, createdOn,
modifiedBy, modifiedOn);
}
@@ -45,15 +45,15 @@ public class AddAccrualEntriesTasklet implements Tasklet {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = loanReadPlatformService.retriveScheduleAccrualData();
Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList = loanReadPlatformService.retriveScheduleAccrualData();
Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDataList) {
if (loanDataMap.containsKey(accrualData.getLoanId())) {
loanDataMap.get(accrualData.getLoanId()).add(accrualData);
} else {
Collection<LoanScheduleAccrualData> accrualDatas = new ArrayList<>();
accrualDatas.add(accrualData);
loanDataMap.put(accrualData.getLoanId(), accrualDatas);
Collection<LoanScheduleAccrualData> accrualDataList = new ArrayList<>();
accrualDataList.add(accrualData);
loanDataMap.put(accrualData.getLoanId(), accrualDataList);
}
}
@@ -62,7 +62,7 @@ public class AddAccrualEntriesTasklet implements Tasklet {
try {
loanAccrualWritePlatformService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue());
} catch (Exception e) {
log.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e);
log.error("Failed to add accrual transaction for loan {}", mapEntry.getKey(), e);
errors.add(e);
}
}
@@ -39,31 +39,31 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
@Override
public void addPeriodicAccruals(final LocalDate tilldate) throws JobExecutionException {
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate);
addPeriodicAccruals(tilldate, loanScheduleAccrualDatas);
public void addPeriodicAccruals(final LocalDate tillDate) throws JobExecutionException {
Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList = this.loanReadPlatformService.retrivePeriodicAccrualData(tillDate);
addPeriodicAccruals(tillDate, loanScheduleAccrualDataList);
}
@Override
public void addPeriodicAccruals(final LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
public void addPeriodicAccruals(final LocalDate tillDate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList)
throws JobExecutionException {
Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDataList) {
if (loanDataMap.containsKey(accrualData.getLoanId())) {
loanDataMap.get(accrualData.getLoanId()).add(accrualData);
} else {
Collection<LoanScheduleAccrualData> accrualDatas = new ArrayList<>();
accrualDatas.add(accrualData);
loanDataMap.put(accrualData.getLoanId(), accrualDatas);
Collection<LoanScheduleAccrualData> accrualDataList = new ArrayList<>();
accrualDataList.add(accrualData);
loanDataMap.put(accrualData.getLoanId(), accrualDataList);
}
}
List<Throwable> errors = new ArrayList<>();
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
try {
this.loanAccrualWritePlatformService.addPeriodicAccruals(tilldate, mapEntry.getKey(), mapEntry.getValue());
this.loanAccrualWritePlatformService.addPeriodicAccruals(tillDate, mapEntry.getKey(), mapEntry.getValue());
} catch (Exception e) {
log.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e);
log.error("Failed to add accrual transaction for loan {}", mapEntry.getKey(), e);
errors.add(e);
}
}
@@ -32,6 +32,7 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -47,6 +48,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -63,63 +65,62 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final AppUserRepositoryWrapper userRepository;
private final PlatformSecurityContext context;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
@Override
@Transactional
public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
throws Exception {
public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualData) {
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccural(loanId);
Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
Collection<LoanTransactionData> loanWaiverTansactionData = new ArrayList<>(1);
Collection<LoanTransactionData> loanWaiverTransactionData = new ArrayList<>(1);
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualData) {
if (accrualData.getWaivedInterestIncome() != null && loanWaiverScheduleData.isEmpty()) {
loanWaiverScheduleData = this.loanReadPlatformService.fetchWaiverInterestRepaymentData(accrualData.getLoanId());
loanWaiverTansactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
loanWaiverTransactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
}
updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), accrualData.getDueDateAsLocaldate());
updateInterestIncome(accrualData, loanWaiverTansactionData, loanWaiverScheduleData, accrualData.getDueDateAsLocaldate());
updateInterestIncome(accrualData, loanWaiverTransactionData, loanWaiverScheduleData, accrualData.getDueDateAsLocaldate());
addAccrualAccounting(accrualData);
}
}
@Override
@Transactional
public void addPeriodicAccruals(final LocalDate tilldate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
throws Exception {
public void addPeriodicAccruals(final LocalDate tillDate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualData) {
boolean firstTime = true;
LocalDate accruredTill = null;
LocalDate accruedTill = null;
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccural(loanId);
Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
Collection<LoanTransactionData> loanWaiverTansactionData = new ArrayList<>(1);
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
Collection<LoanTransactionData> loanWaiverTransactionData = new ArrayList<>(1);
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualData) {
if (accrualData.getWaivedInterestIncome() != null && loanWaiverScheduleData.isEmpty()) {
loanWaiverScheduleData = this.loanReadPlatformService.fetchWaiverInterestRepaymentData(accrualData.getLoanId());
loanWaiverTansactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
loanWaiverTransactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
}
if (accrualData.getDueDateAsLocaldate().isAfter(tilldate)) {
if (accruredTill == null || firstTime) {
accruredTill = accrualData.getAccruedTill();
if (accrualData.getDueDateAsLocaldate().isAfter(tillDate)) {
if (accruedTill == null || firstTime) {
accruedTill = accrualData.getAccruedTill();
firstTime = false;
}
if (accruredTill == null || accruredTill.isBefore(tilldate)) {
updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), tilldate);
updateInterestIncome(accrualData, loanWaiverTansactionData, loanWaiverScheduleData, tilldate);
addAccrualTillSpecificDate(tilldate, accrualData);
if (accruedTill == null || accruedTill.isBefore(tillDate)) {
updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), tillDate);
updateInterestIncome(accrualData, loanWaiverTransactionData, loanWaiverScheduleData, tillDate);
addAccrualTillSpecificDate(tillDate, accrualData);
}
} else {
updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), accrualData.getDueDateAsLocaldate());
updateInterestIncome(accrualData, loanWaiverTansactionData, loanWaiverScheduleData, tilldate);
updateInterestIncome(accrualData, loanWaiverTransactionData, loanWaiverScheduleData, tillDate);
addAccrualAccounting(accrualData);
accruredTill = accrualData.getDueDateAsLocaldate();
accruedTill = accrualData.getDueDateAsLocaldate();
}
}
}
private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanScheduleAccrualData accrualData) throws Exception {
private void addAccrualTillSpecificDate(final LocalDate tillDate, final LoanScheduleAccrualData accrualData) {
LocalDate interestStartDate = accrualData.getFromDateAsLocaldate();
if (accrualData.getInterestCalculatedFrom() != null
&& accrualData.getFromDateAsLocaldate().isBefore(accrualData.getInterestCalculatedFrom())) {
@@ -133,150 +134,150 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
int totalNumberOfDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, accrualData.getDueDateAsLocaldate()));
LocalDate startDate = accrualData.getFromDateAsLocaldate();
if (accrualData.getInterestCalculatedFrom() != null && startDate.isBefore(accrualData.getInterestCalculatedFrom())) {
if (accrualData.getInterestCalculatedFrom().isBefore(tilldate)) {
if (accrualData.getInterestCalculatedFrom().isBefore(tillDate)) {
startDate = accrualData.getInterestCalculatedFrom();
} else {
startDate = tilldate;
startDate = tillDate;
}
}
int daysToBeAccrued = Math.toIntExact(ChronoUnit.DAYS.between(startDate, tilldate));
int daysToBeAccrued = Math.toIntExact(ChronoUnit.DAYS.between(startDate, tillDate));
double interestPerDay = accrualData.getAccruableIncome().doubleValue() / totalNumberOfDays;
BigDecimal amount = BigDecimal.ZERO;
BigDecimal interestportion = null;
BigDecimal feeportion = accrualData.getDueDateFeeIncome();
BigDecimal penaltyportion = accrualData.getDueDatePenaltyIncome();
BigDecimal interestPortion;
BigDecimal feePortion = accrualData.getDueDateFeeIncome();
BigDecimal penaltyPortion = accrualData.getDueDatePenaltyIncome();
if (daysToBeAccrued >= totalNumberOfDays) {
interestportion = accrualData.getAccruableIncome();
interestPortion = accrualData.getAccruableIncome();
} else {
double iterest = interestPerDay * daysToBeAccrued;
interestportion = BigDecimal.valueOf(iterest);
interestPortion = BigDecimal.valueOf(interestPerDay * daysToBeAccrued);
}
interestportion = interestportion.setScale(accrualData.getCurrencyData().decimalPlaces(), MoneyHelper.getRoundingMode());
interestPortion = interestPortion.setScale(accrualData.getCurrencyData().decimalPlaces(), MoneyHelper.getRoundingMode());
BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome();
BigDecimal totalAccPenalty = accrualData.getAccruedPenaltyIncome();
BigDecimal totalAccFee = accrualData.getAccruedFeeIncome();
if (interestportion != null) {
if (totalAccInterest == null) {
totalAccInterest = BigDecimal.ZERO;
}
interestportion = interestportion.subtract(totalAccInterest);
amount = amount.add(interestportion);
totalAccInterest = totalAccInterest.add(interestportion);
if (interestportion.compareTo(BigDecimal.ZERO) == 0) {
interestportion = null;
}
if (totalAccInterest == null) {
totalAccInterest = BigDecimal.ZERO;
}
if (feeportion != null) {
interestPortion = interestPortion.subtract(totalAccInterest);
amount = amount.add(interestPortion);
totalAccInterest = totalAccInterest.add(interestPortion);
if (interestPortion.compareTo(BigDecimal.ZERO) == 0) {
interestPortion = null;
}
if (feePortion != null) {
if (totalAccFee == null) {
totalAccFee = BigDecimal.ZERO;
}
feeportion = feeportion.subtract(totalAccFee);
amount = amount.add(feeportion);
totalAccFee = totalAccFee.add(feeportion);
if (feeportion.compareTo(BigDecimal.ZERO) == 0) {
feeportion = null;
feePortion = feePortion.subtract(totalAccFee);
amount = amount.add(feePortion);
totalAccFee = totalAccFee.add(feePortion);
if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
feePortion = null;
}
}
if (penaltyportion != null) {
if (penaltyPortion != null) {
if (totalAccPenalty == null) {
totalAccPenalty = BigDecimal.ZERO;
}
penaltyportion = penaltyportion.subtract(totalAccPenalty);
amount = amount.add(penaltyportion);
totalAccPenalty = totalAccPenalty.add(penaltyportion);
if (penaltyportion.compareTo(BigDecimal.ZERO) == 0) {
penaltyportion = null;
penaltyPortion = penaltyPortion.subtract(totalAccPenalty);
amount = amount.add(penaltyPortion);
totalAccPenalty = totalAccPenalty.add(penaltyPortion);
if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
penaltyPortion = null;
}
}
if (amount.compareTo(BigDecimal.ZERO) > 0) {
addAccrualAccounting(accrualData, amount, interestportion, totalAccInterest, feeportion, totalAccFee, penaltyportion,
totalAccPenalty, tilldate);
addAccrualAccounting(accrualData, amount, interestPortion, totalAccInterest, feePortion, totalAccFee, penaltyPortion,
totalAccPenalty, tillDate);
}
}
@Transactional
public void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData) throws Exception {
public void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData) {
BigDecimal amount = BigDecimal.ZERO;
BigDecimal interestportion = null;
BigDecimal interestPortion = null;
BigDecimal totalAccInterest = null;
if (scheduleAccrualData.getAccruableIncome() != null) {
interestportion = scheduleAccrualData.getAccruableIncome();
totalAccInterest = interestportion;
interestPortion = scheduleAccrualData.getAccruableIncome();
totalAccInterest = interestPortion;
if (scheduleAccrualData.getAccruedInterestIncome() != null) {
interestportion = interestportion.subtract(scheduleAccrualData.getAccruedInterestIncome());
interestPortion = interestPortion.subtract(scheduleAccrualData.getAccruedInterestIncome());
}
amount = amount.add(interestportion);
if (interestportion.compareTo(BigDecimal.ZERO) == 0) {
interestportion = null;
amount = amount.add(interestPortion);
if (interestPortion.compareTo(BigDecimal.ZERO) == 0) {
interestPortion = null;
}
}
BigDecimal feeportion = null;
BigDecimal feePortion = null;
BigDecimal totalAccFee = null;
if (scheduleAccrualData.getDueDateFeeIncome() != null) {
feeportion = scheduleAccrualData.getDueDateFeeIncome();
totalAccFee = feeportion;
feePortion = scheduleAccrualData.getDueDateFeeIncome();
totalAccFee = feePortion;
if (scheduleAccrualData.getAccruedFeeIncome() != null) {
feeportion = feeportion.subtract(scheduleAccrualData.getAccruedFeeIncome());
feePortion = feePortion.subtract(scheduleAccrualData.getAccruedFeeIncome());
}
amount = amount.add(feeportion);
if (feeportion.compareTo(BigDecimal.ZERO) == 0) {
feeportion = null;
amount = amount.add(feePortion);
if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
feePortion = null;
}
}
BigDecimal penaltyportion = null;
BigDecimal penaltyPortion = null;
BigDecimal totalAccPenalty = null;
if (scheduleAccrualData.getDueDatePenaltyIncome() != null) {
penaltyportion = scheduleAccrualData.getDueDatePenaltyIncome();
totalAccPenalty = penaltyportion;
penaltyPortion = scheduleAccrualData.getDueDatePenaltyIncome();
totalAccPenalty = penaltyPortion;
if (scheduleAccrualData.getAccruedPenaltyIncome() != null) {
penaltyportion = penaltyportion.subtract(scheduleAccrualData.getAccruedPenaltyIncome());
penaltyPortion = penaltyPortion.subtract(scheduleAccrualData.getAccruedPenaltyIncome());
}
amount = amount.add(penaltyportion);
if (penaltyportion.compareTo(BigDecimal.ZERO) == 0) {
penaltyportion = null;
amount = amount.add(penaltyPortion);
if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
penaltyPortion = null;
}
}
if (amount.compareTo(BigDecimal.ZERO) > 0) {
addAccrualAccounting(scheduleAccrualData, amount, interestportion, totalAccInterest, feeportion, totalAccFee, penaltyportion,
addAccrualAccounting(scheduleAccrualData, amount, interestPortion, totalAccInterest, feePortion, totalAccFee, penaltyPortion,
totalAccPenalty, scheduleAccrualData.getDueDateAsLocaldate());
}
}
private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, BigDecimal amount, BigDecimal interestportion,
BigDecimal totalAccInterest, BigDecimal feeportion, BigDecimal totalAccFee, BigDecimal penaltyportion,
private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, BigDecimal amount, BigDecimal interestPortion,
BigDecimal totalAccInterest, BigDecimal feePortion, BigDecimal totalAccFee, BigDecimal penaltyPortion,
BigDecimal totalAccPenalty, final LocalDate accruedTill) throws DataAccessException {
AppUser user = context.authenticatedUser();
String transactionSql = "INSERT INTO m_loan_transaction (loan_id,office_id,is_reversed,transaction_type_enum,transaction_date,amount,interest_portion_derived,"
+ "fee_charges_portion_derived,penalty_charges_portion_derived, submitted_on_date) VALUES (?, ?, false, ?, ?, ?, ?, ?, ?, ?)";
+ "fee_charges_portion_derived,penalty_charges_portion_derived, submitted_on_date, created_by, last_modified_by, created_on_utc, last_modified_on_utc) "
+ "VALUES (?, ?, false, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
this.jdbcTemplate.update(transactionSql, scheduleAccrualData.getLoanId(), scheduleAccrualData.getOfficeId(),
LoanTransactionType.ACCRUAL.getValue(), accruedTill, amount, interestportion, feeportion, penaltyportion,
DateUtils.getBusinessLocalDate());
@SuppressWarnings("deprecation")
final Long transactonId = this.jdbcTemplate.queryForObject("SELECT " + sqlGenerator.lastInsertId(), Long.class); // NOSONAR
LoanTransactionType.ACCRUAL.getValue(), accruedTill, amount, interestPortion, feePortion, penaltyPortion,
DateUtils.getBusinessLocalDate(), user.getId(), user.getId(), DateUtils.getOffsetDateTimeOfTenant(),
DateUtils.getOffsetDateTimeOfTenant());
final Long transactionId = this.jdbcTemplate.queryForObject("SELECT " + sqlGenerator.lastInsertId(), Long.class); // NOSONAR
Map<LoanChargeData, BigDecimal> applicableCharges = scheduleAccrualData.getApplicableCharges();
String chargespaidSql = "INSERT INTO m_loan_charge_paid_by (loan_transaction_id, loan_charge_id, amount,installment_number) VALUES (?,?,?,?)";
String chargesPaidSql = "INSERT INTO m_loan_charge_paid_by (loan_transaction_id, loan_charge_id, amount,installment_number) VALUES (?,?,?,?)";
for (Map.Entry<LoanChargeData, BigDecimal> entry : applicableCharges.entrySet()) {
LoanChargeData chargeData = entry.getKey();
this.jdbcTemplate.update(chargespaidSql, transactonId, chargeData.getId(), entry.getValue(),
this.jdbcTemplate.update(chargesPaidSql, transactionId, chargeData.getId(), entry.getValue(),
scheduleAccrualData.getInstallmentNumber());
}
Map<String, Object> transactionMap = toMapData(transactonId, amount, interestportion, feeportion, penaltyportion,
Map<String, Object> transactionMap = toMapData(transactionId, amount, interestPortion, feePortion, penaltyPortion,
scheduleAccrualData, accruedTill);
String repaymetUpdatesql = "UPDATE m_loan_repayment_schedule SET accrual_interest_derived=?, accrual_fee_charges_derived=?, "
String repaymentUpdateSql = "UPDATE m_loan_repayment_schedule SET accrual_interest_derived=?, accrual_fee_charges_derived=?, "
+ "accrual_penalty_charges_derived=? WHERE id=?";
this.jdbcTemplate.update(repaymetUpdatesql, totalAccInterest, totalAccFee, totalAccPenalty,
this.jdbcTemplate.update(repaymentUpdateSql, totalAccInterest, totalAccFee, totalAccPenalty,
scheduleAccrualData.getRepaymentScheduleId());
String updateLoan = "UPDATE m_loan SET accrued_till=? WHERE id=?";
this.jdbcTemplate.update(updateLoan, accruedTill, scheduleAccrualData.getLoanId());
String updateLoan = "UPDATE m_loan SET accrued_till=?, last_modified_by=?, last_modified_on_utc=? WHERE id=?";
this.jdbcTemplate.update(updateLoan, accruedTill, user.getId(), DateUtils.getOffsetDateTimeOfTenant(),
scheduleAccrualData.getLoanId());
final Map<String, Object> accountingBridgeData = deriveAccountingBridgeData(scheduleAccrualData, transactionMap);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
@@ -301,9 +302,9 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
return accountingBridgeData;
}
public Map<String, Object> toMapData(final Long id, final BigDecimal amount, final BigDecimal interestportion,
final BigDecimal feeportion, final BigDecimal penaltyportion, final LoanScheduleAccrualData loanScheduleAccrualData,
final LocalDate accruredTill) {
public Map<String, Object> toMapData(final Long id, final BigDecimal amount, final BigDecimal interestPortion,
final BigDecimal feePortion, final BigDecimal penaltyPortion, final LoanScheduleAccrualData loanScheduleAccrualData,
final LocalDate accruedTill) {
final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.ACCRUAL);
@@ -312,13 +313,13 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
thisTransactionData.put("officeId", loanScheduleAccrualData.getOfficeId());
thisTransactionData.put("type", transactionType);
thisTransactionData.put("reversed", false);
thisTransactionData.put("date", accruredTill);
thisTransactionData.put("date", accruedTill);
thisTransactionData.put("currency", loanScheduleAccrualData.getCurrencyData());
thisTransactionData.put("amount", amount);
thisTransactionData.put("principalPortion", null);
thisTransactionData.put("interestPortion", interestportion);
thisTransactionData.put("feeChargesPortion", feeportion);
thisTransactionData.put("penaltyChargesPortion", penaltyportion);
thisTransactionData.put("interestPortion", interestPortion);
thisTransactionData.put("feeChargesPortion", feePortion);
thisTransactionData.put("penaltyChargesPortion", penaltyPortion);
thisTransactionData.put("overPaymentPortion", null);
Map<LoanChargeData, BigDecimal> applicableCharges = loanScheduleAccrualData.getApplicableCharges();
@@ -411,8 +412,8 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
}
private void updateInterestIncome(final LoanScheduleAccrualData accrualData,
final Collection<LoanTransactionData> loanWaiverTansactions, final Collection<LoanSchedulePeriodData> loanSchedulePeriodDatas,
final LocalDate tilldate) {
final Collection<LoanTransactionData> loanWaiverTransactions,
final Collection<LoanSchedulePeriodData> loanSchedulePeriodDataList, final LocalDate tillDate) {
BigDecimal interestIncome = BigDecimal.ZERO;
if (accrualData.getInterestIncome() != null) {
@@ -424,17 +425,17 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
BigDecimal remainingAmt = BigDecimal.ZERO;
Collection<LoanTransactionData> loanTransactionDatas = new ArrayList<>();
for (LoanTransactionData loanTransactionData : loanWaiverTansactions) {
for (LoanTransactionData loanTransactionData : loanWaiverTransactions) {
if (!loanTransactionData.dateOf().isAfter(accrualData.getFromDateAsLocaldate())
|| (loanTransactionData.dateOf().isAfter(accrualData.getFromDateAsLocaldate())
&& !loanTransactionData.dateOf().isAfter(accrualData.getDueDateAsLocaldate())
&& !loanTransactionData.dateOf().isAfter(tilldate))) {
&& !loanTransactionData.dateOf().isAfter(tillDate))) {
loanTransactionDatas.add(loanTransactionData);
}
}
Iterator<LoanTransactionData> iterator = loanTransactionDatas.iterator();
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDataList) {
if (recognized.compareTo(BigDecimal.ZERO) <= 0 && unrecognized.compareTo(BigDecimal.ZERO) <= 0 && iterator.hasNext()) {
LoanTransactionData loanTransactionData = iterator.next();
recognized = recognized.add(loanTransactionData.getInterestPortion());
@@ -42,4 +42,6 @@
<include file="parts/0020_add_audit_entries.xml" relativeToChangelogFile="true"/>
<include file="parts/0021_add_spring_batch_db_structure.xml" relativeToChangelogFile="true"/>
<include file="parts/0022_add_batch_business_step_configuration_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0023_use_the_proper_date_or_datetime_type.xml" relativeToChangelogFile="true"/>
<include file="parts/0024_add_audit_entries.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<modifyDataType tableName="m_client_non_person" columnName="incorp_validity_till" newDataType="date"/>
<modifyDataType tableName="m_floating_rates_periods" columnName="from_date" newDataType="date"/>
<modifyDataType tableName="m_holiday" columnName="from_date" newDataType="date"/>
<modifyDataType tableName="m_holiday" columnName="to_date" newDataType="date"/>
<modifyDataType tableName="m_holiday" columnName="repayments_rescheduled_to" newDataType="date"/>
<modifyDataType tableName="m_loan_disbursement_detail" columnName="expected_disburse_date" newDataType="date"/>
<modifyDataType tableName="m_loan_disbursement_detail" columnName="disbursedon_date" newDataType="date"/>
<modifyDataType tableName="m_share_product" columnName="start_date" newDataType="date"/>
<modifyDataType tableName="m_share_product" columnName="end_date" newDataType="date"/>
<modifyDataType tableName="m_surveys" columnName="valid_from" newDataType="date"/>
<modifyDataType tableName="m_surveys" columnName="valid_to" newDataType="date"/>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,241 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!--Client-->
<changeSet id="client-4" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_client</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_client" columnName="created_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_client" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_client" columnName="last_modified_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_client" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="client-4" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_client</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_client" columnName="created_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_client" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_client" columnName="last_modified_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_client" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<!--Loan-->
<changeSet id="loan-7" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_loan</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_loan" columnName="created_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_loan" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_loan" columnName="last_modified_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_loan" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="loan-7" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_loan</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_loan" columnName="created_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_loan" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_loan" columnName="last_modified_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_loan" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<!-- Loan transactions -->
<changeSet id="loan-transaction-13" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_loan_transaction</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_loan_transaction" columnName="created_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_loan_transaction" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_loan_transaction" columnName="last_modified_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_loan_transaction" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="loan-transaction-13" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_loan_transaction</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_loan_transaction" columnName="created_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_loan_transaction" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_loan_transaction" columnName="last_modified_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_loan_transaction" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<!-- Client transaction -->
<changeSet author="fineract" id="client-transaction-1" context="mysql">
<addColumn tableName="m_client_transaction">
<column name="created_on_utc" type="DATETIME"/>
<column name="created_by" type="BIGINT" valueComputed="appuser_id"/>
<column name="last_modified_by" type="BIGINT"/>
<column name="last_modified_on_utc" type="DATETIME"/>
</addColumn>
</changeSet>
<changeSet author="fineract" id="client-transaction-1" context="postgresql">
<addColumn tableName="m_client_transaction">
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
<column name="created_by" type="BIGINT" valueComputed="appuser_id"/>
<column name="last_modified_by" type="BIGINT"/>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
</addColumn>
</changeSet>
<changeSet id="client-transaction-2" author="fineract">
<addColumn tableName="m_client_transaction">
<column name="submitted_on_date" type="DATE" valueComputed="created_date">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="client-transaction-3">
<addForeignKeyConstraint baseColumnNames="created_by" baseTableName="m_client_transaction"
constraintName="FK_client_transaction_created_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="last_modified_by" baseTableName="m_client_transaction"
constraintName="FK_client_transaction_last_modified_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
</changeSet>
<changeSet id="client-transaction-4" author="fineract">
<dropForeignKeyConstraint baseTableName="m_client_transaction" constraintName="FK_m_client_transaction_m_appuser"/>
<dropColumn tableName="m_client_transaction">
<column name="appuser_id"/>
</dropColumn>
</changeSet>
<changeSet id="client-transaction-5" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_client_transaction</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_client_transaction" columnName="created_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_client_transaction" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_client_transaction" columnName="last_modified_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_client_transaction" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="client-transaction-5" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_client_transaction</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_client_transaction" columnName="created_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_client_transaction" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_client_transaction" columnName="last_modified_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_client_transaction" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="client-transaction-6" author="fineract">
<dropNotNullConstraint tableName="m_client_transaction" columnName="created_date" columnDataType="datetime"/>
</changeSet>
<!-- Floating rate -->
<changeSet author="fineract" id="floating-rate-1" context="mysql">
<addColumn tableName="m_floating_rates">
<column name="created_on_utc" type="DATETIME"/>
<column name="last_modified_on_utc" type="DATETIME"/>
</addColumn>
</changeSet>
<changeSet author="fineract" id="floating-rate-1" context="postgresql">
<addColumn tableName="m_floating_rates">
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
</addColumn>
</changeSet>
<changeSet id="floating-rate-2" author="fineract">
<renameColumn tableName="m_floating_rates" oldColumnName="createdby_id" newColumnName="created_by" columnDataType="BIGINT"/>
<renameColumn tableName="m_floating_rates" oldColumnName="lastmodifiedby_id" newColumnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet author="fineract" id="floating-rate-3">
<addForeignKeyConstraint baseColumnNames="created_by" baseTableName="m_floating_rates"
constraintName="FK_floating_rates_created_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="last_modified_by" baseTableName="m_floating_rates"
constraintName="FK_floating_rates_last_modified_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
</changeSet>
<changeSet id="floating-rate-4" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_floating_rates</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_floating_rates" columnName="created_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_floating_rates" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_floating_rates" columnName="last_modified_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_floating_rates" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="floating-rate-4" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_client_transaction</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_floating_rates" columnName="created_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_floating_rates" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_floating_rates" columnName="last_modified_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_floating_rates" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="floating-rate-5" author="fineract">
<dropNotNullConstraint tableName="m_floating_rates" columnName="created_date" columnDataType="datetime"/>
<dropNotNullConstraint tableName="m_floating_rates" columnName="lastmodified_date" columnDataType="datetime"/>
</changeSet>
<!-- Floating rates period -->
<changeSet author="fineract" id="floating-rate-period-1" context="mysql">
<addColumn tableName="m_floating_rates_periods">
<column name="created_on_utc" type="DATETIME"/>
<column name="last_modified_on_utc" type="DATETIME"/>
</addColumn>
</changeSet>
<changeSet author="fineract" id="floating-rate-period-1" context="postgresql">
<addColumn tableName="m_floating_rates_periods">
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
</addColumn>
</changeSet>
<changeSet id="floating-rate-period-2" author="fineract">
<renameColumn tableName="m_floating_rates_periods" oldColumnName="createdby_id" newColumnName="created_by" columnDataType="BIGINT"/>
<renameColumn tableName="m_floating_rates_periods" oldColumnName="lastmodifiedby_id" newColumnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet author="fineract" id="floating-rate-period-3">
<addForeignKeyConstraint baseColumnNames="created_by" baseTableName="m_floating_rates_periods"
constraintName="FK_floating_rates_period_created_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="last_modified_by" baseTableName="m_floating_rates_periods"
constraintName="FK_floating_rates_period_last_modified_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
</changeSet>
<changeSet id="floating-rate-period-4" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_floating_rates_periods</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="created_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="last_modified_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="floating-rate-period-4" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_floating_rates_periods</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="created_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="last_modified_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_floating_rates_periods" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet id="floating-rate-period-5" author="fineract">
<dropNotNullConstraint tableName="m_floating_rates_periods" columnName="created_date" columnDataType="datetime"/>
<dropNotNullConstraint tableName="m_floating_rates_periods" columnName="lastmodified_date" columnDataType="datetime"/>
</changeSet>
</databaseChangeLog>
@@ -30,7 +30,10 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
@@ -442,7 +445,7 @@ public class FixedDepositTest {
}
@Test
public void testFixedDepositAccountClosureTypeWithdrawal_WITH_HOLD_TAX() throws InterruptedException {
public void testFixedDepositAccountClosureTypeWithdrawal_WITH_HOLD_TAX() {
this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
@@ -457,30 +460,23 @@ public class FixedDepositTest {
final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
final Account liabilityAccountForTax = this.accountHelper.createLiabilityAccount();
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
DateFormat currentDateFormat = new SimpleDateFormat("dd");
DateTimeFormatter monthDayFormat = new DateTimeFormatterBuilder().appendPattern("dd MMM").toFormatter();
DateTimeFormatter currentDateFormat = new DateTimeFormatterBuilder().appendPattern("dd").toFormatter();
Calendar todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.MONTH, -20);
final String VALID_FROM = dateFormat.format(todaysDate.getTime());
todaysDate.add(Calendar.YEAR, 10);
final String VALID_TO = dateFormat.format(todaysDate.getTime());
LocalDate todaysDate = Utils.getLocalDateOfTenant();
todaysDate = todaysDate.minusMonths(20);
final String VALID_FROM = Utils.dateFormatter.format(todaysDate);
todaysDate = todaysDate.plusYears(10);
final String VALID_TO = Utils.dateFormatter.format(todaysDate);
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.MONTH, -20);
final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
monthDayFormat.format(todaysDate.getTime());
todaysDate = Utils.getLocalDateOfTenant();
todaysDate = todaysDate.minusMonths(20);
final String SUBMITTED_ON_DATE = Utils.dateFormatter.format(todaysDate);
final String APPROVED_ON_DATE = Utils.dateFormatter.format(todaysDate);
final String ACTIVATION_DATE = Utils.dateFormatter.format(todaysDate);
Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
int numberOfDaysLeft = daysInMonth - currentDate + 1;
todaysDate.add(Calendar.DATE, numberOfDaysLeft);
Calendar closedOn = Calendar.getInstance();
closedOn.add(Calendar.MONTH, -6);
final String CLOSED_ON_DATE = dateFormat.format(closedOn.getTime());
LocalDate closedOn = todaysDate.plusMonths(14);
final String CLOSED_ON_DATE = Utils.dateFormatter.format(closedOn);
Integer clientId = ClientHelper.createClient(requestSpec, responseSpec);
Assertions.assertNotNull(clientId);
@@ -1282,26 +1278,23 @@ public class FixedDepositTest {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
DateFormat currentDateFormat = new SimpleDateFormat("dd");
DateTimeFormatter dateFormat = Utils.dateFormatter;
Calendar todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.MONTH, -3);
final String VALID_FROM = dateFormat.format(todaysDate.getTime());
todaysDate.add(Calendar.YEAR, 10);
final String VALID_TO = dateFormat.format(todaysDate.getTime());
LocalDate todaysDate = Utils.getLocalDateOfTenant();
todaysDate = todaysDate.minusMonths(3);
final String VALID_FROM = dateFormat.format(todaysDate);
todaysDate = todaysDate.plusYears(10);
final String VALID_TO = dateFormat.format(todaysDate);
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.MONTH, -1);
todaysDate.add(Calendar.DAY_OF_MONTH, -1);
final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
monthDayFormat.format(todaysDate.getTime());
todaysDate.add(Calendar.MONTH, 1);
todaysDate.add(Calendar.DAY_OF_MONTH, 1);
final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
todaysDate = Utils.getLocalDateOfTenant();
todaysDate = todaysDate.minusMonths(1);
todaysDate = todaysDate.minusDays(1);
final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate);
final String APPROVED_ON_DATE = dateFormat.format(todaysDate);
final String ACTIVATION_DATE = dateFormat.format(todaysDate);
todaysDate = Utils.getLocalDateOfTenant();
final String CLOSED_ON_DATE = dateFormat.format(todaysDate);
Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
Assertions.assertNotNull(clientId);
@@ -1345,18 +1338,13 @@ public class FixedDepositTest {
log.info("per day = {}", perDay);
double interestPerDay = interestRateInFraction * perDay;
todaysDate.add(Calendar.MONTH, -1);
todaysDate.add(Calendar.DAY_OF_MONTH, -1);
Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
daysInMonth = daysInMonth - currentDate + 1;
Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
todaysDate = todaysDate.minusMonths(1);
todaysDate = todaysDate.minusDays(1);
Float interestPerMonth = (float) (interestPerDay * principal * ChronoUnit.DAYS.between(todaysDate, Utils.getLocalDateOfTenant()));
principal += interestPerMonth;
todaysDate.add(Calendar.DATE, daysInMonth);
log.info("{}", monthDayFormat.format(todaysDate.getTime()));
interestPerMonth = (float) (interestPerDay * principal * currentDate);
log.info("{}", Utils.dateFormatter.format(todaysDate));
log.info("IPM = {}", interestPerMonth);
principal += interestPerMonth;
log.info("principal = {}", principal);
Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);