package com.alexanderlogistics; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.plugins.AbstractPlugin; import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.QueryException; /** * Das InvoicePlugin prüft die Eingaben auf gültigkeit, so dass diese problemlos * nach Cargosoft expoertier werden können. *

* Konkret geht es darum, das im Status 5200 in den ChildItems keine leeren * Zeilen vorkommen dürfen. *

* Im Status 5000 muss immer der payment.type ausgewählt werden. Dies wird vom * Plugin explizit geprüft *

* Das Plugin prüft auch auf doppelte Rechnungsnummern. Dabei wird Systemweit * geprüft ob die Rechnungsnummer schon einmal vorkam. Falls ja, wird eine * Warnung ausgegeben. Der Benutzer kann diese dann skippen. * * * @author rsoika * @version 1.0 * */ public class InvoicePlugin extends AbstractPlugin { public static final int TASK_ERFASSUNG = 5001; public static final int TASK_SACHPRUEFUNG = 5200; public static final int EVENT_FREIGEBEN = 20; public static final String CHILD_ITEM_PROPERTY = "_ChildItems"; public static final String ERROR_MISSING_DATA = "MISSING_DATA"; public static final String ERROR_DUPPLICATE_INVOICE_NUMBER = "DUPPLICATE_INVOICE_NUMBER"; // XX-YYY-####-### public static final String REGEX_POSNUMER = "([A-Z]{2}-[A-Z]{3}-[0-9]{4}-[0-9]{3})"; @SuppressWarnings("unused") private static Logger logger = Logger.getLogger(InvoicePlugin.class.getName()); /** * Test childworkitems for empty lines * * @throws PluginException - if data is missing * **/ @Override public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException { // skip if validaten tag is required=false ItemCollection evalItemCollection = this.getWorkflowService().evalWorkflowResult(event, "validation", workitem); if (evalItemCollection != null) { // evaluate the validation rules... if ("false".equalsIgnoreCase(evalItemCollection.getItemValueString("required"))) { return workitem; } } boolean isPublicEvent = !("0".equals(event.getItemValueString("keypublicresult"))); // Payment.type muss immer eingetragne werden! if (isPublicEvent && workitem.getTaskID() >= TASK_ERFASSUNG && workitem.getEventID() < 900) { if (workitem.getItemValueString("payment.type").trim().isEmpty()) { // throw a plugin exception! throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Eingabefehler - Bitte wählen Sie eine Zahlungsart aus!"); } // doppelte Rechnungsnummer prüfen if (workitem.getTaskID() == TASK_ERFASSUNG ) { validateInvoiceNumber(workitem); } if (workitem.getTaskID() == TASK_ERFASSUNG || workitem.getTaskID() == TASK_SACHPRUEFUNG) { // Buchungsperiode auf plausi prüfen validateBuchungsperiode(workitem); // Cargosoft Kreditorennnummer prüfen validateCargosoftCdtrNumber(workitem); } } // die prüfung der positionsnummern und Category erfolgt nur im Status // Sachprüfung (5200) und nur beim Freigeben (20)! if (workitem.getTaskID() == TASK_SACHPRUEFUNG && workitem.getEventID() == EVENT_FREIGEBEN) { List childs = explodeChildList(workitem); for (ItemCollection posItem : childs) { if (posItem.getItemValueString("name").trim().isEmpty()) { // throw a plugin exception - because name is missing! throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Eingabefehler - Die Positionsnummer in Zeile " + posItem.getItemValueString("numpos") + " muss aufgefüllt sein!"); } // validate pos for regex pattern 'XX-YYY-####-###' if (!posItem.getItemValueString("name").matches(REGEX_POSNUMER)) { // throw a plugin exception - because name is missing! throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Eingabefehler - Die Positionsnummer in Zeile " + posItem.getItemValueString("numpos") + " muss im Format 'XX-YYY-####-###' eingeben werden!"); } if (posItem.getItemValueString("category").trim().isEmpty()) { // throw a plugin exception - because name is missing! throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Eingabefehler - Die Leistungsart in Zeile " + posItem.getItemValueString("numpos") + " muss aufgefüllt sein!"); } if (posItem.getItemValueString("tax").trim().isEmpty()) { // throw a plugin exception - because name is missing! throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Eingabefehler - Die Steuer in Zeile " + posItem.getItemValueString("numpos") + " muss aufgefüllt sein!"); } if (posItem.getItemValueFloat("amount") == 0) { // throw a plugin exception - because name is missing! throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Eingabefehler - Der Nettobetrag in in Zeile " + posItem.getItemValueString("numpos") + " darf nicht 0 sein!"); } } } return workitem; } /** * Diese Method prüft die Buchungsperiode auf Plausibilität * * YYYY(+1)01-12 * * z.b. 202110 oder 202107 oder 202201 * * @param workitem * @throws PluginException */ private void validateBuchungsperiode(ItemCollection workitem) throws PluginException { String period = workitem.getItemValueString("invoice.period"); // buchungsperionde nur prüfen wenn noch nicht archiviert if (!period.isEmpty() && "workitem".equals(workitem.getType())) { LocalDate localDate = LocalDate.now(); int year = localDate.getYear(); // build regex.... String regex = "(" + year + "|" + (year + 1) + ")(1[0-2]|0[1-9])"; if (!period.matches(regex)) { // throw a plugin exception - because name is missing! throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Eingabefehler - Bitte geben Sie ein gültige Buchungsperiode ein!"); } } } /** * Diese Method prüft ob die "invoice.number" bereits einmal im * Rechnugnsworkflow vergeben wurde. Falls ja wird eine PluginExcpetion * ausgelöst. *

* Query Example: *

* NOT $uniqueid:"9f72fa50-4845-41ea-b6b9-ccd518c353be" AND txtcooperatespace:"9dba107e-f8ef-4150-a832-040d75a6eda7" AND invoice.number:"45" * *

* in case a duplicate invoice was detected the item invoice.number.duplicate is * filled. This item is used for a conditional event. The case is displayed as a * warning in the form. * * @throws PluginException * @throws QueryException * */ private void validateInvoiceNumber(ItemCollection workitem) throws PluginException { String invoiceNumber = workitem.getItemValueString("invoice.number"); String invoiceNumberDuplicate = workitem.getItemValueString("invoice.number.duplicate"); // wenn keine Rechnungsnummer eingegeben wurde gehts weiter! if (invoiceNumber.isEmpty()) { return; } // rechnungseingang only workitems... String query = "(type:workitem OR type:workitemarchive) AND ($modelversion:rechnungseingang*) AND NOT ($uniqueid:\"" + workitem.getUniqueID() + "\") AND (invoice.number:\"" + invoiceNumber + "\")"; try { int result = this.getWorkflowService().getDocumentService().count(query, 1); if (result > 0) { // wenn _invoicenumber_duplicate bereits gesetzt ist - dann geht es ohne prüfung // weiter if (!invoiceNumberDuplicate.isEmpty()) { logger.warning("...validateion skipped by user with duplicate invoice number: " + invoiceNumber); } else { // set _invoicenumber_duplicate - dadurch wird die warnmeldung ausgegeben und // der Vorgang nicht weitergeleitet workitem.replaceItemValue("invoice.number.duplicate", invoiceNumber); throw new PluginException(InvoicePlugin.class.getName(), ERROR_DUPPLICATE_INVOICE_NUMBER, "ACHTUNG: Die Rechnungsnummer wurde bereits gebucht. Bitte prüfen Sie den Beleg. Sollte die Belegnummer korrekt sein, wiederholen Sie die Atkion."); } } else { // clear ! workitem.appendItemValue("invoice.number.duplicate.history", workitem.getItemValueString("invoice.number.duplicate")); workitem.replaceItemValue("invoice.number.duplicate", ""); } } catch (QueryException e) { throw new PluginException(PluginException.class.getName(), "QUERY ERROR", e.getMessage(), e); } } private void validateCargosoftCdtrNumber(ItemCollection workitem) throws PluginException { String crdtrNumber = workitem.getItemValueString("cdtr.number"); // wenn keine Nummer eingegeben wurde gehts weiter! if (crdtrNumber.isEmpty()) { return; } // search creditor number in cargosoft... // die cargosoft Kreditornummer beginnt seltsamerweise mit einem K..... try { String query = "(type:cargosoftkreditor) AND (name:K" + crdtrNumber + ")"; List result = this.getWorkflowService().getDocumentService().find(query, 1, 0); if (result == null || result.size() == 0) { throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, "Die Kreditorennummer ist nicht gültig."); } else { // update cargosoft crediotr name ItemCollection cargo = result.get(0); workitem.setItemValue("cdtr.name.cargosoft", cargo.getItemValueString("_VENDOR_Name")); } } catch (QueryException e) { e.printStackTrace(); } } /** * converts the Map List of a workitem into a List of ItemCollectons */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected List explodeChildList(ItemCollection workitem) { // convert current list of childItems into ItemCollection elements ArrayList childItems = new ArrayList(); List mapOrderItems = workitem.getItemValue(CHILD_ITEM_PROPERTY); for (Object mapOderItem : mapOrderItems) { if (mapOderItem instanceof Map) { ItemCollection itemCol = new ItemCollection((Map) mapOderItem); childItems.add(itemCol); } } return childItems; } }