This commit is contained in:
Ralph Soika 2024-06-12 16:34:36 +02:00
parent e3a24515f6
commit 6de7ef5921
5 changed files with 4418 additions and 85 deletions

View file

@ -171,9 +171,8 @@ public class OPListController implements Serializable {
if (rate != 0) { if (rate != 0) {
maxSaldo = maxSaldo / rate; maxSaldo = maxSaldo / rate;
invoice.setItemValue("invoice.saldo.byrate", maxSaldo); invoice.setItemValue("invoice.saldo.byrate", maxSaldo);
} else {
invoice.setItemValue("invoice.saldo.byrate", 0.0);
} }
} }
} }
@ -216,7 +215,15 @@ public class OPListController implements Serializable {
List<String> selection = workflowController.getWorkitem().getItemValue("$workitemref"); List<String> selection = workflowController.getWorkitem().getItemValue("$workitemref");
for (String id : selection) { for (String id : selection) {
if (id != null && !id.isEmpty()) { if (id != null && !id.isEmpty()) {
invoiceList.add(documentService.load(id)); ItemCollection _invoice = documentService.load(id);
// Test if we have 'invoice.base.amount' and 'invoice.rate'
if (!_invoice.hasItem("invoice.base.amount")) {
_invoice.setItemValue("invoice.base.amount", 0.0);
}
if (!_invoice.hasItem("invoice.rate")) {
_invoice.setItemValue("invoice.rate", 0.0);
}
invoiceList.add(_invoice);
} }
} }
} }

View file

@ -27,28 +27,54 @@
package com.alexanderlogistics.api; package com.alexanderlogistics.api;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Calendar; import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.ejb.Stateless; import javax.ejb.Stateless;
import javax.inject.Inject; import javax.inject.Inject;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection; import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.engine.DocumentService;
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;
import org.imixs.workflow.exceptions.QueryException; import org.imixs.workflow.exceptions.QueryException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.alexanderlogistics.xml.CargosoftXMLInvoiceImportService;
/** /**
** Dieser Service korrigiert falsch importierte Cargosoft daten. ** Dieser Service korrigiert falsch importierte Cargosoft daten.
* Ausgangsrechnungen die ein falsches Jahr haben. * Ausgangsrechnungen die ein falsches Jahr haben.
* *
* Oder DAten die über die XML Schnittstelle falsch reinkamen...
*
* @author rsoika * @author rsoika
* @version 1.1 * @version 1.1
*/ */
@ -59,10 +85,18 @@ import org.imixs.workflow.exceptions.QueryException;
public class CargosoftMigrationRestService implements Serializable { public class CargosoftMigrationRestService implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
String log = "";
int errors = 0;
int count = 0;
public static String _QUERY = "($modelversion:rechnungsausgang-de-1.0)\n" + //
"AND $created:[20240604 TO 20240613]";
@Inject @Inject
DocumentService documentService; DocumentService documentService;
@Inject
WorkflowService workflowService;
private static Logger logger = Logger.getLogger(CargosoftMigrationRestService.class.getName()); private static Logger logger = Logger.getLogger(CargosoftMigrationRestService.class.getName());
public CargosoftMigrationRestService() { public CargosoftMigrationRestService() {
@ -83,72 +117,231 @@ public class CargosoftMigrationRestService implements Serializable {
@Produces({ MediaType.TEXT_PLAIN }) @Produces({ MediaType.TEXT_PLAIN })
public String testeFehlerhafteCargosoftDaten() throws QueryException { public String testeFehlerhafteCargosoftDaten() throws QueryException {
String log; logger.info("query=" + _QUERY);
log = "Query\n" + _QUERY;
String query = "($modelversion:rechnungsausgang-de-1.0)\n" + // List<ItemCollection> result = documentService.find(_QUERY, 999, 0);
"AND $created:[20240101 TO 20240501]\n" + //
"AND invoice.date:[20230101 TO 20230501]";
logger.info("query=" + query);
log = "Query\n" + query;
List<ItemCollection> result = documentService.find(query, 100, 0);
log = log + "\n\nCount=" + result.size(); log = log + "\n\nCount=" + result.size();
if (result.size() > 999) {
log = log + "\n\nACHTUNG ES GIBT MEHR ALS 1000 RECHNUNGEN !";
}
return log; return log;
} }
/** /**
* This method loads taxonomy data for a workflow group within a given process * This method fixes the wrong amount of a imported xml invoice.
* and builds a ChartJS data structure in JSON format
* *
* @param workflowgroup * @param workflowgroup
* @param task * @param task
* @return * @return
* @throws QueryException * @throws QueryException
* @throws ModelException
* @throws PluginException
* @throws ProcessingErrorException
* @throws AccessDeniedException
*/ */
@GET @GET
@Path("/fix/{count}") @Path("/fix")
@Produces({ MediaType.TEXT_PLAIN }) @Produces({ MediaType.TEXT_PLAIN })
public String fixFehlerhafteCargosoftDaten(@PathParam("count") int count) throws QueryException { public String fixFehlerhafteCargosoftDaten()
throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException {
String log; logger.info("query=" + _QUERY);
log = "Query\n\n" + _QUERY;
String query = "($modelversion:rechnungsausgang-de-1.0)\n" + // List<ItemCollection> result = documentService.find(_QUERY, 999, 0);
"AND $created:[20240101 TO 20240501]\n" + //
"AND invoice.date:[20230101 TO 20230501]";
logger.info("query=" + query);
log = "Query\n\n" + query;
log = log + "\n\nMax Count=" + count;
List<ItemCollection> result = documentService.find(query, count, 0);
log = log + "\n\nSelection Count=" + result.size(); log = log + "\n\nSelection Count=" + result.size();
log = log + "\n\nFixes\n\n"; log = log + "\n\nFixes\n\n";
for (ItemCollection doc : result) { for (ItemCollection workitem : result) {
ItemCollection snapshot = null;
// load snapshot
String snapshotID = workitem.getItemValueString("$snapshotid");
if (!snapshotID.isEmpty()) {
snapshot = documentService.load(snapshotID);
}
Date invoiceDate = doc.getItemValueDate("invoice.date"); if (snapshot == null) {
doc.setItemValue("invoice.date.wrong", invoiceDate); logger.warning("Unable to load snapshot for document " + workitem.getUniqueID());
log = log + "Unable to load snapshot for document " + workitem.getUniqueID() + " \n";
errors++;
continue;
}
// Convert to Calendar // Get raw data....
Calendar calendar = Calendar.getInstance(); // find the XML Attachment
calendar.setTime(invoiceDate); String xmlFileName = null;
// Add one year List<String> fileNames = snapshot.getFileNames();
calendar.add(Calendar.YEAR, 1); for (String fileName : fileNames) {
// Convert back to Date if (fileName.endsWith(".xml")) {
doc.setItemValue("invoice.date", calendar.getTime()); xmlFileName = fileName;
break;
}
}
if (xmlFileName == null) {
logger.warning("Unable to load XML from document " + workitem.getUniqueID());
log = log + "Unable to load XML from document " + workitem.getUniqueID() + " \n";
errors++;
continue;
}
documentService.save(doc); FileData xmlFileData = snapshot.getFileData(xmlFileName);
log = log + doc.getUniqueID() + " " + invoiceDate + " ==> " + calendar.getTime() + "\n"; Document xmlDoc = createXMLTree(xmlFileData.getContent());
// break! migrateDocument(workitem, xmlDoc);
} }
log = log + "\n\n=== Completed! ===\n\n"; log = log + "\n\n=== Completed! " + count + " fixes! ===\n\n";
return log; return log;
} }
/**
* Diese Methode korrigiert den Saldo um die Netto summe....
*
* @param workitem
* @param xmlDoc
* @throws ModelException
* @throws PluginException
* @throws ProcessingErrorException
* @throws AccessDeniedException
*/
private void migrateDocument(ItemCollection workitem, Document doc)
throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException {
// Haben wir schon das neue Feld 'invoice.total.net' mit dem Netto Betrag?
if (workitem.hasItem("invoice.total.tax")) {
// no migration needed
return;
}
// Net und Tax ermitteln
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
workitem, "invoice.total.net", Double.class);
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VATAmount/Amount/Value",
workitem, "invoice.total.tax", Double.class);
// Der Total entspricht nun dem Net Amount.
// Wenn wir eine Tax haben, dann müssen wir den Total sowie den Saldo um
// diese Tax erhöhen.
// Damit ist dann die Migration abgeschlossen.
double tax = workitem.getItemValueDouble("invoice.total.tax");
if (tax > 0) {
log = log + "...Migriere " + workitem.getItemValueString("invoice.number") + " Korrektur Betrag " +
tax + "\n";
double total = workitem.getItemValueDouble("invoice.total");
double saldo = workitem.getItemValueDouble("invoice.saldo");
// Gutschrift?
if (total < 0) {
total = total - tax;
saldo = saldo - tax;
} else {
// Rechnung
total = total + tax;
saldo = saldo + tax;
}
workitem.setItemValue("invoice.total", total);
workitem.setItemValue("invoice.saldo", saldo);
workitem.setItemValue("cargosoft.fix.xmlerror", tax);
log = log + "....... total neu=" + total + " saldo neu=" + saldo + "\n";
if (5900 == workitem.getTaskID()) {
workitem.setEventID(942);
workflowService.processWorkItem(workitem);
} else {
// save only
documentService.save(workitem);
}
count++;
} else {
log = log + "...Keine Migration notwendig " + workitem.getItemValueString("invoice.number") +
" tax=" + tax + "\n";
}
}
/**
* Erzeugt einen XML Baum aus dem XML Raw Daten
*
* @param snapshot
* @return
*/
private Document createXMLTree(byte[] rawData) {
Document doc = null;
InputStream inputStream = new ByteArrayInputStream(rawData);
InputSource inputSource = new InputSource(inputStream);
DocumentBuilder documentBuilder;
try {
documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
doc = documentBuilder.parse(inputSource);
} catch (ParserConfigurationException | SAXException | IOException e) {
log = log + "Unable to load XML tree: " + e.getMessage() + " \n";
return null;
}
return doc;
}
/**
* Reads a tag value from the xml tree and set the value into the given
* workitem.
*
* /Invoices/Invoice/InvoiceHeader/Client/Code
*
* Beispiel Datum:
* <InvoiceDate>2024-05-13T00:00:00+02:00</InvoiceDate>
*
* @param doc - xml doc
* @param expression - xpath expression
* @param workitem
* @param itemName
* @param itemType
*/
private <T> void readXMLValue(Document doc, String expression, ItemCollection workitem, String itemName,
Class<T> itemType) {
// create XPath...
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
XPathExpression xPathExpression;
try {
xPathExpression = xpath.compile(expression);
// extract node value
Node valueNode = (Node) xPathExpression.evaluate(doc, XPathConstants.NODE);
if (valueNode != null) {
String value = valueNode.getTextContent();
if (itemType == Date.class) {
// 2024-05-13T00:00:00+02:00
SimpleDateFormat formatter = new SimpleDateFormat(CargosoftXMLInvoiceImportService.DATE_FORMAT);
formatter.setTimeZone(TimeZone.getTimeZone("CET"));
try {
workitem.setItemValue(itemName, formatter.parse(value));
} catch (ParseException e) {
logger.warning("Invalid Date Format");
}
return;
}
if (itemType == Double.class && value != null && !value.isEmpty()) {
workitem.setItemValue(itemName, Double.parseDouble(value));
return;
}
if (itemType == Integer.class && value != null && !value.isEmpty()) {
workitem.setItemValue(itemName, Integer.parseInt(value));
return;
}
// Default String format
workitem.setItemValue(itemName, value);
}
} catch (XPathExpressionException e) {
logger.warning("Unable to read data field '" + expression + "' : " + e.getMessage());
}
}
} }

View file

@ -392,17 +392,27 @@ public class CargosoftXMLInvoiceImportService {
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Codes/Code", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Codes/Code",
workitem, "invoice.type", String.class); workitem, "invoice.type", String.class);
// Gutschrift oder Rechnung? // Rechnungssumme errechnen.....
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
workitem, "invoice.total.net", Double.class);
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VATAmount/Amount/Value",
workitem, "invoice.total.tax", Double.class);
if (!workitem.hasItem("invoice.total.tax")) {
workitem.setItemValue("invoice.total.tax", 0.0);
}
// Total = net + tax
double _total = workitem.getItemValueDouble("invoice.total.net");
_total = _total + workitem.getItemValueDouble("invoice.total.tax");
_total = Math.round(_total * 100) / 100.0;
workitem.setItemValue("invoice.total", _total);
workitem.setItemValue("invoice.saldo", _total);
// Bei Gutschrift Wert negieren...
if ("G".equals(workitem.getItemValueString("invoice.type"))) { if ("G".equals(workitem.getItemValueString("invoice.type"))) {
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
workitem, "invoice.total", Double.class);
workitem.setItemValue("invoice.saldo", -workitem.getItemValueDouble("invoice.total")); workitem.setItemValue("invoice.saldo", -workitem.getItemValueDouble("invoice.total"));
workitem.setItemValue("invoice.total", -workitem.getItemValueDouble("invoice.total")); workitem.setItemValue("invoice.total", -workitem.getItemValueDouble("invoice.total"));
} else {
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
workitem, "invoice.total", Double.class);
workitem.setItemValue("invoice.saldo", workitem.getItemValueDouble("invoice.total"));
} }
// Steuer // Steuer
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VAT/VATRate", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VAT/VATRate",
workitem, "invoice.vatrate", Double.class); workitem, "invoice.vatrate", Double.class);
@ -574,6 +584,8 @@ public class CargosoftXMLInvoiceImportService {
.compile("InvoiceAmount/ActivityType/Codes/Code[@Type='cs']/text()"); .compile("InvoiceAmount/ActivityType/Codes/Code[@Type='cs']/text()");
NodeList rowList = doc.getElementsByTagName("InvoiceRow"); NodeList rowList = doc.getElementsByTagName("InvoiceRow");
double _kurs = 0.0;
double _baseAmount = 0.0;
for (int i = 0; i < rowList.getLength(); i++) { for (int i = 0; i < rowList.getLength(); i++) {
ItemCollection childItemCol = new ItemCollection(); ItemCollection childItemCol = new ItemCollection();
Node rowNode = rowList.item(i); Node rowNode = rowList.item(i);
@ -594,14 +606,21 @@ public class CargosoftXMLInvoiceImportService {
double dUmsatz = Double.parseDouble(umsatz); double dUmsatz = Double.parseDouble(umsatz);
childItemCol.setItemValue("datev.umsatz", dUmsatz); childItemCol.setItemValue("datev.umsatz", dUmsatz);
// Kurs // Kurs
String kurs = (String) netAmountExchangeRateExpr.evaluate(rowNode, XPathConstants.STRING); String _kursByRow = (String) netAmountExchangeRateExpr.evaluate(rowNode, XPathConstants.STRING);
if (kurs != null && !kurs.isEmpty()) { if (_kursByRow != null && !_kursByRow.isEmpty()) {
Double dKurs = Double.parseDouble(kurs); Double dKurs = Double.parseDouble(_kursByRow);
_kurs = dKurs;
childItemCol.setItemValue("datev.kurs", dKurs); childItemCol.setItemValue("datev.kurs", dKurs);
// Basisumsatz // Basisumsatz
if (dKurs != 0) { if (dKurs != 0) {
double basisUmsatz = dUmsatz / dKurs; double basisUmsatz = dUmsatz / dKurs;
double gerundeterBasisUmsatz = Math.round(basisUmsatz * 100) / 100.0; double gerundeterBasisUmsatz = Math.round(basisUmsatz * 100) / 100.0;
if ("G".equals(workitem.getItemValueString("invoice.type"))) {
_baseAmount = _baseAmount - gerundeterBasisUmsatz;
} else {
_baseAmount = _baseAmount + gerundeterBasisUmsatz;
}
childItemCol.setItemValue("datev.basisumsatz", gerundeterBasisUmsatz); childItemCol.setItemValue("datev.basisumsatz", gerundeterBasisUmsatz);
} }
} }
@ -621,6 +640,10 @@ public class CargosoftXMLInvoiceImportService {
childItems.add(childItemCol); childItems.add(childItemCol);
} }
} }
// Update invoice.rate und invoice.base.amount
workitem.setItemValue("invoice.rate", _kurs);
_baseAmount = Math.round(_baseAmount * 100) / 100.0;
workitem.setItemValue("invoice.base.amount", _baseAmount);
} catch (XPathExpressionException e) { } catch (XPathExpressionException e) {
logger.warning("Unable to read row : " + e.getMessage()); logger.warning("Unable to read row : " + e.getMessage());

View file

@ -1,15 +1,12 @@
<ui:composition xmlns="http://www.w3.org/1999/xhtml" <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"> xmlns:h="http://xmlns.jcp.org/jsf/html">
<!-- Zeit alle verknüften Zahlungen an --> <!-- Zeit alle verknüften Zahlungen an -->
<h:panelGroup layout="block" styleClass="imixs-form-section" <h:panelGroup layout="block" styleClass="imixs-form-section" id="paymentlist">
id="paymentlist">
<table class="imixsdatatable "> <table class="imixsdatatable ">
@ -25,59 +22,63 @@
<ui:param name="payments" <ui:param name="payments"
value="#{workitemLinkController.getExternalReferences('$workflowgroup:Zahlungseingang')}"></ui:param> value="#{workitemLinkController.getExternalReferences('$workflowgroup:Zahlungseingang')}"></ui:param>
<ui:repeat var="payment_stub" value="#{payments}"> <ui:repeat var="payment_stub" value="#{payments}">
<ui:param name="payment" <ui:param name="payment" value="#{opListController.loadInvoice(payment_stub.getUniqueID())}"></ui:param>
value="#{opListController.loadInvoice(payment_stub.getUniqueID())}"></ui:param>
<ui:param name="paymentDetails" <ui:param name="paymentDetails" value="#{opListController.loadPaymentDetails(payment)}"></ui:param>
value="#{opListController.loadPaymentDetails(payment)}"></ui:param>
<tr> <tr>
<td><h:link outcome="/pages/workitems/workitem"> <td>
#{payment.item['$workflowsummary']} <h:link outcome="/pages/workitems/workitem">
<f:param name="id" value="#{payment.item['$uniqueid']}" /> #{payment.item['$workflowsummary']}
</h:link></td> <f:param name="id" value="#{payment.item['$uniqueid']}" />
</h:link>
</td>
<td><h:outputText <td>
value="#{payment.item['name']}" /></td> <h:outputText value="#{payment.item['name']}" />
</td>
<td style="text-align: right;"><h:outputText
value="#{payment.item['payment.total']}"> <td style="text-align: right;">
<h:outputText value="#{payment.item['payment.total']}">
<f:convertNumber minFractionDigits="2" locale="de" /> <f:convertNumber minFractionDigits="2" locale="de" />
</h:outputText> <h:outputText value=" #{payment.item['payment.currency']}" /></td> </h:outputText>
<h:outputText value=" #{payment.item['payment.currency']}" />
</td>
<!-- show payment amount of corresponding invoice entry --> <!-- show payment amount of corresponding invoice entry -->
<ui:repeat var="paymentDetail" value="#{paymentDetails}"> <ui:repeat var="paymentDetail" value="#{paymentDetails}">
<ui:fragment <ui:fragment rendered="#{paymentDetail.item['$uniqueid'] eq workitem.item['$uniqueid']}">
rendered="#{paymentDetail.item['$uniqueid'] eq workitem.item['$uniqueid']}">
<td style="text-align: right;"><h:outputText <td style="text-align: right;">
value="#{paymentDetail.item['payment.amount']}"> <h:outputText value="#{paymentDetail.item['payment.amount']}">
<f:convertNumber minFractionDigits="2" locale="de" /> <f:convertNumber minFractionDigits="2" locale="de" />
</h:outputText> </h:outputText>
<h:outputText value=" #{payment.item['payment.currency']}" /> <h:outputText value=" #{payment.item['payment.currency']}" />
</td> </td>
<!-- Waehrungsumatz -->
<td style="text-align: right;"> <td style="text-align: right;">
<h:outputText rendered="#{payment.item['payment.currency'] eq 'EUR'}" <ui:fragment rendered="#{! empty workitem.item['invoice.rate']}">
<h:outputText rendered="#{payment.item['payment.currency'] eq 'EUR'}"
value="#{paymentDetail.item['payment.amount']*workitem.item['invoice.rate']}"> value="#{paymentDetail.item['payment.amount']*workitem.item['invoice.rate']}">
<f:convertNumber minFractionDigits="2" maxFractionDigits="2" locale="de" /> <f:convertNumber minFractionDigits="2" maxFractionDigits="2" locale="de" />
</h:outputText> </h:outputText>
<h:outputText rendered="#{payment.item['payment.currency'] eq 'EUR'}" <h:outputText rendered="#{payment.item['payment.currency'] eq 'EUR'}"
value=" #{workitem.item['invoice.currency']}" /> value=" #{workitem.item['invoice.currency']}" />
</ui:fragment>
</td> </td>
</ui:fragment> </ui:fragment>
</ui:repeat> </ui:repeat>
</tr> </tr>
</ui:repeat> </ui:repeat>
</table> </table>

File diff suppressed because it is too large Load diff