OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.erp.generationDoc;

import org.openconcerto.erp.config.ComptaPropsConfiguration;
import org.openconcerto.erp.core.common.ui.PreviewFrame;
import org.openconcerto.erp.core.customerrelationship.mail.EmailTemplate;
import org.openconcerto.erp.core.customerrelationship.mail.ValueListener;
import org.openconcerto.erp.core.sales.invoice.report.VenteFactureXmlSheet;
import org.openconcerto.erp.core.sales.quote.report.PaypalStamper;
import org.openconcerto.erp.generationDoc.element.TypeModeleSQLElement;
import org.openconcerto.erp.model.MouseSheetXmlListeListener;
import org.openconcerto.erp.preferences.PayPalPreferencePanel;
import org.openconcerto.erp.storage.CloudStorageEngine;
import org.openconcerto.erp.storage.StorageEngine;
import org.openconcerto.erp.storage.StorageEngines;
import org.openconcerto.openoffice.OOUtils;
import org.jopendocument.link.Component;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.preferences.SQLPreferences;
import org.openconcerto.sql.view.EditFrame;
import org.openconcerto.sql.view.EditPanel.EditMode;
import org.openconcerto.ui.FrameUtil;
import org.openconcerto.utils.Action;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.i18n.Grammar;
import org.openconcerto.utils.i18n.TranslationManager;

import java.awt.GraphicsEnvironment;
import java.awt.print.PrinterJob;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.JOptionPane;

import org.jopendocument.model.OpenDocument;

public abstract class SheetXml {

    private ITransformer<List<SQLRowAccessor>, List<SQLRowAccessor>> postProcess = null;

    public ITransformer<List<SQLRowAccessor>, List<SQLRowAccessor>> getPostProcess() {
        return postProcess;
    }

    // INSTEAD USE THE PREF IN L.O. CALC --> Formules, toujours recalculer
    private boolean refreshFormulasRequired = false;

    // return null to keep default value
    public interface StorageDirs {
        public File getDocumentOutputDirectory(SheetXml sheet);

        public File getPDFOutputDirectory(SheetXml sheet);

        public String getStoragePath(SheetXml sheet);
    }

    /**
     * Post process line
     * 
     * @param postProcess
     */
    public void setPostProcess(ITransformer<List<SQLRowAccessor>, List<SQLRowAccessor>> postProcess) {
        this.postProcess = postProcess;
    }

    private static StorageDirs STORAGE_DIRS;

    // allow to redirect all documents
    public static void setStorageDirs(StorageDirs d) {
        STORAGE_DIRS = d;
    }

    public static final String DEFAULT_PROPERTY_NAME = "Default";
    protected SQLElement elt;

    // nom de l'imprimante à utiliser
    protected String printer;

    // id
    protected SQLRow row;

    // Language du document
    protected SQLRow rowLanguage;

    protected static final SQLBase base = ((ComptaPropsConfiguration) Configuration.getInstance()).getSQLBaseSociete();

    // single threaded and kill its thread after 3 seconds (to allow the program to exit)
    protected static final ExecutorService runnableQueue = new ThreadPoolExecutor(0, 1, 3L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {

        @Override
        public Thread newThread(Runnable r) {
            final Thread res = new Thread(r);
            res.setUncaughtExceptionHandler(DEFAULT_HANDLER);

            return res;
        }
    });

    protected static UncaughtExceptionHandler DEFAULT_HANDLER = new UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            ExceptionHandler.handle("Erreur de generation", e);
        }
    };

    public final SQLElement getElement() {
        return this.elt;
    }

    public Future<SheetXml> showPrintAndExportAsynchronous(final boolean showDocument, final boolean printDocument, final boolean exportToPDF) {
        return showPrintAndExportAsynchronous(showDocument, printDocument, exportToPDF, Collections.emptyList());
    }

    /**
     * Show, print and export the document to PDF. This method is asynchronous, but is executed in a
     * single threaded queue shared with createDocument
     */
    public Future<SheetXml> showPrintAndExportAsynchronous(final boolean showDocument, final boolean printDocument, final boolean exportToPDF, List<Action> actions) {
        final Callable<SheetXml> c = new Callable<SheetXml>() {
            @Override
            public SheetXml call() throws Exception {
                showPrintAndExport(showDocument, printDocument, exportToPDF, actions);
                return SheetXml.this;
            }
        };
        return runnableQueue.submit(c);

    }

    public Future<SheetXml> showPrintAndExportAsynchronous(final boolean showDocument, final boolean printDocument, final boolean exportToPDF, final SQLElement element, SQLRow row) {
        final Callable<SheetXml> c = new Callable<SheetXml>() {

            @Override
            public SheetXml call() throws Exception {
                final List<Action> actions = new ArrayList<>();
                if (SheetXml.this instanceof AbstractSheetXml) {
                    final SQLTable table = element.getTable();
                    final Action emailAction = new Action(TranslationManager.getInstance().getTranslationForAction("document.pdf.send.email")) {

                        @Override
                        public void run(Object source) {
                            EmailTemplate.askTemplate(null, table.getDBRoot(), new ValueListener() {

                                @Override
                                public void valueSelected(Object value) {
                                    final MouseSheetXmlListeListener l = new MouseSheetXmlListeListener(element, ((AbstractSheetXml) SheetXml.this).getClass());
                                    l.sendMail((EmailTemplate) value, (AbstractSheetXml) SheetXml.this, true);
                                }
                            });

                        }
                    };
                    actions.add(emailAction);
                }
                final Action modifyAction = new Action(TranslationManager.getInstance().getTranslationForAction("modify") + " " + element.getName().getVariant(Grammar.DEFINITE_ARTICLE_SINGULAR)) {

                    @Override
                    public void run(Object source) {
                        if (source instanceof PreviewFrame) {
                            ((PreviewFrame) source).dispose();
                        }

                        final SQLComponent component = element.createDefaultComponent();
                        final EditFrame f = new EditFrame(component, EditMode.MODIFICATION);
                        f.selectionId(row.getID());
                        FrameUtil.show(f);

                    }
                };
                actions.add(modifyAction);
                showPrintAndExport(showDocument, printDocument, exportToPDF, actions);
                return SheetXml.this;
            }
        };
        return runnableQueue.submit(c);

    }

    public static Future<?> submitInQueue(final Runnable r) {

        return runnableQueue.submit(r);
    }

    public void showPrintAndExport(final boolean showDocument, final boolean printDocument, boolean exportToPDF) {
        showPrintAndExport(showDocument, printDocument, exportToPDF, Boolean.getBoolean("org.openconcerto.oo.useODSViewer"), false, Collections.emptyList());
    }

    public void showPrintAndExport(final boolean showDocument, final boolean printDocument, boolean exportToPDF, List<Action> actions) {
        showPrintAndExport(showDocument, printDocument, exportToPDF, Boolean.getBoolean("org.openconcerto.oo.useODSViewer"), false, actions);
    }

    public void showPrintAndExport(final boolean showDocument, final boolean printDocument, boolean exportToPDF, boolean useODSViewer, boolean exportPDFSynch) {
        showPrintAndExport(showDocument, printDocument, exportToPDF, useODSViewer, exportPDFSynch, Collections.emptyList());
    }

    /**
     * Show, print and export the document to PDF. This method is synchronous
     */
    public void showPrintAndExport(final boolean showDocument, final boolean printDocument, boolean exportToPDF, boolean useODSViewer, boolean exportPDFSynch, List<Action> actions) {

        final File generatedFile = getGeneratedFile();
        final File pdfFile = getGeneratedPDFFile();
        if (generatedFile == null || !generatedFile.exists()) {
            JOptionPane.showMessageDialog(null, "Fichier généré manquant : " + generatedFile);
            return;
        }

        try {
            if (!useODSViewer) {
                if (exportToPDF || printDocument) {
                    final Component doc = ComptaPropsConfiguration.getOOConnexion().loadDocument(generatedFile, !showDocument);

                    if (printDocument) {
                        Map<String, Object> map = new HashMap<String, Object>();
                        map.put("Name", this.printer);
                        doc.printDocument(map);
                    }
                    if (exportToPDF) {
                        doc.saveToPDF(pdfFile).get();
                    }
                    if (!showDocument) {
                        doc.close();
                    }
                } else {
                    openDocument(false);
                }
            } else {
                final OpenDocument doc = new OpenDocument(generatedFile);

                if (showDocument) {
                    showPreviewDocument(actions);
                }
                if (printDocument) {
                    // Print !
                    DefaultNXDocumentPrinter printer = new DefaultNXDocumentPrinter();
                    printer.print(Arrays.asList(doc));
                }

                // FIXME Profiler pour utiliser moins de ram --> ex : demande trop de mémoire pour
                // faire
                // un grand livre KD
                if (exportToPDF) {

                    Thread t = new Thread(new Runnable() {

                        @Override
                        public void run() {
                            createPDF(generatedFile, pdfFile, doc, getStoragePath());

                        }

                    }, "convert and upload to pdf");

                    t.setDaemon(true);
                    t.start();
                    if (exportPDFSynch) {
                        t.join();
                    }
                }
            }

        } catch (Exception e) {
            ExceptionHandler.handle("Impossible de charger le document OpenOffice " + pdfFile.getAbsolutePath() + "(viewer : " + useODSViewer + ")", e);
        }
    }

    MetaDataSheet meta;

    /**
     * MetaData à inclure lors de la génération
     * 
     * @param meta
     */
    public void setMetaGeneration(MetaDataSheet meta) {
        this.meta = meta;
    }

    /**
     * MetaData à inclure lors de la génération
     * 
     * @param meta
     */

    public MetaDataSheet getMetaGeneration() {
        return this.meta;
    }

    public void createPDF(final File generatedFile, final File pdfFile, final OpenDocument doc, String storagePath) {
        if (pdfFile == null) {
            throw new IllegalArgumentException("null PDF file");
        }
        try {
            SheetUtils.convert2PDF(doc, pdfFile);
        } catch (Exception e) {
            ExceptionHandler.handle("Impossible de créer le PDF " + pdfFile.getAbsolutePath(), e);
        }
        if (!pdfFile.canRead()) {
            ExceptionHandler.handle("Le fichier PDF " + pdfFile.getAbsolutePath() + " ne peut être lu.");
        }
        storeWithStorageEngines(generatedFile, pdfFile, storagePath);
    }

    protected void storeWithStorageEngines(final File generatedFile, final File pdfFile, String storagePath) {
        List<StorageEngine> engines = StorageEngines.getInstance().getActiveEngines();
        for (StorageEngine storageEngine : engines) {
            if (storageEngine.isConfigured() && storageEngine.allowAutoStorage()) {
                final String path = storagePath;
                try {
                    storageEngine.connect();
                    final BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(pdfFile));
                    storageEngine.store(inStream, path, pdfFile.getName(), true);
                    inStream.close();
                    storageEngine.disconnect();
                } catch (IOException e) {
                    ExceptionHandler.handle("Impossible de sauvegarder le PDF " + pdfFile.getAbsolutePath() + " vers " + path + "(" + storageEngine + ")", e);
                }
                if (storageEngine instanceof CloudStorageEngine) {
                    try {
                        storageEngine.connect();
                        final BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(generatedFile));
                        storageEngine.store(inStream, path, generatedFile.getName(), true);
                        inStream.close();
                        storageEngine.disconnect();
                    } catch (IOException e) {
                        ExceptionHandler.handle("Impossible de sauvegarder le fichier généré " + generatedFile.getAbsolutePath() + " vers " + path + "(" + storageEngine + ")", e);
                    }
                }
            }
        }
    }

    public abstract String getDefaultTemplateId();

    /**
     * Path of the directory used for storage. Ex: Devis/2010
     */
    public final String getStoragePath() {
        final String res = STORAGE_DIRS == null ? null : STORAGE_DIRS.getStoragePath(this);
        if (res != null)
            return res;
        else
            return this.getStoragePathP();
    }

    public final File getDocumentOutputDirectory() {
        final File res = STORAGE_DIRS == null ? null : STORAGE_DIRS.getDocumentOutputDirectory(this);
        if (res != null)
            return res;
        else
            return this.getDocumentOutputDirectoryP();
    }

    public final File getPDFOutputDirectory() {
        final File res = STORAGE_DIRS == null ? null : STORAGE_DIRS.getPDFOutputDirectory(this);
        if (res != null)
            return res;
        else
            return this.getPDFOutputDirectoryP();
    }

    protected abstract String getStoragePathP();

    protected abstract File getDocumentOutputDirectoryP();

    protected abstract File getPDFOutputDirectoryP();

    /**
     * Name of the generated document (without extension), do not rely on this name.
     * 
     * Use getGeneratedFile().getName() to get the generated file name.
     */
    public abstract String getName();

    /**
     * @return the template id for this template (ex: "sales.quote")
     */
    public String getTemplateId() {
        if (this.row != null && this.row.getTable().getFieldsName().contains("ID_MODELE")) {
            if (row.getObject("ID_MODELE") == null || row.isForeignEmpty("ID_MODELE")) {
                TypeModeleSQLElement typeModele = Configuration.getInstance().getDirectory().getElement(TypeModeleSQLElement.class);
                String modele = typeModele.getTemplateMapping().get(this.row.getTable().getName());
                if (modele == null) {
                    System.err.println("No default modele in table TYPE_MODELE for table " + this.row.getTable().getName());
                    Thread.dumpStack();
                    return getDefaultTemplateId();
                } else {
                    return modele;
                }
            } else {
                SQLRow rowModele = this.row.getForeignRow("ID_MODELE");
                return rowModele.getString("NOM");
            }
        }
        return getDefaultTemplateId();
    }

    public abstract Future<SheetXml> createDocumentAsynchronous();

    public void createDocument() throws InterruptedException, ExecutionException {
        createDocumentAsynchronous().get();
    }

    /**
     * get the File that is, or must be generated.
     * 
     * @return a file (not null)
     */
    public abstract File getGeneratedFile();

    public File getGeneratedPDFFile() {
        return SheetUtils.getFileWithExtension(getGeneratedFile(), ".pdf");
    }

    public SQLRow getRowLanguage() {
        return this.rowLanguage;
    }

    public String getReference() {
        return "";
    }

    /**
     * Creates the document if needed and returns the generated file (OpenDocument)
     */
    public File getOrCreateDocumentFile() throws Exception {
        File f = getGeneratedFile();
        if (!f.exists()) {
            return createDocumentAsynchronous().get().getGeneratedFile();
        } else {
            return f;
        }
    }

    /**
     * Creates the document if needed and returns the generated file (OpenDocument)
     * 
     * @param createRecent true for recreate the pdf document if older than ods
     * @return
     * @throws Exception
     * 
     */
    public File getOrCreatePDFDocumentFile(boolean createRecent) throws Exception {
        return getOrCreatePDFDocumentFile(createRecent, Boolean.getBoolean("org.openconcerto.oo.useODSViewer"));
    }

    public File getOrCreatePDFDocumentFile(boolean createRecent, boolean useODSViewer) throws Exception {
        File f = getGeneratedPDFFile();
        if (!f.exists()) {
            getOrCreateDocumentFile();
            showPrintAndExport(false, false, true, useODSViewer, true, Collections.emptyList());
            return f;
        } else {
            File fODS = getOrCreateDocumentFile();
            if (fODS.lastModified() > f.lastModified()) {
                showPrintAndExport(false, false, true, useODSViewer, true, Collections.emptyList());
            }
            return f;
        }
    }

    /**
     * Open the document with the native application
     * 
     * @param synchronous
     */
    public void openDocument(boolean synchronous) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                File f;
                try {
                    f = getOrCreateDocumentFile();
                    if (f != null && f.exists()) {
                        OOUtils.open(f);
                    } else {
                        if (!GraphicsEnvironment.isHeadless()) {
                            if (f != null) {
                                JOptionPane.showMessageDialog(null, "Le fichier " + f.getAbsolutePath() + " est manquant");
                            } else {
                                JOptionPane.showMessageDialog(null, "Fichier manquant");
                            }
                        } else {
                            if (f != null) {
                                throw new FileNotFoundException(f.getAbsolutePath() + " missing");
                            } else {
                                throw new NullPointerException("null document");
                            }
                        }
                    }
                } catch (Exception e) {
                    ExceptionHandler.handle("Impossible d'ouvrir le document.", e);
                }
            }
        };
        if (synchronous) {
            r.run();
        } else {
            Thread thread = new Thread(r, "openDocument: " + getGeneratedFile().getAbsolutePath());
            thread.setDaemon(true);
            thread.start();
        }

    }

    public void showPreviewDocument() throws Exception {
        showPreviewDocument(Collections.emptyList());
    }

    public void showPreviewDocument(List<Action> actions) throws Exception {
        File f = null;
        f = getOrCreateDocumentFile();
        PreviewFrame.show(f, actions);
    }

    public void printDocument() {
        printDocument(null);
    }

    public void printDocument(PrinterJob job) {

        try {
            final File f = getOrCreateDocumentFile();

            if (!Boolean.getBoolean("org.openconcerto.oo.useODSViewer")) {
                final Component doc = ComptaPropsConfiguration.getOOConnexion().loadDocument(f, true);
                doc.printDocument(job);
                doc.close();
            } else {
                // Load the spreadsheet.
                final OpenDocument doc = new OpenDocument(f);
                // Print !
                DefaultNXDocumentPrinter printer = new DefaultNXDocumentPrinter();
                printer.print(Arrays.asList(doc), job);
            }

        } catch (Exception e) {
            ExceptionHandler.handle("Impossible d'imprimer le document OpenOffice", e);
            e.printStackTrace();
        }
    }

    public SQLRow getSQLRow() {
        return this.row;
    }

    /**
     * Remplace tous les caracteres non alphanumeriques (seul le _ est autorisé) par un -. Cela
     * permet d'avoir toujours un nom de fichier valide.
     * 
     * @param fileName nom du fichier à créer ex:FACTURE_2007/03/001
     * @return un nom fichier valide ex:FACTURE_2007-03-001
     */
    public static String getValidFileName(String fileName) {
        final StringBuffer result = new StringBuffer(fileName.length());
        for (int i = 0; i < fileName.length(); i++) {
            char ch = fileName.charAt(i);

            // Si c'est un caractere alphanumerique
            if (Character.isLetterOrDigit(ch) || (ch == '_') || (ch == ' ')) {
                result.append(ch);
            } else {
                result.append('-');
            }
        }
        return result.toString();
    }

    public String getPrinter() {
        return this.printer;
    }

    public void setRefreshFormulasRequired(boolean refreshFormulasRequired) {
        this.refreshFormulasRequired = refreshFormulasRequired;
    }

    public boolean isRefreshFormulasRequired() {
            return refreshFormulasRequired;
    }

}