From 6e12e9e62acfc9d41bf1930e1865c263f756eebc Mon Sep 17 00:00:00 2001 From: Michal Dudzinski Date: Mon, 12 Nov 2012 16:53:46 +0100 Subject: [PATCH] support for MIFOSX-73: Loan Officer reassignment History. --- ...0013-loan_officer_reassignment_history.sql | 14 +++ .../platform/api/StaffApiResource.java | 5 +- .../commands/BulkLoanReassignmentCommand.java | 12 ++- .../api/data/LoanReassignmentData.java | 6 ++ ...GsonPortfolioApiJsonSerializerService.java | 3 +- ...PortfolioApiDataConversionServiceImpl.java | 7 +- .../LoanOfficerAssignmentException.java | 11 ++- .../mifosng/platform/loan/domain/Loan.java | 47 +++++++++- .../domain/LoanOfficerAssignmentHistory.java | 86 +++++++++++++++++++ ...WritePlatformServiceJpaRepositoryImpl.java | 8 +- .../BulkLoanReassignmentCommandValidator.java | 2 + 11 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 mifosng-db/patches/patch-0013-loan_officer_reassignment_history.sql create mode 100644 mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanOfficerAssignmentHistory.java diff --git a/mifosng-db/patches/patch-0013-loan_officer_reassignment_history.sql b/mifosng-db/patches/patch-0013-loan_officer_reassignment_history.sql new file mode 100644 index 000000000..d68b716b8 --- /dev/null +++ b/mifosng-db/patches/patch-0013-loan_officer_reassignment_history.sql @@ -0,0 +1,14 @@ +CREATE TABLE `m_loan_officer_assignment_history` ( +`id` bigint(20) NOT NULL AUTO_INCREMENT, +`loan_id` bigint(20) NOT NULL , +`loan_officer_id` bigint(20) DEFAULT NULL, +`start_date` date NOT NULL, +`end_date` date DEFAULT NULL, +`createdby_id` bigint(20) DEFAULT NULL, +`created_date` datetime DEFAULT NULL, +`lastmodified_date` datetime DEFAULT NULL, +`lastmodifiedby_id` bigint(20) DEFAULT NULL, +PRIMARY KEY (`id`), +CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`), +CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/StaffApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/StaffApiResource.java index 236582ce0..7e413e7f6 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/StaffApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/StaffApiResource.java @@ -18,6 +18,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; import org.mifosng.platform.api.commands.BulkLoanReassignmentCommand; import org.mifosng.platform.api.commands.StaffCommand; import org.mifosng.platform.api.data.EntityIdentifier; @@ -179,8 +180,8 @@ public class StaffApiResource { staffAccountSummaryCollectionData = this.readPlatformService.retrieveLoanOfficerAccountSummary(loanOfficerId); } - final LoanReassignmentData loanReassignmentData = new LoanReassignmentData(officeId, loanOfficerId, offices, - loanOfficers, staffAccountSummaryCollectionData); + final LoanReassignmentData loanReassignmentData = new LoanReassignmentData(officeId, loanOfficerId, + new LocalDate(), offices, loanOfficers, staffAccountSummaryCollectionData); return this.apiJsonSerializerService.serializeLoanReassignmentDataToJson(prettyPrint, responseParameters, loanReassignmentData); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BulkLoanReassignmentCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BulkLoanReassignmentCommand.java index 359c44556..a685b03c8 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BulkLoanReassignmentCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BulkLoanReassignmentCommand.java @@ -1,5 +1,7 @@ package org.mifosng.platform.api.commands; +import org.joda.time.LocalDate; + /** * Immutable data object for reassigning loan officers on loans in bulk. */ @@ -7,13 +9,15 @@ public class BulkLoanReassignmentCommand { private final Long fromLoanOfficerId; private final Long toLoanOfficerId; + private final LocalDate assignmentDate; private final String[] loans; public BulkLoanReassignmentCommand(Long fromLoanOfficerId, - Long toLoanOfficerId, String[] loans) { + Long toLoanOfficerId, LocalDate assignmentDate, String[] loans) { this.fromLoanOfficerId = fromLoanOfficerId; this.toLoanOfficerId = toLoanOfficerId; + this.assignmentDate = assignmentDate; this.loans = loans; } @@ -25,7 +29,11 @@ public class BulkLoanReassignmentCommand { return toLoanOfficerId; } - public String[] getLoans() { + public LocalDate getAssignmentDate() { + return assignmentDate; + } + + public String[] getLoans() { return loans; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanReassignmentData.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanReassignmentData.java index e59618bfb..1c7218cc6 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanReassignmentData.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanReassignmentData.java @@ -1,5 +1,7 @@ package org.mifosng.platform.api.data; +import org.joda.time.LocalDate; + import java.util.Collection; /** @@ -11,6 +13,8 @@ public class LoanReassignmentData { private final Long officeId; @SuppressWarnings("unused") private final Long fromLoanOfficerId; + @SuppressWarnings("unused") + private final LocalDate assignmentDate; //template @SuppressWarnings("unused") @@ -23,11 +27,13 @@ public class LoanReassignmentData { public LoanReassignmentData( final Long officeId, final Long fromLoanOfficerId, + final LocalDate assignmentDate, final Collection officeOptions, final Collection loanOfficerOptions, final StaffAccountSummaryCollectionData accountSummaryCollection) { this.officeId = officeId; this.fromLoanOfficerId = fromLoanOfficerId; + this.assignmentDate = assignmentDate; this.officeOptions = officeOptions; this.loanOfficerOptions = loanOfficerOptions; this.accountSummaryCollection = accountSummaryCollection; diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonPortfolioApiJsonSerializerService.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonPortfolioApiJsonSerializerService.java index c66d53c80..5f98b777d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonPortfolioApiJsonSerializerService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonPortfolioApiJsonSerializerService.java @@ -194,7 +194,8 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi Arrays.asList("id", "type", "date", "currency", "amount")); private static final Set LOAN_REASSIGNMENT_DATA_PARAMETERS = new HashSet( - Arrays.asList("officeId", "fromLoanOfficerId", "officeOptions", "loanOfficerOptions", "accountSummaryCollection") + Arrays.asList("officeId", "fromLoanOfficerId", "assignmentDate", + "officeOptions", "loanOfficerOptions", "accountSummaryCollection") ); private static final Set CHARGES_DATA_PARAMETERS = new HashSet( diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/PortfolioApiDataConversionServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/PortfolioApiDataConversionServiceImpl.java index 58b5383cb..6bb8da4ea 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/PortfolioApiDataConversionServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/PortfolioApiDataConversionServiceImpl.java @@ -167,7 +167,7 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo Map requestMap = gsonConverter.fromJson(json, typeOfMap); Set supportedParams = new HashSet( - Arrays.asList("fromLoanOfficerId", "toLoanOfficerId","loans") + Arrays.asList("fromLoanOfficerId", "toLoanOfficerId", "assignmentDate", "locale", "dateFormat", "loans") ); checkForUnsupportedParameters(requestMap, supportedParams); @@ -176,6 +176,7 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo Long fromLoanOfficerId = extractLongParameter("fromLoanOfficerId", requestMap, modifiedParameters); Long toLoanOfficerId = extractLongParameter("toLoanOfficerId", requestMap, modifiedParameters); + LocalDate assignmentDate = extractLocalDateParameter("assignmentDate", requestMap, modifiedParameters); // check array JsonParser parser = new JsonParser(); @@ -195,7 +196,7 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo } // - return new BulkLoanReassignmentCommand(fromLoanOfficerId, toLoanOfficerId, loans); + return new BulkLoanReassignmentCommand(fromLoanOfficerId, toLoanOfficerId, assignmentDate, loans); } @Override @@ -365,7 +366,7 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo checkForUnsupportedParameters(requestMap, supportedParams); Set modifiedParameters = new HashSet(); - + String externalId = extractStringParameter("externalId", requestMap, modifiedParameters); Long officeId = extractLongParameter("officeId", requestMap, modifiedParameters); LocalDate joiningDate = extractLocalDateParameter("joiningDate", requestMap, modifiedParameters); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/LoanOfficerAssignmentException.java b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/LoanOfficerAssignmentException.java index 6c4d66842..71b9aa145 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/LoanOfficerAssignmentException.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/LoanOfficerAssignmentException.java @@ -1,11 +1,18 @@ package org.mifosng.platform.exceptions; +import org.joda.time.LocalDate; + public class LoanOfficerAssignmentException extends AbstractPlatformDomainRuleException { - public LoanOfficerAssignmentException(final Long loanId, final Long loanOfficerId) { + public LoanOfficerAssignmentException(final Long loanId, final Long fromLoanOfficerId) { super("error.msg.loan.not.assigned.to.loan.officer", "Loan with identifier " + loanId + - " is not assigned to Loan Officer with identifier " + loanOfficerId + ".", loanId); + " is not assigned to Loan Officer with identifier " + fromLoanOfficerId + ".", loanId); + } + + public LoanOfficerAssignmentException(final Long loanId, final LocalDate date){ + super("error.msg.loan.assignment.date.is.before.last.assignment.date", "Loan with identifier " + loanId + + " was already assigned before date " + date.toString()); } } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java index 24fa249da..421b60073 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java @@ -36,6 +36,7 @@ import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.exceptions.InvalidLoanStateTransitionException; import org.mifosng.platform.exceptions.InvalidLoanTransactionTypeException; import org.mifosng.platform.exceptions.LoanChargeCannotBeAddedException; +import org.mifosng.platform.exceptions.LoanOfficerAssignmentException; import org.mifosng.platform.fund.domain.Fund; import org.mifosng.platform.group.domain.Group; import org.mifosng.platform.infrastructure.AbstractAuditableCustom; @@ -146,6 +147,10 @@ public class Loan extends AbstractAuditableCustom { @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) private Set charges; + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) + private Set loanOfficerAssignmentHistorySet; + // see // http://stackoverflow.com/questions/4334970/hibernate-cannot-simultaneously-fetch-multiple-bags @LazyCollection(LazyCollectionOption.FALSE) @@ -183,6 +188,7 @@ public class Loan extends AbstractAuditableCustom { this.loanProduct = null; this.loanRepaymentScheduleDetail = null; this.charges = null; + this.loanOfficerAssignmentHistorySet = null; } private Loan( @@ -208,6 +214,7 @@ public class Loan extends AbstractAuditableCustom { } else { this.charges = null; } + this.loanOfficerAssignmentHistorySet = null; } private Loan( @@ -233,6 +240,7 @@ public class Loan extends AbstractAuditableCustom { } else { this.charges = null; } + this.loanOfficerAssignmentHistorySet = null; } private BigDecimal deriveSumTotalOfChargesDueAtDisbursement() { @@ -434,7 +442,7 @@ public class Loan extends AbstractAuditableCustom { return this.loanProduct; } - public LoanProductRelatedDetail repaymentScheduleDetail() { + public LoanProductRelatedDetail repaymentScheduleDetail() { return this.loanRepaymentScheduleDetail; } @@ -683,6 +691,13 @@ public class Loan extends AbstractAuditableCustom { "cannot.be.a.future.date", errorMessage, getApprovedOnDate()); } + + if (this.loanofficer != null){ + LoanOfficerAssignmentHistory loanOfficerAssignmentHistory = + LoanOfficerAssignmentHistory.createNew(this, this.loanofficer, approvedOn); + this.loanOfficerAssignmentHistorySet.add(loanOfficerAssignmentHistory); + } + } public void undoApproval(LoanLifecycleStateMachine loanLifecycleStateMachine) { @@ -691,6 +706,8 @@ public class Loan extends AbstractAuditableCustom { this.loanStatus = statusEnum.getValue(); this.approvedOnDate = null; + + this.loanOfficerAssignmentHistorySet.clear(); } public void disburseWithModifiedRepaymentSchedule( @@ -1584,7 +1601,31 @@ public class Loan extends AbstractAuditableCustom { return this.loanRepaymentScheduleDetail.getCurrency(); } - public void updateLoanofficer(final Staff newLoanOfficer) { - this.loanofficer = newLoanOfficer; + public void updateLoanOfficer(final Staff newLoanOfficer, final LocalDate assignmentDate) { + boolean createNewLoanOfficerAssignmentHistoryRecord = true; + for (LoanOfficerAssignmentHistory lastLoanOfficerAssignmentHistoryRecord : this.loanOfficerAssignmentHistorySet){ + if (lastLoanOfficerAssignmentHistoryRecord.getEndDate() == null){ + if (this.loanofficer.identifiedBy(newLoanOfficer)){ + lastLoanOfficerAssignmentHistoryRecord.updateStartDate(assignmentDate); + createNewLoanOfficerAssignmentHistoryRecord = false; + } else if (lastLoanOfficerAssignmentHistoryRecord.getStartDate().isEqual(assignmentDate)){ + lastLoanOfficerAssignmentHistoryRecord.updateLoanOfficer(newLoanOfficer); + createNewLoanOfficerAssignmentHistoryRecord = false; + } else if (assignmentDate.isBefore(lastLoanOfficerAssignmentHistoryRecord.getStartDate())){ + throw new LoanOfficerAssignmentException(this.getId(), assignmentDate); + } else { + lastLoanOfficerAssignmentHistoryRecord.updateEndDate(assignmentDate); + } + break; + } + } + + if (createNewLoanOfficerAssignmentHistoryRecord){ + LoanOfficerAssignmentHistory loanOfficerAssignmentHistory = + LoanOfficerAssignmentHistory.createNew(this, this.loanofficer, assignmentDate); + this.loanOfficerAssignmentHistorySet.add(loanOfficerAssignmentHistory); + } + + this.loanofficer = newLoanOfficer; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanOfficerAssignmentHistory.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanOfficerAssignmentHistory.java new file mode 100644 index 000000000..230597af9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanOfficerAssignmentHistory.java @@ -0,0 +1,86 @@ +package org.mifosng.platform.loan.domain; + + +import org.joda.time.LocalDate; +import org.mifosng.platform.infrastructure.AbstractAuditableCustom; +import org.mifosng.platform.staff.domain.Staff; +import org.mifosng.platform.user.domain.AppUser; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +@Entity +@Table(name = "m_loan_officer_assignment_history") +public class LoanOfficerAssignmentHistory extends AbstractAuditableCustom { + + @SuppressWarnings("unused") + @ManyToOne + @JoinColumn(name = "loan_id", nullable = false) + private Loan loan; + + @SuppressWarnings("unused") + @ManyToOne + @JoinColumn(name = "loan_officer_id", nullable = true) + private Staff loanOfficer; + + @Temporal(TemporalType.DATE) + @Column(name = "start_date") + private Date startDate; + + @SuppressWarnings("unused") + @Temporal(TemporalType.DATE) + @Column(name = "end_date") + private Date endDate; + + public static LoanOfficerAssignmentHistory createNew(Loan loan, Staff loanOfficer, + LocalDate startDate){ + return new LoanOfficerAssignmentHistory(loan, loanOfficer, startDate.toDate(), null); + } + + @SuppressWarnings("unused") + protected LoanOfficerAssignmentHistory() { + this.loan = null; + this.loanOfficer = null; + this.startDate = null; + this.endDate = null; + } + + private LoanOfficerAssignmentHistory(Loan loan, Staff loanOfficer, + Date startDate, Date endDate) { + this.loan = loan; + this.loanOfficer = loanOfficer; + this.startDate = startDate; + this.endDate = endDate; + } + + public Loan getLoan() { + return loan; + } + + public LocalDate getStartDate() { + return new LocalDate(startDate); + } + + public Date getEndDate() { + return endDate; + } + + public void updateLoanOfficer(Staff loanOfficer) { + this.loanOfficer = loanOfficer; + } + + public void updateStartDate(LocalDate startDate){ + this.startDate = startDate.toDate(); + } + + public void updateEndDate(LocalDate endDate){ + this.endDate = endDate.toDate(); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 8fe77e4ca..0a8774b4c 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -76,7 +76,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final ChargeRepository chargeRepository; private final LoanChargeRepository loanChargeRepository; private final LoanChargeAssembler loanChargeAssembler; - + @Autowired public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler, @@ -217,7 +217,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.approve(eventDate, defaultLoanLifecycleStateMachine()); this.loanRepository.save(loan); - + String noteText = command.getNote(); if (StringUtils.isNotBlank(noteText)) { Note note = Note.loanNote(loan, noteText); @@ -237,7 +237,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.undoApproval(defaultLoanLifecycleStateMachine()); this.loanRepository.save(loan); - + String noteText = command.getNote(); if (StringUtils.isNotBlank(noteText)) { Note note = Note.loanNote(loan, noteText); @@ -754,7 +754,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf throw new LoanOfficerAssignmentException(loan.getId(), fromLoanOfficer.getId()); } - loan.updateLoanofficer(toLoanOfficer); + loan.updateLoanOfficer(toLoanOfficer, command.getAssignmentDate()); this.loanRepository.save(loan); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/staff/service/BulkLoanReassignmentCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/staff/service/BulkLoanReassignmentCommandValidator.java index 294c0f705..972ad9192 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/staff/service/BulkLoanReassignmentCommandValidator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/staff/service/BulkLoanReassignmentCommandValidator.java @@ -26,6 +26,8 @@ public class BulkLoanReassignmentCommandValidator { baseDataValidator.reset().parameter("toLoanOfficerId").value(command.getToLoanOfficerId()).notNull(). integerGreaterThanZero().notSameAsParameter("fromLoanOfficerId", command.getFromLoanOfficerId()); + baseDataValidator.reset().parameter("assignmentDate").value(command.getAssignmentDate()).notNull(); + baseDataValidator.reset().parameter("loans").value(command.getLoans()).arrayNotEmpty(); if (!dataValidationErrors.isEmpty()) {