fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From my...@apache.org
Subject [fineract-cn-accounting] 07/36: added ability to post payroll payments added sorting to journal entry retrieval
Date Mon, 22 Jan 2018 15:43:43 GMT
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-accounting.git

commit f560b31f8597b0817f575f1add8c840dd6913005
Author: mgeiss <mgeiss@mifos.org>
AuthorDate: Wed Sep 6 11:13:09 2017 +0200

    added ability to post payroll payments
    added sorting to journal entry retrieval
---
 .../io/mifos/accounting/api/v1/EventConstants.java |   3 +
 .../accounting/api/v1/client/LedgerManager.java    |  36 +++-
 .../client/PayrollPaymentValidationException.java  |  19 +++
 .../api/v1/domain/PayrollCollectionHistory.java    |  68 ++++++++
 .../api/v1/domain/PayrollCollectionSheet.java      |  51 ++++++
 .../accounting/api/v1/domain/PayrollPayment.java   |  63 +++++++
 .../api/v1/domain/PayrollPaymentPage.java          |  61 +++++++
 .../io/mifos/accounting/TestPayrollPayment.java    | 188 +++++++++++++++++++++
 .../listener/PayrollPaymentListener.java           |  54 ++++++
 service/build.gradle                               |   1 +
 .../service/AccountingServiceConfiguration.java    |   7 +
 .../command/ProcessPayrollPaymentCommand.java      |  31 ++++
 .../command/handler/PayrollPaymentAggregate.java   | 142 ++++++++++++++++
 .../internal/mapper/PayrollPaymentMapper.java      |  34 ++++
 .../repository/PayrollCollectionEntity.java        |  90 ++++++++++
 .../repository/PayrollCollectionRepository.java    |  23 +++
 .../internal/repository/PayrollPaymentEntity.java  |  86 ++++++++++
 .../repository/PayrollPaymentRepository.java       |  26 +++
 .../internal/service/JournalEntryService.java      |   3 +-
 .../internal/service/PayrollPaymentService.java    |  81 +++++++++
 .../internal/service/helper/CustomerAdapter.java   |  44 +++++
 .../service/rest/AccountRestController.java        |   5 +
 .../service/rest/PayrollPaymentRestController.java | 106 ++++++++++++
 .../mariadb/V6__add_payroll_distribution.sql       |  36 ++++
 shared.gradle                                      |   1 +
 25 files changed, 1257 insertions(+), 2 deletions(-)

diff --git a/api/src/main/java/io/mifos/accounting/api/v1/EventConstants.java b/api/src/main/java/io/mifos/accounting/api/v1/EventConstants.java
index c204ed8..66134e2 100644
--- a/api/src/main/java/io/mifos/accounting/api/v1/EventConstants.java
+++ b/api/src/main/java/io/mifos/accounting/api/v1/EventConstants.java
@@ -64,4 +64,7 @@ public interface EventConstants {
   String SELECTOR_POST_TX_TYPE = SELECTOR_NAME + " = '" + POST_TX_TYPE + "'";
   String PUT_TX_TYPE = "put-tx-type";
   String SELECTOR_PUT_TX_TYPE = SELECTOR_NAME + " = '" + PUT_TX_TYPE + "'";
+
+  String POST_PAYROLL_PAYMENT = "post-payroll-payment";
+  String SELECTOR_POST_PAYROLL_PAYMENT = SELECTOR_NAME + " = '" + POST_PAYROLL_PAYMENT + "'";
 }
diff --git a/api/src/main/java/io/mifos/accounting/api/v1/client/LedgerManager.java b/api/src/main/java/io/mifos/accounting/api/v1/client/LedgerManager.java
index 62e10a3..cfa37cd 100644
--- a/api/src/main/java/io/mifos/accounting/api/v1/client/LedgerManager.java
+++ b/api/src/main/java/io/mifos/accounting/api/v1/client/LedgerManager.java
@@ -24,6 +24,9 @@ import io.mifos.accounting.api.v1.domain.ChartOfAccountEntry;
 import io.mifos.accounting.api.v1.domain.JournalEntry;
 import io.mifos.accounting.api.v1.domain.Ledger;
 import io.mifos.accounting.api.v1.domain.LedgerPage;
+import io.mifos.accounting.api.v1.domain.PayrollCollectionHistory;
+import io.mifos.accounting.api.v1.domain.PayrollCollectionSheet;
+import io.mifos.accounting.api.v1.domain.PayrollPaymentPage;
 import io.mifos.accounting.api.v1.domain.TransactionType;
 import io.mifos.accounting.api.v1.domain.TransactionTypePage;
 import io.mifos.accounting.api.v1.domain.TrialBalance;
@@ -359,10 +362,41 @@ public interface LedgerManager {
       value = "/accounts/{identifier}/actions",
       method = RequestMethod.GET,
       produces = MediaType.APPLICATION_JSON_VALUE,
-      consumes = MediaType.ALL_VALUE
+      consumes = MediaType.APPLICATION_JSON_VALUE
   )
   @ThrowsExceptions({
       @ThrowsException(status = HttpStatus.NOT_FOUND, exception = AccountNotFoundException.class)
   })
   List<AccountCommand> fetchActions(@PathVariable(value = "identifier") final String identifier);
+
+  @RequestMapping(
+      value = "/payroll",
+      method = RequestMethod.POST,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = PayrollPaymentValidationException.class)
+  })
+  void postPayrollPayments(@RequestBody final PayrollCollectionSheet payrollCollectionSheet);
+
+  @RequestMapping(
+      value = "/payroll",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  List<PayrollCollectionHistory> getPayrollCollectionHistory();
+
+  @RequestMapping(
+      value = "/payroll/{identifier}/payments",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  PayrollPaymentPage getPayrollPaymentHistory(@PathVariable("identifier") final String identifier,
+                                              @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                                              @RequestParam(value = "size", required = false) final Integer size,
+                                              @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                                              @RequestParam(value = "sortDirection", required = false) final String sortDirection);
 }
diff --git a/api/src/main/java/io/mifos/accounting/api/v1/client/PayrollPaymentValidationException.java b/api/src/main/java/io/mifos/accounting/api/v1/client/PayrollPaymentValidationException.java
new file mode 100644
index 0000000..ad87a26
--- /dev/null
+++ b/api/src/main/java/io/mifos/accounting/api/v1/client/PayrollPaymentValidationException.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.api.v1.client;
+
+public class PayrollPaymentValidationException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollCollectionHistory.java b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollCollectionHistory.java
new file mode 100644
index 0000000..326dc9e
--- /dev/null
+++ b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollCollectionHistory.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.constraints.NotNull;
+
+public class PayrollCollectionHistory {
+
+  @ValidIdentifier
+  private String identifier;
+  @ValidIdentifier(maxLength = 34)
+  private String sourceAccountNumber;
+  @ValidIdentifier
+  private String createdBy;
+  @NotNull
+  private String createdOn;
+
+  public PayrollCollectionHistory() {
+    super();
+  }
+
+  public String getIdentifier() {
+    return this.identifier;
+  }
+
+  public void setIdentifier(final String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getSourceAccountNumber() {
+    return this.sourceAccountNumber;
+  }
+
+  public void setSourceAccountNumber(final String sourceAccountNumber) {
+    this.sourceAccountNumber = sourceAccountNumber;
+  }
+
+  public String getCreatedBy() {
+    return this.createdBy;
+  }
+
+  public void setCreatedBy(final String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public String getCreatedOn() {
+    return this.createdOn;
+  }
+
+  public void setCreatedOn(final String createdOn) {
+    this.createdOn = createdOn;
+  }
+}
diff --git a/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollCollectionSheet.java b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollCollectionSheet.java
new file mode 100644
index 0000000..d635b80
--- /dev/null
+++ b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollCollectionSheet.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+public class PayrollCollectionSheet {
+
+  @ValidIdentifier(maxLength = 34)
+  private String sourceAccountNumber;
+  @NotNull
+  @Valid
+  private List<PayrollPayment> payrollPayments;
+
+  public PayrollCollectionSheet() {
+    super();
+  }
+
+  public String getSourceAccountNumber() {
+    return this.sourceAccountNumber;
+  }
+
+  public void setSourceAccountNumber(final String sourceAccountNumber) {
+    this.sourceAccountNumber = sourceAccountNumber;
+  }
+
+  public List<PayrollPayment> getPayrollPayments() {
+    return this.payrollPayments;
+  }
+
+  public void setPayrollPayments(final List<PayrollPayment> payrollPayments) {
+    this.payrollPayments = payrollPayments;
+  }
+}
diff --git a/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollPayment.java b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollPayment.java
new file mode 100644
index 0000000..a20dc8e
--- /dev/null
+++ b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollPayment.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.constraints.DecimalMax;
+import javax.validation.constraints.DecimalMin;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+public class PayrollPayment {
+
+  @ValidIdentifier
+  private String customerIdentifier;
+  @NotNull
+  private String employer;
+  @NotNull
+  @DecimalMin("0.001")
+  @DecimalMax("9999999999.99999")
+  private BigDecimal salary;
+
+  public PayrollPayment() {
+    super();
+  }
+
+  public String getCustomerIdentifier() {
+    return this.customerIdentifier;
+  }
+
+  public void setCustomerIdentifier(final String customerIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+  }
+
+  public String getEmployer() {
+    return this.employer;
+  }
+
+  public void setEmployer(final String employer) {
+    this.employer = employer;
+  }
+
+  public BigDecimal getSalary() {
+    return this.salary;
+  }
+
+  public void setSalary(final BigDecimal salary) {
+    this.salary = salary;
+  }
+}
diff --git a/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollPaymentPage.java b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollPaymentPage.java
new file mode 100644
index 0000000..7bd4d8f
--- /dev/null
+++ b/api/src/main/java/io/mifos/accounting/api/v1/domain/PayrollPaymentPage.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.api.v1.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PayrollPaymentPage {
+
+  private List<PayrollPayment> payrollPayments;
+  private Integer totalPages;
+  private Long totalElements;
+
+  public PayrollPaymentPage() {
+    super();
+  }
+
+  public List<PayrollPayment> getPayrollPayments() {
+    return this.payrollPayments;
+  }
+
+  public void setPayrollPayments(final List<PayrollPayment> payrollPayments) {
+    this.payrollPayments = payrollPayments;
+  }
+
+  public Integer getTotalPages() {
+    return this.totalPages;
+  }
+
+  public void setTotalPages(final Integer totalPages) {
+    this.totalPages = totalPages;
+  }
+
+  public Long getTotalElements() {
+    return this.totalElements;
+  }
+
+  public void setTotalElements(final Long totalElements) {
+    this.totalElements = totalElements;
+  }
+
+  public void add(final PayrollPayment payrollPayment) {
+    if (this.payrollPayments == null) {
+      this.payrollPayments = new ArrayList<>();
+    }
+    this.payrollPayments.add(payrollPayment);
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/accounting/TestPayrollPayment.java b/component-test/src/main/java/io/mifos/accounting/TestPayrollPayment.java
new file mode 100644
index 0000000..dc0574b
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/accounting/TestPayrollPayment.java
@@ -0,0 +1,188 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import io.mifos.accounting.api.v1.EventConstants;
+import io.mifos.accounting.api.v1.domain.Account;
+import io.mifos.accounting.api.v1.domain.AccountType;
+import io.mifos.accounting.api.v1.domain.Ledger;
+import io.mifos.accounting.api.v1.domain.PayrollCollectionHistory;
+import io.mifos.accounting.api.v1.domain.PayrollCollectionSheet;
+import io.mifos.accounting.api.v1.domain.PayrollPayment;
+import io.mifos.accounting.api.v1.domain.PayrollPaymentPage;
+import io.mifos.accounting.service.internal.service.helper.CustomerAdapter;
+import io.mifos.customer.api.v1.domain.PayrollAllocation;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+public class TestPayrollPayment extends AbstractAccountingTest {
+
+  @MockBean
+  private CustomerAdapter customerAdapterSpy;
+
+  public TestPayrollPayment() {
+    super();
+  }
+
+  @Test
+  public void shouldProcessPayrollPayment() throws Exception {
+    super.eventRecorder.clear();
+
+    final String postfix = RandomStringUtils.randomAlphanumeric(8);
+    this.setupResources(postfix);
+
+    final String customerIdentifier = RandomStringUtils.randomAlphanumeric(32);
+
+    final PayrollCollectionSheet payrollCollectionSheet = new PayrollCollectionSheet();
+    payrollCollectionSheet.setSourceAccountNumber("7250." + postfix);
+
+    final PayrollPayment payrollPayment = new PayrollPayment();
+    payrollPayment.setCustomerIdentifier(customerIdentifier);
+    payrollPayment.setEmployer("ACME, Inc.");
+    payrollPayment.setSalary(BigDecimal.valueOf(1000.00D));
+    payrollCollectionSheet.setPayrollPayments(Lists.newArrayList(payrollPayment));
+
+    Mockito
+        .doAnswer(invocation -> this.createPayrollDistribution(postfix))
+        .when(this.customerAdapterSpy).findPayrollDistribution(customerIdentifier);
+
+    super.testSubject.postPayrollPayments(payrollCollectionSheet);
+    Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_PAYROLL_PAYMENT, payrollCollectionSheet.getSourceAccountNumber()));
+    Assert.assertTrue(super.eventRecorder.waitForMatch(EventConstants.RELEASE_JOURNAL_ENTRY, Objects::nonNull));
+
+    final Account payrollDeductionReceivableAccount = super.testSubject.findAccount(payrollCollectionSheet.getSourceAccountNumber());
+    Assert.assertTrue(BigDecimal.valueOf(3000.00D).compareTo(BigDecimal.valueOf(payrollDeductionReceivableAccount.getBalance())) == 0);
+
+    final Account memberChequingAccount = super.testSubject.findAccount("9140." + postfix);
+    Assert.assertTrue(BigDecimal.valueOf(826.55D).compareTo(BigDecimal.valueOf(memberChequingAccount.getBalance())) == 0);
+
+    final Account memberLoanAccount = super.testSubject.findAccount("7010." + postfix);
+    Assert.assertTrue(BigDecimal.valueOf(1876.55D).compareTo(BigDecimal.valueOf(memberLoanAccount.getBalance())) == 0);
+
+    final Account memberSavingsAccount = super.testSubject.findAccount("9110." + postfix);
+    Assert.assertTrue(BigDecimal.valueOf(275.00D).compareTo(BigDecimal.valueOf(memberSavingsAccount.getBalance())) == 0);
+
+    final List<PayrollCollectionHistory> payrollCollectionHistories = super.testSubject.getPayrollCollectionHistory();
+    Assert.assertFalse(payrollCollectionHistories.isEmpty());
+
+    final PayrollCollectionHistory payrollCollectionHistory = payrollCollectionHistories.get(0);
+    Assert.assertEquals(payrollCollectionSheet.getSourceAccountNumber(), payrollCollectionHistory.getSourceAccountNumber());
+    Assert.assertEquals(AbstractAccountingTest.TEST_USER, payrollCollectionHistory.getCreatedBy());
+
+    final PayrollPaymentPage payrollPaymentPage =
+        super.testSubject.getPayrollPaymentHistory(payrollCollectionHistory.getIdentifier(), 0, 20, null, null);
+    Assert.assertEquals(Long.valueOf(1L), payrollPaymentPage.getTotalElements());
+    final PayrollPayment fetchedPayrollPayment = payrollPaymentPage.getPayrollPayments().get(0);
+    Assert.assertEquals(payrollPayment.getCustomerIdentifier(), fetchedPayrollPayment.getCustomerIdentifier());
+    Assert.assertEquals(payrollPayment.getEmployer(), fetchedPayrollPayment.getEmployer());
+    Assert.assertTrue(payrollPayment.getSalary().compareTo(fetchedPayrollPayment.getSalary()) == 0);
+
+    super.eventRecorder.clear();
+  }
+
+  private Optional<PayrollDistribution> createPayrollDistribution(final String postfix) {
+    final PayrollDistribution payrollDistribution = new PayrollDistribution();
+    payrollDistribution.setMainAccountNumber("9140." + postfix);
+
+    final PayrollAllocation loanAllocation = new PayrollAllocation();
+    loanAllocation.setAccountNumber("7010." + postfix);
+    loanAllocation.setAmount(BigDecimal.valueOf(123.45D));
+
+    final PayrollAllocation savingsAllocation = new PayrollAllocation();
+    savingsAllocation.setAccountNumber("9110." + postfix);
+    savingsAllocation.setAmount(BigDecimal.valueOf(5.00D));
+    savingsAllocation.setProportional(Boolean.TRUE);
+
+    payrollDistribution.setPayrollAllocations(Sets.newHashSet(loanAllocation, savingsAllocation));
+
+    return Optional.of(payrollDistribution);
+  }
+
+  private void setupResources(final String postfix) throws Exception {
+    super.eventRecorder.clear();
+
+    final Ledger assetLedger = new Ledger();
+    assetLedger.setIdentifier("7000." + postfix);
+    assetLedger.setType(AccountType.ASSET.name());
+    assetLedger.setName("Asset Subledger");
+    assetLedger.setShowAccountsInChart(Boolean.TRUE);
+
+    super.testSubject.createLedger(assetLedger);
+    Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, assetLedger.getIdentifier()));
+
+    final Ledger equityLedger = new Ledger();
+    equityLedger.setIdentifier("9000." + postfix);
+    equityLedger.setType(AccountType.EQUITY.name());
+    equityLedger.setName("Equity Subledger");
+    equityLedger.setShowAccountsInChart(Boolean.TRUE);
+
+    super.testSubject.createLedger(equityLedger);
+    Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, equityLedger.getIdentifier()));
+
+    final Account payrollDeductionReceivableAccount = new Account();
+    payrollDeductionReceivableAccount.setIdentifier("7250." + postfix);
+    payrollDeductionReceivableAccount.setType(AccountType.ASSET.name());
+    payrollDeductionReceivableAccount.setLedger(assetLedger.getIdentifier());
+    payrollDeductionReceivableAccount.setName("Payroll Deduction Receivable");
+    payrollDeductionReceivableAccount.setBalance(2000.00D);
+
+    super.testSubject.createAccount(payrollDeductionReceivableAccount);
+    Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, payrollDeductionReceivableAccount.getIdentifier()));
+
+    final Account memberLoanAccount = new Account();
+    memberLoanAccount.setIdentifier("7010." + postfix);
+    memberLoanAccount.setType(AccountType.ASSET.name());
+    memberLoanAccount.setLedger(assetLedger.getIdentifier());
+    memberLoanAccount.setName("Member Loan");
+    memberLoanAccount.setBalance(2000.00D);
+
+    super.testSubject.createAccount(memberLoanAccount);
+    Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, memberLoanAccount.getIdentifier()));
+
+    final Account memberChequingAccount = new Account();
+    memberChequingAccount.setIdentifier("9140." + postfix);
+    memberChequingAccount.setType(AccountType.EQUITY.name());
+    memberChequingAccount.setLedger(equityLedger.getIdentifier());
+    memberChequingAccount.setName("Member Chequing");
+    memberChequingAccount.setBalance(0.00D);
+
+    super.testSubject.createAccount(memberChequingAccount);
+    Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, memberChequingAccount.getIdentifier()));
+
+    final Account memberSavingsAccount = new Account();
+    memberSavingsAccount.setIdentifier("9110." + postfix);
+    memberSavingsAccount.setType(AccountType.EQUITY.name());
+    memberSavingsAccount.setLedger(equityLedger.getIdentifier());
+    memberSavingsAccount.setName("Member Savings");
+    memberSavingsAccount.setBalance(225.00D);
+
+    super.testSubject.createAccount(memberSavingsAccount);
+    Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, memberSavingsAccount.getIdentifier()));
+
+    super.eventRecorder.clear();
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/accounting/listener/PayrollPaymentListener.java b/component-test/src/main/java/io/mifos/accounting/listener/PayrollPaymentListener.java
new file mode 100644
index 0000000..70770ed
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/accounting/listener/PayrollPaymentListener.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.listener;
+
+import io.mifos.accounting.AbstractAccountingTest;
+import io.mifos.accounting.api.v1.EventConstants;
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PayrollPaymentListener {
+
+  private final Logger logger;
+  private final EventRecorder eventRecorder;
+
+  @SuppressWarnings("SpringJavaAutowiringInspection")
+  @Autowired
+  public PayrollPaymentListener(@Qualifier(AbstractAccountingTest.TEST_LOGGER) final Logger logger,
+                                final EventRecorder eventRecorder) {
+    super();
+    this.logger = logger;
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_POST_PAYROLL_PAYMENT,
+      subscription = EventConstants.DESTINATION
+  )
+  public void onPayrollPayment(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                           final String payload) {
+    this.logger.debug("Payroll payment processed.");
+    this.eventRecorder.event(tenant, EventConstants.POST_PAYROLL_PAYMENT, payload, String.class);
+  }
+}
diff --git a/service/build.gradle b/service/build.gradle
index 0fe30be..cd6fc0c 100644
--- a/service/build.gradle
+++ b/service/build.gradle
@@ -31,6 +31,7 @@ dependencies {
             [group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'],
             [group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'],
             [group: 'io.mifos.accounting', name: 'api', version: project.version],
+            [group: 'io.mifos.customer', name: 'api', version: versions.frameworkcustomer],
             [group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis],
             [group: 'com.google.code.gson', name: 'gson'],
             [group: 'io.mifos.core', name: 'lang', version: versions.frameworkanubis],
diff --git a/service/src/main/java/io/mifos/accounting/service/AccountingServiceConfiguration.java b/service/src/main/java/io/mifos/accounting/service/AccountingServiceConfiguration.java
index be9f996..636c67c 100644
--- a/service/src/main/java/io/mifos/accounting/service/AccountingServiceConfiguration.java
+++ b/service/src/main/java/io/mifos/accounting/service/AccountingServiceConfiguration.java
@@ -22,10 +22,12 @@ import io.mifos.core.command.config.EnableCommandProcessing;
 import io.mifos.core.lang.config.EnableServiceException;
 import io.mifos.core.lang.config.EnableTenantContext;
 import io.mifos.core.mariadb.config.EnableMariaDB;
+import io.mifos.customer.api.v1.client.CustomerManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
@@ -46,6 +48,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
     "io.mifos.accounting.service.rest",
     "io.mifos.accounting.service.internal"
 })
+@EnableFeignClients(
+    clients = {
+        CustomerManager.class
+    }
+)
 public class AccountingServiceConfiguration extends WebMvcConfigurerAdapter {
 
   public AccountingServiceConfiguration() {
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/command/ProcessPayrollPaymentCommand.java b/service/src/main/java/io/mifos/accounting/service/internal/command/ProcessPayrollPaymentCommand.java
new file mode 100644
index 0000000..f4524cc
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/command/ProcessPayrollPaymentCommand.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.command;
+
+import io.mifos.accounting.api.v1.domain.PayrollCollectionSheet;
+
+public class ProcessPayrollPaymentCommand {
+  private final PayrollCollectionSheet payrollCollectionSheet;
+
+  public ProcessPayrollPaymentCommand(final PayrollCollectionSheet payrollCollectionSheet) {
+    super();
+    this.payrollCollectionSheet = payrollCollectionSheet;
+  }
+
+  public PayrollCollectionSheet payrollCollectionSheet() {
+    return this.payrollCollectionSheet;
+  }
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/command/handler/PayrollPaymentAggregate.java b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/PayrollPaymentAggregate.java
new file mode 100644
index 0000000..5a471a4
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/PayrollPaymentAggregate.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.command.handler;
+
+import com.google.common.collect.Sets;
+import io.mifos.accounting.api.v1.EventConstants;
+import io.mifos.accounting.api.v1.domain.Creditor;
+import io.mifos.accounting.api.v1.domain.Debtor;
+import io.mifos.accounting.api.v1.domain.JournalEntry;
+import io.mifos.accounting.api.v1.domain.PayrollCollectionSheet;
+import io.mifos.accounting.service.ServiceConstants;
+import io.mifos.accounting.service.internal.command.CreateJournalEntryCommand;
+import io.mifos.accounting.service.internal.command.ProcessPayrollPaymentCommand;
+import io.mifos.accounting.service.internal.repository.PayrollCollectionEntity;
+import io.mifos.accounting.service.internal.repository.PayrollCollectionRepository;
+import io.mifos.accounting.service.internal.repository.PayrollPaymentEntity;
+import io.mifos.accounting.service.internal.repository.PayrollPaymentRepository;
+import io.mifos.accounting.service.internal.service.helper.CustomerAdapter;
+import io.mifos.core.api.util.UserContextHolder;
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.DateConverter;
+import org.apache.commons.lang.RandomStringUtils;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.time.Clock;
+import java.time.LocalDateTime;
+import java.util.HashSet;
+import java.util.UUID;
+
+@Aggregate
+public class PayrollPaymentAggregate {
+
+  private final Logger logger;
+  private final PayrollCollectionRepository payrollCollectionRepository;
+  private final PayrollPaymentRepository payrollPaymentRepository;
+  private final CustomerAdapter customerAdapter;
+  private final CommandGateway commandGateway;
+
+  @Autowired
+  public PayrollPaymentAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+                                 final PayrollCollectionRepository payrollCollectionRepository,
+                                 final PayrollPaymentRepository payrollPaymentRepository,
+                                 final CustomerAdapter customerAdapter,
+                                 final CommandGateway commandGateway) {
+    super();
+    this.logger = logger;
+    this.payrollCollectionRepository = payrollCollectionRepository;
+    this.payrollPaymentRepository = payrollPaymentRepository;
+    this.customerAdapter = customerAdapter;
+    this.commandGateway = commandGateway;
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.POST_PAYROLL_PAYMENT)
+  public String process(final ProcessPayrollPaymentCommand processPayrollPaymentCommand) {
+    final PayrollCollectionSheet payrollCollectionSheet = processPayrollPaymentCommand.payrollCollectionSheet();
+
+    final PayrollCollectionEntity payrollCollectionEntity = new PayrollCollectionEntity();
+    payrollCollectionEntity.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+    payrollCollectionEntity.setSourceAccountNumber(payrollCollectionSheet.getSourceAccountNumber());
+    payrollCollectionEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+    payrollCollectionEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+
+    this.payrollCollectionRepository.save(payrollCollectionEntity);
+
+    final MathContext mathContext = new MathContext(2, RoundingMode.HALF_EVEN);
+
+    payrollCollectionSheet.getPayrollPayments().forEach(payrollPayment ->
+        this.customerAdapter.findPayrollDistribution(payrollPayment.getCustomerIdentifier()).ifPresent(payrollDistribution -> {
+          final PayrollPaymentEntity payrollPaymentEntity = new PayrollPaymentEntity();
+          payrollPaymentEntity.setCollectionIdentifier(payrollCollectionEntity.getIdentifier());
+          payrollPaymentEntity.setCustomerIdentifier(payrollPayment.getCustomerIdentifier());
+          payrollPaymentEntity.setEmployer(payrollPayment.getEmployer());
+          payrollPaymentEntity.setSalary(payrollPayment.getSalary());
+          this.payrollPaymentRepository.save(payrollPaymentEntity);
+
+          final JournalEntry journalEntry = new JournalEntry();
+          journalEntry.setTransactionIdentifier("acct-sala-" + UUID.randomUUID().toString());
+          journalEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
+          journalEntry.setTransactionType("SALA");
+          journalEntry.setClerk(payrollCollectionEntity.getCreatedBy());
+          journalEntry.setNote("Payroll Distribution");
+
+          final Debtor debtor = new Debtor();
+          debtor.setAccountNumber(payrollCollectionSheet.getSourceAccountNumber());
+          debtor.setAmount(payrollPayment.getSalary().toString());
+          journalEntry.setDebtors(Sets.newHashSet(debtor));
+
+          final HashSet<Creditor> creditors = new HashSet<>();
+          journalEntry.setCreditors(creditors);
+
+          payrollDistribution.getPayrollAllocations().forEach(payrollAllocation -> {
+            final Creditor allocationCreditor = new Creditor();
+            allocationCreditor.setAccountNumber(payrollAllocation.getAccountNumber());
+            if (!payrollAllocation.getProportional()) {
+              allocationCreditor.setAmount(payrollAllocation.getAmount().toString());
+            } else {
+              final BigDecimal value = payrollPayment.getSalary().multiply(
+                  payrollAllocation.getAmount().divide(BigDecimal.valueOf(100.00D), mathContext)
+              ).round(mathContext);
+              allocationCreditor.setAmount(value.toString());
+            }
+            creditors.add(allocationCreditor);
+          });
+
+          final BigDecimal currentCreditorSum =
+              BigDecimal.valueOf(creditors.stream().mapToDouble(value -> Double.valueOf(value.getAmount())).sum());
+
+          final Creditor mainCreditor = new Creditor();
+          mainCreditor.setAccountNumber(payrollDistribution.getMainAccountNumber());
+          mainCreditor.setAmount(payrollPayment.getSalary().subtract(currentCreditorSum).toString());
+          creditors.add(mainCreditor);
+
+          this.commandGateway.process(new CreateJournalEntryCommand(journalEntry));
+        })
+    );
+    return payrollCollectionSheet.getSourceAccountNumber();
+  }
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/mapper/PayrollPaymentMapper.java b/service/src/main/java/io/mifos/accounting/service/internal/mapper/PayrollPaymentMapper.java
new file mode 100644
index 0000000..0d84597
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/mapper/PayrollPaymentMapper.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.mapper;
+
+import io.mifos.accounting.api.v1.domain.PayrollPayment;
+import io.mifos.accounting.service.internal.repository.PayrollPaymentEntity;
+
+public class PayrollPaymentMapper {
+
+  private PayrollPaymentMapper() {
+    super();
+  }
+
+  public static PayrollPayment map(final PayrollPaymentEntity payrollPaymentEntity) {
+    final PayrollPayment payrollPayment = new PayrollPayment();
+    payrollPayment.setCustomerIdentifier(payrollPaymentEntity.getCustomerIdentifier());
+    payrollPayment.setEmployer(payrollPaymentEntity.getEmployer());
+    payrollPayment.setSalary(payrollPaymentEntity.getSalary());
+    return payrollPayment;
+  }
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollCollectionEntity.java b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollCollectionEntity.java
new file mode 100644
index 0000000..de835eb
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollCollectionEntity.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "thoth_payroll_collections")
+public class PayrollCollectionEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id", nullable = false)
+  private Long id;
+  @Column(name = "identifier", nullable = false, length = 32)
+  private String identifier;
+  @Column(name = "source_account_number", nullable = false, length = 34)
+  private String sourceAccountNumber;
+  @Column(name = "created_by", nullable = false, length = 32)
+  private String createdBy;
+  @Column(name = "created_on")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime createdOn;
+
+  public PayrollCollectionEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return this.id;
+  }
+
+  public void setId(final Long id) {
+    this.id = id;
+  }
+
+  public String getIdentifier() {
+    return this.identifier;
+  }
+
+  public void setIdentifier(final String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getSourceAccountNumber() {
+    return this.sourceAccountNumber;
+  }
+
+  public void setSourceAccountNumber(final String sourceAccountNumber) {
+    this.sourceAccountNumber = sourceAccountNumber;
+  }
+
+  public String getCreatedBy() {
+    return this.createdBy;
+  }
+
+  public void setCreatedBy(final String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public LocalDateTime getCreatedOn() {
+    return this.createdOn;
+  }
+
+  public void setCreatedOn(final LocalDateTime createdOn) {
+    this.createdOn = createdOn;
+  }
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollCollectionRepository.java b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollCollectionRepository.java
new file mode 100644
index 0000000..138bbd4
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollCollectionRepository.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PayrollCollectionRepository extends JpaRepository<PayrollCollectionEntity, Long> {
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollPaymentEntity.java b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollPaymentEntity.java
new file mode 100644
index 0000000..d5e3996
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollPaymentEntity.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.math.BigDecimal;
+
+@Entity
+@Table(name = "thoth_payroll_payments")
+public class PayrollPaymentEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id", nullable = false)
+  private Long id;
+  @Column(name = "collection_identifier", nullable = false, length = 32)
+  private String collectionIdentifier;
+  @Column(name = "customer_identifier", nullable = false, length = 32)
+  private String customerIdentifier;
+  @Column(name = "employer", nullable = false, length = 256)
+  private String employer;
+  @Column(name = "salary", nullable = false, precision = 15, scale = 5)
+  private BigDecimal salary;
+
+  public PayrollPaymentEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return this.id;
+  }
+
+  public void setId(final Long id) {
+    this.id = id;
+  }
+
+  public String getCollectionIdentifier() {
+    return this.collectionIdentifier;
+  }
+
+  public void setCollectionIdentifier(final String collectionIdentifier) {
+    this.collectionIdentifier = collectionIdentifier;
+  }
+
+  public String getCustomerIdentifier() {
+    return this.customerIdentifier;
+  }
+
+  public void setCustomerIdentifier(final String customerIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+  }
+
+  public String getEmployer() {
+    return this.employer;
+  }
+
+  public void setEmployer(final String employer) {
+    this.employer = employer;
+  }
+
+  public BigDecimal getSalary() {
+    return this.salary;
+  }
+
+  public void setSalary(final BigDecimal salary) {
+    this.salary = salary;
+  }
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollPaymentRepository.java b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollPaymentRepository.java
new file mode 100644
index 0000000..dc8746a
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/repository/PayrollPaymentRepository.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PayrollPaymentRepository extends JpaRepository<PayrollPaymentEntity, Long> {
+  Page<PayrollPaymentEntity> findByCollectionIdentifier(final String identifier, final Pageable pageable);
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/service/JournalEntryService.java b/service/src/main/java/io/mifos/accounting/service/internal/service/JournalEntryService.java
index 3f56da2..4bd5433 100644
--- a/service/src/main/java/io/mifos/accounting/service/internal/service/JournalEntryService.java
+++ b/service/src/main/java/io/mifos/accounting/service/internal/service/JournalEntryService.java
@@ -23,7 +23,6 @@ import io.mifos.accounting.service.internal.repository.JournalEntryEntity;
 import io.mifos.accounting.service.internal.repository.JournalEntryRepository;
 import io.mifos.accounting.service.internal.repository.TransactionTypeEntity;
 import io.mifos.accounting.service.internal.repository.TransactionTypeRepository;
-import io.mifos.core.api.annotation.ThrowsException;
 import io.mifos.core.lang.DateRange;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -32,6 +31,7 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
@@ -78,6 +78,7 @@ public class JournalEntryService {
                           )
                   ) == 0
               )
+              .sorted(Comparator.comparing(JournalEntryEntity::getTransactionDate))
               .collect(Collectors.toList());
 
       final List<TransactionTypeEntity> transactionTypes = this.transactionTypeRepository.findAll();
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/service/PayrollPaymentService.java b/service/src/main/java/io/mifos/accounting/service/internal/service/PayrollPaymentService.java
new file mode 100644
index 0000000..491e3e8
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/service/PayrollPaymentService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.service;
+
+import io.mifos.accounting.api.v1.domain.PayrollCollectionHistory;
+import io.mifos.accounting.api.v1.domain.PayrollPaymentPage;
+import io.mifos.accounting.service.ServiceConstants;
+import io.mifos.accounting.service.internal.mapper.PayrollPaymentMapper;
+import io.mifos.accounting.service.internal.repository.PayrollCollectionRepository;
+import io.mifos.accounting.service.internal.repository.PayrollPaymentEntity;
+import io.mifos.accounting.service.internal.repository.PayrollPaymentRepository;
+import io.mifos.core.lang.DateConverter;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class PayrollPaymentService {
+
+  private final Logger logger;
+  private final PayrollCollectionRepository payrollCollectionRepository;
+  private final PayrollPaymentRepository payrollPaymentRepository;
+
+  @Autowired
+  public PayrollPaymentService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+                               final PayrollPaymentRepository payrollPaymentRepository,
+                               final PayrollCollectionRepository payrollCollectionRepository) {
+    this.logger = logger;
+    this.payrollPaymentRepository = payrollPaymentRepository;
+    this.payrollCollectionRepository = payrollCollectionRepository;
+  }
+
+  public PayrollPaymentPage fetchPayrollPayments(final String identifier, final Pageable pageable) {
+
+    final Page<PayrollPaymentEntity> payrollPaymentEntities =
+        this.payrollPaymentRepository.findByCollectionIdentifier(identifier, pageable);
+
+    final PayrollPaymentPage payrollPaymentPage = new PayrollPaymentPage();
+    payrollPaymentPage.setTotalPages(payrollPaymentEntities.getTotalPages());
+    payrollPaymentPage.setTotalElements(payrollPaymentEntities.getTotalElements());
+    if (payrollPaymentEntities.hasContent()) {
+      payrollPaymentEntities.forEach(payrollPaymentEntity -> payrollPaymentPage.add(PayrollPaymentMapper.map(payrollPaymentEntity)));
+    }
+
+    return payrollPaymentPage;
+  }
+
+  public List<PayrollCollectionHistory> fetchPayrollCollectionHistory() {
+    final List<PayrollCollectionHistory> payrollCollectionHistories = new ArrayList<>();
+
+    this.payrollCollectionRepository.findAll().forEach(payrollCollectionEntity -> {
+      final PayrollCollectionHistory payrollCollectionHistory = new PayrollCollectionHistory();
+      payrollCollectionHistory.setIdentifier(payrollCollectionEntity.getIdentifier());
+      payrollCollectionHistory.setSourceAccountNumber(payrollCollectionEntity.getSourceAccountNumber());
+      payrollCollectionHistory.setCreatedBy(payrollCollectionEntity.getCreatedBy());
+      payrollCollectionHistory.setCreatedOn(DateConverter.toIsoString(payrollCollectionEntity.getCreatedOn()));
+      payrollCollectionHistories.add(payrollCollectionHistory);
+    });
+
+    return payrollCollectionHistories;
+  }
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/internal/service/helper/CustomerAdapter.java b/service/src/main/java/io/mifos/accounting/service/internal/service/helper/CustomerAdapter.java
new file mode 100644
index 0000000..fd21287
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/internal/service/helper/CustomerAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.internal.service.helper;
+
+import io.mifos.customer.api.v1.client.CustomerManager;
+import io.mifos.customer.api.v1.client.CustomerNotFoundException;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class CustomerAdapter {
+
+  private final CustomerManager customerManager;
+
+  @Autowired
+  public CustomerAdapter(final CustomerManager customerManager) {
+    super();
+    this.customerManager = customerManager;
+  }
+
+  public Optional<PayrollDistribution> findPayrollDistribution(final String customerIdentifier) {
+    try {
+      return Optional.ofNullable(this.customerManager.getPayrollDistribution(customerIdentifier));
+    } catch (final CustomerNotFoundException cnfex) {
+      return Optional.empty();
+    }
+  }
+}
diff --git a/service/src/main/java/io/mifos/accounting/service/rest/AccountRestController.java b/service/src/main/java/io/mifos/accounting/service/rest/AccountRestController.java
index 00a7f74..3b02033 100644
--- a/service/src/main/java/io/mifos/accounting/service/rest/AccountRestController.java
+++ b/service/src/main/java/io/mifos/accounting/service/rest/AccountRestController.java
@@ -234,6 +234,9 @@ public class AccountRestController {
           if (state.equals(Account.State.OPEN) || state.equals(Account.State.LOCKED)) {
             this.commandGateway.process(new CloseAccountCommand(identifier, accountCommand.getComment()));
           }
+          if (account.getBalance() != 0.00D) {
+            throw ServiceException.conflict("Account {0} has remaining balance.", identifier);
+          }
           break;
         case LOCK:
           if (state.equals(Account.State.OPEN)) {
@@ -250,6 +253,8 @@ public class AccountRestController {
             this.commandGateway.process(new ReopenAccountCommand(identifier, accountCommand.getComment()));
           }
           break;
+        default:
+          throw ServiceException.badRequest("Invalid state change.");
       }
       return ResponseEntity.accepted().build();
     } else {
diff --git a/service/src/main/java/io/mifos/accounting/service/rest/PayrollPaymentRestController.java b/service/src/main/java/io/mifos/accounting/service/rest/PayrollPaymentRestController.java
new file mode 100644
index 0000000..d4d96fc
--- /dev/null
+++ b/service/src/main/java/io/mifos/accounting/service/rest/PayrollPaymentRestController.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+package io.mifos.accounting.service.rest;
+
+import io.mifos.accounting.api.v1.PermittableGroupIds;
+import io.mifos.accounting.api.v1.domain.PayrollCollectionHistory;
+import io.mifos.accounting.api.v1.domain.PayrollCollectionSheet;
+import io.mifos.accounting.api.v1.domain.PayrollPaymentPage;
+import io.mifos.accounting.service.ServiceConstants;
+import io.mifos.accounting.service.internal.command.ProcessPayrollPaymentCommand;
+import io.mifos.accounting.service.internal.service.PayrollPaymentService;
+import io.mifos.accounting.service.rest.paging.PageableBuilder;
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RestController
+@RequestMapping("/payroll")
+public class PayrollPaymentRestController {
+
+  private final Logger logger;
+  private final CommandGateway commandGateway;
+  private final PayrollPaymentService payrollPaymentService;
+
+  @Autowired
+  public PayrollPaymentRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+                                      final CommandGateway commandGateway,
+                                      final PayrollPaymentService payrollPaymentService) {
+    super();
+    this.logger = logger;
+    this.commandGateway = commandGateway;
+    this.payrollPaymentService = payrollPaymentService;
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.THOTH_ACCOUNT)
+  @RequestMapping(
+      method = RequestMethod.POST,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ResponseBody
+  public ResponseEntity<Void> post(@RequestBody @Valid final PayrollCollectionSheet payrollCollectionSheet) {
+    this.commandGateway.process(new ProcessPayrollPaymentCommand(payrollCollectionSheet));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.THOTH_ACCOUNT)
+  @RequestMapping(
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  @ResponseBody
+  ResponseEntity<List<PayrollCollectionHistory>> getPayrollCollectionHistory() {
+    return ResponseEntity.ok(this.payrollPaymentService.fetchPayrollCollectionHistory());
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.THOTH_ACCOUNT)
+  @RequestMapping(
+      value = "/{identifier}/payments",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  @ResponseBody
+  ResponseEntity<PayrollPaymentPage> getPayrollPaymentHistory(@PathVariable("identifier") final String identifier,
+                                                              @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                                                              @RequestParam(value = "size", required = false) final Integer size,
+                                                              @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                                                              @RequestParam(value = "sortDirection", required = false) final String sortDirection) {
+    final Pageable pageable = PageableBuilder.create(
+        pageIndex, size, sortColumn != null ? sortColumn : "customerIdentifier", sortDirection
+    );
+
+    return ResponseEntity.ok(this.payrollPaymentService.fetchPayrollPayments(identifier, pageable));
+  }
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V6__add_payroll_distribution.sql b/service/src/main/resources/db/migrations/mariadb/V6__add_payroll_distribution.sql
new file mode 100644
index 0000000..6053abb
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V6__add_payroll_distribution.sql
@@ -0,0 +1,36 @@
+--
+-- 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.
+--
+
+CREATE TABLE thoth_payroll_collections (
+  id                    BIGINT       NOT NULL AUTO_INCREMENT,
+  identifier            VARCHAR(32)  NOT NULL,
+  source_account_number VARCHAR(34)  NOT NULL,
+  created_by            VARCHAR(32)  NOT NULL,
+  created_on            TIMESTAMP(3) NOT NULL,
+  CONSTRAINT thoth_payroll_collections_pk PRIMARY KEY (id),
+  CONSTRAINT thoth_pay_col_identifier_uq UNIQUE (identifier)
+);
+
+CREATE TABLE thoth_payroll_payments (
+  id                    BIGINT        NOT NULL AUTO_INCREMENT,
+  collection_identifier VARCHAR(32)   NOT NULL,
+  customer_identifier   VARCHAR(34)   NOT NULL,
+  employer              VARCHAR(256)  NOT NULL,
+  salary                NUMERIC(15,5) NOT NULL,
+  CONSTRAINT thoth_payroll_payments_pk PRIMARY KEY (id)
+);
+
+INSERT INTO thoth_tx_types (identifier, a_name) VALUES ('SALA', 'Payroll/Salary Payment');
\ No newline at end of file
diff --git a/shared.gradle b/shared.gradle
index 762e60d..3cb4584 100644
--- a/shared.gradle
+++ b/shared.gradle
@@ -10,6 +10,7 @@ ext.versions = [
         frameworkcommand   : '0.1.0-BUILD-SNAPSHOT',
         frameworktest      : '0.1.0-BUILD-SNAPSHOT',
         frameworkasync     : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcustomer  : '0.1.0-BUILD-SNAPSHOT',
         apachecsvreader    : '1.4',
         validator   : '5.3.0.Final'
 ]

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

Mime
View raw message