You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by aw...@apache.org on 2019/06/03 19:02:53 UTC

[fineract-cn-payroll] 18/50: added validation for accounts and customers

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 <mg...@mifos.org>
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