Java tutorial
/******************************************************************************* * Copyright (c) 2006-2011, G. Weirich and Elexis * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * G. Weirich - initial implementation * T. Huster - updated for 4.4 and various fixes * *******************************************************************************/ /* BITTE KEINE NDERUNGEN AN DIESEM FILE OHNE RCKSPRACHE MIT MIR weirich@elexis.ch */ /* THIS FILE IS FROZEN. DO NOT MODIFY */ package ch.elexis.TarmedRechnung; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Properties; import javax.xml.transform.Source; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.jdom.Document; import org.jdom.Element; import org.jdom.Namespace; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import org.jdom.transform.JDOMSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.elexis.base.ch.ebanking.esr.ESR; import ch.elexis.core.constants.Preferences; import ch.elexis.core.constants.StringConstants; import ch.elexis.core.data.activator.CoreHub; import ch.elexis.core.data.interfaces.IRnOutputter; import ch.elexis.core.data.preferences.CorePreferenceInitializer; import ch.elexis.core.data.util.PlatformHelper; import ch.elexis.core.model.IDiagnose; import ch.elexis.core.ui.util.SWTHelper; import ch.elexis.data.Fall; import ch.elexis.data.Kontakt; import ch.elexis.data.Mandant; import ch.elexis.data.NamedBlob; import ch.elexis.data.Patient; import ch.elexis.data.Rechnung; import ch.elexis.data.RnStatus; import ch.elexis.data.TrustCenters; import ch.elexis.tarmedprefs.PreferenceConstants; import ch.elexis.tarmedprefs.TarmedRequirements; import ch.rgw.tools.ExHandler; import ch.rgw.tools.Money; import ch.rgw.tools.Result; import ch.rgw.tools.StringTool; import ch.rgw.tools.TimeTool; import ch.rgw.tools.XMLTool; /** * Exportiert eine Elexis-Rechnung im XML 4.0 Format von xmldata.ch Bitte KEINE nderungen an dieser * Klasse durchfhren. Senden Sie Verbesserungsvorschlge oder Wnsche als Mail oder direkt als * Patch an weirich@elexis.ch. * * zur Weiterverarbeitung verwendet werden. DoExport(..) liefert ein JDOM-Dokument, das die * gewnschte Rechnung enthlt. Diese kann vom Aufrufer dann an einen Intermedir oder auf einen * Drucker ausgegeben werden. Der Output dieses Exporters ist TrustX zertifiziert. nderungen * sollten in den seltensten Fllen ntig sein. Falls doch: Fehlermeldungen bitte an * weirich@elexis.ch * * @author gerry * */ public class XMLExporter implements IRnOutputter { private static Logger logger = LoggerFactory.getLogger(XMLExporter.class); // constants to access vat information from the extinfo of the Rechnungssteller public static final String VAT_ISMANDANTVAT = "at.medevit.medelexis.vat_ch/IsMandantVat"; public static final String VAT_MANDANTVATNUMBER = "at.medevit.medelexis.vat_ch/MandantVatNumber"; public static final String ATTR_REMARK = "remark"; //$NON-NLS-1$ public static final String ELEMENT_TIERS_PAYANT = "tiers_payant"; //$NON-NLS-1$ public static final String ELEMENT_TIERS_GARANT = "tiers_garant"; //$NON-NLS-1$ public static final String ATTR_CODE = "code"; //$NON-NLS-1$ public static final String BIRTHDEFECT = "birthdefect"; //$NON-NLS-1$ public static final String DISEASE = "disease"; //$NON-NLS-1$ public static final String FREETEXT = "freetext"; //$NON-NLS-1$ public static final String ATTR_BIRTHDATE = "birthdate"; //$NON-NLS-1$ public static final String ELEMENT_VAT = "vat"; //$NON-NLS-1$ public static final String ELEMENT_VAT_NUMBER = "vat_number"; //$NON-NLS-1$ public static final String ATTR_VAT_RATE = "vat_rate"; //$NON-NLS-1$ public static final String ATTR_TARIFF_TYPE = "tariff_type"; //$NON-NLS-1$ public static final String ELEMENT_REMARK = ATTR_REMARK; //$NON-NLS-1$ private static final String ELEMENT_PAYLOAD = "payload"; //$NON-NLS-1$ private static final String ATTR_PAYLOAD_TYPE = "type"; //$NON-NLS-1$ private static final String ATTR_PAYLOAD_COPY = "copy"; //$NON-NLS-1$ private static final String ATTR_PAYLOAD_STORNO = "storno"; //$NON-NLS-1$ public static final String ATTR_EAN_PARTY = "ean_party"; //$NON-NLS-1$ private static final String ATTR_MODUS = "modus"; //$NON-NLS-1$ private static final String ATTR_LANGUAGE = "language"; //$NON-NLS-1$ private static final String ELEMENT_REQUEST = "request"; //$NON-NLS-1$ private static final String ATTR_REQUEST_TIMESTAMP = "request_timestamp"; //$NON-NLS-1$ public static final String ATTR_REQUEST_DATE = "request_date"; //$NON-NLS-1$ public static final String ATTR_REQUEST_ID = "request_id"; //$NON-NLS-1$ public static final String TIERS_GARANT = "TG"; //$NON-NLS-1$ public static final String TIERS_PAYANT = "TP"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_PHYSIO = "amount_physio"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_MIGEL = "amount_migel"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_LAB = "amount_lab"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_DRUG = "amount_drug"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_UNCLASSIFIED = "amount_unclassified"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_CANTONAL = "amount_cantonal"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_TARMED_TT = "amount_tarmed.tt"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_TARMED_MT = "amount_tarmed.mt"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_TARMED = "amount_tarmed"; //$NON-NLS-1$ public static final String ATTR_AMOUNT = "amount"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_REMINDER = "amount_reminder"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_TT = "amount_tt"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_MT = "amount_mt"; //$NON-NLS-1$ public static final String ATTR_QUANTITY = "quantity"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_DUE = "amount_due"; //$NON-NLS-1$ public static final String ATTR_AMOUNT_PREPAID = "amount_prepaid"; //$NON-NLS-1$ public static final String ELEMENT_BALANCE = "balance"; //$NON-NLS-1$ public static final String ELEMENT_INVOICE = "invoice"; //$NON-NLS-1$ public static final String ELEMENT_BODY = "body"; //$NON-NLS-1$ public static final String ATTR_BODY_ROLE = "role"; //$NON-NLS-1$ public static final String ATTR_BODY_PLACE = "place"; //$NON-NLS-1$ public static final String ELEMENT_ANNULMENT = "annulment"; //$NON-NLS-1$ public static final String FIELDNAME_TIMESTAMPXML = "TimeStampXML"; //$NON-NLS-1$ public static final String ELEMENT_REMINDER = "reminder"; public static final String ATTR_REMINDER_LEVEL = "reminder_level"; public static final Namespace ns = Namespace.getNamespace("http://www.forum-datenaustausch.ch/invoice"); //$NON-NLS-1$ public static final Namespace nsinvoice = Namespace.getNamespace("invoice", //$NON-NLS-1$ "http://www.forum-datenaustausch.ch/invoice"); //$NON-NLS-1$ public static final Namespace nsxsi = Namespace.getNamespace("xsi", //$NON-NLS-1$ "http://www.w3.org/2001/XMLSchema-instance"); //$NON-NLS-1$ public static final Namespace nsxenc = Namespace.getNamespace("nsxenc", "http://www.w3.org/2001/04/xmlenc#"); //$NON-NLS-1$ //$NON-NLS-2$ public static final Namespace nsds = Namespace.getNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); //$NON-NLS-1$ //$NON-NLS-2$ Fall actFall; Patient actPatient; Mandant actMandant; String tiers; Rechnung rn; private XMLExporterBalance xmlBalance; private XMLExporterTreatment xmlTreatment; private ESR besr; static TarmedACL ta; private String outputDir; private XMLExporterEsr9 esr9; // default true, keep old behavior private boolean printAtIntermediate = true; public static final String PREFIX = "TarmedRn:"; //$NON-NLS-1$ /** * Reset exporter */ public void clear() { actFall = null; actPatient = null; actMandant = null; rn = null; } public XMLExporter() { ta = TarmedACL.getInstance(); clear(); } /** * Output a Collection of bills. This essentially lets the user modify the output settings (if * any) and then calls doExport() fr each bill in rnn * * @param type * desired mode (original, copy, storno) * @param rnn * a Collection of Rechnung - Objects to output */ @Override public Result<Rechnung> doOutput(final IRnOutputter.TYPE type, final Collection<Rechnung> rnn, Properties props) { Result<Rechnung> ret = new Result<Rechnung>(); if (outputDir == null) { SWTHelper.SimpleDialog dlg = new SWTHelper.SimpleDialog(new SWTHelper.IControlProvider() { @Override public Control getControl(Composite parent) { return createSettingsControl(parent); } @Override public void beforeClosing() { // Nothing } }); if (dlg.open() != Dialog.OK) { return ret; } } ProgressMonitorDialog progress = new ProgressMonitorDialog(Display.getDefault().getActiveShell()); try { progress.run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask(Messages.RechnungsDrucker_PrintingBills, rnn.size()); for (Rechnung rn : rnn) { if (doExport(rn, outputDir + File.separator + rn.getNr() + ".xml", type, //$NON-NLS-1$ false) == null) { ret.add(Result.SEVERITY.ERROR, 1, Messages.XMLExporter_ErrorInBill + rn.getNr(), rn, true); } monitor.worked(1); if (monitor.isCanceled()) { break; } } monitor.done(); } }); } catch (InvocationTargetException | InterruptedException e) { LoggerFactory.getLogger(XMLExporter.class).error("Error outputting bills", e); MessageDialog.openError(Display.getDefault().getActiveShell(), Messages.RechnungsDrucker_MessageErrorWhilePrinting, Messages.RechnungsDrucker_MessageErrorWhilePrinting + "[" + e.getMessage() + "]"); } return ret; } /** * Wa want to be informed on cancellings of any bills * * @param rn * we don't mind, we always return true */ @Override public boolean canStorno(final Rechnung rn) { return true; } public boolean isPrintAtIntermediate() { return printAtIntermediate; } public void setPrintAtIntermediate(boolean value) { printAtIntermediate = value; } private void negate(Element el, String attr) { String v = el.getAttributeValue(attr); if (!StringTool.isNothing(v)) { if (!v.equals(StringConstants.DOUBLE_ZERO)) { if (v.startsWith(StringConstants.DASH)) { v = v.substring(1); } else { v = StringConstants.DASH + v; } el.setAttribute(attr, v); } } } /** * Export a bill as XML. We do, in fact first check whether this bill was exported already. And * if so we do not create it again but load the old one. There is deliberately no possibility to * avoid this behaviour. (One can only delete or storno a bill and recreate it (even then the * stored xml remains stored. Additionally, the caller can chose to store the bill as XML in the * file system. This is done if the parameter dest ist given. On success the caller will receive * a JDOM Document containing the bill. * * @param rechnung * the bill to export * @param dest * a full filepath to save the final document (or null to not save it) * @param type * Type of output (original, copy, storno) * @param doVerify * true if the bill should be sent trough a verifyer after creation. * @return the jdom XML-Document that contains the bill. Might be null on failure. */ public Document doExport(final Rechnung rechnung, final String dest, final IRnOutputter.TYPE type, final boolean doVerify) { clear(); // create a object for managing vat rates and values on invoice level VatRateSum vatSummer = new VatRateSum(); rn = rechnung; if (xmlBillExists(rechnung)) { logger.info("Updating existing bill for " + rechnung.getNr()); Document updated = updateExistingXmlBill(rechnung, dest, type, doVerify); if (updated != null) { return updated; } } if (type.equals(TYPE.STORNO)) { SWTHelper.showError(Messages.XMLExporter_StornoImpossibleCaption, Messages.XMLExporter_StornoImpossibleText); return null; } actFall = rn.getFall(); actPatient = actFall.getPatient(); actMandant = rn.getMandant(); Kontakt kostentraeger = actFall.getRequiredContact(TarmedRequirements.INSURANCE); if (kostentraeger == null) { kostentraeger = actPatient; } logger.info("Creating new bill for " + rechnung.getNr()); Document xmlRn; Element root = new Element(ELEMENT_REQUEST, nsinvoice); root.addNamespaceDeclaration(nsxsi); root.addNamespaceDeclaration(nsxenc); root.addNamespaceDeclaration(nsxsi); root.addNamespaceDeclaration(nsinvoice); root.setAttribute("schemaLocation", //$NON-NLS-1$ "http://www.forum-datenaustausch.ch/invoice generalInvoiceRequest_440.xsd", nsxsi); //$NON-NLS-1$ root.setAttribute(ATTR_MODUS, getRole(actFall)); root.setAttribute(ATTR_LANGUAGE, Locale.getDefault().getLanguage()); xmlRn = new Document(root); // services are needed for the balance XMLExporterServices services = null; services = XMLExporterServices.buildServices(rn, vatSummer); //balance is needed by other parts so initialize first initBalanceData(rechnung, services, vatSummer); //processing XMLExporterProcessing processing = XMLExporterProcessing.buildProcessing(rechnung, this); root.addContent(processing.getElement()); //payload Element payload = new Element(ELEMENT_PAYLOAD, nsinvoice); payload.setAttribute(ATTR_PAYLOAD_TYPE, "invoice"); //$NON-NLS-1$ payload.setAttribute(ATTR_PAYLOAD_COPY, type.equals(IRnOutputter.TYPE.COPY) ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ payload.setAttribute(ATTR_PAYLOAD_STORNO, type.equals(IRnOutputter.TYPE.STORNO) ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ //invoice String ts = null; if (type.equals(IRnOutputter.TYPE.COPY)) { ts = rn.getExtInfo(FIELDNAME_TIMESTAMPXML); if (StringTool.isNothing(ts)) { ts = Long.toString(new Date().getTime() / 1000); rn.setExtInfo(FIELDNAME_TIMESTAMPXML, ts); } } else { ts = Long.toString(new Date().getTime() / 1000); rn.setExtInfo(FIELDNAME_TIMESTAMPXML, ts); } Element invoice = new Element(ELEMENT_INVOICE, nsinvoice); invoice.setAttribute(ATTR_REQUEST_TIMESTAMP, ts); invoice.setAttribute(ATTR_REQUEST_ID, rn.getRnId()); invoice.setAttribute(ATTR_REQUEST_DATE, new TimeTool(rn.getDatumRn()).toString(TimeTool.DATE_MYSQL) + "T00:00:00"); // 10154 //$NON-NLS-1$ payload.addContent(invoice); //body Element body = new Element(ELEMENT_BODY, nsinvoice); body.setAttribute(ATTR_BODY_ROLE, "physician"); body.setAttribute(ATTR_BODY_PLACE, "practice"); //prolog XMLExporterProlog prolog = XMLExporterProlog.buildProlog(rechnung, this); body.addContent(prolog.getElement()); //remark String bem = rn.getBemerkung(); if (!StringTool.isNothing(bem)) { Element remark = new Element(ELEMENT_REMARK, nsinvoice); remark.setText(rn.getBemerkung()); body.addContent(remark); } // add the balance body.addContent(xmlBalance.getElement()); //esr9 esr9 = XMLExporterEsr9.buildEsr9(rechnung, xmlBalance, this); body.addContent(esr9.getElement()); //tiers garant or payant XMLExporterTiers xmlTiers = XMLExporterTiers.buildTiers(rechnung, this); tiers = xmlTiers.getTiers(); body.addContent(xmlTiers.getElement()); //insurance XMLExporterInsurance xmlInsurance = XMLExporterInsurance.buildInsurance(rechnung, this); body.addContent(xmlInsurance.getElement()); xmlTreatment = XMLExporterTreatment.buildTreatment(rechnung, this); body.addContent(xmlTreatment.getElement()); if (services != null) { body.addContent(services.getElement()); } else { logger.warn("services is null!"); } payload.addContent(body); root.addContent(payload); if (rn.setBetrag(xmlBalance.getAmount().roundTo5()) == false) { rn.reject(RnStatus.REJECTCODE.SUM_MISMATCH, Messages.XMLExporter_SumMismatch); } else if (doVerify) { new Validator().checkBill(this, new Result<Rechnung>()); } checkXML(xmlRn, dest, rn, doVerify); if (rn.getStatus() != RnStatus.FEHLERHAFT) { try { StringWriter stringWriter = new StringWriter(); XMLOutputter xout = new XMLOutputter(Format.getCompactFormat()); xout.output(xmlRn, stringWriter); NamedBlob blob = NamedBlob.load(PREFIX + rn.getNr()); blob.putString(stringWriter.toString()); if (dest != null) { writeFile(xmlRn, dest); } } catch (Exception ex) { ExHandler.handle(ex); SWTHelper.alert(Messages.XMLExporter_ErrorCaption, MessageFormat.format(Messages.XMLExporter_CouldNotWriteFile, dest)); return null; } } return xmlRn; } private Document updateExistingXmlBill(Rechnung rechnung, String dest, TYPE type, boolean doVerify) { // If the bill exists already in the database, it has been output // earlier, so we don't // recreate it. We must, however, reflect changes that happened // since it was output: // Payments, state changes, obligations NamedBlob blob = NamedBlob.load(PREFIX + rechnung.getNr()); SAXBuilder builder = new SAXBuilder(); // initialize variables actFall = rechnung.getFall(); actMandant = rechnung.getMandant(); try { Document ret = builder.build(new StringReader(blob.getString())); Element root = ret.getRootElement(); if (getXmlVersion(root).equals("4.0")) { updateExisting4Xml(root, type, rechnung); } else if (getXmlVersion(root).equals("4.4")) { updateExisting44Xml(root, type, rechnung); int status = rechnung.getStatus(); if (status == RnStatus.MAHNUNG_1 || status == RnStatus.MAHNUNG_1_GEDRUCKT) { if (dest != null) { dest = dest.toLowerCase().replaceFirst("\\.xml$", "_m1.xml"); } addReminderEntry(root, rechnung, "1"); } else if (status == RnStatus.MAHNUNG_2 || status == RnStatus.MAHNUNG_2_GEDRUCKT) { if (dest != null) { dest = dest.toLowerCase().replaceFirst("\\.xml$", "_m2.xml"); } addReminderEntry(root, rechnung, "2"); } else if (status == RnStatus.MAHNUNG_3 || status == RnStatus.MAHNUNG_3_GEDRUCKT) { if (dest != null) { dest = dest.toLowerCase().replaceFirst("\\.xml$", "_m3.xml"); } addReminderEntry(root, rechnung, "3"); } } else { logger.warn("Bill in unknown XML version " + getXmlVersion(root) + ", recreating bill."); return null; } checkXML(ret, dest, rn, doVerify); if (dest != null) { if (type.equals(TYPE.STORNO)) { writeFile(ret, dest.toLowerCase().replaceFirst("\\.xml$", "_storno.xml")); //$NON-NLS-1$ //$NON-NLS-2$ } else { writeFile(ret, dest); } } StringWriter stringWriter = new StringWriter(); XMLOutputter xout = new XMLOutputter(Format.getCompactFormat()); xout.output(ret, stringWriter); blob.putString(stringWriter.toString()); return ret; } catch (Exception ex) { ExHandler.handle(ex); SWTHelper.showError(Messages.XMLExporter_ReadErrorCaption, Messages.XMLExporter_ReadErrorText); // What should we do -> We create it from scratch return null; } } private void addReminderEntry(Element root, Rechnung rechnung, String reminderLevel) { boolean firstReminder = false; Element payload = root.getChild("payload", XMLExporter.nsinvoice);//$NON-NLS-1$ payload.setAttribute(ATTR_PAYLOAD_TYPE, "reminder"); //$NON-NLS-1$ TimeTool tt = new TimeTool(new Date()); String timestamp = Long.toString(tt.getTimeInMillis() / 1000); String dateString = tt.toString(TimeTool.DATE_MYSQL) + "T00:00:00"; Element reminder = payload.getChild(ELEMENT_REMINDER, nsinvoice); if (reminder == null) { reminder = new Element(ELEMENT_REMINDER, nsinvoice); firstReminder = true; } reminder.setAttribute(ATTR_REQUEST_TIMESTAMP, timestamp); //$NON-NLS-1$ reminder.setAttribute(ATTR_REQUEST_DATE, dateString); //$NON-NLS-1$ reminder.setAttribute(ATTR_REQUEST_ID, rechnung.getRnId()); //$NON-NLS-1$ reminder.setAttribute(ATTR_REMINDER_LEVEL, reminderLevel); //$NON-NLS-1$ // add amount reminder and recalculate amount due Element body = payload.getChild("body", XMLExporter.nsinvoice); if (body != null) { Element balance = body.getChild("balance", XMLExporter.nsinvoice); Money amountReminder = rechnung.getRemindersBetrag(); balance.setAttribute(XMLExporter.ATTR_AMOUNT_REMINDER, XMLTool.moneyToXmlDouble(amountReminder)); // rewrite amount due Money mDue = new Money(rechnung.getBetrag()); mDue.addMoney(amountReminder); mDue.subtractMoney(rechnung.getAnzahlung()); balance.setAttribute(XMLExporter.ATTR_AMOUNT_DUE, XMLTool.moneyToXmlDouble(mDue)); } if (firstReminder) { @SuppressWarnings("unchecked") List<Element> children = payload.getChildren(); List<Element> newChildren = new ArrayList<>(); for (int i = 0; i < children.size(); i++) { newChildren.add(children.get(i)); // add reminder after invoice if (children.get(i).getName().equals("invoice")) { newChildren.add(reminder); } } payload.removeContent(); payload.setContent(newChildren); } } private void updateExisting44Xml(Element root, TYPE type, Rechnung rechnung) { Money mPaid = rn.getAnzahlung(); // update processing, print_at_intermediate and transport via EAN Element processing = root.getChild("processing", XMLExporter.nsinvoice);//$NON-NLS-1$ String intermediatePrint = processing.getAttributeValue(XMLExporterProcessing.ATTR_INTERMEDIAT_PRINT); if (("1".equals(intermediatePrint) || "true".equals(intermediatePrint)) && !isPrintAtIntermediate()) { processing.setAttribute(XMLExporterProcessing.ATTR_INTERMEDIAT_PRINT, "0"); } else if (("0".equals(intermediatePrint) || "false".equals(intermediatePrint)) && isPrintAtIntermediate()) { processing.setAttribute(XMLExporterProcessing.ATTR_INTERMEDIAT_PRINT, "1"); } Element transport = processing.getChild(XMLExporterProcessing.ELEMENT_TRANSPORT, XMLExporter.nsinvoice); if (transport != null) { Element via = transport.getChild(XMLExporterProcessing.ELEMENT_TRANSPORT_VIA, XMLExporter.nsinvoice); String iEAN = XMLExporterProcessing.getIntermediateEAN(rechnung, this); if (iEAN != null && !iEAN.isEmpty()) { via.setAttribute(XMLExporterProcessing.ATTR_TRANSPORT_VIA_VIA, iEAN); } } // update payload and balance Element payload = root.getChild("payload", XMLExporter.nsinvoice);//$NON-NLS-1$ Element body = payload.getChild("body", XMLExporter.nsinvoice);//$NON-NLS-1$ Element balance = body.getChild("balance", XMLExporter.nsinvoice);//$NON-NLS-1$ XMLExporterBalance xmlBalance = new XMLExporterBalance(balance); // fix for erroneous bills without amount_prepaid (https://redmine.medelexis.ch/issues/6624) tryToFixPrepaid(xmlBalance, mPaid); if (!mPaid.equals(xmlBalance.getPrepaid())) { xmlBalance.setPrepaid(mPaid); Money mDue = xmlBalance.getAmount(); mDue.addMoney(xmlBalance.getReminder()); mDue.subtractMoney(mPaid); mDue.roundTo5(); xmlBalance.setDue(mDue); } if (type.equals(IRnOutputter.TYPE.COPY)) { payload.setAttribute("copy", Boolean.toString(true));//$NON-NLS-1$ } else if (type.equals(TYPE.STORNO)) { payload.setAttribute("storno", Boolean.toString(true));//$NON-NLS-1$ Element services = body.getChild("services", XMLExporter.nsinvoice);//$NON-NLS-1$ XMLExporterServices xmlServices = new XMLExporterServices(services); xmlServices.negateAll(); xmlBalance.negateAmount(); xmlBalance.negateAmountObligations(); xmlBalance.setDue(new Money()); xmlBalance.setPrepaid(new Money()); } } private void tryToFixPrepaid(XMLExporterBalance xmlBalance, Money mPaid) { if (!xmlBalance.hasPrepaid()) { xmlBalance.setPrepaid(mPaid); } Money xmlAmount = xmlBalance.getAmount(); Money xmlDue = xmlBalance.getDue(); Money xmlPrepaid = xmlBalance.getPrepaid(); Money xmlReminder = xmlBalance.getReminder(); double diffDouble = (xmlAmount.doubleValue() + xmlReminder.doubleValue()) - (xmlPrepaid.doubleValue() + xmlDue.doubleValue()); // this is an erroneous bill if (Math.abs(diffDouble) > 1) { xmlBalance.setDue( new Money((xmlAmount.doubleValue() + xmlReminder.doubleValue()) - xmlPrepaid.doubleValue()) .roundTo5()); } } private void updateExisting4Xml(Element root, TYPE type, Rechnung rechnung) { Namespace namespace = Namespace.getNamespace("http://www.xmlData.ch/xmlInvoice/XSD"); //$NON-NLS-1$ Money mPaid = rn.getAnzahlung(); Element invoice = root.getChild("invoice", namespace);//$NON-NLS-1$ fixCanton(invoice, namespace); Element balance = invoice.getChild("balance", namespace);//$NON-NLS-1$ Money anzInBill = XMLTool.xmlDoubleToMoney(balance.getAttributeValue("amount_prepaid"));//$NON-NLS-1$ if (!mPaid.equals(anzInBill)) { Money mAmount = XMLTool.xmlDoubleToMoney(balance.getAttributeValue("amount"));//$NON-NLS-1$ // never pay more than the total on XML bill, those cases are handled in Elexis Rechnung and UI if (mPaid.isMoreThan(mAmount)) { mPaid = mAmount; } balance.setAttribute("amount_prepaid", XMLTool.moneyToXmlDouble(mPaid));//$NON-NLS-1$ Money mDue = new Money(mAmount).subtractMoney(mPaid).roundTo5(); balance.setAttribute("amount_due", XMLTool.moneyToXmlDouble(mDue));//$NON-NLS-1$ } if (type.equals(IRnOutputter.TYPE.COPY)) { invoice.setAttribute("resend", Boolean.toString(true));//$NON-NLS-1$ } else if (type.equals(TYPE.STORNO)) { Element detail = invoice.getChild("detail", namespace);//$NON-NLS-1$ Element services = detail.getChild("services", namespace);//$NON-NLS-1$ @SuppressWarnings("unchecked") List<Element> sr = services.getChildren(); for (Element el : sr) { try { negate(el, "quantity");//$NON-NLS-1$ negate(el, "amount.mt");//$NON-NLS-1$ negate(el, "amount.tt");//$NON-NLS-1$ negate(el, "amount");//$NON-NLS-1$ } catch (Exception ex) { ExHandler.handle(ex); } } negate(balance, "amount");//$NON-NLS-1$ negate(balance, "amount_tarmed");//$NON-NLS-1$ negate(balance, "amount_tarmed.mt");//$NON-NLS-1$ negate(balance, "amount_tarmed.tt");//$NON-NLS-1$ negate(balance, "amount_cantonal");//$NON-NLS-1$ negate(balance, "amount_unclassified");//$NON-NLS-1$ negate(balance, "amount_drug");//$NON-NLS-1$ negate(balance, "amount_lab");//$NON-NLS-1$ negate(balance, "amount_migel");//$NON-NLS-1$ negate(balance, "amount_physio");//$NON-NLS-1$ negate(balance, "amount_obligations");//$NON-NLS-1$ balance.setAttribute("amount_due", StringConstants.DOUBLE_ZERO);//$NON-NLS-1$ balance.setAttribute("amount_prepaid", StringConstants.DOUBLE_ZERO);//$NON-NLS-1$ // change the purpose if a payant element is present Element payant = invoice.getChild("tiers_payant");//$NON-NLS-1$ if (payant != null) { payant.setAttribute("purpose", ELEMENT_ANNULMENT); //$NON-NLS-1$ } } } private void fixCanton(Element invoice, Namespace namespace) { Element detail = invoice.getChild("detail", namespace); String canton = detail.getAttributeValue("canton", namespace); if (canton == null || canton.isEmpty()) { detail.setAttribute("canton", "AG"); } } public static String getXmlVersion(Element root) { String location = root.getAttributeValue("schemaLocation", //$NON-NLS-1$ Namespace.getNamespace("http://www.w3.org/2001/XMLSchema-instance"));//$NON-NLS-1$ if (location != null && !location.isEmpty()) { if (location.contains("InvoiceRequest_400")) {//$NON-NLS-1$ return "4.0";//$NON-NLS-1$ } else if (location.contains("InvoiceRequest_440")) {//$NON-NLS-1$ return "4.4";//$NON-NLS-1$ } } return location;//$NON-NLS-1$ } private boolean xmlBillExists(Rechnung rechnung) { return NamedBlob.exists(PREFIX + rechnung.getNr()); } protected Element buildGuarantor(Kontakt garant, Kontakt patient) { // Patient wird im override des MediPort Plugins verwendet // Hinweis: // XML Standard: // http://www.forum-datenaustausch.ch/mdinvoicerequest_xml4.00_v1.2_d.pdf // Dort steht beim Feld 11310: Gesetzlicher Vertreter des Patienten. Element guarantor = new Element("guarantor", XMLExporter.nsinvoice); //$NON-NLS-1$ guarantor.addContent(XMLExporterUtil.buildAdressElement(garant)); return guarantor; } @Override public String getDescription() { return Messages.XMLExporter_TarmedForTrustCenter; } /** * Validate XML of the created bill against the appropriate schema. Subclasses can override to * provide specific handling of errors. The default implementation will mark the bill as * erroneous if STRICT_BILLING is active and XML Schema errors are present. * * @param xmlDoc * the bill * @param dest * the destination path if the user chose output to file. Might be null * @param rn * the bill to output * @param doVerify * false if the user doesn't want strict validity check (subclasses may ignore) */ protected void checkXML(final Document xmlDoc, String dest, final Rechnung rn, final boolean doVerify) { if (CoreHub.userCfg.get(Preferences.LEISTUNGSCODES_BILLING_STRICT, true)) { Source source = new JDOMSource(xmlDoc); String path = PlatformHelper.getBasePath("ch.elexis.base.ch.arzttarife") + File.separator + "rsc"; //$NON-NLS-1$ //$NON-NLS-2$ List<String> errs = null; // validate depending on tarmed version if (getXmlVersion(xmlDoc.getRootElement()).equals("4.0")) { logger.info("Validating XML against MDInvoiceRequest_400.xsd"); errs = XMLTool.validateSchema(path + File.separator + "MDInvoiceRequest_400.xsd", source); //$NON-NLS-1$ } else if (getXmlVersion(xmlDoc.getRootElement()).equals("4.4")) { logger.info("Validating XML against generalInvoiceRequest_440.xsd"); errs = XMLTool.validateSchema(path + File.separator + "generalInvoiceRequest_440.xsd", source); //$NON-NLS-1$ } else { errs = Collections .singletonList("Bill in unknown XML version " + getXmlVersion(xmlDoc.getRootElement())); } if (!errs.isEmpty()) { StringBuilder sb = new StringBuilder(); for (String err : errs) { sb.append(err).append(StringConstants.LF); } logger.error(sb.toString()); rn.reject(RnStatus.REJECTCODE.VALIDATION_ERROR, sb.toString()); XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat()); StringWriter sw = new StringWriter(); try { xout.output(xmlDoc, sw); } catch (IOException e) { logger.error("Failed getting document as String.", e); return; } logger.debug(sw.toString()); } } } @Override public Control createSettingsControl(final Object parent) { final Composite parentInc = (Composite) parent; Composite ret = new Composite(parentInc, SWT.NONE); ret.setLayout(new GridLayout(2, false)); Label l = new Label(ret, SWT.NONE); l.setText(Messages.XMLExporter_PleaseEnterOutputDirectoryForBills); l.setLayoutData(SWTHelper.getFillGridData(2, true, 1, false)); final Text text = new Text(ret, SWT.READ_ONLY | SWT.BORDER); text.setLayoutData(SWTHelper.getFillGridData(1, true, 1, true)); Button b = new Button(ret, SWT.PUSH); b.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { outputDir = new DirectoryDialog(parentInc.getShell(), SWT.OPEN).open(); CoreHub.localCfg.set(PreferenceConstants.RNN_EXPORTDIR, outputDir); text.setText(outputDir); } }); b.setText(Messages.XMLExporter_Change); outputDir = CoreHub.localCfg.get(PreferenceConstants.RNN_EXPORTDIR, CorePreferenceInitializer.getDefaultDBPath()); text.setText(outputDir); return ret; } protected void writeFile(final Document doc, final String dest) throws IOException { FileOutputStream fout = new FileOutputStream(dest); OutputStreamWriter cout = new OutputStreamWriter(fout, "UTF-8"); //$NON-NLS-1$ XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat()); xout.output(doc, cout); cout.close(); fout.close(); int status_vorher = rn.getStatus(); if ((status_vorher == RnStatus.OFFEN) || (status_vorher == RnStatus.MAHNUNG_1) || (status_vorher == RnStatus.MAHNUNG_2) || (status_vorher == RnStatus.MAHNUNG_3)) { rn.setStatus(status_vorher + 1); } rn.addTrace(Rechnung.OUTPUT, getDescription() + ": " //$NON-NLS-1$ + RnStatus.getStatusText(rn.getStatus())); } @Override public boolean canBill(final Fall fall) { Kontakt garant = fall.getGarant(); Kontakt kostentraeger = fall.getRequiredContact(TarmedRequirements.INSURANCE); if ((garant != null) && (kostentraeger != null)) { if (garant.isValid()) { if (kostentraeger.isValid()) { if (kostentraeger.istOrganisation()) { return true; } } } } return false; } @Override public void saveComposite() { // Nothing } protected String getIntermediateEAN(final Fall fall) { // Try to find the intermediate EAN. If we have explicitely set // an intermediate EAN, we'll use this one. Otherweise, we'll // check whether the mandator has a TC contract. if so, we try to // find the TC's EAN. // If nothing appropriate is found, we'll try to use the receiver EAN // or at least the guarantor EAN. // If everything fails we use a pseudo EAN to make the Validators happy String iEAN = TarmedRequirements.getIntermediateEAN(actFall); if (iEAN.length() == 0) { if (TarmedRequirements.hasTCContract(actMandant)) { String trustCenter = TarmedRequirements.getTCName(actMandant); if (trustCenter.length() > 0) { iEAN = TrustCenters.getTCEAN(trustCenter); } } } return iEAN; } protected String getSenderEAN(Mandant actMandant) { return TarmedRequirements.getEAN(actMandant); } /** * Class for keeping track of vat scales and corresponding amounts. * * @author thomas * */ class VatRateSum { class VatRateElement implements Comparable<VatRateElement> { double scale; double sumamount; double sumvat; VatRateElement(double scale) { this.scale = scale; sumamount = 0; sumvat = 0; } void add(double amount) { this.sumamount += amount; sumvat += (amount / (100.0 + scale)) * scale; } @Override public int compareTo(VatRateElement other) { if (scale < other.scale) return -1; else if (scale > other.scale) return 1; else return 0; } } HashMap<Double, VatRateElement> rates = new HashMap<Double, VatRateElement>(); double sumvat = 0.0; public void add(double scale, double amount) { VatRateElement element = rates.get(Double.valueOf(scale)); if (element == null) { element = new VatRateElement(scale); rates.put(new Double(scale), element); } element.add(amount); sumvat += (amount / (100.0 + scale)) * scale; } } /** * Initialize balance related data structures of the export. * * @param rechnung * @param services * @param vatSummer */ private void initBalanceData(Rechnung rechnung, XMLExporterServices services, VatRateSum vatSummer) { xmlBalance = XMLExporterBalance.buildBalance(rechnung, services, vatSummer, this); besr = new ESR(actMandant.getRechnungssteller().getInfoString(XMLExporter.ta.ESRNUMBER), actMandant.getRechnungssteller().getInfoString(XMLExporter.ta.ESRSUB), rechnung.getRnId(), ESR.ESR27); } public ESR getBesr() { return besr; } public Money getDueMoney() { return xmlBalance.getDue(); } public List<IDiagnose> getDiagnoses() { return xmlTreatment.getDiagnoses(); } protected String getRole(final Fall fall) { return "production"; //$NON-NLS-1$ } }