472 lines
No EOL
18 KiB
Java
472 lines
No EOL
18 KiB
Java
package com.alexanderlogistics.datev;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.text.ParseException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.logging.Logger;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
import org.imixs.marty.team.TeamService;
|
|
import org.imixs.workflow.FileData;
|
|
import org.imixs.workflow.ItemCollection;
|
|
import org.imixs.workflow.SignalAdapter;
|
|
import org.imixs.workflow.datev.imports.DatevImportAdapter;
|
|
import org.imixs.workflow.engine.DocumentService;
|
|
import org.imixs.workflow.engine.WorkflowService;
|
|
import org.imixs.workflow.exceptions.AccessDeniedException;
|
|
import org.imixs.workflow.exceptions.AdapterException;
|
|
import org.imixs.workflow.exceptions.ModelException;
|
|
import org.imixs.workflow.exceptions.PluginException;
|
|
import org.imixs.workflow.exceptions.ProcessingErrorException;
|
|
import org.imixs.workflow.exceptions.QueryException;
|
|
|
|
import com.alexanderlogistics.KreditorDebitorService;
|
|
|
|
/**
|
|
* Der DatevCargosoftImportAdapter liest eine von Cargosoft bereitgestellte
|
|
* DATEV Datei ein und erzeugt Ausgangsrechnungs Workitems. Die Cargosoft DATEV
|
|
* Datei enthält auch Eingangsrechnugen, welche von diesem Adapter ignoriert
|
|
* werden.
|
|
* <p>
|
|
* In der Spalte Belegart-4 steht das Fäligkeitsdatum.
|
|
* <p>
|
|
* Beim Import wird auch der Kreditor geprüft und aus diesem das
|
|
* Länderkennzeichen übernommen.
|
|
* <p>
|
|
* Zusätzlich wird anhand der 1. Positionsnummer eine Zuordnung zu einem Bereich
|
|
* gemacht. Dazu werden aus den Bereichen die Konfigurations Items 'pos.mapping'
|
|
* ausgelesen, welche Regular Expressions enthalten können. Gibt es einen match,
|
|
* wird der Space der Rechnung zugeordnet.
|
|
*
|
|
* @version 1.0
|
|
* @author rsoika
|
|
*/
|
|
public class DatevCargosoftImportAdapter implements SignalAdapter {
|
|
|
|
private static Logger logger = Logger.getLogger(DatevCargosoftImportAdapter.class.getName());
|
|
|
|
public static final String ENCODING = "ISO-8859-1";
|
|
public static final String CHILD_ITEM_PROPERTY = "_ChildItems";
|
|
public static final String REGEX_IMPORTTEXTPATTERN = "^R[1-8] .*$";
|
|
|
|
@Inject
|
|
WorkflowService workflowService;
|
|
|
|
@Inject
|
|
DocumentService documentService;
|
|
|
|
@Inject
|
|
KreditorDebitorService kreditorDebitorService;
|
|
|
|
@Inject
|
|
TeamService teamService;
|
|
|
|
/**
|
|
* This method finds or create the Datev Export and adds a reference
|
|
* ($workitemref) to the current invoice.
|
|
*
|
|
* @throws PluginException
|
|
*/
|
|
@Override
|
|
public ItemCollection execute(ItemCollection workitem, ItemCollection event)
|
|
throws AdapterException, PluginException {
|
|
|
|
logger.info("...parse datev file import...");
|
|
try {
|
|
|
|
// Datei einlesen
|
|
List<String> log = readData(workitem);
|
|
workitem.setItemValue("import.log", log);
|
|
} catch (ModelException e) {
|
|
throw new AdapterException(PluginException.class.getSimpleName(), DatevImportAdapter.DATEV_IMPORT_ERROR,
|
|
"Failed to import datev data!", e);
|
|
|
|
}
|
|
return workitem;
|
|
}
|
|
|
|
/**
|
|
* Liest die datenzeilen ab Zeile 3 aus und bildet worktiems gruppiert nach
|
|
* belegnummer
|
|
*
|
|
* <p>
|
|
* Belegfeld-1 ist hier ein eindeutiger Schlüssel für die Erzeugung von
|
|
* Splitbuchungen
|
|
*
|
|
* @param document
|
|
* @throws PluginException
|
|
* @throws ModelException
|
|
* @throws ProcessingErrorException
|
|
* @throws AccessDeniedException
|
|
*/
|
|
private List<String> readData(ItemCollection workitem)
|
|
throws PluginException, AccessDeniedException, ProcessingErrorException, ModelException {
|
|
List<String> logBuffer = new ArrayList<String>();
|
|
int line = 0;
|
|
int positions = 0;
|
|
int invoices = 0;
|
|
int dupplicates = 0;
|
|
int rueckstellungen = 0;
|
|
int eingangsrechnungen = 0;
|
|
|
|
String dataLine;
|
|
// Start Datum Wirtschaftsjahr berechnen (JJJJMMTT)
|
|
String wjBeginn = workitem.getItemValueString("datev.WJ-Beginn");
|
|
String wjBeginnMonat = wjBeginn.substring(4, 6);
|
|
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd");
|
|
|
|
// load spaces Pos Expressions
|
|
Map<String, List<String>> spaces = loadSpacesConfig();
|
|
|
|
|
|
ItemCollection invoiceWorkitem = null;
|
|
List<ItemCollection> splitBuchungen = new ArrayList<ItemCollection>();
|
|
|
|
FileData fileData = workitem.getFileData().get(0);
|
|
ByteArrayInputStream imputStream = new ByteArrayInputStream(fileData.getContent());
|
|
|
|
log(logBuffer, "Importdatei: " + fileData.getName());
|
|
try {
|
|
BufferedReader in = new BufferedReader(new InputStreamReader(imputStream, ENCODING));
|
|
|
|
// skip header
|
|
in.readLine();
|
|
in.readLine();
|
|
|
|
// read content....
|
|
line = 0;
|
|
int blockSize = 0;
|
|
while ((dataLine = in.readLine()) != null) {
|
|
line++;
|
|
if (blockSize >= 100) {
|
|
blockSize = 0;
|
|
logger.info(line + " entries read....");
|
|
}
|
|
|
|
// alle werte parsen
|
|
String[] header1List = dataLine.split(";(?=([^\"]*\"[^\"]*\")*[^\"]*$)", 99);
|
|
if (header1List.length < 6) {
|
|
throw new PluginException(DatevCargosoftImportAdapter.class.getName(),
|
|
DatevImportAdapter.DATEV_IMPORT_ERROR, "Invalid data in line " + line);
|
|
}
|
|
|
|
String belegNummer = DatevImportAdapter.csvVal(header1List[10]);
|
|
String gegenKonto = DatevImportAdapter.csvVal(header1List[7]);
|
|
String text = DatevImportAdapter.csvVal(header1List[13]);
|
|
String buchungsTag = DatevImportAdapter.csvVal(header1List[9]);
|
|
// cargosoft laesst gerne die führende 0 weg....
|
|
if (buchungsTag.length() == 3) {
|
|
buchungsTag = "0" + buchungsTag;
|
|
}
|
|
// berechne Buchungsdatum DDMM
|
|
String tag = buchungsTag.substring(0, 2);
|
|
String monat = buchungsTag.substring(2, 4);
|
|
String jahr = wjBeginn.substring(0, 4);
|
|
// compute next year if start of WJ is not January
|
|
if (Integer.parseInt(wjBeginnMonat) > Integer.parseInt(monat)) {
|
|
jahr = Integer.parseInt(jahr + 1) + "";
|
|
}
|
|
Date dateBuchung = dateFormatter.parse(jahr + monat + tag);
|
|
// Fälligkeit
|
|
Date dueDate = new Date();
|
|
try {
|
|
dateFormatter.parse(DatevImportAdapter.csvVal(header1List[23]));
|
|
} catch (Exception e) {
|
|
dueDate = new Date();
|
|
}
|
|
|
|
String paymentTerm = DatevImportAdapter.csvVal(header1List[31]);
|
|
String waehrung = DatevImportAdapter.csvVal(header1List[2]);
|
|
|
|
// kurse
|
|
double kurs = parseBetrag(DatevImportAdapter.csvVal(header1List[3]));
|
|
double basisumsatz = parseBetrag(DatevImportAdapter.csvVal(header1List[4]));
|
|
|
|
// Skip Eingangsrechnungen
|
|
if (!gegenKonto.startsWith("1")) {
|
|
eingangsrechnungen++;
|
|
// skip
|
|
continue;
|
|
}
|
|
|
|
// Skip Rückstellung
|
|
// text darf nicht mit R1 bis R8 beginnen
|
|
// ^R[1-8] .
|
|
if (text.matches(REGEX_IMPORTTEXTPATTERN)) {
|
|
rueckstellungen++;
|
|
// skip
|
|
continue;
|
|
}
|
|
|
|
positions++;
|
|
logger.info("...read dbtr: " + gegenKonto + " beleg-nr: " + belegNummer);
|
|
|
|
// Erzeuge einen Buchungssatz
|
|
ItemCollection dataItemCol = new ItemCollection();
|
|
dataItemCol.setItemValue("datev.text", text);
|
|
dataItemCol.setItemValue("datev.umsatz", parseBetrag(DatevImportAdapter.csvVal(header1List[0])));
|
|
dataItemCol.setItemValue("datev.shzeichen", DatevImportAdapter.csvVal(header1List[1]));
|
|
dataItemCol.setItemValue("datev.wkz", waehrung);
|
|
dataItemCol.setItemValue("datev.kurs", parseBetrag(DatevImportAdapter.csvVal(header1List[3])));
|
|
dataItemCol.setItemValue("datev.basisumsatz", parseBetrag(DatevImportAdapter.csvVal(header1List[4])));
|
|
dataItemCol.setItemValue("datev.konto", DatevImportAdapter.csvVal(header1List[6]));
|
|
|
|
// Haben wir schon eine Invoice Workitem angelegt?
|
|
if (invoiceWorkitem == null) {
|
|
logger.info("...create new workitem....");
|
|
// erzeuge ein neues
|
|
try {
|
|
invoiceWorkitem = createEmptyInvoice(belegNummer, text, gegenKonto, dateBuchung, dueDate,
|
|
paymentTerm, waehrung, kurs, basisumsatz);
|
|
invoices++;
|
|
} catch (AdapterException e) {
|
|
dupplicates++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// ist es noch die selbe Belegnummer?
|
|
if (belegNummer.equals(invoiceWorkitem.getItemValueString("invoice.number"))) {
|
|
// adde die Splitbuchung
|
|
splitBuchungen.add(dataItemCol);
|
|
logger.info("...add splitbuchung.2...");
|
|
} else {
|
|
logger.info("...close workitem....");
|
|
|
|
// nein - also invoice schließen
|
|
closeInvoiceWorkitem(invoiceWorkitem, splitBuchungen,spaces);
|
|
invoiceWorkitem = null;
|
|
// neue Splitbuchungstabelle anlegen
|
|
splitBuchungen = new ArrayList<ItemCollection>();
|
|
// invoice zurücksetzten
|
|
// erzeuge ein neues
|
|
try {
|
|
invoiceWorkitem = createEmptyInvoice(belegNummer, text, gegenKonto, dateBuchung, dueDate,
|
|
paymentTerm, waehrung, kurs, basisumsatz);
|
|
invoices++;
|
|
} catch (AdapterException e) {
|
|
dupplicates++;
|
|
continue;
|
|
}
|
|
splitBuchungen.add(dataItemCol);
|
|
}
|
|
|
|
} // wile end
|
|
closeInvoiceWorkitem(invoiceWorkitem, splitBuchungen,spaces);
|
|
|
|
} catch (IOException | ParseException e) {
|
|
throw new PluginException(DatevCargosoftImportAdapter.class.getName(),
|
|
DatevImportAdapter.DATEV_IMPORT_ERROR, "Unable to read file", e);
|
|
}
|
|
|
|
log(logBuffer, line + " Zeilen eingelesen ");
|
|
log(logBuffer, positions + " Rechnungspositionen eingelesen ");
|
|
log(logBuffer, invoices + " Rechnungen importiert ");
|
|
|
|
if (rueckstellungen > 0) {
|
|
log(logBuffer, rueckstellungen + " Rückstellungen wurden übersprungen.");
|
|
}
|
|
if (eingangsrechnungen > 0) {
|
|
log(logBuffer, eingangsrechnungen + " Eingangsrechnungen wurden übersprungen.");
|
|
}
|
|
|
|
if (dupplicates > 0) {
|
|
log(logBuffer, dupplicates + " Rechnungen wurden übersprungen da diese bereits importiert wurden!");
|
|
}
|
|
|
|
// return log
|
|
return logBuffer;
|
|
|
|
}
|
|
|
|
/**
|
|
* Helper method that loads all expressions from the spaces
|
|
* @return List of list of expressions
|
|
*/
|
|
private Map<String,List<String>> loadSpacesConfig() {
|
|
Map<String,List<String>> result =new HashMap<>();
|
|
// als erstes lesen wir die pos.mappings aus den Spaces ein, auf die dann eine
|
|
// Rechnung emapped werden kann.
|
|
List<ItemCollection> spaces_subs = teamService.getSpaces();
|
|
for (ItemCollection stub: spaces_subs) {
|
|
// load full workitem
|
|
ItemCollection space = documentService.load(stub.getUniqueID());
|
|
if (space!=null) {
|
|
result.put(space.getUniqueID(),space.getItemValueList("pos.mapping", String.class));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method returns a new empty invoice ItemCollection.
|
|
* <p>
|
|
* The method tests if an invoice with this number already exits. If so, the
|
|
* method throws a AdapterException
|
|
*
|
|
* @param belegNummer
|
|
* @param text
|
|
* @param gegenKonto
|
|
* @param dateBuchung
|
|
* @param dueDate
|
|
* @param paymentTerm
|
|
* @param currency
|
|
* @param kurs
|
|
* @param basisUmsatz
|
|
* @return
|
|
* @throws AdapterException
|
|
*/
|
|
private ItemCollection createEmptyInvoice(String belegNummer, String text, String gegenKonto, Date dateBuchung,
|
|
Date dueDate, String paymentTerm, String currency, double kurs, double basisUmsatz)
|
|
throws AdapterException {
|
|
|
|
// test if invoice was already imported before
|
|
String sQuery = "((type:workitem OR type:workitemarchive) AND $modelversion:rechnungsausgang* AND invoice.number:\""
|
|
+ belegNummer + "\")";
|
|
try {
|
|
// find the textblock...
|
|
List<ItemCollection> result = documentService.find(sQuery, 1, 0);
|
|
if (result.size() > 0) {
|
|
throw new AdapterException(DatevCargosoftImportAdapter.class.getName(), "DUPPLICATE",
|
|
"Invoice already imported");
|
|
}
|
|
} catch (QueryException e) {
|
|
logger.warning("failed to search invoices by query: " + e.getMessage());
|
|
}
|
|
|
|
ItemCollection invoiceWorkitem = new ItemCollection();
|
|
invoiceWorkitem.setItemValue("invoice.number", belegNummer);
|
|
invoiceWorkitem.setItemValue("invoice.currency", currency);
|
|
invoiceWorkitem.setItemValue("invoice.text", text);
|
|
invoiceWorkitem.setItemValue("invoice.date", dateBuchung);
|
|
invoiceWorkitem.setItemValue("dbtr.number", gegenKonto);
|
|
invoiceWorkitem.setItemValue("invoice.duedate", dueDate);
|
|
invoiceWorkitem.setItemValue("invoice.reminder", dueDate);
|
|
invoiceWorkitem.setItemValue("payment.term", paymentTerm);
|
|
|
|
invoiceWorkitem.setItemValue("invoice.rate", kurs);
|
|
invoiceWorkitem.setItemValue("invoice.base.amount", basisUmsatz);
|
|
|
|
return invoiceWorkitem;
|
|
}
|
|
|
|
private void log(List<String> log, String message) {
|
|
log.add(new Date() + ": " + message + "\n");
|
|
}
|
|
|
|
/**
|
|
* . entfernen , mit punkt ersetzten
|
|
*
|
|
* @param value
|
|
* @return
|
|
*/
|
|
public Double parseBetrag(String value) {
|
|
|
|
value = value.replace(".", "");
|
|
value = value.replace(",", ".");
|
|
|
|
return new Double(value);
|
|
}
|
|
|
|
/**
|
|
* Diese Hilfsmethod schließt das Invoice Workitem ab
|
|
*
|
|
* @throws ModelException
|
|
* @throws PluginException
|
|
* @throws ProcessingErrorException
|
|
* @throws AccessDeniedException
|
|
*/
|
|
private void closeInvoiceWorkitem(ItemCollection invoiceWorkitem, List<ItemCollection> splitBuchungen,
|
|
Map<String, List<String>> expressionsMap)
|
|
throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException {
|
|
if (invoiceWorkitem == null) {
|
|
return; // no op
|
|
}
|
|
|
|
// berechne Total:
|
|
double total = 0;
|
|
for (ItemCollection zeile : splitBuchungen) {
|
|
if ("H".equals(zeile.getItemValueString("datev.shzeichen"))) {
|
|
total = total + zeile.getItemValueDouble("datev.umsatz");
|
|
} else {
|
|
total = total - zeile.getItemValueDouble("datev.umsatz");
|
|
}
|
|
}
|
|
|
|
invoiceWorkitem.setItemValue("invoice.total", total);
|
|
invoiceWorkitem.setItemValue("invoice.saldo", total);
|
|
// implode die Splitbuchungen
|
|
implodeChildList(invoiceWorkitem, splitBuchungen);
|
|
|
|
// set dbtr.name
|
|
ItemCollection dbtrItemCol = kreditorDebitorService
|
|
.findDebitor(invoiceWorkitem.getItemValueString("dbtr.number"));
|
|
if (dbtrItemCol != null) {
|
|
invoiceWorkitem.setItemValue("dbtr.name", dbtrItemCol.getItemValueString("_VENDOR_Name"));
|
|
// Anhand der Creditoren Stammdaten errrechnen wir die Sprache für diese
|
|
// Rechnung
|
|
String country = dbtrItemCol.getItemValueString("_VENDOR_COUNTRY");
|
|
invoiceWorkitem.setItemValue("dbtr.country", country);
|
|
if (country.equalsIgnoreCase("de") || country.equalsIgnoreCase("ch") || country.equalsIgnoreCase("at")) {
|
|
invoiceWorkitem.setItemValue("invoice.language", "DE");
|
|
} else {
|
|
invoiceWorkitem.setItemValue("invoice.language", "EN");
|
|
}
|
|
}
|
|
|
|
// Jezt noch das Space mapping
|
|
// dazu kucken wir in alle spaces den config wert 'pos.mapping'
|
|
String text = invoiceWorkitem.getItemValueString("invoice.text");
|
|
if (!text.isEmpty()) {
|
|
for (Map.Entry<String, List<String> > entry : expressionsMap.entrySet()) {
|
|
List<String> expressionsList=entry.getValue();
|
|
for (String expr : expressionsList) {
|
|
|
|
Pattern p = Pattern.compile(expr);
|
|
Matcher m = p.matcher(text);
|
|
if (m.find()) {
|
|
// we have a match
|
|
invoiceWorkitem.setItemValue("space.ref", entry.getKey());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Processe die Invoice
|
|
invoiceWorkitem.model("rechnungsausgang-de-1.0").task(5001).event(980);
|
|
workflowService.processWorkItem(invoiceWorkitem);
|
|
|
|
}
|
|
|
|
/**
|
|
* Convert the List of ItemCollections back into a List of Map elements
|
|
*
|
|
* @param workitem
|
|
*/
|
|
@SuppressWarnings({ "rawtypes" })
|
|
private void implodeChildList(ItemCollection workitem, List<ItemCollection> childItems) {
|
|
List<Map> mapOrderItems = new ArrayList<Map>();
|
|
// convert the child ItemCollection elements into a List of Map
|
|
if (childItems != null) {
|
|
logger.fine("Convert child items into Map...");
|
|
// iterate over all order items..
|
|
for (ItemCollection orderItem : childItems) {
|
|
mapOrderItems.add(orderItem.getAllItems());
|
|
}
|
|
workitem.replaceItemValue(CHILD_ITEM_PROPERTY, mapOrderItems);
|
|
}
|
|
}
|
|
|
|
} |