This is an automated email from the ASF dual-hosted git repository. awasum pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract-cn-payroll.git
commit d04212776bf9840307dc04c170312ceb2eb738ae Author: mgeiss <[email protected]> AuthorDate: Wed Sep 20 10:47:12 2017 +0200 added validation for accounts and customers --- .../api/v1/domain/PayrollCollectionSheet.java | 4 +-- .../payroll/api/v1/domain/PayrollPayment.java | 18 +++++++++++++ .../io/mifos/payroll/TestPayrollDistribution.java | 12 +++++++++ .../handler/PayrollConfigurationAggregate.java | 1 - .../handler/PayrollDistributionAggregate.java | 25 ++++++++++------- .../internal/mapper/PayrollPaymentMapper.java | 2 ++ .../internal/repository/PayrollPaymentEntity.java | 20 ++++++++++++++ .../service/adaptor/AccountingAdaptor.java | 31 ++++++++++++++++------ .../internal/service/adaptor/CustomerAdaptor.java | 7 +++-- .../rest/PayrollConfigurationRestController.java | 26 +++++++++++------- .../rest/PayrollDistributionRestController.java | 13 +++------ .../V2__add_distribution_processing_behavior.sql | 18 +++++++++++++ 12 files changed, 137 insertions(+), 40 deletions(-) diff --git a/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollCollectionSheet.java b/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollCollectionSheet.java index 99f2f78..043ca02 100644 --- a/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollCollectionSheet.java +++ b/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollCollectionSheet.java @@ -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; diff --git a/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollPayment.java b/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollPayment.java index 50daa4b..dbee945 100644 --- a/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollPayment.java +++ b/api/src/main/java/io/mifos/payroll/api/v1/domain/PayrollPayment.java @@ -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 class PayrollPayment { 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; + } } diff --git a/component-test/src/main/java/io/mifos/payroll/TestPayrollDistribution.java b/component-test/src/main/java/io/mifos/payroll/TestPayrollDistribution.java index 0bcae44..661b586 100644 --- a/component-test/src/main/java/io/mifos/payroll/TestPayrollDistribution.java +++ b/component-test/src/main/java/io/mifos/payroll/TestPayrollDistribution.java @@ -25,6 +25,7 @@ import io.mifos.payroll.api.v1.domain.PayrollConfiguration; 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 class TestPayrollDistribution extends AbstractPayrollTest { .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 class TestPayrollDistribution extends AbstractPayrollTest { 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) { diff --git a/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollConfigurationAggregate.java b/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollConfigurationAggregate.java index 43e954d..b5383bd 100644 --- a/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollConfigurationAggregate.java +++ b/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollConfigurationAggregate.java @@ -72,7 +72,6 @@ public class PayrollConfigurationAggregate { if (optionalPayrollConfiguration.isPresent()) { payrollConfigurationEntity = optionalPayrollConfiguration.get(); this.payrollAllocationRepository.deleteByPayrollConfiguration(payrollConfigurationEntity); - this.payrollAllocationRepository.flush(); payrollConfigurationEntity.setLastModifiedBy(UserContextHolder.checkedGetUser()); diff --git a/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollDistributionAggregate.java b/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollDistributionAggregate.java index 86f4aae..93f9596 100644 --- a/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollDistributionAggregate.java +++ b/service/src/main/java/io/mifos/payroll/service/internal/command/handler/PayrollDistributionAggregate.java @@ -37,6 +37,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Clock; import java.time.LocalDateTime; +import java.util.Optional; @Aggregate public class PayrollDistributionAggregate { @@ -79,16 +80,22 @@ public class PayrollDistributionAggregate { 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(); diff --git a/service/src/main/java/io/mifos/payroll/service/internal/mapper/PayrollPaymentMapper.java b/service/src/main/java/io/mifos/payroll/service/internal/mapper/PayrollPaymentMapper.java index fd7538a..738d8ed 100644 --- a/service/src/main/java/io/mifos/payroll/service/internal/mapper/PayrollPaymentMapper.java +++ b/service/src/main/java/io/mifos/payroll/service/internal/mapper/PayrollPaymentMapper.java @@ -29,6 +29,8 @@ public class PayrollPaymentMapper { payrollPayment.setCustomerIdentifier(payrollPaymentEntity.getCustomerIdentifier()); payrollPayment.setEmployer(payrollPaymentEntity.getEmployer()); payrollPayment.setSalary(payrollPaymentEntity.getSalary()); + payrollPayment.setProcessed(payrollPaymentEntity.getProcessed()); + payrollPayment.setMessage(payrollPaymentEntity.getMessage()); return payrollPayment; } } diff --git a/service/src/main/java/io/mifos/payroll/service/internal/repository/PayrollPaymentEntity.java b/service/src/main/java/io/mifos/payroll/service/internal/repository/PayrollPaymentEntity.java index 3b782ed..c04dc8d 100644 --- a/service/src/main/java/io/mifos/payroll/service/internal/repository/PayrollPaymentEntity.java +++ b/service/src/main/java/io/mifos/payroll/service/internal/repository/PayrollPaymentEntity.java @@ -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 class PayrollPaymentEntity { 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; + } } diff --git a/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/AccountingAdaptor.java b/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/AccountingAdaptor.java index 41f19df..14aa326 100644 --- a/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/AccountingAdaptor.java +++ b/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/AccountingAdaptor.java @@ -57,14 +57,17 @@ public class AccountingAdaptor { 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 class AccountingAdaptor { 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."); + } } } diff --git a/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/CustomerAdaptor.java b/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/CustomerAdaptor.java index 9fd9d0c..dde236f 100644 --- a/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/CustomerAdaptor.java +++ b/service/src/main/java/io/mifos/payroll/service/internal/service/adaptor/CustomerAdaptor.java @@ -42,10 +42,13 @@ public class CustomerAdaptor { 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(); } } diff --git a/service/src/main/java/io/mifos/payroll/service/rest/PayrollConfigurationRestController.java b/service/src/main/java/io/mifos/payroll/service/rest/PayrollConfigurationRestController.java index 100319d..70e3221 100644 --- a/service/src/main/java/io/mifos/payroll/service/rest/PayrollConfigurationRestController.java +++ b/service/src/main/java/io/mifos/payroll/service/rest/PayrollConfigurationRestController.java @@ -21,6 +21,7 @@ import io.mifos.anubis.annotation.Permittables; 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.ResponseBody; 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 class PayrollConfigurationRestController { 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)); diff --git a/service/src/main/java/io/mifos/payroll/service/rest/PayrollDistributionRestController.java b/service/src/main/java/io/mifos/payroll/service/rest/PayrollDistributionRestController.java index 86d3203..7a2a8ea 100644 --- a/service/src/main/java/io/mifos/payroll/service/rest/PayrollDistributionRestController.java +++ b/service/src/main/java/io/mifos/payroll/service/rest/PayrollDistributionRestController.java @@ -82,17 +82,12 @@ public class PayrollDistributionRestController { 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)); diff --git a/service/src/main/resources/db/migrations/mariadb/V2__add_distribution_processing_behavior.sql b/service/src/main/resources/db/migrations/mariadb/V2__add_distribution_processing_behavior.sql new file mode 100644 index 0000000..c7d1ab3 --- /dev/null +++ b/service/src/main/resources/db/migrations/mariadb/V2__add_distribution_processing_behavior.sql @@ -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; \ No newline at end of file
