Skip to content
Permalink
Browse files
added validation for accounts and customers
  • Loading branch information
mgeiss committed Sep 20, 2017
1 parent 5955e08 commit d04212776bf9840307dc04c170312ceb2eb738ae
Showing 12 changed files with 137 additions and 40 deletions.
@@ -16,16 +16,16 @@
package io.mifos.payroll.api.v1.domain;

import io.mifos.core.lang.validation.constraints.ValidIdentifier;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;

public class PayrollCollectionSheet {

@ValidIdentifier(maxLength = 34)
private String sourceAccountNumber;
@NotNull
@NotEmpty
@Valid
private List<PayrollPayment> payrollPayments;

@@ -32,6 +32,8 @@ public class PayrollPayment {
@DecimalMin("0.001")
@DecimalMax("9999999999.99999")
private BigDecimal salary;
private Boolean processed;
private String message;

public PayrollPayment() {
super();
@@ -60,4 +62,20 @@ public BigDecimal getSalary() {
public void setSalary(final BigDecimal salary) {
this.salary = salary;
}

public Boolean getProcessed() {
return this.processed;
}

public void setProcessed(final Boolean processed) {
this.processed = processed;
}

public String getMessage() {
return this.message;
}

public void setMessage(final String message) {
this.message = message;
}
}
@@ -25,6 +25,7 @@
import io.mifos.payroll.api.v1.domain.PayrollPayment;
import io.mifos.payroll.api.v1.domain.PayrollPaymentPage;
import io.mifos.payroll.domain.DomainObjectGenerator;
import io.mifos.payroll.service.internal.repository.PayrollCollectionEntity;
import io.mifos.payroll.service.internal.service.adaptor.AccountingAdaptor;
import io.mifos.payroll.service.internal.service.adaptor.CustomerAdaptor;
import org.apache.commons.lang3.RandomStringUtils;
@@ -70,6 +71,14 @@ public void shouldDistributePayments() throws Exception {
.doAnswer(invocation -> Optional.of(new Account()))
.when(this.accountingAdaptorSpy).findAccount(Matchers.eq(payrollCollectionSheet.getSourceAccountNumber()));

Mockito
.doAnswer(invocation -> Optional.empty())
.when(this.accountingAdaptorSpy).postPayrollPayment(
Matchers.any(PayrollCollectionEntity.class),
Matchers.refEq(payrollPayment),
Matchers.any(PayrollConfiguration.class)
);

super.testSubject.distribute(payrollCollectionSheet);
Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_DISTRIBUTION, payrollCollectionSheet.getSourceAccountNumber()));

@@ -80,6 +89,9 @@ public void shouldDistributePayments() throws Exception {
final PayrollPaymentPage payrollPaymentPage =
super.testSubject.fetchPayments(payrollCollectionHistory.getIdentifier(), 0, 10, null, null);
Assert.assertEquals(Long.valueOf(1L), payrollPaymentPage.getTotalElements());

final PayrollPayment fetchedPayrollPayment = payrollPaymentPage.getPayrollPayments().get(0);
Assert.assertTrue(fetchedPayrollPayment.getProcessed());
}

private void prepareMocks(final String customerIdentifier, final PayrollConfiguration payrollConfiguration) {
@@ -72,7 +72,6 @@ public String process(final PutPayrollConfigurationCommand putPayrollConfigurati
if (optionalPayrollConfiguration.isPresent()) {
payrollConfigurationEntity = optionalPayrollConfiguration.get();
this.payrollAllocationRepository.deleteByPayrollConfiguration(payrollConfigurationEntity);

this.payrollAllocationRepository.flush();

payrollConfigurationEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
@@ -37,6 +37,7 @@

import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Optional;

@Aggregate
public class PayrollDistributionAggregate {
@@ -79,16 +80,22 @@ public String process(final DistributePayrollCommand distributePayrollCommand) {
this.payrollConfigurationService
.findPayrollConfiguration(payrollPayment.getCustomerIdentifier())
.ifPresent(payrollConfiguration -> {
final PayrollPaymentEntity payrollPaymentEntity = new PayrollPaymentEntity();
payrollPaymentEntity.setPayrollCollection(savedPayrollCollectionEntity);
payrollPaymentEntity.setCustomerIdentifier(payrollPayment.getCustomerIdentifier());
payrollPaymentEntity.setEmployer(payrollPayment.getEmployer());
payrollPaymentEntity.setSalary(payrollPayment.getSalary());
final PayrollPaymentEntity payrollPaymentEntity = new PayrollPaymentEntity();
payrollPaymentEntity.setPayrollCollection(savedPayrollCollectionEntity);
payrollPaymentEntity.setCustomerIdentifier(payrollPayment.getCustomerIdentifier());
payrollPaymentEntity.setEmployer(payrollPayment.getEmployer());
payrollPaymentEntity.setSalary(payrollPayment.getSalary());

this.payrollPaymentRepository.save(payrollPaymentEntity);

this.accountingAdaptor.postPayrollPayment(savedPayrollCollectionEntity, payrollPayment, payrollConfiguration);
})
final Optional<String> optionalErrorMessage =
this.accountingAdaptor.postPayrollPayment(savedPayrollCollectionEntity, payrollPayment, payrollConfiguration);
if (optionalErrorMessage.isPresent()) {
payrollPaymentEntity.setMessage(optionalErrorMessage.get());
payrollPaymentEntity.setProcessed(Boolean.FALSE);
} else {
payrollPaymentEntity.setProcessed(Boolean.TRUE);
}
this.payrollPaymentRepository.save(payrollPaymentEntity);
})
);

return payrollCollectionSheet.getSourceAccountNumber();
@@ -29,6 +29,8 @@ public static PayrollPayment map(final PayrollPaymentEntity payrollPaymentEntity
payrollPayment.setCustomerIdentifier(payrollPaymentEntity.getCustomerIdentifier());
payrollPayment.setEmployer(payrollPaymentEntity.getEmployer());
payrollPayment.setSalary(payrollPaymentEntity.getSalary());
payrollPayment.setProcessed(payrollPaymentEntity.getProcessed());
payrollPayment.setMessage(payrollPaymentEntity.getMessage());
return payrollPayment;
}
}
@@ -42,6 +42,10 @@ public class PayrollPaymentEntity {
private String employer;
@Column(name = "salary", nullable = false, precision = 15, scale = 5)
private BigDecimal salary;
@Column(name = "processed", nullable = false)
private Boolean processed;
@Column(name = "message", nullable = true)
private String message;

public PayrollPaymentEntity() {
super();
@@ -86,4 +90,20 @@ public BigDecimal getSalary() {
public void setSalary(final BigDecimal salary) {
this.salary = salary;
}

public Boolean getProcessed() {
return this.processed;
}

public void setProcessed(final Boolean processed) {
this.processed = processed;
}

public String getMessage() {
return this.message;
}

public void setMessage(final String message) {
this.message = message;
}
}
@@ -57,14 +57,17 @@ public AccountingAdaptor(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger l

public Optional<Account> findAccount(final String accountIdentifier) {
try {
return Optional.of(this.ledgerManager.findAccount(accountIdentifier));
final Account account = this.ledgerManager.findAccount(accountIdentifier);
if (account.getState().equals(Account.State.OPEN.name())) {
return Optional.of(account);
}
} catch (final AccountNotFoundException anfex) {
this.logger.warn("Account {} not found.", accountIdentifier);
return Optional.empty();
}
return Optional.empty();
}

public void postPayrollPayment(final PayrollCollectionEntity payrollCollectionEntity,
public Optional<String> postPayrollPayment(final PayrollCollectionEntity payrollCollectionEntity,
final PayrollPayment payrollPayment,
final PayrollConfiguration payrollConfiguration) {

@@ -102,11 +105,23 @@ public void postPayrollPayment(final PayrollCollectionEntity payrollCollectionEn
final BigDecimal currentCreditorSum =
BigDecimal.valueOf(creditors.stream().mapToDouble(value -> Double.valueOf(value.getAmount())).sum());

final Creditor mainCreditor = new Creditor();
mainCreditor.setAccountNumber(payrollConfiguration.getMainAccountNumber());
mainCreditor.setAmount(payrollPayment.getSalary().subtract(currentCreditorSum).toString());
creditors.add(mainCreditor);
final int comparedValue = currentCreditorSum.compareTo(payrollPayment.getSalary());
if (comparedValue > 0) {
return Optional.of("Allocated amount would exceed posted salary.");
}
if (comparedValue < 0) {
final Creditor mainCreditor = new Creditor();
mainCreditor.setAccountNumber(payrollConfiguration.getMainAccountNumber());
mainCreditor.setAmount(payrollPayment.getSalary().subtract(currentCreditorSum).toString());
creditors.add(mainCreditor);
}

this.ledgerManager.createJournalEntry(journalEntry);
try {
this.ledgerManager.createJournalEntry(journalEntry);
return Optional.empty();
} catch (final Throwable th) {
this.logger.warn("Could not process journal entry for customer {}.", payrollPayment.getCustomerIdentifier(), th);
return Optional.of("Error while processing journal entry.");
}
}
}
@@ -42,10 +42,13 @@ public CustomerAdaptor(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger log

public Optional<Customer> findCustomer(final String customerIdentifier) {
try {
return Optional.of(this.customerManager.findCustomer(customerIdentifier));
final Customer customer = this.customerManager.findCustomer(customerIdentifier);
if (customer.getCurrentState().equals(Customer.State.ACTIVE.name())) {
return Optional.of(customer);
}
} catch (final CustomerNotFoundException cnfex) {
this.logger.warn("Customer {} not found.", customerIdentifier);
return Optional.empty();
}
return Optional.empty();
}
}
@@ -21,6 +21,7 @@
import io.mifos.core.command.gateway.CommandGateway;
import io.mifos.core.lang.ServiceException;
import io.mifos.payroll.api.v1.PermittableGroupIds;
import io.mifos.payroll.api.v1.domain.PayrollAllocation;
import io.mifos.payroll.api.v1.domain.PayrollConfiguration;
import io.mifos.payroll.service.ServiceConstants;
import io.mifos.payroll.service.internal.command.PutPayrollConfigurationCommand;
@@ -38,6 +39,7 @@
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.Set;

@RestController
@RequestMapping("/customers/{identifier}/payroll")
@@ -73,19 +75,25 @@ public PayrollConfigurationRestController(@Qualifier(ServiceConstants.LOGGER_NAM
public ResponseEntity<Void> setPayrollConfiguration(@PathVariable(value = "identifier") final String customerIdentifier,
@RequestBody @Valid final PayrollConfiguration payrollConfiguration) {
this.payrollConfigurationService.findCustomer(customerIdentifier)
.orElseThrow(() -> ServiceException.notFound("Customer {0} not found.", customerIdentifier)
.orElseThrow(() -> ServiceException.notFound("Customer {0} not available.", customerIdentifier)
);

this.payrollConfigurationService.findAccount(payrollConfiguration.getMainAccountNumber())
.orElseThrow(() -> ServiceException.notFound("Main account {0} not found.", payrollConfiguration.getMainAccountNumber()));
.orElseThrow(() -> ServiceException.notFound("Main account {0} not available.", payrollConfiguration.getMainAccountNumber()));

if (payrollConfiguration.getPayrollAllocations() != null
&& payrollConfiguration.getPayrollAllocations()
.stream()
.filter(payrollAllocation ->
!this.payrollConfigurationService.findAccount(payrollAllocation.getAccountNumber()).isPresent()
).count() > 0L) {
throw ServiceException.notFound("Certain allocated accounts not found.");
if (payrollConfiguration.getPayrollAllocations() != null) {

final Set<PayrollAllocation> payrollAllocations = payrollConfiguration.getPayrollAllocations();

if (payrollAllocations.stream().anyMatch(payrollAllocation ->
payrollAllocation.getAccountNumber().equals(payrollConfiguration.getMainAccountNumber()))) {
throw ServiceException.conflict("Main account should not be used in allocations.");
}

if (payrollAllocations.stream().anyMatch(payrollAllocation ->
!this.payrollConfigurationService.findAccount(payrollAllocation.getAccountNumber()).isPresent())) {
throw ServiceException.notFound("Certain allocated accounts not available.");
}
}

this.commandGateway.process(new PutPayrollConfigurationCommand(customerIdentifier, payrollConfiguration));
@@ -82,17 +82,12 @@ public PayrollDistributionRestController(@Qualifier(ServiceConstants.LOGGER_NAME
public ResponseEntity<Void> distribute(@RequestBody @Valid final PayrollCollectionSheet payrollCollectionSheet) {

this.payrollConfigurationService.findAccount(payrollCollectionSheet.getSourceAccountNumber())
.orElseThrow(() -> ServiceException.notFound("Account {0} not found.", payrollCollectionSheet.getSourceAccountNumber()));
.orElseThrow(() -> ServiceException.notFound("Account {0} not available.", payrollCollectionSheet.getSourceAccountNumber()));

if (payrollCollectionSheet.getPayrollPayments()
.stream()
.filter(
payrollPayment -> !this.payrollConfigurationService
.findPayrollConfiguration(payrollPayment.getCustomerIdentifier())
.isPresent()
)
.count() > 0L) {
throw ServiceException.conflict("Missing payroll configuration for certain customers.");
.stream().anyMatch(payrollPayment ->
!this.payrollConfigurationService.findPayrollConfiguration(payrollPayment.getCustomerIdentifier()).isPresent())) {
throw ServiceException.conflict("Payroll configuration for certain customers not available.");
}

this.commandGateway.process(new DistributePayrollCommand(payrollCollectionSheet));
@@ -0,0 +1,18 @@
--
-- Copyright 2017 The Mifos Initiative.
--
-- Licensed 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.
--

ALTER TABLE meketre_payroll_payments ADD processed BOOLEAN NOT NULL;
ALTER TABLE meketre_payroll_payments ADD message VARCHAR(256) NULL;

0 comments on commit d042127

Please sign in to comment.