package com.alexanderlogistics; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.imixs.marty.team.TeamService; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.exceptions.QueryException; import org.imixs.workflow.faces.data.WorkflowController; import org.imixs.workflow.office.forms.AnalyticController; import org.imixs.workflow.office.forms.AnalyticEvent; import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; /** * Der AGLAnalyticController berechnet verschiedene Analyse Daten * * @author rsoika * */ @Named @RequestScoped public class AGLAnalyticControllerSteuer implements Serializable { private static final long serialVersionUID = 1L; private static Logger logger = Logger.getLogger(AnalyticController.class.getName()); @Inject protected DocumentService documentService; @Inject TeamService teamService; @Inject protected WorkflowController workflowController; @Inject protected DebitorStatistikController debitorStatistikController; List steuerbescheide = null; public void onEvent(@Observes AnalyticEvent event) { if (!"workitem".equals(event.getWorkitem().getType()) || !event.getWorkitem().getModelVersion().startsWith("analyse-steuer")) { // no op return; } ItemCollection process = documentService .load(workflowController.getWorkitem().getItemValueString("process.ref")); String link = "/pages/workitems/worklist.xhtml"; if (process != null) { link = link + "?processref=" + process.getUniqueID() + "&workflowgroup=Steuerbescheid"; } if ("analytic.steuer.eust".equals(event.getKey())) { Object eustTotal = computeSteuerbescheideEUST(); event.getData().setItemValue("value", eustTotal); event.getData().setItemValue("label", "EUR"); event.getData().setItemValue("description", "Gesamt Einfuhrumsatzsteuer (EUSt)"); event.getWorkitem().setItemValue("eust.total", eustTotal); event.getWorkitem().setItemValue("link", link); } if ("analytic.steuer.zoll".equals(event.getKey())) { event.getData().setItemValue("value", computeSteuerbescheideZoll()); event.getData().setItemValue("label", "EUR"); event.getData().setItemValue("description", "Gesamt Zölle (ZOLLEU)"); event.getWorkitem().setItemValue("link", link); } if ("analytic.steuer.total".equals(event.getKey())) { event.getData().setItemValue("value", computeSteuerbescheideTotal()); event.getData().setItemValue("label", "EUR"); event.getData().setItemValue("description", "Alle Steuerbescheide die noch nicht abgerechnet wurden."); event.getWorkitem().setItemValue("link", link); } if ("analytic.steuer.saldo".equals(event.getKey())) { event.getData().setItemValue("value", computeSteuerbescheideSaldo()); event.getData().setItemValue("label", "EUR"); event.getData().setItemValue("description", "Saldo aller Steuerbescheide die noch nicht abgerechnet wurden."); event.getWorkitem().setItemValue("link", link); } if ("analytic.steuer.due".equals(event.getKey())) { event.getData().setItemValue("value", computeSteuerbescheideSaldoDue()); event.getData().setItemValue("label", "EUR"); event.getData().setItemValue("description", "Saldo aller überfälligen Steuerbescheide die noch nicht abgerechnet wurden."); // event.getWorkitem().setItemValue("link", link + "&task=Überfällig"); event.getWorkitem().setItemValue("link", link + "&task=1200"); } // Charts if ("analytic.steuer.trend".equals(event.getKey())) { event.getData().setItemValue("value", accumulateSteuerbescheideByWeek()); event.getData().setItemValue("label", "Zoll & EUST"); event.getData().setItemValue("description", "Fälligkeiten nach KW"); } if ("analytic.steuer.trend.abteilung".equals(event.getKey())) { event.getData().setItemValue("value", accumulateSteuerbescheideAbteilungByWeek()); event.getData().setItemValue("label", "Nach Abteilungen"); event.getData().setItemValue("description", "Offene Salden nach KW pro Abteilung"); } } /** * Berechnet die Summe aller offenen EUST Beträge // tax.code=[0], * amount=[3885.16], activity.type=[EUST], // filenumber=[LA-PAP-2408-024]} * * @return */ private double computeSteuerbescheideEUST() { logger.fine("compute EUST..."); List list = getSteuerbescheide(); double result = 0; for (ItemCollection steuerItemCol : list) { result = result + getZollBySteuerBeleg(steuerItemCol, "EUST"); } return InvoiceUtil.round(result); } /** * Berechnet die Summe aller offenen Zoll Beträge * * @return */ private double computeSteuerbescheideZoll() { logger.fine("compute Zoll..."); List list = getSteuerbescheide(); double result = 0; for (ItemCollection steuerItemCol : list) { result = result + getZollBySteuerBeleg(steuerItemCol, "ZOLL"); } return InvoiceUtil.round(result); } /** * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 * * @return */ private double computeSteuerbescheideSaldo() { logger.fine("compute saldo..."); List list = getSteuerbescheide(); double result = 0; for (ItemCollection steuerItemCol : list) { result = result + steuerItemCol.getItemValueDouble("invoice.saldo"); } return InvoiceUtil.round(result); } /** * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 * * @return */ private double computeSteuerbescheideTotal() { logger.fine("compute total..."); List list = getSteuerbescheide(); double result = 0; for (ItemCollection steuerItemCol : list) { result = result + steuerItemCol.getItemValueDouble("invoice.total.net"); } return InvoiceUtil.round(result); } /** * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 * * @return */ private double computeSteuerbescheideSaldoDue() { logger.fine("compute EUST..."); List list = getSteuerbescheide(); double result = 0; for (ItemCollection steuerItemCol : list) { if (steuerItemCol.getTaskID() == 1200) { result = result + steuerItemCol.getItemValueDouble("invoice.saldo"); } } return InvoiceUtil.round(result); } /** * Berechnet die summ zoll/eust für einen Beleg * * @param steuerItemCol * @return */ private double getZollBySteuerBeleg(ItemCollection steuerItemCol, String type) { double result = 0; List positionsTabelle = InvoiceUtil.explodeChildList(steuerItemCol); for (ItemCollection pos : positionsTabelle) { if (type.equalsIgnoreCase(pos.getItemValueString("activity.type"))) { result = result + pos.getItemValueDouble("amount"); } } return result; } private List getSteuerbescheide() { if (steuerbescheide != null) { return steuerbescheide; } // compute steuerbescheide.... logger.info("Load Steuerbescheide for analytics...."); String query = "(type:workitem ) AND ($workflowgroup:\"Steuerbescheid\")"; try { steuerbescheide = documentService.find(query, 999, 0, "$created", false); return steuerbescheide; } catch (QueryException e) { logger.severe("Failed to get invoices: " + e.getMessage()); } return null; } /** * Berechnet Chart daten aller Steuerbescheide bei KW * *
     *
       chartCfg = {
    	  type: 'bar',
    	  data: {
    		datasets: [{
    		  data: [20, 10],
    		}],
    		labels: ['1', '2']
    	  }
        }
     *
     * 
* * @return */ private String accumulateSteuerbescheideByWeek() { logger.fine("accumulate steuer by week..."); List list = getSteuerbescheide(); Map saldoByWeek = new HashMap<>(); Calendar calendar = Calendar.getInstance(); int earliestWeek = Integer.MAX_VALUE; int latestWeek = Integer.MIN_VALUE; // Step 1: Group saldo by week for (ItemCollection steuerItemCol : list) { double saldo = steuerItemCol.getItemValueDouble("invoice.saldo"); double total = steuerItemCol.getItemValueDouble("invoice.total.net"); Date date = steuerItemCol.getItemValueDate("invoice.date"); // Get the week number and year from the date calendar.setTime(date); int weekNumber = calendar.get(Calendar.WEEK_OF_YEAR); int year = calendar.get(Calendar.YEAR); int yearWeek = year * 100 + weekNumber; // Unique key for each year + week combination // Accumulate the saldo per week SteuerData steuerDataByWeek = saldoByWeek.getOrDefault(yearWeek, new SteuerData()); double eust = getZollBySteuerBeleg(steuerItemCol, "EUST"); double zoll = getZollBySteuerBeleg(steuerItemCol, "ZOLL"); steuerDataByWeek.add(eust, zoll, total, saldo, ""); saldoByWeek.put(yearWeek, steuerDataByWeek); // Track the earliest and latest weeks earliestWeek = Math.min(earliestWeek, yearWeek); latestWeek = Math.max(latestWeek, yearWeek); } // Step 2: Prepare the JSON structure and ensure we have all weeks from earliest // to latest JsonArrayBuilder dataBuilderSaldo = Json.createArrayBuilder(); JsonArrayBuilder dataBuilderEust = Json.createArrayBuilder(); JsonArrayBuilder dataBuilderZoll = Json.createArrayBuilder(); JsonArrayBuilder labelsBuilder = Json.createArrayBuilder(); Calendar startCal = Calendar.getInstance(); startCal.set(Calendar.YEAR, earliestWeek / 100); startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); Calendar endCal = Calendar.getInstance(); endCal.set(Calendar.YEAR, latestWeek / 100); endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); while (!startCal.after(endCal)) { int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); int year = startCal.get(Calendar.YEAR); int yearWeek = year * 100 + weekNumber; // Add the saldo for the week or 0.0 if no data SteuerData steuerData = saldoByWeek.getOrDefault(yearWeek, new SteuerData()); dataBuilderSaldo.add(steuerData.saldo); dataBuilderEust.add(steuerData.eust); dataBuilderZoll.add(steuerData.zoll); labelsBuilder.add(weekNumber + "/" + year); // Label as 'week/year' // Move to the next week startCal.add(Calendar.WEEK_OF_YEAR, 1); } // Step 3: Prepare the JSON structure JsonObjectBuilder jsonBuilder = Json .createObjectBuilder().add("type", "bar").add("data", Json .createObjectBuilder().add("datasets", Json.createArrayBuilder() .add(Json.createObjectBuilder() // First dataset for 'tax' .add("label", "Zoll").add("backgroundColor", "#96f").add("borderWidth", 1) .add("data", dataBuilderZoll)) .add(Json.createObjectBuilder() // Second dataset for 'eust' .add("label", "EUSt").add("backgroundColor", "#36a2eb").add("borderWidth", 1) .add("data", dataBuilderEust)) .add(Json.createObjectBuilder() // Third dataset for 'Saldo' .add("label", "Saldo").add("backgroundColor", "#ff6384").add("borderWidth", 1) .add("data", dataBuilderSaldo))) .add("labels", labelsBuilder)); // Step 4: Return the JSON structure as a string JsonObject chartCfg = jsonBuilder.build(); return chartCfg.toString(); } /** * Berechnet Chart daten aller Steuerbescheide bei KW * *
     *
       chartCfg = {
    	  type: 'bar',
    	  data: {
    		datasets: [{
    		  data: [20, 10],
    		}],
    		labels: ['1', '2']
    	  }
        }
     *
     * 
* * @return */ private String accumulateSteuerbescheideAbteilungByWeek() { logger.fine("accumulate steuer by Abteilung week..."); String[] colors = { "#36a2eb", "#ff6384", "#4bc0c0", "#ff9f40", "#96f", "#ffcd56", "#c9cbcf" }; List list = getSteuerbescheide(); Map totalByWeek = new HashMap<>(); Calendar calendar = Calendar.getInstance(); int earliestWeek = Integer.MAX_VALUE; int latestWeek = Integer.MIN_VALUE; List abteilungsListe = new ArrayList<>(); // Step 1: Group total by week for (ItemCollection steuerItemCol : list) { String abteilung = steuerItemCol.getItemValueString("space.name"); if (abteilung.isEmpty()) { abteilung = "Keine Zuordnung"; } if (!abteilungsListe.contains(abteilung)) { abteilungsListe.add(abteilung); } double saldo = steuerItemCol.getItemValueDouble("invoice.saldo"); double total = steuerItemCol.getItemValueDouble("invoice.total.net"); Date date = steuerItemCol.getItemValueDate("invoice.date"); logger.fine(".... " + steuerItemCol.getItemValueString("$workflowSummary") + " = " + total); // Get the week number and year from the date calendar.setTime(date); int weekNumber = calendar.get(Calendar.WEEK_OF_YEAR); int year = calendar.get(Calendar.YEAR); int yearWeek = year * 100 + weekNumber; String key = abteilung + "~" + yearWeek;// Unique key Import Forest~202405 // Accumulate the total per week SteuerData steuerDataByWeek = totalByWeek.getOrDefault(key, new SteuerData()); double eust = getZollBySteuerBeleg(steuerItemCol, "EUST"); double zoll = getZollBySteuerBeleg(steuerItemCol, "ZOLL"); steuerDataByWeek.add(eust, zoll, total, saldo, abteilung); totalByWeek.put(key, steuerDataByWeek); // Track the earliest and latest weeks earliestWeek = Math.min(earliestWeek, yearWeek); latestWeek = Math.max(latestWeek, yearWeek); } // Step 2: Prepare the JSON structure and ensure we have all weeks from earliest // to latest JsonArrayBuilder labelsBuilder = Json.createArrayBuilder(); Calendar startCal = Calendar.getInstance(); startCal.set(Calendar.YEAR, earliestWeek / 100); startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); Calendar endCal = Calendar.getInstance(); endCal.set(Calendar.YEAR, latestWeek / 100); endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); // Build labels - je eines pro woche while (!startCal.after(endCal)) { int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); int year = startCal.get(Calendar.YEAR); labelsBuilder.add(weekNumber + "/" + year); // Label as 'week/year' // Move to the next week startCal.add(Calendar.WEEK_OF_YEAR, 1); } // Step 3: Baue die Balken pro abrteilung JsonArrayBuilder datasets = Json.createArrayBuilder(); JsonObjectBuilder jsonBuilder = Json.createObjectBuilder().add("type", "bar"); JsonObjectBuilder dataDing = Json.createObjectBuilder(); int iColor = 0; for (String _abteilung : abteilungsListe) { JsonArrayBuilder dataBuilderTotals = Json.createArrayBuilder(); startCal = Calendar.getInstance(); startCal.set(Calendar.YEAR, earliestWeek / 100); startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); endCal = Calendar.getInstance(); endCal.set(Calendar.YEAR, latestWeek / 100); endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); while (!startCal.after(endCal)) { int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); int year = startCal.get(Calendar.YEAR); int yearWeek = year * 100 + weekNumber; String key = _abteilung + "~" + yearWeek;// Unique key Import Forest~202405 // Add the total for the week or 0.0 if no data SteuerData steuerData = totalByWeek.getOrDefault(key, new SteuerData()); logger.fine(" " + yearWeek + "=" + steuerData.total); dataBuilderTotals.add(steuerData.total); // Move to the next week startCal.add(Calendar.WEEK_OF_YEAR, 1); } datasets.add(Json.createObjectBuilder() // First dataset for 'tax' .add("label", _abteilung).add("backgroundColor", colors[iColor]).add("borderWidth", 1) .add("data", dataBuilderTotals)); iColor++; if (iColor > colors.length) { iColor = 0; } } dataDing.add("datasets", datasets); dataDing.add("labels", labelsBuilder); jsonBuilder.add("data", dataDing); // Step 4: Return the JSON structure as a string JsonObject chartCfg = jsonBuilder.build(); return chartCfg.toString(); } /** * Helper method to hold zoll and eust totals in an object. Needed to build * totals per week. */ class SteuerData { double total = 0.0; double zoll = 0.0; double eust = 0.0; double saldo = 0.0; String abteilung = ""; public SteuerData() { // default constructor } /** * Adds new zoll and eust values and updates the total * * @param eustNew eust value to be added * @param zollNew zoll value to be added */ public void add(double eustNew, double zollNew, double totalNew, double saldoNew, String abteilungNew) { eust = eust + eustNew; zoll = zoll + zollNew; saldo = saldo + saldoNew; total = total + totalNew; abteilung = abteilungNew; } } }