package com.alexanderlogistics; import java.io.Serializable; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.YearMonth; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.logging.Logger; import java.util.stream.Collectors; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.QueryException; import org.imixs.workflow.faces.data.WorkflowController; import org.imixs.workflow.office.views.SearchController; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ConversationScoped; import jakarta.faces.context.FacesContext; import jakarta.inject.Inject; import jakarta.inject.Named; /** * Der InvoiceAnalyseController dient zur Auswertung der Sachprüfungs WOrklfow * Schritte. Die Analyse Seite ist büer das Admin Menü erreichbar. * * @author rsoika * */ @Named @ConversationScoped // @SessionScoped public class DebitorStatistikController implements Serializable { private static final long serialVersionUID = 1L; private static Logger logger = Logger.getLogger(DebitorStatistikController.class.getName()); @Inject protected DocumentService documentService; TreeMap stats = null; @Inject protected WorkflowController workflowController; @Inject protected SearchController searchController; @Inject protected KreditorDebitorService kreditorDebitorService; /** * Initialize default behavior initialize an opitonal id from the query string * */ @PostConstruct public void init() { // extract the processref and page from the query string FacesContext fc = FacesContext.getCurrentInstance(); Map paramMap = fc.getExternalContext().getRequestParameterMap(); String id = paramMap.get("dbtr.number"); if (id != null && !id.isEmpty()) { workflowController.getWorkitem().setItemValue("dbtr.number", id); try { ItemCollection dbtr = kreditorDebitorService.findDebitor(id); if (dbtr != null) { workflowController.getWorkitem().setItemValue("dbtr.name", dbtr.getItemValueString("_vendor_name")); } } catch (PluginException e) { // e.printStackTrace(); } } } public ItemCollection getWorkitem() { return workflowController.getWorkitem(); } public int getZahlungsmoral() { return 0; } /** * Gibt eine bookmarkable url zurück. * Wir vom Aktualisieren button in der Page genutzt * * @return */ public String getBookmark() { return "/pages/dbtr-stats.xhtml?dbtr.number=" + getDbtNr() + "&faces-redirect=true"; } /* * Gesamt */ public String getQueryRechnungen() { String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\""; return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " gesamt&query=" + query; } public int getRechnungen() { int result = 0; if (getDbtNr().isEmpty()) { return 0; } try { result = documentService .count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\""); } catch (QueryException e) { logger.severe("Failed to get statistic: " + e.getMessage()); } return result; } /* * Bezahlt */ public String getQueryRechnungenBezahlt() { String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"; return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " bezahlt&query=" + query; } public int getRechnungenBezahlt() { int result = 0; if (getDbtNr().isEmpty()) { return 0; } try { result = documentService .count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"); } catch (QueryException e) { logger.severe("Failed to get statistic: " + e.getMessage()); } return result; } /* * Offen */ public String getQueryRechnungenOffen() { String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5000 TO 5099])"; return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " offen&query=" + query; } public int getRechnungenOffen() { int result = 0; if (getDbtNr().isEmpty()) { return 0; } try { result = documentService .count("(type:workitem) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5000 TO 5099])"); } catch (QueryException e) { logger.severe("Failed to get statistic: " + e.getMessage()); } return result; } /* * Fällig */ public String getQueryRechnungenFaellig() { String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5100 TO 5199])"; return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " fällig&query=" + query; } public int getRechnungenFaellig() { int result = 0; if (getDbtNr().isEmpty()) { return 0; } try { result = documentService .count("(type:workitem) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5100 TO 5199])"); } catch (QueryException e) { logger.severe("Failed to get statistic: " + e.getMessage()); } return result; } /* * In Mahnung */ public String getQueryRechnungenGemahnt() { String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5200 TO 5299])"; return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Mahnung&query=" + query; } public int getRechnungenGemahnt() { int result = 0; if (getDbtNr().isEmpty()) { return 0; } try { result = documentService .count("(type:workitem ) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5200 TO 5299])"); } catch (QueryException e) { logger.severe("Failed to get statistic: " + e.getMessage()); } return result; } /* * Inkasso */ public String getQueryRechnungenInkasso() { String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5300 TO 5399])"; return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Inkasso&query=" + query; } public int getRechnungenInkasso() { int result = 0; if (getDbtNr().isEmpty()) { return 0; } try { result = documentService .count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5300 TO 5399])"); } catch (QueryException e) { logger.severe("Failed to get statistic: " + e.getMessage()); } return result; } /** * Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt * * @return */ public String getDbtNr() { String dbtNr = getWorkitem().getItemValueString("dbtr.number"); if (dbtNr.startsWith("D") || dbtNr.startsWith("K")) { dbtNr = dbtNr.substring(1); } return dbtNr; } public long getDurchschnittZahlungsziel() { getStats(); int count = 0; long gesamt = 0; for (Map.Entry entry : stats.entrySet()) { DebitorStatistikData statData = entry.getValue(); if (statData.count > 0) { gesamt = gesamt + statData.getAverageDueDays(); count++; } } return Math.round((double) gesamt / count); } public long getDurchschnittZahlungsdauer() { getStats(); int count = 0; long gesamt = 0; for (Map.Entry entry : stats.entrySet()) { DebitorStatistikData statData = entry.getValue(); if (statData.count > 0) { gesamt = gesamt + statData.getAveragePaymentDays(); count++; } } return Math.round((double) gesamt / count); } /** * Sucht alle Rechnugnen aus einem Zeitraum und sammelt Zahlungsziel und * Zahlungszeitspanne gruppiert nach monaten * */ public Map getStats() { if (stats != null) { return stats; } // recompute stats = new TreeMap(); if (getDbtNr().isEmpty()) { return stats; } // invoice.date:[20210101 TO 20210201] DateFormat df = new SimpleDateFormat("yyyymmdd"); String query = "(type:workitemarchive) AND invoice.date:[20221201 TO 20500101] AND dbtr.number:" + getDbtNr() + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"; // query = query + " AND (invoice.date:[" + df.format(from) + " TO " + // df.format(to) + "])"; logger.fine("query = " + query); try { List invoices = documentService .findStubs(query, 9999, 0, "$created", false); for (ItemCollection invoice : invoices) { try { logger.fine("Rechnung: " + invoice.getUniqueID()); Date invoiceDate = invoice.getItemValueDate("invoice.date"); Date invoiceDueDate = invoice.getItemValueDate("invoice.duedate"); Date paymentDate = findPaymentDateByWorkitem(invoice); if (paymentDate == null) { logger.warning("Es wurde kein Zahlungseingang gefunden"); continue; } if (invoiceDate != null && paymentDate != null && invoiceDueDate != null) { // do we have a stats object? DebitorStatistikData statData = stats.get(getYearMonth(invoiceDate)); if (statData == null) { // create a new one statData = new DebitorStatistikData(invoiceDate); } // jetzt irgendwas ausrechnen statData.compute(invoiceDate, invoiceDueDate, paymentDate); // .. und wieder speichern stats.put(statData.toString(), statData); } else { logger.warning("No payment Date found for invoice " + invoice.getUniqueID()); } } catch (ParseException e) { logger.warning("Unable to parse payment Date for invoice " + invoice.getUniqueID()); } } } catch (QueryException e) { logger.severe("Failed to get statistic: " + e.getMessage()); } // Step 2: Sort the list based on the alphanumeric order of the keys completeMissingMonths(); return stats; } /** * Hilfsmethode ergänzt die fehlenden Monate * * @param yearMonths * @return */ private void completeMissingMonths() { if (stats == null || stats.size() < 3) { return; } // Convert the list of strings to a list of LocalDate objects List dates = new ArrayList<>(); Set yearMonths = this.stats.keySet(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM"); for (String yearMonth : yearMonths) { dates.add(YearMonth.parse(yearMonth, formatter).atDay(1)); } // Find the lowest and highest dates LocalDate lowestDate = Collections.min(dates); LocalDate highestDate = Collections.max(dates); LocalDate current = lowestDate; current = current.plusMonths(1); while (!current.isAfter(highestDate)) { // existiert der monat? String formattedDate = current.format(formatter); DebitorStatistikData entry = stats.get(formattedDate); if (entry == null) { // add missing entry stats.put(formattedDate, new DebitorStatistikData(current.getYear(), current.getMonthValue())); } current = current.plusMonths(1); } } /** * returns * * 202304 from a given date */ public String getYearMonth(Date date) { // Create a Calendar instance and set the date Calendar calendar = Calendar.getInstance(); calendar.setTime(date); // Get the year from the Calendar object int year = calendar.get(Calendar.YEAR); // Get the month from the Calendar object int month = calendar.get(Calendar.MONTH); // Increment the month by 1 since Calendar months are zero-based month++; // Convert the month to a String with leading "0" if necessary return "" + year + (month < 10 ? "0" + month : "" + month); } /** * Finds the payment.date for a invoice. * * Wir suchen alle zugeordneten Zahlungseingägne und nehmen den letzten. * * WICHTIG: Es muss ggf. der index neu aufgebaut werden, da payment.date nun ein * index feld ist * * @param invoice * @return * @throws ParseException */ public Date findPaymentDateByWorkitem(ItemCollection invoice) throws ParseException { // Wir selektieren alle Zahlungseingänge interessieren usn aber nur für den // letzten String sQuery = " (type:\"workitem\" OR type:\"workitemarchive\") " + // " AND ($workflowgroup:Zahlungseingang) AND ($workitemref:\"" + invoice.getUniqueID() + "\" )"; List workitems = null; try { workitems = documentService.findStubs(sQuery, 99, 0, "payment.date", true); if (workitems.size() > 0) { return workitems.get(0).getItemValueDate("payment.date"); } } catch (QueryException e) { e.printStackTrace(); } // no date found! return null; } /** * Finds the date when a workitem last reached the current task 5900 by reading * the eventLog * * This method is not so save as the method findPaymentDateByWorkitem that is * searching the payment workitem. * * 2023-06-28T12:40:17.437|rechnungsausgang-de-1.0|5001.50|5900| * * @param invoice * @return * @throws ParseException */ public Date findPaymentDateByLog(ItemCollection invoice) throws ParseException { // Create a SimpleDateFormat instance with the desired format SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); List logEntries = invoice.getItemValue("$eventlog"); // Iterate through the List from the last element to the first for (int i = logEntries.size() - 1; i >= 0; i--) { String entry = logEntries.get(i); if (entry.endsWith(invoice.getTaskID() + "|")) { // found! String dateString = entry.substring(0, entry.indexOf("|") - 1); // Parse the date string into a Date object Date date = sdf.parse(dateString); // Print the Date object System.out.println("Date: " + date); return date; } } // no date found! return null; } /** * Diese Methode baut die Datenstruktur für das Chart Diagram zusammen * * *
		{
		 labels: ["January", "February", "March", "April", "May", "June", "July"],
		 datasets: [{
			label: 'Dataset 1',
			//backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
			//borderColor: window.chartColors.red,
			borderWidth: 1,
			data: [
				70, 70, 70, 70, 79, 50, 50
			]
		}, {
			label: 'Dataset 2',
			//backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
			//borderColor: window.chartColors.blue,
			borderWidth: 1,
			data: [
				70, 70, 170, 7, 79, 50, 50
			]
		}]
	   }
	 * 
* * @return */ public String buildChartData() { getStats(); // build a list of all lables.... List statusLabels = new ArrayList(); Set keys = stats.keySet(); for (String _key : keys) { statusLabels.add(_key); } String result = "{"; // Lables result = result + "labels : [ "; result = result + statusLabels.stream().collect(Collectors.joining("','", "'", "'")); result = result + "],"; result = result + "datasets: ["; // Datasets 1 result = result + "{label: 'Zahlungsziel',borderWidth: 1,"; result = result + " borderColor: [\"#3B6B82\"],"; result = result + " \"backgroundColor\" : [\"#CFE9F5\"], fill: true,tension: 0.5,"; result = result + "data: ["; for (Map.Entry entry : stats.entrySet()) { result = result + entry.getValue().getAverageDueDays() + ","; } // cut last comma result = result.substring(0, result.length() - 1); result = result + "]"; result = result + "}, "; // Datasets 2 result = result + "{label: 'Zahldauer',borderWidth: 1,"; result = result + " borderColor: [\"#E73B65\"],\"backgroundColor\" : [\"#70B088\" ], tension: 0.5,fill: true,"; result = result + " trendlineLinear: { colorMin: \"red\", colorMax: \"green\", lineStyle: \"dotted\", width: 2 , projection: true },"; result = result + "data: ["; for (Map.Entry entry : stats.entrySet()) { result = result + entry.getValue().getAveragePaymentDays() + ","; } // cut last comma result = result.substring(0, result.length() - 1); result = result + "]"; result = result + "} "; // ende result = result + "] }"; return result; } }