Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

household income screens #347

Merged
merged 39 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1c3501b
household employment status
lkemperman-cfa Oct 10, 2023
aba489a
household annual income
lkemperman-cfa Oct 10, 2023
c78d371
progress on the job screens - still need a way to process the data
lkemperman-cfa Oct 12, 2023
211307c
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 12, 2023
e210a35
make subflow in flows-config and add templates
lkemperman-cfa Oct 13, 2023
8111d21
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 13, 2023
5135918
add proper input for messages.properties
lkemperman-cfa Oct 13, 2023
0c40b00
delete temp file
lkemperman-cfa Oct 13, 2023
f796130
progress on income subflow
lkemperman-cfa Oct 13, 2023
2c14e10
more progress on condition and screens
lkemperman-cfa Oct 13, 2023
8956203
progress on screens and properties
lkemperman-cfa Oct 16, 2023
62728a2
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 16, 2023
19b881f
progress on income review screen
lkemperman-cfa Oct 16, 2023
ea6b476
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 16, 2023
b611654
styling the review page
lkemperman-cfa Oct 16, 2023
97ce597
more custom css and adaptations to fix designs
lkemperman-cfa Oct 16, 2023
4a5d462
adapt message based on varying pay period
lkemperman-cfa Oct 16, 2023
11ec4de
fix codeql detected missing override
lkemperman-cfa Oct 17, 2023
34b68ed
throw number format exception in signature
lkemperman-cfa Oct 17, 2023
cd1fe40
remove unused variable in submission utilities
lkemperman-cfa Oct 17, 2023
d3a5e54
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 17, 2023
db5c810
some stylistic improvements and adding postfix
lkemperman-cfa Oct 17, 2023
dc0aa73
add house outline to icons
lkemperman-cfa Oct 17, 2023
fecd873
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 17, 2023
bc1d621
design tweaks
lkemperman-cfa Oct 17, 2023
0a3f422
add the classes with important
lkemperman-cfa Oct 17, 2023
42fdf13
adapt calculations from annual to monthly
lkemperman-cfa Oct 17, 2023
625b187
adapt content for monthly income
lkemperman-cfa Oct 17, 2023
58c7b35
add monthly household income instead of annual to inputs
lkemperman-cfa Oct 17, 2023
0b09c81
use null safe comparison
lkemperman-cfa Oct 17, 2023
5d4fe97
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 17, 2023
0c9f790
add constraints on hours per week and pay period amount
lkemperman-cfa Oct 17, 2023
7befcd0
finalize base journey test
lkemperman-cfa Oct 17, 2023
24da0ef
add 100 hour upper limit for hours per week
lkemperman-cfa Oct 17, 2023
3693d04
delete pay period varies
lkemperman-cfa Oct 17, 2023
8abc95b
make all of the money and hours fields required
lkemperman-cfa Oct 18, 2023
12abb22
make more fields required and remove footer from job paid by the hour
lkemperman-cfa Oct 18, 2023
42b68fa
fix vertical alignment for job paid by the hour
lkemperman-cfa Oct 18, 2023
0b61291
Merge branch 'main' into employment-income-questions
lkemperman-cfa Oct 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/main/java/org/ladocuploader/app/inputs/LaDigitalAssister.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package org.ladocuploader.app.inputs;

import formflow.library.data.FlowInputs;
import formflow.library.data.validators.Money;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.NumberFormat;

import java.util.List;

Expand Down Expand Up @@ -94,6 +99,18 @@ public class LaDigitalAssister extends FlowInputs {

private String citizenshipInd;

private String selfEmploymentIncome;

@NotBlank
private String householdMemberJobAdd;

@NotBlank
private String employerName;

private String selfEmployed;

private String jobPaidByHour;

private String nonCitizens;

private String citizenshipNumber;
Expand Down Expand Up @@ -150,6 +167,25 @@ public class LaDigitalAssister extends FlowInputs {

private String personalSituationDisability;


@Money
@NotBlank
private String hourlyWage;

@Range(min=1, max=100)
@NotBlank
private String hoursPerWeek;

@NotBlank
private String payPeriod;

@Money
@NotBlank
private String payPeriodAmount;

private String moneyOnHand;

private String monthlyHouseholdIncome;

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.ladocuploader.app.submission.conditions;

import formflow.library.config.submission.Condition;
import formflow.library.data.Submission;

import java.util.ArrayList;
import java.util.Map;

public abstract class AbstractSubflowCondition implements Condition {

protected Map<String, Object> currentSubflowItem(Submission submission, String subflow, String uuid) {
var inputData = submission.getInputData();
var items = (ArrayList<Map<String, Object>>) inputData.getOrDefault(subflow, new ArrayList<Map<String, Object>>());
return items
.stream()
.filter(item -> item.get("uuid").equals(uuid))
.findFirst()
.orElse(null);
}

protected Map<String, Object> currentIncomeSubflowItem(Submission submission, String uuid) {
return currentSubflowItem(submission, "income", uuid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.ladocuploader.app.submission.conditions;
import formflow.library.data.Submission;
import org.springframework.stereotype.Component;

@Component
public class JobPaidByTheHour extends AbstractSubflowCondition {
@Override
public Boolean run(Submission submission, String uuid) {
Fixed Show fixed Hide fixed
var item = currentIncomeSubflowItem(submission, uuid);

return item != null &&
item.getOrDefault("jobPaidByHour", "false").equals("true");
}
}
52 changes: 52 additions & 0 deletions src/main/java/org/ladocuploader/app/utils/IncomeCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.ladocuploader.app.utils;

import formflow.library.data.Submission;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Slf4j
public class IncomeCalculator {
Submission submission;
public IncomeCalculator(Submission submission) {
this.submission = submission;
}

public Double totalFutureEarnedIncome() {
// TODO: check for annualIncome key and use that if it is entered?
// if submission.getInputData().
var jobs = (List<Map<String, Object>>) submission.getInputData().getOrDefault("income", new ArrayList<Map<String, Object>>());
var total = jobs.stream()
.map(IncomeCalculator::futureIncomeForJob)
.reduce(0.0d, Double::sum);

return total;
}

public static double futureIncomeForJob(Map<String, Object> job) throws NumberFormatException {
if (job.getOrDefault("jobPaidByHour", "false").toString().equals("true")) {
var hoursPerWeek = Double.parseDouble(job.get("hoursPerWeek").toString());
Fixed Show fixed Hide fixed
var hourlyWage = Double.parseDouble(job.get("hourlyWage").toString());
Fixed Show fixed Hide fixed
log.info("Returning hourly wage");
return hoursPerWeek * hourlyWage * (52.0 / 12);
} else {
var payPeriod = job.getOrDefault("payPeriod", "It varies").toString();
var payPeriodAmount = Double.parseDouble(job.get("payPeriodAmount").toString());
Fixed Show fixed Hide fixed
if (Objects.equals(payPeriod, "Every week")){
return payPeriodAmount * (52.0 / 12);
} else if (Objects.equals(payPeriod, "Every 2 weeks")){
return (payPeriodAmount * ((52.0 / 2) / 12));
} else if (Objects.equals(payPeriod, "Twice a month")){
return payPeriodAmount * 2;
} else if (Objects.equals(payPeriod, "Every month")){
return payPeriodAmount;
}
log.info("Using 30D estimate");
// based on 30D estimate
return payPeriodAmount;
}
}
}
108 changes: 108 additions & 0 deletions src/main/java/org/ladocuploader/app/utils/SubmissionUtilities.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.ladocuploader.app.utils;

import formflow.library.data.Submission;

import java.text.DecimalFormat;
import java.util.*;

public class SubmissionUtilities {

public static String formatMoney(String value) {
if (value == null) {
return "";
}

double numericVal;
try {
numericVal = Double.parseDouble(value);
} catch (NumberFormatException _e) {
return value;
}

return formatMoney(numericVal);
}

public static String formatMoney(Double value) {
DecimalFormat decimalFormat = new DecimalFormat("###.##");
return "$" + decimalFormat.format(value);
}

public static String householdMemberFullName(Map<String, String> householdMember) {
return householdMember.get("householdMemberFirstName") + " " + householdMember.get("householdMemberLastName");
}

public static List<String> getHouseholdMemberNames(Submission submission) {
ArrayList<String> names = new ArrayList<>();

var applicantName = submission.getInputData().get("firstName") + " " + submission.getInputData().get("lastName");
var householdMembers = (List<Map<String, String>>) submission.getInputData().getOrDefault("household", new ArrayList<HashMap<String, Object>>());

names.add(applicantName);
householdMembers.forEach(hh -> names.add(householdMemberFullName(hh)));

return names;
}

public static ArrayList<HashMap<String, Object>> getHouseholdIncomeReviewItems(Submission submission) {
var applicantFullName = submission.getInputData().getOrDefault("firstName", "") + " " + submission.getInputData().getOrDefault("lastName", "");
var notYetShownNames = getHouseholdMemberNames(submission);
ArrayList<HashMap<String, Object>> items = new ArrayList<>();

for (var job : (List<HashMap<String, Object>>) submission.getInputData().getOrDefault("income", new ArrayList<HashMap<String, Object>>())) {
var item = new HashMap<String, Object>();
var name = job.get("householdMemberJobAdd").equals("you") ? applicantFullName : job.get("householdMemberJobAdd");
item.put("name", name);
item.put("itemType", "job");
item.put("jobName", job.get("employerName"));
item.put("isApplicant", name.equals(applicantFullName));
// TODO: handle income types - hourly vs. non hourly
var payPeriod = job.getOrDefault("jobPaidByHour", "false").equals("true") ? "Hourly, " + job.get("hoursPerWeek").toString() + " hours per week" : job.getOrDefault("payPeriod", "It varies").toString();
item.put("payPeriod", payPeriod);

// TODO: add wage amount and not future income
var payAmount = job.getOrDefault("jobPaidByHour", "false").equals("true") ? job.get("hourlyWage").toString() : job.get("payPeriodAmount").toString();
item.put("income", formatMoney(payAmount));
item.put("uuid", job.get("uuid"));

notYetShownNames.remove(name);
items.add(item);
}

// Add any household members that didn't have income entries
notYetShownNames.forEach(name -> {
var item = new HashMap<String, Object>();
item.put("name", name);
item.put("itemType", "no-jobs-added");
item.put("isApplicant", name.equals(applicantFullName));

items.add(item);
});

// Sort the list so the applicant shows up first and the rest of the names are alphabetical
items.sort(Comparator.comparing(
job -> (String)job.get("name"),
(a, b) -> {
if (a.equals(applicantFullName) && !b.equals(applicantFullName)) {
return -1;
} else if (b.equals(applicantFullName) && !a.equals(applicantFullName)) {
return 1;
} else {
return a.compareTo(b);
}
}));

// Set combineWithPrevious on items after the first one for the same person
for (var i = 0; i < items.size(); i++) {
var combineWithPrevious = (i > 0) && items.get(i - 1).get("name").equals(items.get(i).get("name"));
items.get(i).put("combineWithPrevious", combineWithPrevious);
}

items.add(new HashMap<String, Object>() {{
put("name", null);
put("itemType", "household-total");
put("income", formatMoney(new IncomeCalculator(submission).totalFutureEarnedIncome()));
}});

return items;
}
}
49 changes: 31 additions & 18 deletions src/main/resources/flows-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -239,37 +239,42 @@ flow:
nextScreens:
- name: householdIncomeWho
householdIncomeWho:
subflow: income
nextScreens:
- name: householdEmployerName
householdIncomeDeleteConfirmation:
nextScreens: null
householdAnnualIncome:
householdMonthlyIncome:
nextScreens:
- name: householdIncomeList
householdEmployerName:
nextScreens: null
subflow: income
nextScreens:
- name: householdSelfEmployment
householdSelfEmployment:
nextScreens: null
subflow: income
nextScreens:
- name: jobPaidByHour
jobPaidByHour:
nextScreens: null
subflow: income
nextScreens:
- name: jobHourlyWage
condition: JobPaidByTheHour
- name: jobPayPeriod
jobHourlyWage:
subflow: income
nextScreens:
- name: jobHoursPerWeek
jobHoursPerWeek:
subflow: income
nextScreens:
- name: householdIncomeConfirmation
jobPayPeriod:
nextScreens: null
jobWeeklyPay:
subflow: income
nextScreens:
- name: householdIncomeConfirmation
jobBiWeeklyPay:
nextScreens:
- name: householdIncomeConfirmation
jobBiMonthlyPay:
nextScreens:
- name: householdIncomeConfirmation
jobMonthlyPay:
nextScreens:
- name: householdIncomeConfirmation
jobVariablePay:
- name: jobPayAmount
jobPayAmount:
subflow: income
nextScreens:
- name: householdIncomeConfirmation
householdIncomeConfirmation:
Expand All @@ -289,6 +294,9 @@ flow:
expensesSignPost:
nextScreens:
- name: householdHomeExpenses
householdHomeExpenses:
nextScreens:
- name: householdEmployerName
expensesHome:
nextScreens:
- name: utilities
Expand Down Expand Up @@ -402,4 +410,9 @@ subflows:
entryScreen: multiplePersonHousehold
iterationStartScreen: householdInfo
reviewScreen: householdList
deleteConfirmationScreen: householdDeleteConfirmation
deleteConfirmationScreen: householdDeleteConfirmation
income:
entryScreen: householdIncomeByJob
iterationStartScreen: householdIncomeWho
reviewScreen: householdIncomeList
deleteConfirmationScreen: householdIncomeDeleteConfirmation
Loading