From d9f26954b880863cb259f8588583751fa53dfc33 Mon Sep 17 00:00:00 2001 From: Steve Schafer Date: Fri, 12 Jun 2026 14:39:43 -0600 Subject: [PATCH] Update spring tools. Update spring boot version. Add -proc:full to maven compiler args. Change mysql connector to mariadb connector in pom. Add detail.hidden. Enhance report filtering. Add paid and paid-by to report. Change mysql driver to mariadb driver. --- .factorypath | 167 ++++++++++-------- build | 2 +- eclipse | 2 +- pom.xml | 16 +- .../stephenschafer/budget/ReportCategory.java | 30 ++-- .../budget/ReportCategoryFilters.java | 18 ++ .../budget/ReportController.java | 124 +++++++++++-- .../stephenschafer/budget/ReportDetail.java | 31 ++++ .../budget/WebSecurityConfig.java | 3 +- src/main/resources/application.properties | 3 +- 10 files changed, 290 insertions(+), 106 deletions(-) create mode 100644 src/main/java/com/stephenschafer/budget/ReportCategoryFilters.java diff --git a/.factorypath b/.factorypath index 7c47bf9..3c6a756 100644 --- a/.factorypath +++ b/.factorypath @@ -1,80 +1,107 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + - - - - + + + + + + + + + + + + + + + + + diff --git a/build b/build index 39d9343..6997a49 100755 --- a/build +++ b/build @@ -4,7 +4,7 @@ ROOT=$(pwd) ./stop # export JAVA_HOME="/usr/lib/jvm/java-11-openjdk" mkdir -p logs -if ! mvn clean package > logs/build.log 2> logs/build.err.log; then +if ! mvn clean install > logs/build.log 2> logs/build.err.log; then echo "build failed" exit 1 fi diff --git a/eclipse b/eclipse index 748a886..ce62fe9 100755 --- a/eclipse +++ b/eclipse @@ -2,5 +2,5 @@ cd "$(dirname "${BASH_SOURCE[0]}")" LOG=./logs mkdir -p $LOG -/home/eclipse/sts-4.30.0.RELEASE/SpringToolSuite4 -data .. \ +/home/eclipse/sts-5.2.0.RELEASE/SpringToolsForEclipse -data .. \ >$LOG/eclipse-sts.log 2>$LOG/eclipse-sts.err.log & diff --git a/pom.xml b/pom.xml index 85f9d64..8646f2f 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.7 + 4.1.0 @@ -62,8 +62,8 @@ 0.9.0 - com.mysql - mysql-connector-j + org.mariadb.jdbc + mariadb-java-client runtime @@ -88,7 +88,6 @@ - 17 @@ -97,6 +96,15 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:full + + + diff --git a/src/main/java/com/stephenschafer/budget/ReportCategory.java b/src/main/java/com/stephenschafer/budget/ReportCategory.java index fc94919..67ab5cc 100644 --- a/src/main/java/com/stephenschafer/budget/ReportCategory.java +++ b/src/main/java/com/stephenschafer/budget/ReportCategory.java @@ -103,7 +103,9 @@ public class ReportCategory { public BigDecimal getDetailTotal() { var amount = new BigDecimal(0); for (final ReportDetail detail : details) { - amount = amount.add(detail.amount); + if (!detail.hidden) { + amount = amount.add(detail.amount); + } } return amount; } @@ -111,10 +113,12 @@ public class ReportCategory { public BigDecimal getMonthTotal(final int month) { var amount = new BigDecimal(0); for (final ReportDetail detail : details) { - final Calendar cal = new GregorianCalendar(); - cal.setTime(detail.date); - if (month == cal.get(Calendar.MONTH)) { - amount = amount.add(detail.amount); + if (!detail.hidden) { + final Calendar cal = new GregorianCalendar(); + cal.setTime(detail.date); + if (month == cal.get(Calendar.MONTH)) { + amount = amount.add(detail.amount); + } } } return amount; @@ -125,14 +129,16 @@ public class ReportCategory { if (monthTotals == null) { monthTotals = new HashMap<>(); for (final ReportDetail detail : details) { - final Calendar cal = new GregorianCalendar(); - cal.setTime(detail.date); - final int month = cal.get(Calendar.MONTH); - BigDecimal monthTotal = monthTotals.get(month); - if (monthTotal == null) { - monthTotal = new BigDecimal(0); + if (!detail.hidden) { + final Calendar cal = new GregorianCalendar(); + cal.setTime(detail.date); + final int month = cal.get(Calendar.MONTH); + BigDecimal monthTotal = monthTotals.get(month); + if (monthTotal == null) { + monthTotal = new BigDecimal(0); + } + monthTotals.put(month, monthTotal.add(detail.amount)); } - monthTotals.put(month, monthTotal.add(detail.amount)); } this.monthTotals = monthTotals; } diff --git a/src/main/java/com/stephenschafer/budget/ReportCategoryFilters.java b/src/main/java/com/stephenschafer/budget/ReportCategoryFilters.java new file mode 100644 index 0000000..4460a19 --- /dev/null +++ b/src/main/java/com/stephenschafer/budget/ReportCategoryFilters.java @@ -0,0 +1,18 @@ +package com.stephenschafer.budget; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ReportCategoryFilters { + final String year; + final boolean includeIncome; + final boolean includePayments; + final boolean includeInvestments; + final boolean includeExpenses; + final boolean excludePaypalPayments; + final boolean excludeWholefoodsPayments; + final boolean excludeAmazonPayments; + final int monthCount; +} diff --git a/src/main/java/com/stephenschafer/budget/ReportController.java b/src/main/java/com/stephenschafer/budget/ReportController.java index 082fe2f..c05657d 100644 --- a/src/main/java/com/stephenschafer/budget/ReportController.java +++ b/src/main/java/com/stephenschafer/budget/ReportController.java @@ -95,6 +95,7 @@ public class ReportController { @RequestParam(required = false, name = "startDate") final String startDateString, @RequestParam(required = false, name = "endDate") final String endDateString, @RequestParam(required = false) final List includes, + @RequestParam(required = false) final List excludes, final HttpServletRequest request) throws SQLException, UnsupportedEncodingException, FileNotFoundException, IOException { log.info("GET /report"); @@ -127,18 +128,45 @@ public class ReportController { else if ("investments".equalsIgnoreCase(include)) { tmpIncludeInvestments = true; } + else { + return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), + "Unrecognized includes value", null); + } } } final var includeExpenses = tmpIncludeExpenses; final var includeIncome = tmpIncludeIncome; final var includePayments = tmpIncludePayments; final var includeInvestments = tmpIncludeInvestments; + var tmpExcludePaypalPayments = false; + var tmpExcludeWholefoodsPayments = false; + var tmpExcludeAmazonPayments = false; + if (excludes != null && !excludes.isEmpty()) { + for (final String exclude : excludes) { + if ("paypalPayments".equalsIgnoreCase(exclude)) { + tmpExcludePaypalPayments = true; + } + else if ("wholefoodsPayments".equalsIgnoreCase(exclude)) { + tmpExcludeWholefoodsPayments = true; + } + else if ("amazonPayments".equalsIgnoreCase(exclude)) { + tmpExcludeAmazonPayments = true; + } + else { + return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), + "Unrecognized excludes value", null); + } + } + } + final var excludePaypalPayments = tmpExcludePaypalPayments; + final var excludeWholefoodsPayments = tmpExcludeWholefoodsPayments; + final var excludeAmazonPayments = tmpExcludeAmazonPayments; final DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); final Date startDate; - final Date endDate; + Date tmpEndDate; try { startDate = startDateString == null ? null : df.parse(startDateString); - endDate = endDateString == null ? null : df.parse(endDateString); + tmpEndDate = endDateString == null ? null : df.parse(endDateString); } catch (final ParseException e) { return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Invalid date", null); @@ -153,19 +181,25 @@ public class ReportController { startMonth = 0; } final int endMonth; - if (endDate != null) { + final Date endDate; + if (tmpEndDate != null) { final Calendar endCal = new GregorianCalendar(); - endCal.setTime(endDate); + endCal.setTime(tmpEndDate); endMonth = endCal.get(Calendar.MONTH); + endCal.add(Calendar.DAY_OF_MONTH, 1); + endDate = endCal.getTime(); } else { endMonth = 11; + endDate = null; } final int monthCount = endMonth - startMonth + 1; final Map response = new HashMap<>(); for (final String year : years) { - final var categoryMap = loadCategories(year, includeIncome, includePayments, - includeInvestments, includeExpenses, monthCount); + final var filters = new ReportCategoryFilters(year, includeIncome, includePayments, + includeInvestments, includeExpenses, excludePaypalPayments, + excludeWholefoodsPayments, excludeAmazonPayments, monthCount); + final var categoryMap = loadCategories(filters); final String createBudgetTableSql = Util.getResourceAsString( "createBudgetAmountsTable.sql") // .replace("${ifNotExists}", "if not exists ") // @@ -188,6 +222,7 @@ public class ReportController { } } }); + final Map> detailMap = new HashMap<>(); final String sql = Util.getResourceAsString("getTransactionDetail.sql") // .replace("${databaseName}", "budget_" + year) // .replace("${regexDatabaseName}", "budget"); @@ -214,7 +249,14 @@ public class ReportController { category.isIncluded()) { category.addDetail(detail); } + List detailList = detailMap.get(detail.source); + if (detailList == null) { + detailList = new ArrayList<>(); + detailMap.put(detail.source, detailList); + } + detailList.add(detail); }); + hidePaymentDetails(detailMap); final var rootCategory = new ReportCategory(-1, null, "total", monthCount); for (final Integer categoryId : categoryMap.keySet()) { final var category = categoryMap.get(categoryId); @@ -229,6 +271,41 @@ public class ReportController { return new ApiResponse<>(HttpStatus.OK.value(), "Report fetched successfully", response); } + static final String[] payerSources = new String[] { "chase", "firstbank" }; + static final String[] payeeSources = new String[] { "paypal", "amz.sandy.dig.org", + "amz.steve.dig.ord", "amz.sandy.retail.ord", "amz.steve.retail.ord" }; + + private void hidePaymentDetails(final Map> detailMap) { + final List payerDetails = new ArrayList<>(); + for (final String source : payerSources) { + final List details = detailMap.get(source); + if (details != null) { + for (final ReportDetail detail : details) { + payerDetails.add(detail); + } + } + } + final List payeeDetails = new ArrayList<>(); + for (final String source : payeeSources) { + final List details = detailMap.get(source); + if (details != null) { + for (final ReportDetail detail : details) { + payeeDetails.add(detail); + } + } + } + for (final ReportDetail payeeDetail : payeeDetails) { + for (final ReportDetail payerDetail : payerDetails) { + if (payeeDetail.isPaidBy(payerDetail)) { + payerDetail.setHidden(true); + payeeDetail.setPaidByTransactionId(payerDetail.transactionId); + payerDetail.setPaidTransactionId(payeeDetail.transactionId); + break; + } + } + } + } + List getYears() throws IOException, SQLException { final String sql = Util.getResourceAsString("getYears.sql") // .replace("${databaseName}", "budget"); @@ -244,9 +321,7 @@ public class ReportController { boolean include(ReportCategory category); } - private Map loadCategories(final String year, - final boolean includeIncome, final boolean includePayments, - final boolean includeInvestments, final boolean includeExpenses, final int monthCount) + private Map loadCategories(final ReportCategoryFilters filters) throws SQLException, IOException { ReportCategory unknownCategory = null; final Map categories = new HashMap<>(); @@ -260,7 +335,7 @@ public class ReportController { parentId = null; } final var name = rs.getString(3); - final var category = new ReportCategory(id, parentId, name, monthCount); + final var category = new ReportCategory(id, parentId, name, filters.monthCount); return category; }); for (final ReportCategory category : list) { @@ -274,29 +349,48 @@ public class ReportController { category.updateParent(categories); } if (unknownCategory == null) { - unknownCategory = new ReportCategory(-1, null, "unknown", monthCount); + unknownCategory = new ReportCategory(-1, null, "unknown", filters.monthCount); categories.put(unknownCategory.getId(), unknownCategory); } for (final Integer categoryId : categories.keySet()) { final var category = categories.get(categoryId); final CategoryFilter filter = category1 -> { final String categoryName = category1.getName(); + final ReportCategory parentCategory = category1.getParent(); + final String parentCategoryName = parentCategory == null ? null + : parentCategory.getName(); if ("income".equals(categoryName) || "refunds".equals(categoryName)) { - if (!includeIncome) { + if (!filters.includeIncome) { return false; } } else if ("payment".equals(categoryName)) { - if (!includePayments) { + if (!filters.includePayments) { return false; } } else if ("investment".equals(categoryName)) { - if (!includeInvestments) { + if (!filters.includeInvestments) { return false; } } - else if (!includeExpenses) { + else if ("paypal".equals(categoryName) && "payment".equals(parentCategoryName)) { + if (filters.excludePaypalPayments) { + return false; + } + } + else if ("wholefoods".equals(categoryName) + && "payment".equals(parentCategoryName)) { + if (filters.excludeWholefoodsPayments) { + return false; + } + } + else if ("amazon".equals(categoryName) && "payment".equals(parentCategoryName)) { + if (filters.excludeAmazonPayments) { + return false; + } + } + else if (!filters.includeExpenses) { return false; } return true; diff --git a/src/main/java/com/stephenschafer/budget/ReportDetail.java b/src/main/java/com/stephenschafer/budget/ReportDetail.java index eb6a8c4..f9a669c 100644 --- a/src/main/java/com/stephenschafer/budget/ReportDetail.java +++ b/src/main/java/com/stephenschafer/budget/ReportDetail.java @@ -20,6 +20,9 @@ class ReportDetail implements Comparable { int flags; String requiredSource; String extraDescription; + boolean hidden = false; + Integer paidByTransactionId = null; + Integer paidTransactionId = null; @Override public int compareTo(final ReportDetail arg1) { @@ -77,4 +80,32 @@ class ReportDetail implements Comparable { public boolean isBefore(final Date endDate) { return endDate == null || endDate.getTime() > this.date.getTime(); } + + public boolean isPaidBy(final ReportDetail payingDetail) { + if (payingDetail.description == null) { + return false; + } + if (payingDetail.description.toLowerCase().indexOf(this.source.toLowerCase()) < 0) { + return false; + } + if (payingDetail.date == null) { + return false; + } + if (this.date == null) { + return false; + } + if (payingDetail.date.getTime() < this.date.getTime()) { + return false; + } + if (payingDetail.amount == null) { + return false; + } + if (this.amount == null) { + return false; + } + if (!payingDetail.amount.equals(this.amount)) { + return false; + } + return true; + } } \ No newline at end of file diff --git a/src/main/java/com/stephenschafer/budget/WebSecurityConfig.java b/src/main/java/com/stephenschafer/budget/WebSecurityConfig.java index dc7cada..0d48953 100644 --- a/src/main/java/com/stephenschafer/budget/WebSecurityConfig.java +++ b/src/main/java/com/stephenschafer/budget/WebSecurityConfig.java @@ -27,8 +27,7 @@ public class WebSecurityConfig { @Bean AuthenticationManager authenticationManager(final UserDetailsService userDetailsService, final PasswordEncoder passwordEncoder) { - final var provider = new DaoAuthenticationProvider(); - provider.setUserDetailsService(userDetailsService); + final var provider = new DaoAuthenticationProvider(userDetailsService); provider.setPasswordEncoder(passwordEncoder); return new ProviderManager(provider); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 21c58ec..f08a0f3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,5 @@ -spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.driver-class-name=org.mariadb.jdbc.Driver +#com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false spring.datasource.username=elephant # spring.datasource.password=CHANGEME