diff --git a/mifosng-db/patches-beta-3-heavens-family/patch-0001-fund-and-loanproduct-changes - Copy.sql b/mifosng-db/patches-beta-3-heavens-family/patch-0001-fund-and-loanproduct-changes.sql similarity index 100% rename from mifosng-db/patches-beta-3-heavens-family/patch-0001-fund-and-loanproduct-changes - Copy.sql rename to mifosng-db/patches-beta-3-heavens-family/patch-0001-fund-and-loanproduct-changes.sql diff --git a/mifosng-db/patches-beta-3-heavens-family/patch-0005-branch-transfer-of-funds.sql b/mifosng-db/patches-beta-3-heavens-family/patch-0005-branch-transfer-of-funds.sql index 7a04d9121..e7d98efbf 100644 --- a/mifosng-db/patches-beta-3-heavens-family/patch-0005-branch-transfer-of-funds.sql +++ b/mifosng-db/patches-beta-3-heavens-family/patch-0005-branch-transfer-of-funds.sql @@ -1,6 +1,6 @@ -- create fund table -DROP TABLE IF EXISTS `mifosngprovider`.`portfolio_office_funds_transfers`; -CREATE TABLE `portfolio_office_funds_transfers` ( +DROP TABLE IF EXISTS `mifosngprovider`.`portfolio_office_monetary_transfers`; +CREATE TABLE `portfolio_office_monetary_transfers` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `from_office_id` bigint(20) DEFAULT NULL, `to_office_id` bigint(20) DEFAULT NULL, diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/OfficeApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/OfficeApiResource.java index fa5a57af3..7372c1e98 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/OfficeApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/OfficeApiResource.java @@ -1,6 +1,8 @@ package org.mifosng.platform.api; +import java.math.BigDecimal; import java.util.Collection; +import java.util.Locale; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -15,6 +17,7 @@ import javax.ws.rs.core.MediaType; 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.BranchMoneyTransferCommand; import org.mifosng.platform.api.commands.OfficeCommand; @@ -22,6 +25,7 @@ import org.mifosng.platform.api.data.EntityIdentifier; import org.mifosng.platform.api.data.OfficeData; import org.mifosng.platform.api.infrastructure.ApiDataConversionService; import org.mifosng.platform.api.infrastructure.ApiJSONFormattingService; +import org.mifosng.platform.exceptions.UnrecognizedQueryParamException; import org.mifosng.platform.organisation.service.OfficeReadPlatformService; import org.mifosng.platform.organisation.service.OfficeWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; @@ -129,14 +133,38 @@ public class OfficeApiResource { @POST @Path("{officeId}") - @Consumes({ MediaType.APPLICATION_JSON}) - @Produces({ MediaType.APPLICATION_JSON}) - public Response transferMoney(@PathParam("officeId") final Long loanId, + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public Response transferMoneyFrom(@PathParam("officeId") final Long officeId, @QueryParam("command") final String commandParam, final BranchMoneyTransferCommand command) { - Long officeId = this.writePlatformService.transferMoney(command); + command.setFromOfficeId(officeId); + + LocalDate transactionLocalDate = apiDataConversionService.convertFrom(command.getTransactionDate(), "transactionDate", command.getDateFormat()); + command.setTransactionLocalDate(transactionLocalDate); + + Locale clientLocale = this.apiDataConversionService.localeFromString(command.getLocale()); - return Response.ok().entity(new EntityIdentifier(officeId)).build(); + BigDecimal transactionAmountValue = apiDataConversionService.convertFrom(command.getTransactionAmount(), "transactionAmount", clientLocale); + command.setTransactionAmountValue(transactionAmountValue); + + Response response = null; + + if (is(commandParam, "transfer")) { + Long id = this.writePlatformService.transferMoney(command); + response = Response.ok().entity(new EntityIdentifier(id)).build(); + } + + if (response == null) { + throw new UnrecognizedQueryParamException("command", commandParam); + } + + return response; + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) + && commandParam.trim().equalsIgnoreCase(commandValue); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BranchMoneyTransferCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BranchMoneyTransferCommand.java index 1113181ce..e09161615 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BranchMoneyTransferCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/BranchMoneyTransferCommand.java @@ -21,6 +21,7 @@ public class BranchMoneyTransferCommand { private LocalDate transactionLocalDate; private String locale; + private String currencyCode; private String transactionAmount; private BigDecimal transactionAmountValue; @@ -99,4 +100,12 @@ public class BranchMoneyTransferCommand { public void setTransactionAmountValue(BigDecimal transactionAmountValue) { this.transactionAmountValue = transactionAmountValue; } + + public String getCurrencyCode() { + return currencyCode; + } + + public void setCurrencyCode(String currencyCode) { + this.currencyCode = currencyCode; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java index a701db9ea..6519a53aa 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/Office.java @@ -182,4 +182,12 @@ public class Office extends AbstractAuditableCustom { public String getHierarchy() { return hierarchy; } + + public boolean hasParentOf(final Office office) { + boolean isParent = false; + if (this.parent != null) { + isParent = this.parent.equals(office); + } + return isParent; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/OfficeMonetaryTransfer.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/OfficeMonetaryTransfer.java new file mode 100644 index 000000000..61ddc3d82 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/OfficeMonetaryTransfer.java @@ -0,0 +1,65 @@ +package org.mifosng.platform.organisation.domain; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.joda.time.LocalDate; +import org.mifosng.platform.currency.domain.MonetaryCurrency; +import org.mifosng.platform.currency.domain.Money; +import org.mifosng.platform.infrastructure.AbstractAuditableCustom; +import org.mifosng.platform.user.domain.AppUser; + +@Entity +@Table(name = "portfolio_office_monetary_transfers") +public class OfficeMonetaryTransfer extends AbstractAuditableCustom { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "from_office_id") + private Office from; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "to_office_id") + private Office to; + + @Column(name = "transaction_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date transactionDate; + + @Embedded + private MonetaryCurrency currency; + + @Column(name = "transaction_amount", scale = 6, precision = 19, nullable = false) + private BigDecimal transactionAmount; + + protected OfficeMonetaryTransfer() { + this.transactionDate = null; + } + + public static OfficeMonetaryTransfer create(Office fromOffice, Office toOffice, LocalDate transactionLocalDate, Money amount) { + + Date transactionDate = null; + if (transactionLocalDate != null) { + transactionDate = transactionLocalDate.toDate(); + } + + return new OfficeMonetaryTransfer(fromOffice, toOffice, transactionDate, amount); + } + + private OfficeMonetaryTransfer(final Office fromOffice,final Office toOffice, final Date transactionDate, final Money amount) { + this.from = fromOffice; + this.to = toOffice; + this.transactionDate = transactionDate; + this.currency = amount.getCurrency(); + this.transactionAmount = amount.getAmount(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/OfficeMonetaryTransferRepository.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/OfficeMonetaryTransferRepository.java new file mode 100644 index 000000000..03eb5c6dc --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/domain/OfficeMonetaryTransferRepository.java @@ -0,0 +1,10 @@ +package org.mifosng.platform.organisation.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface OfficeMonetaryTransferRepository extends + JpaRepository, + JpaSpecificationExecutor { + // no added behaviour +} diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/BranchMoneyTransferCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/BranchMoneyTransferCommandValidator.java new file mode 100644 index 000000000..0c390c7a0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/BranchMoneyTransferCommandValidator.java @@ -0,0 +1,35 @@ +package org.mifosng.platform.organisation.service; + +import java.util.ArrayList; +import java.util.List; + +import org.mifosng.platform.DataValidatorBuilder; +import org.mifosng.platform.api.commands.BranchMoneyTransferCommand; +import org.mifosng.platform.api.data.ApiParameterError; +import org.mifosng.platform.exceptions.PlatformApiDataValidationException; + +public class BranchMoneyTransferCommandValidator { + + private final BranchMoneyTransferCommand command; + + public BranchMoneyTransferCommandValidator(final BranchMoneyTransferCommand command) { + this.command = command; + } + + public void validate() { + + List dataValidationErrors = new ArrayList(); + + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("office.money.transfer"); + + baseDataValidator.reset().parameter("fromOfficeId").value(command.getFromOfficeId()).notNull(); + baseDataValidator.reset().parameter("toOfficeId").value(command.getToOfficeId()).notNull(); + baseDataValidator.reset().parameter("transactionDate").value(command.getTransactionLocalDate()).notNull(); + baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).notBlank(); + baseDataValidator.reset().parameter("transactionAmount").value(command.getTransactionAmountValue()).notNull(); + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); + } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java index a2f8d6b70..471b73950 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeWritePlatformServiceJpaRepositoryImpl.java @@ -4,12 +4,17 @@ import static org.mifosng.platform.Specifications.officesThatMatch; import org.mifosng.platform.api.commands.BranchMoneyTransferCommand; import org.mifosng.platform.api.commands.OfficeCommand; +import org.mifosng.platform.currency.domain.ApplicationCurrency; +import org.mifosng.platform.currency.domain.ApplicationCurrencyRepository; import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.Money; +import org.mifosng.platform.exceptions.CurrencyNotFoundException; import org.mifosng.platform.exceptions.NoAuthorizationException; import org.mifosng.platform.exceptions.OfficeNotFoundException; import org.mifosng.platform.exceptions.PlatformDataIntegrityException; import org.mifosng.platform.organisation.domain.Office; +import org.mifosng.platform.organisation.domain.OfficeMonetaryTransfer; +import org.mifosng.platform.organisation.domain.OfficeMonetaryTransferRepository; import org.mifosng.platform.organisation.domain.OfficeRepository; import org.mifosng.platform.security.PlatformSecurityContext; import org.mifosng.platform.user.domain.AppUser; @@ -27,11 +32,18 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP private final PlatformSecurityContext context; private final OfficeRepository officeRepository; + private final OfficeMonetaryTransferRepository officeMonetaryTransferRepository; + private final ApplicationCurrencyRepository applicationCurrencyRepository; @Autowired - public OfficeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final OfficeRepository officeRepository) { + public OfficeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, + final OfficeRepository officeRepository, + final OfficeMonetaryTransferRepository officeMonetaryTransferRepository, + final ApplicationCurrencyRepository applicationCurrencyRepository) { this.context = context; this.officeRepository = officeRepository; + this.officeMonetaryTransferRepository = officeMonetaryTransferRepository; + this.applicationCurrencyRepository = applicationCurrencyRepository; } @Transactional @@ -92,31 +104,42 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP @Transactional @Override - public Long transferMoney(BranchMoneyTransferCommand command) { + public Long transferMoney(final BranchMoneyTransferCommand command) { AppUser currentUser = context.authenticatedUser(); - Office fromOffice = this.officeRepository.findOne(command.getFromOfficeId()); - if (fromOffice == null) { - throw new OfficeNotFoundException(command.getFromOfficeId()); + BranchMoneyTransferCommandValidator validator = new BranchMoneyTransferCommandValidator(command); + validator.validate(); + + Office fromOffice = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getFromOfficeId()); + Office toOffice = null; + if (fromOffice.isHeadOffice()) { + toOffice = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, command.getToOfficeId()); + } else { + toOffice = this.officeRepository.findOne(command.getToOfficeId()); + if (toOffice == null) { + throw new OfficeNotFoundException(command.getToOfficeId()); + } + + if (!fromOffice.hasParentOf(toOffice)) { + throw new NoAuthorizationException("User does not have sufficient priviledges to act on the provided office."); + } + } + + final String currencyCode = command.getCurrencyCode(); + ApplicationCurrency appCurrency = this.applicationCurrencyRepository.findOneByCode(currencyCode); + if (appCurrency == null) { + throw new CurrencyNotFoundException(currencyCode); } - Office toOffice = this.officeRepository.findOne(command.getToOfficeId()); - if (toOffice == null) { - throw new OfficeNotFoundException(command.getToOfficeId()); - } - - MonetaryCurrency currency = new MonetaryCurrency("USD", 2); + MonetaryCurrency currency = new MonetaryCurrency(appCurrency.getCode(), appCurrency.getDecimalPlaces()); Money amount = Money.of(currency, command.getTransactionAmountValue()); - OfficeFundsTransfer entity = OfficeFundsTransfer.create(currentUser.getOrganisation(), fromOffice, toOffice, - - command.getTransactionDate(), amount); + OfficeMonetaryTransfer entity = OfficeMonetaryTransfer.create(fromOffice, toOffice, command.getTransactionLocalDate(), amount); - this.officeFundsTransferRepository.save(entity); + this.officeMonetaryTransferRepository.save(entity); return entity.getId(); - return null; } /*