Add /categories-cvs
This commit is contained in:
parent
31f9272409
commit
3e978a831c
15 changed files with 365 additions and 43 deletions
18
.classpath
18
.classpath
|
|
@ -2,7 +2,6 @@
|
|||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
|
|
@ -38,5 +37,22 @@
|
|||
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="target/generated-sources/annotations">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="ignore_optional_problems" value="true"/>
|
||||
<attribute name="m2e-apt" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="ignore_optional_problems" value="true"/>
|
||||
<attribute name="m2e-apt" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
|
|
|||
2
.settings/org.eclipse.jdt.apt.core.prefs
Normal file
2
.settings/org.eclipse.jdt.apt.core.prefs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.apt.aptEnabled=false
|
||||
|
|
@ -7,5 +7,6 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
|||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.processAnnotations=disabled
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=17
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
|
||||
|
||||
<wb-module deploy-name="budget.web-0.0.1-SNAPSHOT">
|
||||
|
||||
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
|
||||
|
||||
<wb-resource deploy-path="/" source-path="/src/main/webapp"/>
|
||||
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
|
||||
|
||||
<wb-resource deploy-path="/" source-path="/WebContent" tag="defaultRootSource"/>
|
||||
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-sources/annotations"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-test-sources/test-annotations"/>
|
||||
|
||||
<property name="java-output-path" value="/com.stephenschafer.budget.web/build/classes"/>
|
||||
|
||||
<property name="context-root" value="budget.web"/>
|
||||
|
||||
</wb-module>
|
||||
|
||||
</project-modules>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
<fixed facet="java"/>
|
||||
<fixed facet="wst.jsdt.web"/>
|
||||
<installed facet="wst.jsdt.web" version="1.0"/>
|
||||
<installed facet="jst.web" version="4.0"/>
|
||||
<installed facet="java" version="17"/>
|
||||
<installed facet="jst.web" version="2.5"/>
|
||||
</faceted-project>
|
||||
|
|
|
|||
|
|
@ -20,4 +20,15 @@
|
|||
<url-pattern>/categories</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>CategoriesCsvPage</servlet-name>
|
||||
<servlet-class>com.stephenschafer.budget.web.CategoriesCsvPage</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>CategoriesCsvPage</servlet-name>
|
||||
<url-pattern>/categories-csv</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
if mvn -f pom.xml clean install > build-jar.log 2> build-jar.err.log; then
|
||||
if mvn -f pom.xml clean install > build-war.log 2> build-war.err.log; then
|
||||
echo "success"
|
||||
cp target/*.war /tmp
|
||||
else
|
||||
|
|
|
|||
|
|
@ -0,0 +1,242 @@
|
|||
package com.stephenschafer.budget.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class CategoriesCsvPage extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public void init(final ServletConfig config) throws ServletException {
|
||||
try {
|
||||
Configuration.INSTANCE.load();
|
||||
}
|
||||
catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
var paramYear = request.getParameterValues("year");
|
||||
if (paramYear == null || paramYear.length == 0) {
|
||||
final Calendar cal = new GregorianCalendar();
|
||||
cal.setTime(new Date());
|
||||
final var year = cal.get(Calendar.YEAR);
|
||||
paramYear = new String[] { String.valueOf(year) };
|
||||
}
|
||||
var tmpIncludeExpenses = true;
|
||||
var tmpIncludeIncome = true;
|
||||
var tmpIncludePayments = false;
|
||||
var tmpIncludeInvestments = false;
|
||||
final var includes = request.getParameterValues("include");
|
||||
if (includes != null && includes.length > 0) {
|
||||
tmpIncludeExpenses = false;
|
||||
tmpIncludeIncome = false;
|
||||
tmpIncludePayments = false;
|
||||
tmpIncludeInvestments = false;
|
||||
for (final String include : includes) {
|
||||
if ("expenses".equalsIgnoreCase(include)) {
|
||||
tmpIncludeExpenses = true;
|
||||
}
|
||||
else if ("income".equalsIgnoreCase(include)) {
|
||||
tmpIncludeIncome = true;
|
||||
}
|
||||
else if ("payments".equalsIgnoreCase(include)) {
|
||||
tmpIncludePayments = true;
|
||||
}
|
||||
else if ("investments".equalsIgnoreCase(include)) {
|
||||
tmpIncludeInvestments = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
final var includeExpenses = tmpIncludeExpenses;
|
||||
final var includeIncome = tmpIncludeIncome;
|
||||
final var includePayments = tmpIncludePayments;
|
||||
final var includeInvestments = tmpIncludeInvestments;
|
||||
final var startDates = request.getParameterValues("startDate");
|
||||
final var endDates = request.getParameterValues("endDate");
|
||||
final var startDateString = startDates == null ? null
|
||||
: startDates.length == 0 ? null : startDates[0];
|
||||
final var endDateString = endDates == null ? null
|
||||
: endDates.length == 0 ? null : endDates[0];
|
||||
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||
final Date startDate;
|
||||
final Date endDate;
|
||||
try {
|
||||
startDate = startDateString == null ? null : df.parse(startDateString);
|
||||
endDate = endDateString == null ? null : df.parse(endDateString);
|
||||
}
|
||||
catch (final ParseException e) {
|
||||
response.sendError(400, "Invalid date");
|
||||
return;
|
||||
}
|
||||
final Calendar startCal = new GregorianCalendar();
|
||||
startCal.setTime(startDate);
|
||||
final int startMonth = startCal.get(Calendar.MONTH);
|
||||
final Calendar endCal = new GregorianCalendar();
|
||||
endCal.setTime(endDate);
|
||||
final int endMonth = endCal.get(Calendar.MONTH);
|
||||
final int monthCount = endMonth - startMonth + 1;
|
||||
final var out = response.getWriter();
|
||||
response.setHeader("Content-Type", "text/csv");
|
||||
try (var connection = Configuration.INSTANCE.getConnection()) {
|
||||
for (final String year : paramYear) {
|
||||
final var categoryMap = loadCategories(connection, year, includeIncome,
|
||||
includePayments, includeInvestments, includeExpenses);
|
||||
final var sql = Util.getResourceAsString("getTransactionDetail.sql") //
|
||||
.replace("${databaseName}", "budget_" + year) //
|
||||
.replace("${regexDatabaseName}", "budget");
|
||||
try (var stmt = connection.prepareStatement(sql)) {
|
||||
try (var rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
final var detail = new ReportDetail();
|
||||
var i = 0;
|
||||
detail.transactionId = rs.getInt(++i);
|
||||
detail.date = rs.getDate(++i);
|
||||
detail.source = rs.getString(++i);
|
||||
detail.description = rs.getString(++i);
|
||||
detail.amount = rs.getBigDecimal(++i);
|
||||
if (rs.wasNull()) {
|
||||
detail.amount = new BigDecimal(0);
|
||||
}
|
||||
final var categoryId = rs.getInt(++i);
|
||||
detail.regex = rs.getString(++i);
|
||||
detail.flags = rs.getInt(++i);
|
||||
detail.requiredSource = rs.getString(++i);
|
||||
detail.extraDescription = rs.getString(++i);
|
||||
final var category = categoryMap.get(categoryId);
|
||||
if (category != null && //
|
||||
detail.isAfter(startDate) && //
|
||||
detail.isBefore(endDate) && //
|
||||
category.isIncluded()) {
|
||||
category.addDetail(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final var rootCategory = new ReportCategory(-1, null, "root");
|
||||
for (final Integer categoryId : categoryMap.keySet()) {
|
||||
final var category = categoryMap.get(categoryId);
|
||||
if ((category != null) && (category.getParent() == null)) {
|
||||
rootCategory.addChild(category);
|
||||
}
|
||||
}
|
||||
out.print(Util.getResourceAsString("categoryCsvHead.template"));
|
||||
generateCategoryTable(out, rootCategory, 0, monthCount);
|
||||
}
|
||||
}
|
||||
catch (final SQLException | NamingException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateCategoryTable(final PrintWriter out, final ReportCategory parent,
|
||||
final int level, final int monthCount) throws IOException {
|
||||
final var categoryGrandAverage = parent.getGrandAverage(monthCount);
|
||||
final var categoryGrandAmount = parent.getGrandTotal();
|
||||
var categoryCsvLine = Util.getResourceAsString("categoryCsv.template") //
|
||||
.replace("${name}", parent.getQualifiedName()) //
|
||||
.replace("${id}", parent.getId().toString()) //
|
||||
.replace("${average}", categoryGrandAverage.toString()) //
|
||||
.replace("${total}", categoryGrandAmount.toString()) //
|
||||
.replace("${indent}", String.valueOf(level * 20) + "px");
|
||||
parent.getLargestMonth();
|
||||
for (int month = 0; month < 12; month++) {
|
||||
categoryCsvLine = categoryCsvLine //
|
||||
.replace("${total-" + month + "}", parent.getMonthGrandTotal(month).toString());
|
||||
}
|
||||
out.print(categoryCsvLine);
|
||||
final var categories = parent.getChildCategories();
|
||||
for (final ReportCategory category : categories) {
|
||||
generateCategoryTable(out, category, level + 1, monthCount);
|
||||
}
|
||||
}
|
||||
|
||||
interface CategoryFilter {
|
||||
boolean include(ReportCategory category);
|
||||
}
|
||||
|
||||
private Map<Integer, ReportCategory> loadCategories(final Connection connection, final String year,
|
||||
final boolean includeIncome, final boolean includePayments,
|
||||
final boolean includeInvestments, final boolean includeExpenses)
|
||||
throws SQLException, IOException {
|
||||
ReportCategory unknownCategory = null;
|
||||
final Map<Integer, ReportCategory> categories = new HashMap<>();
|
||||
final var sql = Util.getResourceAsString("getCategories.sql").replace("${databaseName}",
|
||||
"budget");
|
||||
try (var stmt = connection.prepareStatement(sql)) {
|
||||
try (var rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
final Integer id = rs.getInt(1);
|
||||
Integer parentId = rs.getInt(2);
|
||||
if (rs.wasNull()) {
|
||||
parentId = null;
|
||||
}
|
||||
final var name = rs.getString(3);
|
||||
final var category = new ReportCategory(id, parentId, name);
|
||||
categories.put(id, category);
|
||||
if ("unknown".equals(name) && parentId == null) {
|
||||
unknownCategory = category;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final Integer categoryId : categories.keySet()) {
|
||||
final var category = categories.get(categoryId);
|
||||
category.updateParent(categories);
|
||||
}
|
||||
if (unknownCategory == null) {
|
||||
unknownCategory = new ReportCategory(-1, null, "unknown");
|
||||
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();
|
||||
if ("income".equals(categoryName) || "refunds".equals(categoryName)) {
|
||||
if (!includeIncome) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ("payment".equals(categoryName)) {
|
||||
if (!includePayments) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ("investment".equals(categoryName)) {
|
||||
if (!includeInvestments) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!includeExpenses) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
final boolean included = filter.include(category);
|
||||
category.setIncluded(included);
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.stephenschafer.budget.web;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.DateFormat;
|
||||
|
|
@ -94,6 +95,13 @@ public class CategoriesPage extends HttpServlet {
|
|||
response.sendError(400, "Invalid date");
|
||||
return;
|
||||
}
|
||||
final Calendar startCal = new GregorianCalendar();
|
||||
startCal.setTime(startDate);
|
||||
final int startMonth = startCal.get(Calendar.MONTH);
|
||||
final Calendar endCal = new GregorianCalendar();
|
||||
endCal.setTime(endDate);
|
||||
final int endMonth = endCal.get(Calendar.MONTH);
|
||||
final int monthCount = endMonth - startMonth + 1;
|
||||
final var out = response.getWriter();
|
||||
response.setHeader("Content-Type", "text/html");
|
||||
try (var connection = Configuration.INSTANCE.getConnection()) {
|
||||
|
|
@ -118,13 +126,16 @@ public class CategoriesPage extends HttpServlet {
|
|||
try (var stmt = connection.prepareStatement(sql)) {
|
||||
try (var rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
final var detail = new Detail();
|
||||
final var detail = new ReportDetail();
|
||||
var i = 0;
|
||||
detail.transactionId = rs.getInt(++i);
|
||||
detail.date = rs.getDate(++i);
|
||||
detail.source = rs.getString(++i);
|
||||
detail.description = rs.getString(++i);
|
||||
detail.amount = rs.getBigDecimal(++i);
|
||||
if (rs.wasNull()) {
|
||||
detail.amount = new BigDecimal(0);
|
||||
}
|
||||
final var categoryId = rs.getInt(++i);
|
||||
detail.regex = rs.getString(++i);
|
||||
detail.flags = rs.getInt(++i);
|
||||
|
|
@ -140,7 +151,7 @@ public class CategoriesPage extends HttpServlet {
|
|||
}
|
||||
}
|
||||
}
|
||||
final var rootCategory = new Category(-1, null, "root");
|
||||
final var rootCategory = new ReportCategory(-1, null, "root");
|
||||
for (final Integer categoryId : categoryMap.keySet()) {
|
||||
final var category = categoryMap.get(categoryId);
|
||||
if ((category != null) && (category.getParent() == null)) {
|
||||
|
|
@ -155,7 +166,7 @@ public class CategoriesPage extends HttpServlet {
|
|||
out.println(grandHead);
|
||||
out.println("</thead>");
|
||||
out.println("<tbody>");
|
||||
generateCategoryTable(out, rootCategory, 0);
|
||||
generateCategoryTable(out, rootCategory, 0, monthCount);
|
||||
out.println("</tbody>");
|
||||
out.println("</table>");
|
||||
out.println("</div>");
|
||||
|
|
@ -168,15 +179,15 @@ public class CategoriesPage extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
private void generateCategoryTable(final PrintWriter out, final Category parent,
|
||||
final int level) throws IOException {
|
||||
private void generateCategoryTable(final PrintWriter out, final ReportCategory parent,
|
||||
final int level, final int monthCount) throws IOException {
|
||||
final var details = parent.getDetails();
|
||||
final var categoryAmount = parent.getDetailTotal();
|
||||
final var categoryGrandAverage = parent.getGrandAverage(monthCount);
|
||||
final var categoryGrandAmount = parent.getGrandTotal();
|
||||
var categoryHeadHtml = Util.getResourceAsString("categoryHead.html") //
|
||||
.replace("${name}", parent.getName()) //
|
||||
.replace("${id}", parent.getId().toString()) //
|
||||
.replace("${amount}", categoryAmount.toString()) //
|
||||
.replace("${average}", categoryGrandAverage.toString()) //
|
||||
.replace("${total}", categoryGrandAmount.toString()) //
|
||||
.replace("${indent}", String.valueOf(level * 20) + "px");
|
||||
final int largestMonth = parent.getLargestMonth();
|
||||
|
|
@ -195,7 +206,7 @@ public class CategoriesPage extends HttpServlet {
|
|||
out.print(categoryDetailHeadHtml);
|
||||
}
|
||||
Collections.sort(details);
|
||||
for (final Detail detail : details) {
|
||||
for (final ReportDetail detail : details) {
|
||||
final var categoryDetailHtml = Util.getResourceAsString("categoryDetail.html") //
|
||||
.replace("${source}", detail.source) //
|
||||
.replace("${description}", detail.description) //
|
||||
|
|
@ -214,8 +225,8 @@ public class CategoriesPage extends HttpServlet {
|
|||
out.println("</td>");
|
||||
out.println("</tr>");
|
||||
final var categories = parent.getChildCategories();
|
||||
for (final Category category : categories) {
|
||||
generateCategoryTable(out, category, level + 1);
|
||||
for (final ReportCategory category : categories) {
|
||||
generateCategoryTable(out, category, level + 1, monthCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,15 +263,15 @@ public class CategoriesPage extends HttpServlet {
|
|||
}
|
||||
|
||||
interface CategoryFilter {
|
||||
boolean include(Category category);
|
||||
boolean include(ReportCategory category);
|
||||
}
|
||||
|
||||
private Map<Integer, Category> loadCategories(final Connection connection, final String year,
|
||||
private Map<Integer, ReportCategory> loadCategories(final Connection connection, final String year,
|
||||
final boolean includeIncome, final boolean includePayments,
|
||||
final boolean includeInvestments, final boolean includeExpenses)
|
||||
throws SQLException, IOException {
|
||||
Category unknownCategory = null;
|
||||
final Map<Integer, Category> categories = new HashMap<>();
|
||||
ReportCategory unknownCategory = null;
|
||||
final Map<Integer, ReportCategory> categories = new HashMap<>();
|
||||
final var sql = Util.getResourceAsString("getCategories.sql").replace("${databaseName}",
|
||||
"budget");
|
||||
try (var stmt = connection.prepareStatement(sql)) {
|
||||
|
|
@ -272,7 +283,7 @@ public class CategoriesPage extends HttpServlet {
|
|||
parentId = null;
|
||||
}
|
||||
final var name = rs.getString(3);
|
||||
final var category = new Category(id, parentId, name);
|
||||
final var category = new ReportCategory(id, parentId, name);
|
||||
categories.put(id, category);
|
||||
if ("unknown".equals(name) && parentId == null) {
|
||||
unknownCategory = category;
|
||||
|
|
@ -285,7 +296,7 @@ public class CategoriesPage extends HttpServlet {
|
|||
category.updateParent(categories);
|
||||
}
|
||||
if (unknownCategory == null) {
|
||||
unknownCategory = new Category(-1, null, "unknown");
|
||||
unknownCategory = new ReportCategory(-1, null, "unknown");
|
||||
categories.put(unknownCategory.getId(), unknownCategory);
|
||||
}
|
||||
for (final Integer categoryId : categories.keySet()) {
|
||||
|
|
|
|||
|
|
@ -1,29 +1,33 @@
|
|||
package com.stephenschafer.budget.web;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Category {
|
||||
public class ReportCategory {
|
||||
private final Integer id;
|
||||
private final Integer parentId;
|
||||
private final String name;
|
||||
private Category parent;
|
||||
private ReportCategory parent;
|
||||
private boolean included;
|
||||
private List<Detail> details = new ArrayList<>();
|
||||
private final Map<Integer, Category> children = new HashMap<>();
|
||||
private List<ReportDetail> details = new ArrayList<>();
|
||||
private Set<Integer> months = null;
|
||||
private final Map<Integer, ReportCategory> children = new HashMap<>();
|
||||
|
||||
public Category(final Integer id, final Integer parentId, final String name) {
|
||||
public ReportCategory(final Integer id, final Integer parentId, final String name) {
|
||||
this.id = id;
|
||||
this.parentId = parentId;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void updateParent(final Map<Integer, Category> categories) {
|
||||
public void updateParent(final Map<Integer, ReportCategory> categories) {
|
||||
if (parentId == null) {
|
||||
parent = null;
|
||||
}
|
||||
|
|
@ -33,15 +37,15 @@ public class Category {
|
|||
}
|
||||
}
|
||||
|
||||
public void addChild(final Category category) {
|
||||
public void addChild(final ReportCategory category) {
|
||||
children.put(category.id, category);
|
||||
}
|
||||
|
||||
public Category getParent() {
|
||||
public ReportCategory getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(final Category parent) {
|
||||
public void setParent(final ReportCategory parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
|
|
@ -67,25 +71,25 @@ public class Category {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
public List<Detail> getDetails() {
|
||||
public List<ReportDetail> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(final List<Detail> details) {
|
||||
public void setDetails(final List<ReportDetail> details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public void addDetail(final Detail detail) {
|
||||
public void addDetail(final ReportDetail detail) {
|
||||
details.add(detail);
|
||||
}
|
||||
|
||||
public Map<Integer, Category> getChildren() {
|
||||
public Map<Integer, ReportCategory> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public BigDecimal getDetailTotal() {
|
||||
var amount = new BigDecimal(0);
|
||||
for (final Detail detail : details) {
|
||||
for (final ReportDetail detail : details) {
|
||||
amount = amount.add(detail.amount);
|
||||
}
|
||||
return amount;
|
||||
|
|
@ -93,7 +97,7 @@ public class Category {
|
|||
|
||||
public BigDecimal getMonthTotal(final int month) {
|
||||
var amount = new BigDecimal(0);
|
||||
for (final Detail detail : details) {
|
||||
for (final ReportDetail detail : details) {
|
||||
final Calendar cal = new GregorianCalendar();
|
||||
cal.setTime(detail.date);
|
||||
if (month == cal.get(Calendar.MONTH)) {
|
||||
|
|
@ -103,6 +107,21 @@ public class Category {
|
|||
return amount;
|
||||
}
|
||||
|
||||
public Set<Integer> getMonths() {
|
||||
Set<Integer> months = this.months;
|
||||
if (months == null) {
|
||||
months = new HashSet<>();
|
||||
for (final ReportDetail detail : details) {
|
||||
final Calendar cal = new GregorianCalendar();
|
||||
cal.setTime(detail.date);
|
||||
final int month = cal.get(Calendar.MONTH);
|
||||
months.add(Integer.valueOf(month));
|
||||
}
|
||||
this.months = months;
|
||||
}
|
||||
return months;
|
||||
}
|
||||
|
||||
public BigDecimal getGrandTotal() {
|
||||
var total = getDetailTotal();
|
||||
for (final Integer categoryId : children.keySet()) {
|
||||
|
|
@ -112,6 +131,14 @@ public class Category {
|
|||
return total;
|
||||
}
|
||||
|
||||
public BigDecimal getGrandAverage(final int monthCount) {
|
||||
final BigDecimal grandTotal = getGrandTotal();
|
||||
if (monthCount == 0) {
|
||||
return new BigDecimal(0);
|
||||
}
|
||||
return grandTotal.divide(new BigDecimal(monthCount), RoundingMode.HALF_DOWN);
|
||||
}
|
||||
|
||||
public BigDecimal getMonthGrandTotal(final int month) {
|
||||
var total = getMonthTotal(month);
|
||||
for (final Integer categoryId : children.keySet()) {
|
||||
|
|
@ -139,8 +166,8 @@ public class Category {
|
|||
return maxCount > 1 ? -1 : maxMonth;
|
||||
}
|
||||
|
||||
public List<Category> getChildCategories() {
|
||||
final List<Category> categories = new ArrayList<>();
|
||||
public List<ReportCategory> getChildCategories() {
|
||||
final List<ReportCategory> categories = new ArrayList<>();
|
||||
for (final Integer categoryId : children.keySet()) {
|
||||
final var category = children.get(categoryId);
|
||||
categories.add(category);
|
||||
|
|
@ -3,7 +3,7 @@ package com.stephenschafer.budget.web;
|
|||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
class Detail implements Comparable<Detail> {
|
||||
class ReportDetail implements Comparable<ReportDetail> {
|
||||
int transactionId;
|
||||
String source;
|
||||
String description;
|
||||
|
|
@ -15,7 +15,7 @@ class Detail implements Comparable<Detail> {
|
|||
String extraDescription;
|
||||
|
||||
@Override
|
||||
public int compareTo(final Detail arg1) {
|
||||
public int compareTo(final ReportDetail arg1) {
|
||||
final var arg0 = this;
|
||||
int comparison;
|
||||
if (arg0.date == null) {
|
||||
1
src/main/resources/categoryCsv.template
Normal file
1
src/main/resources/categoryCsv.template
Normal file
|
|
@ -0,0 +1 @@
|
|||
${name},${average}, ${total}, ${total-0}, ${total-1}, ${total-2}, ${total-3}, ${total-4}, ${total-5}, ${total-6}, ${total-7}, ${total-8}, ${total-9}, ${total-10}, ${total-11}
|
||||
1
src/main/resources/categoryCsvHead.template
Normal file
1
src/main/resources/categoryCsvHead.template
Normal file
|
|
@ -0,0 +1 @@
|
|||
Category, Average, Total, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<h2 style="cursor: pointer; font-weight: normal;" onclick="showDetail(this, ${id})">${name}</h2>
|
||||
</td>
|
||||
<td style="padding-left: 15px; text-align: right; ">
|
||||
<h2 style="font-weight: normal; ">${amount}</h2>
|
||||
<h2 style="font-weight: normal; ">${average}</h2>
|
||||
</td>
|
||||
<td style="padding-left: 15px; text-align: right; ">
|
||||
<h2 style="font-weight: normal; ">${total}</h2>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Amount</th>
|
||||
<th>Average</th>
|
||||
<th>Total</th>
|
||||
<th>Jan</th>
|
||||
<th>Feb</th>
|
||||
|
|
|
|||
Loading…
Reference in a new issue