diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cda376a..671e411 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,13 @@ # Versionen +### 1.2.19 (Development) -### 1.2.18 (Development) + - Neue XML Invoice Schnittstelle + - Mahnwesen englisch (noch nicht begonnen!) + + +### 1.2.18 - Fix in Archiv Suche CustomSearchController.java - requestscoped diff --git a/office-alexander-logistics-app/pom.xml b/office-alexander-logistics-app/pom.xml index a128136..1a89ad9 100644 --- a/office-alexander-logistics-app/pom.xml +++ b/office-alexander-logistics-app/pom.xml @@ -6,7 +6,7 @@ office-alexander-logistics com.alexander-logistics - 1.2.18 + 1.2.19 office-alexander-logistics-app war diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java index b0241e1..8ba2c41 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java @@ -195,10 +195,8 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { } else { catSumUSD = catSumUSD + invoice.getItemValueDouble("invoice.saldo"); - } } - categoryRow.getCell(6).setCellValue(catSumEUR); categoryRow.getCell(8).setCellValue(catSumUSD); diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java index 12b2be8..062dc82 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java @@ -23,7 +23,6 @@ import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.xssf.usermodel.XSSFCell; -import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; @@ -156,8 +155,12 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { private void insertInvoiceRows(ItemCollection document, String fileName) throws PluginException, QueryException { - double saldoUSD = 0; - double saldoEUR = 0; + double saldoUSD1 = 0; + double saldoEUR1 = 0; + double saldoUSD2 = 0; + double saldoEUR2 = 0; + double saldoUSD3 = 0; + double saldoEUR3 = 0; List spaceNames = new ArrayList(); // compute all departments @@ -196,12 +199,23 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { // First we create a column for each Space..... XSSFRow rowSpacesLabels = sheet.getRow(9); - XSSFCellStyle superDuperStyle = rowSpacesLabels.getCell(0).getCellStyle(); - XSSFRow rowSpacesCurrencies = sheet.getRow(10); - XSSFRow rowValues = sheet.getRow(11); + XSSFRow referenceRowSpacesCurrencies = sheet.getRow(10); + XSSFRow referenceRowValues = sheet.getRow(11); + XSSFRow referenceRowCurrentWeek = sheet.getRow(12); + + // Aktuelle KW berechnen + calendar.setTime(new Date()); + int currentWeekNumber = calendar.get(Calendar.WEEK_OF_YEAR); + int currentYear = calendar.get(Calendar.YEAR); + String currentWeekCategory = currentYear + "/"; + if (currentWeekNumber < 10) { + currentWeekCategory = currentWeekCategory + "0" + currentWeekNumber; + } else { + currentWeekCategory = currentWeekCategory + currentWeekNumber; + } XSSFCell cellSpaceLabelReference = rowSpacesLabels.getCell(2); - XSSFCell cellSpaceCurrencyReference = rowSpacesCurrencies.getCell(1); + XSSFCell cellSpaceCurrencyReference = referenceRowSpacesCurrencies.getCell(1); int column = 1; for (ItemCollection space : spaces) { XSSFCell cell = null; @@ -210,13 +224,13 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { spaceNames.add(spaceName); // Space currency EUR - cell = rowSpacesCurrencies.getCell(column); + cell = referenceRowSpacesCurrencies.getCell(column); cell.copyCellFrom(cellSpaceCurrencyReference, new CellCopyPolicy()); cell.setCellValue("EUR"); // Space currency USD column++; - cell = rowSpacesCurrencies.getCell(column); + cell = referenceRowSpacesCurrencies.getCell(column); cell.copyCellFrom(cellSpaceCurrencyReference, new CellCopyPolicy()); cell.setCellValue("USD"); @@ -255,11 +269,31 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { Collections.sort(sortedWeekList); int rowPos = 11; + + /* + * Nun müssen wir so viele Zeilen nach unten schieben wie wir gleich einfügen. + * Dazu müssen wir kurz einen Probelauf machen um die geplante Anzahl zu + * errechnen.... + */ + int insertCount = sortedWeekList.size(); + sheet.shiftRows(rowPos + 1, 2999, insertCount, true, true); + + /* + * Jetzt Zeile für Zeile einfügen.... + */ + boolean ueberFaellig = true; for (String week : sortedWeekList) { // now create a new line.. rowPos++; + logger.info("--> Zeile " + rowPos + " Week=" + week); XSSFRow row = sheet.createRow(rowPos); - row.copyRowFrom(rowValues, new CellCopyPolicy()); + + if (week.equals(currentWeekCategory)) { + row.copyRowFrom(referenceRowCurrentWeek, new CellCopyPolicy()); + ueberFaellig = false; + } else { + row.copyRowFrom(referenceRowValues, new CellCopyPolicy()); + } // insert values row.getCell(0).setCellValue(week); @@ -275,8 +309,21 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { if (weekData != null) { logger.info(" EUR=" + weekData.totalEUR + " USD=" + weekData.totalUSD); row.getCell(column).setCellValue(weekData.totalEUR); + column++; row.getCell(column).setCellValue(weekData.totalUSD); + saldoUSD3 = InvoiceUtil.round(saldoUSD3 + weekData.totalUSD); + + if (ueberFaellig) { + saldoEUR1 = InvoiceUtil.round(saldoEUR1 + weekData.totalEUR); + saldoUSD1 = InvoiceUtil.round(saldoUSD1 + weekData.totalUSD); + + } else { + saldoEUR2 = InvoiceUtil.round(saldoEUR2 + weekData.totalEUR); + saldoUSD2 = InvoiceUtil.round(saldoUSD2 + weekData.totalUSD); + + } + column++; } else { @@ -286,14 +333,27 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { } } - // update style - // if (currentWeek.equals(week)) { - // for (int i = 0; i < 15; i++) { - // row.getCell(i).setCellStyle(superDuperStyle); - // } - // } - } + // delete reference rows + logger.info("..shift 12 to " + rowPos); + sheet.shiftRows(12, rowPos, -1, true, true); + sheet.shiftRows(rowPos + 2, rowPos + 6, -3, true, true); + + // now lets update the totals... + // Überfällig + XSSFCell cTotalEUR1 = getCellByRef(doc, sheet, "TOTALEUR1"); + XSSFCell cTotalUSD1 = getCellByRef(doc, sheet, "TOTALUSD1"); + cTotalEUR1.setCellValue(saldoEUR1); + cTotalUSD1.setCellValue(saldoUSD1); + XSSFCell cTotalEUR2 = getCellByRef(doc, sheet, "TOTALEUR2"); + XSSFCell cTotalUSD2 = getCellByRef(doc, sheet, "TOTALUSD2"); + cTotalEUR2.setCellValue(saldoEUR2); + cTotalUSD2.setCellValue(saldoUSD2); + + XSSFCell cTotalEUR3 = getCellByRef(doc, sheet, "TOTALEUR3"); + XSSFCell cTotalUSD3 = getCellByRef(doc, sheet, "TOTALUSD3"); + cTotalEUR3.setCellValue(InvoiceUtil.round(saldoEUR1 + saldoEUR2)); + cTotalUSD3.setCellValue(InvoiceUtil.round(saldoUSD1 + saldoUSD2)); // write back the file ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java index f7d72c1..c865cc1 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java @@ -132,8 +132,8 @@ public class OPListExportScheduler implements Scheduler { } /** - * Prüfen ob es Rechnugen in diesem Space gibt die entweder die 2. Mahnstufe - * erreicht habne oder deren Fälligkeit um mehr als 21 Tage überschritten ist. + * Prüfen ob es Rechnungen in diesem Space gibt die entweder die 2. Mahnstufe + * erreicht haben oder deren Fälligkeit um mehr als 21 Tage überschritten ist. * * @param space * @return diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportImportService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportImportService.java new file mode 100644 index 0000000..3b9eb6e --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportImportService.java @@ -0,0 +1,297 @@ +/* + * Imixs-Workflow + * + * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, + * http://www.imixs.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You can receive a copy of the GNU General Public + * License at http://www.gnu.org/licenses/gpl.html + * + * Project: + * https://www.imixs.org + * https://github.com/imixs/imixs-workflow + * + * Contributors: + * Imixs Software Solutions GmbH - Project Management + * Ralph Soika - Software Developer + */ + +package com.alexanderlogistics.xml; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.logging.Logger; + +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.enterprise.event.Observes; + +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPSClient; +import org.imixs.archive.importer.DocumentImportEvent; +import org.imixs.archive.importer.DocumentImportService; +import org.imixs.workflow.ItemCollection; +import org.imixs.workflow.engine.ModelService; +import org.imixs.workflow.engine.WorkflowService; +import org.imixs.workflow.exceptions.AccessDeniedException; +import org.imixs.workflow.exceptions.ModelException; +import org.imixs.workflow.exceptions.PluginException; +import org.imixs.workflow.exceptions.ProcessingErrorException; + +/** + * Der CargosoftXMLInvoiceImportImportService erweitert den FTPImportService und + * kann XML Invoice Dokumente von Cargosoft importieren. + * Der Service reagiert auf DocumentImportEvents und kann über das Options Feld + * konfiguriert werden. + *

+ * Im Gegensatz zum FTPImportService werden die XML Dateien nach einem + * speziellen + * Verfahren geparsed und dann als Ausgangsrechnungen importiert. + * Der Service ersetzt den alten DATEV Import. Falls eine Rechnung schon + * existiert (Rechnungsnummer) wird der Datensatz vom FTP Laufwerk gelöscht und + * nicht importiert. + *

+ * In den Optionen muss die option "mandant.id" hinterlegt sein. Stimmt + * diese beim Import nicht mit der Mandanten Nummer in der XML Datei überein, + * wird das XML Dokument gelöscht und nicht importiert. + * + * + * + * @author rsoika + * + */ +@Stateless +public class CargosoftXMLInvoiceImportImportService { + + private static Logger logger = Logger.getLogger(CargosoftXMLInvoiceImportImportService.class.getName()); + public static final String OPTION_MANDANT_ID = "mandant.id"; + + @EJB + WorkflowService workflowService; + + @EJB + ModelService modelService; + + @EJB + DocumentImportService documentImportService; + + /** + * This method reacts on a CDI ImportEvent and reads documents form a ftp + * server. + * + * + */ + public void onEvent(@Observes DocumentImportEvent event) { + + // check if source is already completed + if (event.getResult() == DocumentImportEvent.PROCESSING_COMPLETED) { + logger.finest("...... import source already completed - no processing will be performed."); + return; + } + + if (!"CARGOSOFT_INVOICE_XML".equalsIgnoreCase(event.getSource().getItemValueString("type"))) { + // ignore data source + logger.finest("...... type '" + event.getSource().getItemValueString("type") + "' skipped."); + return; + } + + Properties sourceOptions = documentImportService.getOptionsProperties(event.getSource()); + // Read Mandand ID form Option list + String mandantID = sourceOptions.getProperty(OPTION_MANDANT_ID, ""); + if (mandantID == null || mandantID.isEmpty()) { + documentImportService.logMessage("...CARGOSOFT_INVOICE_XML Import failed - missing mandant.id in options!", + event); + event.setResult(DocumentImportEvent.PROCESSING_ERROR); + return; + } + + documentImportService.logMessage("...CARGOSOFT_INVOICE_XML Import started: mandant.id=" + mandantID, + event); + String ftpServer = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_SERVER); + String ftpPort = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_PORT); + String ftpUser = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_USER); + String ftpPassword = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_PASSWORD); + String ftpPath = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_SELECTOR); + + if (!ftpPath.startsWith("/") && !ftpPath.startsWith("./")) { + ftpPath = "/" + ftpPath; + } + if (!ftpPath.endsWith("/")) { + ftpPath = ftpPath + "/"; + } + + // if no server is given we exit + if (ftpServer.isEmpty()) { + logger.warning("...... no server specified!"); + return; + } + + if (ftpPort.isEmpty()) { + // set default port + ftpPort = "21"; + } + + FTPClient ftpClient = null; + try { + logger.finest("......read directories ..."); + + documentImportService.logMessage("...connecting to FTP server: " + ftpServer, event); + + // TLS + ftpClient = new FTPSClient("TLS", false); + ftpClient.setControlEncoding("UTF-8"); + ftpClient.connect(ftpServer, Integer.parseInt(ftpPort)); + if (ftpClient.login(ftpUser, ftpPassword) == false) { + documentImportService.logMessage("FTP file transfer failed: login failed!", event); + event.setResult(DocumentImportEvent.PROCESSING_ERROR); + return; + } + + ftpClient.enterLocalPassiveMode(); + logger.finest("...... FileType=" + FTP.BINARY_FILE_TYPE); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + ftpClient.setControlEncoding("UTF-8"); + + // try to enter the working directory.... + boolean bWorkingDir = ftpClient.changeWorkingDirectory(ftpPath); + if (bWorkingDir == true) { + + documentImportService.logMessage("...working directory: " + ftpClient.printWorkingDirectory(), event); + + // scann directories... + + FTPFile[] subDirectories = ftpClient.listDirectories(ftpPath); + + for (FTPFile ftpSubDirectory : subDirectories) { + ItemCollection space = null; + + String ftpSubDirectoryName = ftpSubDirectory.getName(); + + logger.info("......scan files from path=" + ftpPath + + ftpSubDirectory.getName()); + + /* + * Nun importieren wir alle Files + */ + + FTPFile[] allFiles = ftpClient.listFiles(ftpPath + ftpSubDirectory.getName()); + + /* + * create a new workitem for each document + */ + + int count = 0; + if (allFiles.length > 0) { + documentImportService.logMessage("..." + allFiles.length + " files found ", event); + ftpClient.changeWorkingDirectory(ftpPath + ftpSubDirectory.getName()); + + for (FTPFile file : allFiles) { + // if this is a directory or symlink then we do ignore this entry + if (!file.isFile()) { + documentImportService.logMessage( + "...'" + file.getName() + "' is not a valid file, object will be ignored!", + event); + continue; + } + logger.info("import file " + file.getName() + "..."); + // String fullFileName = ftpPath + "/" + file.getName(); + try (ByteArrayOutputStream is = new ByteArrayOutputStream();) { + ftpClient.retrieveFile(file.getName(), is); + byte[] rawData = is.toByteArray(); + if (rawData != null && rawData.length > 0) { + logger.finest("......file '" + file.getName() + "' successfull read - bytes size = " + + rawData.length); + // create new workitem + createWorkitem(event.getSource(), file.getName(), rawData, space); + // documentImportService.logMessage("....imported '" + file.getName() + "'", + // event); + count++; + } else { + documentImportService.logMessage("...Warning - invalid file content '" + + file.getName() + "' - file will be deleted!", event); + } + // finally delete the file.... + ftpClient.deleteFile(file.getName()); + } catch (AccessDeniedException | ProcessingErrorException | PluginException + | ModelException e) { + + documentImportService.logMessage("...FTP import failed: " + e.getMessage(), event); + event.setResult(DocumentImportEvent.PROCESSING_ERROR); + return; + } + } + documentImportService.logMessage("..." + count + " new files imported.", event); + + } else { + documentImportService.logMessage("...no files found, directory '" + ftpPath + "' is empty", + event); + } + } + } else { + documentImportService.logMessage("...failed to change into working directory: " + ftpPath, event); + } + + } catch (IOException e) { + logger.severe("FTP I/O Error: " + e.getMessage()); + int r = ftpClient.getReplyCode(); + logger.severe("FTP ReplyCode=" + r); + + documentImportService.logMessage("...FTP file transfer failed (replyCode=" + r + ") : " + e.getMessage(), + event); + event.setResult(DocumentImportEvent.PROCESSING_ERROR); + return; + + } finally { + // do logout.... + try { + + ftpClient.logout(); + ftpClient.disconnect(); + } catch (IOException e) { + documentImportService.logMessage("...FTP file transfer failed: " + e.getMessage(), event); + event.setResult(DocumentImportEvent.PROCESSING_ERROR); + return; + } + } + + // completed + event.setResult(DocumentImportEvent.PROCESSING_COMPLETED); + + } + + /** + * Creates and processes a new workitem with a given xml information from the + * filedata + * + * + * @return + * @throws ModelException + * @throws PluginException + * @throws ProcessingErrorException + * @throws AccessDeniedException + */ + public ItemCollection createWorkitem(ItemCollection source, String fileName, byte[] rawData, ItemCollection space) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + ItemCollection workitem = new ItemCollection(); + workitem.model(source.getItemValueString(DocumentImportService.SOURCE_ITEM_MODELVERSION)); + workitem.task(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_TASK)); + workitem.event(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_EVENT)); + workitem.setWorkflowGroup(source.getItemValueString("workflowgroup")); + + return workitem; + } + +} diff --git a/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml index 20472b8..74378cf 100644 --- a/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml +++ b/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml @@ -1,13 +1,8 @@ - + @@ -16,22 +11,22 @@ @@ -62,175 +57,189 @@

- +
-

Define a crontab to schedule the import processor.

+

Define a crontab to schedule the + import processor.

- +
- +
- +
-

Define a destination IMAP server and assign mail addresses to specific workflow groups.

+

Define a destination IMAP server and + assign mail addresses to specific workflow groups.

- + - + - +
- - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
PosTypeWorkflowgroupModelversionTaskEvent - -
PosTypeWorkflowgroupModelversionTaskEvent + +
- # - - - - - - - - - - - - - - - - - - - - - -
+ + # + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - + + onevent="updateOptions" /> - - - + + + - - -

Order ID #

+ + +

Order ID # + +

Type
- - - - + + + + + - - + +
- +
Workflowgroup
- - - - + + + - +
-
-
- +
+
+
Workflowmodel
- - + +
Task ID
- - + +
Event ID
- - + +
- +
@@ -239,13 +248,13 @@
- : :
- - + +
User
@@ -254,11 +263,11 @@
- +
Password
-
@@ -271,7 +280,8 @@ -

A selector can be an optional regular expression to be used to select a subset of data

+

A selector can be an optional regular + expression to be used to select a subset of data

@@ -287,35 +297,34 @@
- - - - + + + +
- -
- -
- -
- - - - + + + + + + + + + + - +