Skip to content

Commit

Permalink
[MODFISTO-481] - Separate credits from expenditures in mod-finance-st…
Browse files Browse the repository at this point in the history
…orage (#414)

* [MODFISTO-481] - Separate credits from expenditures in mod-finance-storage

* Improved tests

* Fixed issues

* Added tests and improvement

* Added tests and improvement

* Added tests and improvement

* added to do

* Updated credit and expended percentage calculation

* Updated credit and expended percentage calculation

* fixed unit test

* fixed logic of credited calculation

* fixed unit test

* removed if statament for credit

* fixed unit test

* simplified code
  • Loading branch information
azizbekxm authored Jun 19, 2024
1 parent 147ae34 commit 808aa04
Show file tree
Hide file tree
Showing 22 changed files with 569 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import javax.money.CurrencyUnit;
import javax.money.Monetary;
import javax.money.MonetaryAmount;
import java.math.BigDecimal;
import java.util.Map;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -46,7 +45,8 @@ public Future<LedgerFiscalYearRolloverBudget> getBudgetWithUpdatedExpenseClassTo

private List<BudgetExpenseClassTotal> buildBudgetExpenseClassesTotals(List<ExpenseClass> expenseClasses, List<Transaction> transactions, LedgerFiscalYearRolloverBudget budget) {

double totalExpended = getBudgetTotalExpended(budget);
double totalExpended = budget.getExpenditures();
double totalCredited = budget.getCredits();

Map<String, List<Transaction>> groupedByExpenseClassId = transactions.stream()
.filter(transaction -> Objects.nonNull(transaction.getExpenseClassId()))
Expand All @@ -55,25 +55,24 @@ private List<BudgetExpenseClassTotal> buildBudgetExpenseClassesTotals(List<Expen
Map<ExpenseClass, List<Transaction>> groupedByExpenseClass = expenseClasses.stream()
.collect(toMap(Function.identity(), expenseClass -> groupedByExpenseClassId.getOrDefault(expenseClass.getId(), Collections.emptyList())));

return buildBudgetExpenseClassesTotals(groupedByExpenseClass, totalExpended);
return buildBudgetExpenseClassesTotals(groupedByExpenseClass, totalExpended, totalCredited);
}

private double getBudgetTotalExpended(LedgerFiscalYearRolloverBudget budget) {
BigDecimal totalExpended = BigDecimal.valueOf(budget.getExpenditures()).add(BigDecimal.valueOf(budget.getOverExpended()));
return totalExpended.doubleValue();
}

private List<BudgetExpenseClassTotal> buildBudgetExpenseClassesTotals(Map<ExpenseClass, List<Transaction>> groupedByExpenseClass, double totalExpended) {
private List<BudgetExpenseClassTotal> buildBudgetExpenseClassesTotals(Map<ExpenseClass, List<Transaction>> groupedByExpenseClass,
double totalExpended, double totalCredited) {
return groupedByExpenseClass.entrySet().stream()
.map(entry -> buildBudgetExpenseClassTotals(entry.getKey(), entry.getValue(), totalExpended))
.map(entry -> buildBudgetExpenseClassTotals(entry.getKey(), entry.getValue(), totalExpended, totalCredited))
.collect(Collectors.toList());
}

private BudgetExpenseClassTotal buildBudgetExpenseClassTotals(ExpenseClass expenseClass, List<Transaction> transactions, double totalExpended) {
private BudgetExpenseClassTotal buildBudgetExpenseClassTotals(ExpenseClass expenseClass, List<Transaction> transactions,
double totalExpended, double totalCredited) {
double encumbered = 0d;
double awaitingPayment = 0d;
double expended = 0d;
Double expendedPercentage = 0d;
double credited = 0d;
Double creditedPercentage = 0d;

if (!transactions.isEmpty()) {
CurrencyUnit currency = Monetary.getCurrency(transactions.get(0).getCurrency());
Expand All @@ -86,12 +85,14 @@ private BudgetExpenseClassTotal buildBudgetExpenseClassTotals(ExpenseClass expen

MonetaryAmount tmpExpended = MoneyUtils.calculateTotalAmount(
transactionGroupedByType.getOrDefault(Transaction.TransactionType.PAYMENT, Collections.emptyList()), currency);
tmpExpended = tmpExpended.subtract(MoneyUtils.calculateTotalAmount(
transactionGroupedByType.getOrDefault(Transaction.TransactionType.CREDIT, Collections.emptyList()), currency));
MonetaryAmount tmpCredited = MoneyUtils.calculateTotalAmount(
transactionGroupedByType.getOrDefault(Transaction.TransactionType.CREDIT, Collections.emptyList()), currency);

expended = tmpExpended.with(Monetary.getDefaultRounding()).getNumber().doubleValue();
credited = tmpCredited.with(Monetary.getDefaultRounding()).getNumber().doubleValue();

expendedPercentage = totalExpended == 0 ? null : MoneyUtils.calculateExpendedPercentage(tmpExpended, totalExpended);
creditedPercentage = totalCredited == 0 ? null : MoneyUtils.calculateCreditedPercentage(tmpCredited, totalCredited);
}

return new BudgetExpenseClassTotal()
Expand All @@ -101,7 +102,9 @@ private BudgetExpenseClassTotal buildBudgetExpenseClassTotals(ExpenseClass expen
.withEncumbered(encumbered)
.withAwaitingPayment(awaitingPayment)
.withExpended(expended)
.withPercentageExpended(expendedPercentage);
.withPercentageExpended(expendedPercentage)
.withCredited(credited)
.withPercentageCredited(creditedPercentage);
}

private List<BudgetExpenseClassTotal> updateExpenseClassStatus(List<BudgetExpenseClassTotal> budgetExpenseClassTotals,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@ private void updateBudget(Budget budget, CurrencyUnit currency, Transaction encu
double newAmount = subtractMoney(encumbrance.getEncumbrance().getInitialAmountEncumbered(),
existingEncumbrance.getEncumbrance().getAmountAwaitingPayment(), currency);
newAmount = subtractMoney(newAmount, existingEncumbrance.getEncumbrance().getAmountExpended(), currency);
newAmount = sumMoney(newAmount, encumbrance.getEncumbrance().getAmountCredited(), currency);
encumbrance.setAmount(newAmount);
newEncumbered = sumMoney(currency, newEncumbered, newAmount);
} else if (isTransitionFromReleasedToUnreleased(encumbrance, existingEncumbrance)) {
double newAmount = subtractMoney(encumbrance.getEncumbrance().getInitialAmountEncumbered(),
encumbrance.getEncumbrance().getAmountAwaitingPayment(), currency);
newAmount = subtractMoney(newAmount, encumbrance.getEncumbrance().getAmountExpended(), currency);
newAmount = sumMoney(newAmount, encumbrance.getEncumbrance().getAmountCredited(), currency);
encumbrance.setAmount(newAmount);
newEncumbered = sumMoney(newEncumbered, newAmount, currency);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.folio.rest.jaxrs.model.Encumbrance;
import org.folio.rest.jaxrs.model.Transaction;
import org.folio.rest.jaxrs.model.Transaction.TransactionType;
import org.folio.utils.MoneyUtils;
import org.javamoney.moneta.Money;

import javax.money.CurrencyUnit;
Expand Down Expand Up @@ -73,23 +72,26 @@ private void updateEncumbranceToCancelTransaction(Transaction transaction, Trans
// NOTE: we can't unrelease the encumbrance automatically because the payment/credit does not say if the encumbrance
// was released automatically or not (as opposed to a pending payment).
double expended = encumbrance.getEncumbrance().getAmountExpended();
double credited = encumbrance.getEncumbrance().getAmountCredited();
double amount = encumbrance.getAmount();
if (transaction.getTransactionType() == PAYMENT) {
expended = subtractMoney(expended, transaction.getAmount(), currency);
amount = sumMoney(amount, transaction.getAmount(), currency);
} else {
expended = sumMoney(expended, transaction.getAmount(), currency);
credited = subtractMoney(credited, transaction.getAmount(), currency);
if (Encumbrance.Status.RELEASED != encumbrance.getEncumbrance().getStatus()) {
amount = subtractMoney(amount, transaction.getAmount(), currency);
}
}
encumbrance.getEncumbrance().setAmountExpended(expended);
encumbrance.getEncumbrance().setAmountCredited(credited);
encumbrance.setAmount(amount);
}

private void updateEncumbranceToApplyTransaction(Transaction transaction, Transaction encumbrance, CurrencyUnit currency,
Map<String, Transaction> existingTransactionMap) {
MonetaryAmount expended = Money.of(encumbrance.getEncumbrance().getAmountExpended(), currency);
MonetaryAmount credited = Money.of(encumbrance.getEncumbrance().getAmountCredited(), currency);
MonetaryAmount awaitingPayment = Money.of(encumbrance.getEncumbrance().getAmountAwaitingPayment(), currency);
MonetaryAmount amount = Money.of(transaction.getAmount(), currency);
Transaction existingTransaction = existingTransactionMap.get(transaction.getId());
Expand All @@ -100,11 +102,12 @@ private void updateEncumbranceToApplyTransaction(Transaction transaction, Transa
expended = expended.add(amount);
awaitingPayment = awaitingPayment.subtract(amount);
} else {
expended = expended.subtract(amount);
credited = credited.add(amount);
awaitingPayment = awaitingPayment.add(amount);
}
encumbrance.getEncumbrance()
.withAmountExpended(expended.getNumber().doubleValue())
.withAmountCredited(credited.getNumber().doubleValue())
.withAmountAwaitingPayment(awaitingPayment.getNumber().doubleValue());
}

Expand All @@ -129,20 +132,23 @@ private void updateBudgets(List<Transaction> transactions, BatchTransactionHolde

private void cancelTransaction(Budget budget, Transaction transaction) {
double expenditures = budget.getExpenditures();
double credits = budget.getCredits();
CurrencyUnit currency = Monetary.getCurrency(transaction.getCurrency());
if (transaction.getTransactionType() == PAYMENT) {
expenditures = MoneyUtils.subtractMoney(expenditures, transaction.getAmount(), currency);
expenditures = subtractMoney(expenditures, transaction.getAmount(), currency);
} else {
expenditures = MoneyUtils.sumMoney(expenditures, transaction.getAmount(), currency);
credits = subtractMoney(credits, transaction.getAmount(), currency);
}
transaction.setVoidedAmount(transaction.getAmount());
transaction.setAmount(0d);
budget.setExpenditures(expenditures);
budget.setCredits(credits);
}

private void applyTransaction(Budget budget, Transaction transaction, Transaction existingTransaction) {
CurrencyUnit currency = Monetary.getCurrency(transaction.getCurrency());
MonetaryAmount expenditures = Money.of(budget.getExpenditures(), currency);
MonetaryAmount credits = Money.of(budget.getCredits(), currency);
MonetaryAmount awaitingPayment = Money.of(budget.getAwaitingPayment(), currency);
MonetaryAmount amount = Money.of(transaction.getAmount(), currency);
if (existingTransaction != null) {
Expand All @@ -152,10 +158,11 @@ private void applyTransaction(Budget budget, Transaction transaction, Transactio
expenditures = expenditures.add(amount);
awaitingPayment = awaitingPayment.subtract(amount);
} else {
expenditures = expenditures.subtract(amount);
credits = credits.add(amount);
awaitingPayment = awaitingPayment.add(amount);
}
budget.setExpenditures(expenditures.getNumber().doubleValue());
budget.setCredits(credits.getNumber().doubleValue());
budget.setAwaitingPayment(awaitingPayment.getNumber().doubleValue());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,13 @@ private static void checkRestrictedExpenditures(Budget budget, String currency,
double allowableExpenditure = Money.of(budget.getAllowableExpenditure(), currency).divide(100d)
.getNumber().doubleValue();

MonetaryAmount credited = Money.of(budget.getCredits(), currency);
MonetaryAmount expended = Money.of(budget.getExpenditures(), currency);
MonetaryAmount awaitingPayment = Money.of(budget.getAwaitingPayment(), currency);
MonetaryAmount netTransfers = Money.of(budget.getNetTransfers(), currency);

MonetaryAmount totalFunding = allocated.add(netTransfers);
MonetaryAmount unavailable = awaitingPayment.add(expended);
MonetaryAmount unavailable = awaitingPayment.add(expended).subtract(credited);

double remaining = totalFunding.multiply(allowableExpenditure).subtract(unavailable).getNumber().doubleValue();
if (remaining < 0) {
Expand All @@ -235,6 +236,7 @@ private static void checkRestrictedExpenditures(Budget budget, String currency,
parameters.add(new Parameter().withKey("netTransfers").withValue(Double.toString(netTransfers.getNumber().doubleValue())));
parameters.add(new Parameter().withKey("allowableExpenditure").withValue(Double.toString(allowableExpenditure)));
parameters.add(new Parameter().withKey("awaitingPayment").withValue(Double.toString(awaitingPayment.getNumber().doubleValue())));
parameters.add(new Parameter().withKey("credited").withValue(Double.toString(credited.getNumber().doubleValue())));
parameters.add(new Parameter().withKey("expended").withValue(Double.toString(expended.getNumber().doubleValue())));
Error error = BUDGET_RESTRICTED_EXPENDITURES_ERROR.toError().withParameters(parameters);
throw new HttpException(422, error);
Expand All @@ -248,11 +250,12 @@ private static void checkRestrictedEncumbrance(Budget budget, String currency, B
double allowableEncumbered = Money.of(budget.getAllowableEncumbrance(), currency).divide(100d).getNumber().doubleValue();
Money encumbered = Money.of(budget.getEncumbered(), currency);
Money awaitingPayment = Money.of(budget.getAwaitingPayment(), currency);
Money credited = Money.of(budget.getCredits(), currency);
Money expended = Money.of(budget.getExpenditures(), currency);
Money netTransfers = Money.of(budget.getNetTransfers(), currency);

Money totalFunding = allocated.add(netTransfers);
Money unavailable = encumbered.add(awaitingPayment).add(expended);
Money unavailable = encumbered.add(awaitingPayment).add(expended).subtract(credited);

double remaining = totalFunding.multiply(allowableEncumbered).subtract(unavailable).getNumber().doubleValue();
if (remaining < 0) {
Expand All @@ -265,6 +268,7 @@ private static void checkRestrictedEncumbrance(Budget budget, String currency, B
parameters.add(new Parameter().withKey("allowableEncumbered").withValue(Double.toString(allowableEncumbered)));
parameters.add(new Parameter().withKey("encumbered").withValue(Double.toString(encumbered.getNumber().doubleValue())));
parameters.add(new Parameter().withKey("awaitingPayment").withValue(Double.toString(awaitingPayment.getNumber().doubleValue())));
parameters.add(new Parameter().withKey("credited").withValue(Double.toString(credited.getNumber().doubleValue())));
parameters.add(new Parameter().withKey("expended").withValue(Double.toString(expended.getNumber().doubleValue())));
Error error = BUDGET_RESTRICTED_ENCUMBRANCE_ERROR.toError().withParameters(parameters);
throw new HttpException(422, error);
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/folio/utils/CalculationUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public static void calculateBudgetSummaryFields(Budget budget) {
budget.getAllocationFrom(),
budget.getAllocationTo(),
budget.getNetTransfers(),
budget.getCredits(),
budget.getExpenditures(),
budget.getEncumbered(),
budget.getAwaitingPayment()
Expand All @@ -72,6 +73,7 @@ public static void calculateBudgetSummaryFields(LedgerFiscalYearRolloverBudget r
rolloverBudget.getAllocationFrom(),
rolloverBudget.getAllocationTo(),
rolloverBudget.getNetTransfers(),
rolloverBudget.getCredits(),
rolloverBudget.getExpenditures(),
rolloverBudget.getEncumbered(),
rolloverBudget.getAwaitingPayment()
Expand All @@ -90,6 +92,7 @@ private static Map<String, BigDecimal> calculate(Double dInitialAllocation,
Double dAllocationFrom,
Double dAllocationTo,
Double dNetTransfers,
Double dCredits,
Double dExpenditures,
Double dEncumbered,
Double dAwaitingPayment) {
Expand All @@ -98,6 +101,7 @@ private static Map<String, BigDecimal> calculate(Double dInitialAllocation,
BigDecimal allocationTo = BigDecimal.valueOf(dAllocationTo);

BigDecimal netTransfers = BigDecimal.valueOf(dNetTransfers);
BigDecimal credited = BigDecimal.valueOf(dCredits);
BigDecimal expended = BigDecimal.valueOf(dExpenditures);
BigDecimal encumbered = BigDecimal.valueOf(dEncumbered);
BigDecimal awaitingPayment = BigDecimal.valueOf(dAwaitingPayment);
Expand All @@ -107,7 +111,7 @@ private static Map<String, BigDecimal> calculate(Double dInitialAllocation,
BigDecimal totalFunding = allocated.add(netTransfers);
BigDecimal cashBalance = totalFunding.subtract(expended);
BigDecimal available = totalFunding.subtract(unavailable);
BigDecimal overExpended = expended.add(awaitingPayment).subtract(totalFunding.max(BigDecimal.ZERO)).max(BigDecimal.ZERO);
BigDecimal overExpended = expended.subtract(credited).add(awaitingPayment).subtract(totalFunding.max(BigDecimal.ZERO)).max(BigDecimal.ZERO);
BigDecimal overCommitted = unavailable.subtract(totalFunding.max(BigDecimal.ZERO)).max(BigDecimal.ZERO);
BigDecimal overEncumbered = overCommitted.subtract(overExpended);

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/folio/utils/MoneyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public static double calculateExpendedPercentage(MonetaryAmount expended, double
return expended.divide(totalExpended).multiply(100).with(Monetary.getDefaultRounding()).getNumber().doubleValue();
}

public static double calculateCreditedPercentage(MonetaryAmount credited, double totalCredited) {
return credited.divide(totalCredited).multiply(100).with(Monetary.getDefaultRounding()).getNumber().doubleValue();
}

public static MonetaryAmount calculateTotalAmount(List<Transaction> transactions, CurrencyUnit currency) {
return transactions.stream()
.map(transaction -> (MonetaryAmount) Money.of(transaction.getAmount(), currency))
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/data/budgets-8.4.0/AFRICAHIST-FY24.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"fundId": "7fbd5d84-62d1-44c6-9c45-6cb173998bbd",
"name": "AFRICAHIST-FY2024",
"netTransfers": 0,
"credits": 0,
"expenditures": 0,
"encumbered": 0,
"initialAllocation": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"awaitingPayment": 0,
"budgetStatus": "Planned",
"cashBalance": 0.00,
"credits": 0,
"encumbered": 0,
"expenditures": 0,
"fiscalYearId": "43aba0df-70e4-435e-8463-d8f730d19e0b",
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/data/budgets-8.4.0/GRANT-SUBN-FY20.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"available": 0.00,
"awaitingPayment": 0.00,
"budgetStatus": "Active",
"credits": 0.00,
"encumbered": 0.00,
"expenditures": 0.00,
"fiscalYearId": "78110b4e-2f8e-4eef-81ee-3058c0c7a9ee",
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/data/budgets-8.4.0/HIST-FY24.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"available": 0,
"awaitingPayment": 0,
"budgetStatus": "Active",
"credits": 0,
"encumbered": 0,
"expenditures": 0,
"fiscalYearId": "7a4c4d30-3b63-4102-8e2d-3ee5792d7d02",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"available": 0.00,
"awaitingPayment": 0.00,
"budgetStatus": "Frozen",
"credits": 0.00,
"encumbered": 0.00,
"expenditures": 0.00,
"fiscalYearId": "78110b4e-2f8e-4eef-81ee-3058c0c7a9ee",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"awaitingPayment": 0,
"budgetStatus": "Frozen",
"cashBalance": 0.00,
"credits": 0,
"encumbered": 0,
"expenditures": 0,
"fiscalYearId": "0bb73cfd-9251-44ce-a7de-3ef16875ba6a",
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/data/budgets-8.4.0/SLAVHIST-FY19.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"allowableExpenditure": 100,
"awaitingPayment": 0,
"budgetStatus": "Active",
"credits": 0,
"encumbered": 0,
"expenditures": 0,
"fiscalYearId": "8da275b8-e099-49a1-9a31-b8241ff26ffc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"allowableExpenditure": 100,
"awaitingPayment": 0.00,
"budgetStatus": "Active",
"credits": 0.00,
"encumbered": 0.00,
"expenditures": 0.00,
"fiscalYearId": "8da275b8-e099-49a1-9a31-b8241ff26ffc",
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/data/budgets-8.4.0/STATE-SUBN-FY19.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"available": 0.00,
"awaitingPayment": 0.00,
"budgetStatus": "Active",
"credits": 0.00,
"encumbered": 0.00,
"expenditures": 0.00,
"fiscalYearId": "8da275b8-e099-49a1-9a31-b8241ff26ffc",
Expand Down
Loading

0 comments on commit 808aa04

Please sign in to comment.