print @ Home client side and main server complete, web iface todo
authorKonrad Rosenbaum <konrad@silmor.de>
Wed, 21 Dec 2016 15:12:23 +0000 (16:12 +0100)
committerKonrad Rosenbaum <konrad@silmor.de>
Wed, 21 Dec 2016 15:12:23 +0000 (16:12 +0100)
Change-Id: Ib8f904276ac96643e5d4ca12bc16fad7aaf27ff8

89 files changed:
.gitignore
commonlib/mapplication.cpp
commonlib/stick/stick.cpp
commonlib/stick/stick.h
commonlib/templates/billrender.cpp [new file with mode: 0644]
commonlib/templates/billrender.h [new file with mode: 0644]
commonlib/templates/odtrender.cpp
commonlib/templates/odtrender.h
commonlib/templates/office.cpp
commonlib/templates/office.h
commonlib/templates/templates.pri
commonlib/templates/ticketrender.cpp
commonlib/templates/ticketrender.h
doc/logo.png [changed from file to symlink]
icons/arrows/arrowdiag.png [new file with mode: 0644]
icons/arrows/arrowdown.png [new file with mode: 0644]
icons/arrows/arrowleft.png [new file with mode: 0644]
icons/arrows/arrowright.png [new file with mode: 0644]
icons/arrows/arrowup.png [new file with mode: 0644]
icons/logo/icon.png [new file with mode: 0644]
icons/logo/icon.xcf [moved from src/images/icon.xcf with 100% similarity]
icons/logo/logo.png [new file with mode: 0644]
icons/logo/logo.xcf [moved from doc/logo.xcf with 100% similarity]
icons/misc/cancel.png [new file with mode: 0644]
icons/misc/done.png [new file with mode: 0644]
icons/misc/next.png [new file with mode: 0644]
icons/misc/prev.png [new file with mode: 0644]
icons/misc/separator.png [new file with mode: 0644]
icons/oxygen/COPYING [new file with mode: 0644]
icons/oxygen/dialog-cancel.png [new file with mode: 0644]
icons/oxygen/dialog-cancel.svgz [new file with mode: 0644]
icons/oxygen/emblem-important.png [new file with mode: 0644]
icons/oxygen/emblem-important.svgz [new file with mode: 0644]
icons/oxygen/favorites.png [new file with mode: 0644]
icons/oxygen/favorites.svgz [new file with mode: 0644]
icons/oxygen/hidef-preferences-system.svgz [new file with mode: 0644]
icons/oxygen/preferences-system.png [new file with mode: 0644]
icons/oxygen/preferences-system.svgz [new file with mode: 0644]
icons/oxygen/printer.png [new file with mode: 0644]
icons/oxygen/printer.svgz [new file with mode: 0644]
icons/wikipedia/README [new file with mode: 0644]
icons/wikipedia/de.png [new file with mode: 0644]
icons/wikipedia/en.png [new file with mode: 0644]
iface/misc/boxwrapper.cpp
iface/misc/boxwrapper.h
iface/templates/templates.cpp
iface/templates/templates.h
pack
printathome/README.icons [new file with mode: 0644]
printathome/clientcfg.cpp
printathome/clientcfg.h
printathome/examplemail.mime [new file with mode: 0644]
printathome/files.qrc
printathome/icon-print.xcf
printathome/icon-problem.png [new file with mode: 0644]
printathome/pah.cpp
printathome/pah.h
printathome/printathome.pro
printathome/printrun.cpp [new file with mode: 0644]
printathome/printrun.h [new file with mode: 0644]
printathome/servercfg.cpp
printathome/servercfg.h
src/dialogs/orderwin.cpp
src/dialogs/orderwin.h
src/images/arrowdiag.png [changed from file to symlink]
src/images/arrowdown.png [changed from file to symlink]
src/images/arrowleft.png [changed from file to symlink]
src/images/arrowright.png [changed from file to symlink]
src/images/arrowup.png [changed from file to symlink]
src/images/cancel.png [changed from file to symlink]
src/images/done.png [changed from file to symlink]
src/images/icon.png [changed from file to symlink]
src/images/next.png [changed from file to symlink]
src/images/prev.png [changed from file to symlink]
src/images/separator.png [changed from file to symlink]
wob/classes/order.wolf
wob/transact/order.wolf
www/config.php.template
www/images/README
www/images/arrowdown.png [changed from file to symlink]
www/images/de.png [changed from file to symlink]
www/images/en.png [changed from file to symlink]
www/images/maintenance.png [new symlink]
www/inc/wext/customer.php
www/inc/wext/order.php
www/index.php
www/machine.php
www/maintenance.php.template [new file with mode: 0644]
www/template/en/ordermail.txt

index dd8d391..6df853e 100644 (file)
@@ -4,6 +4,7 @@
 *.kdev*
 *.tag
 doxygen*.db
+*.kate-swp
 
 #Build files, libs, executables
 libQtZip*.*
@@ -14,6 +15,7 @@ core.*
 object_script*
 *.prl
 .qmake.stash
+/dists/
 
 #generated sources, translations, docu
 doc/wob
@@ -33,7 +35,10 @@ tmp/
 
 #User files
 *.mshk
+*.mshh
 www/config.php
+*.od?t
+*.xtt
 
 #Distribution files for various platforms
 dist-*
index 7a47193..e9f8aa1 100644 (file)
@@ -80,8 +80,8 @@ class MProgressWrapperGui:public MProgressWrapper
                 MProgressWrapperGui(QString label,QString button)
                 {
                         pd=new QProgressDialog(label,button,0,0);
-                        pd->setMinimumDuration(0);
-                        pd->show();
+                        pd->setMinimumDuration(500);
+                        //pd->show();
                 }
                 ~MProgressWrapperGui()
                 {
@@ -100,6 +100,10 @@ class MProgressWrapperGui:public MProgressWrapper
                 {
                         pd->cancel();
                 }
+                virtual void hide()
+               {
+                       pd->hide();
+               }
                 virtual void setRange(int mi,int ma)
                 {
                         pd->setRange(mi,ma);
index 223730a..ab83d26 100644 (file)
@@ -123,11 +123,26 @@ void MStickRenderer::initEngine ( ELAM::Engine&engine )
                        return ELAM::Exception(ELAM::Exception::ArgumentListError,"expecting 1 string argument");
                return xmlize(args[0].value<QString>());
        });
-       engine.setFunction("xml",[](const QList<QVariant>&args,ELAM::Engine&)->QVariant{
+       engine.setFunction("raw",[](const QList<QVariant>&args,ELAM::Engine&)->QVariant{
                if(args.size()!=1||!args[0].canConvert<QString>())
                        return ELAM::Exception(ELAM::Exception::ArgumentListError,"expecting 1 string argument");
                return args[0];
        });
+       //MIME
+       engine.setFunction("base64",[](const QList<QVariant>&args,ELAM::Engine&)->QVariant{
+               if(args.size()!=1||!args[0].canConvert<QByteArray>())
+                       return ELAM::Exception(ELAM::Exception::ArgumentListError,"expecting 1 blob/byteArray argument");
+               const auto base=args[0].toByteArray().toBase64();
+               QString r;
+               for(int i=0;i<base.size();i+=76)
+                       r+=QString::fromLatin1(base.mid(i,76))+"\r\n";
+               return r;
+       });
+       engine.setFunction("implode",[](const QList<QVariant>&args,ELAM::Engine&)->QVariant{
+               if(args.size()!=2||!args[0].canConvert<QStringList>()||!args[1].canConvert<QString>())
+                       return ELAM::Exception(ELAM::Exception::ArgumentListError,"expecting 2 arguments: (stringlist elements, string separator)");
+               return args[0].toStringList().join(args[1].toString());
+       });
        //Access Rights
        engine.setFunction("checkflags",[](const QList<QVariant>&args,ELAM::Engine&)->QVariant{
                if(args.size()!=1)
index 3215751..1076937 100644 (file)
 class MStickToken;
 namespace ELAM {class Engine;}
 
-///A Twig-like renderer for HTML pages.
+///A Twig-like renderer for HTML pages or other text based documents.
 class MAGICSMOKE_COMMON_EXPORT MStickRenderer
 {
 public:
+       ///instantiate the renderer
        MStickRenderer();
+       ///clean up
        virtual ~MStickRenderer();
 
+       ///sets the template text file
        virtual void setTemplateFile(QString filename);
+       ///sets the template as plain text
        virtual void setTemplate(QString text);
 
+       ///makes a property available as a variable in the renderer
        virtual void setProperty(QString name,QVariant value);
+       ///makes several properties available as variables in the renderer
        virtual void setProperties(QMap<QString,QVariant>properties);
+       ///deletes a property/variable
        virtual void removeProperty(QString name);
 
+       ///returns a specific property or a NULL variant if not found
        virtual QVariant property(QString name)const;
+       ///returns the names of all known properties
        virtual QStringList propertyNames()const;
 
+       ///executes one rendering run on the template and returns the result as string
        virtual QString render();
 
 private:
        QMap<QString,QVariant>mproperties;
        MStickToken*mtemplate=nullptr;
+
+       ///initializes the internal ELAM engine
        void initEngine(ELAM::Engine&);
 };
 
diff --git a/commonlib/templates/billrender.cpp b/commonlib/templates/billrender.cpp
new file mode 100644 (file)
index 0000000..fb35cd5
--- /dev/null
@@ -0,0 +1,206 @@
+//
+// C++ Implementation: bill odtrender
+//
+// Description:
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2016
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#include "billrender.h"
+#include <QVariant>
+#include <QDebug>
+
+static inline bool compare(const MOTicket&a,const MOTicket&b)
+{
+       if(a.eventid()!=b.eventid())return false;
+       if(a.price()!=b.price())return false;
+       return true;
+}
+
+MBillRenderer::MBillRenderer ( const MOOrder&o, const MTemplate& tmp)
+       :MOdtRenderer(tmp),m_order(o)
+{
+       //get tickets (only valid ones, only those that are to be paid)
+       QList<MOTicket>tlst=m_order.tickets();
+       for(int i=0;i<tlst.size();i++)
+               if(!tlst[i].ticketid().isNull() && (tlst[i].status()&MOTicket::MaskPay)!=0){
+                       printBuffer.tickets.append(tlst[i]);
+                }
+       //accumulated view on tickets
+       for(int i=0;i<printBuffer.tickets.size();i++){
+               MOTicket t=printBuffer.tickets[i];
+               bool found=false;
+               for(int j=0;j<printBuffer.tickinfo.size();j++){
+                       if(compare(printBuffer.tickinfo[j].proto,t)){
+                               found=true;
+                               printBuffer.tickinfo[j].amount++;
+                               break;
+                       }
+               }
+               if(!found)printBuffer.tickinfo.append(t);
+       }
+       //get valid vouchers
+       QList<MOVoucher>vlst=m_order.vouchers();
+       for(int i=0;i<vlst.size();i++)
+               if(vlst[i].price()>0||vlst[i].value()>0)
+                       printBuffer.vouchers.append(vlst[i]);
+}
+
+QVariant MBillRenderer::getVariable(QString vn)
+{
+       if(vn=="ORDERDATE"){
+               return m_order.ordertime().value();
+       }else
+       if(vn=="ORDERDATETIME"){
+               return m_order.ordertime().value();
+       }else
+       if(vn=="SENTDATE"){
+               return m_order.senttime().value();
+       }else
+       if(vn=="SENTDATETIME"){
+               return m_order.senttime().value();
+       }else
+       if(vn=="CUSTOMERID")return QString::number(m_order.customerid());else
+       if(vn=="ORDERID")return QString::number(m_order.orderid());else
+       if(vn=="ADDRESS")return m_order.fullInvoiceAddress();else
+       if(vn=="FULLADDRESS")return m_order.fullInvoiceAddress();else
+       if(vn=="NAME")return m_order.customer().value().fullName();else
+       if(vn=="DELIVERYADDRESS")return m_order.fullDeliveryAddress();else
+       if(vn=="FINALADDRESS")return m_order.fullDeliveryAddress();else
+       if(vn=="TOTALPRICE"){
+               return m_order.totalprice().value();
+       }else
+       if(vn=="AMOUNTPAID"){
+               return m_order.amountpaid().value();
+       }else
+       if(vn=="SELLER")return m_order.soldby().value();else
+       if(vn=="COMMENT")return m_order.comments().value();else
+       if(vn=="AMOUNTTOPAY"){
+               return m_order.amountToPay();
+       }else
+       if(vn=="AMOUNTTOREFUND"){
+               return m_order.amountToRefund();
+       }else
+       if(vn=="TICKETS"){
+               return printBuffer.tickets.size();
+       }else
+       if(vn=="ACCTICKETS"){
+               return printBuffer.tickinfo.size();
+       }else
+       if(vn=="VOUCHERS"){
+               return printBuffer.vouchers.size();
+       }else
+       if(vn=="ADDRESSLINES"){
+               return m_order.fullInvoiceAddress().split("\n").size();
+       }else
+       if(vn=="SHIPPING")return m_order.shippingtype().value().description().value();else
+       if(vn=="SHIPPINGPRICE"){
+               return m_order.shippingtype().value().cost().value();
+       }else{
+               if(vn.contains(':')){
+                       QStringList sl=vn.split(':');
+                       int it=-1;
+                       if(m_loopiter.contains(sl[0]))it=m_loopiter[sl[0]];
+                       return getLoopVariable(sl[0],it,sl[1]);
+               }
+       }
+//     qDebug()<<"got variable"<<vn<<"value"<<value;
+       qDebug()<<"Warning: requested unknown variable"<<vn;
+       return QVariant();
+}
+
+int MBillRenderer::getLoopIterations(QString loopname)
+{
+       if(loopname=="TICKETS")return printBuffer.tickets.size();
+       if(loopname=="ACCTICKETS")return printBuffer.tickinfo.size();
+       if(loopname=="VOUCHERS")return printBuffer.vouchers.size();
+       if(loopname=="ADDRESSLINES")return m_order.fullInvoiceAddress().split("\n").size();
+//     qDebug()<<"loop"<<loopname<<"has"<<iterations<<"iterations";
+       //fall back
+       qDebug()<<"Warning: requested unknown loop"<<loopname;
+       return -1;
+}
+
+void MBillRenderer::setLoopIteration(QString loopname, int iteration)
+{
+//     qDebug()<<"setting loop iter"<<loopname<<iteration;
+       if(iteration<0)return;
+       int max=getLoopIterations(loopname);
+       if(iteration>=max)return;
+       m_loopiter.insert(loopname,iteration);
+}
+
+
+QVariant MBillRenderer::getLoopVariable(QString loopname,int it,QString vn)
+{
+       if(loopname=="TICKETS"){
+               QList<MOTicket> &tickets=printBuffer.tickets;
+               if(it<0 || it>=tickets.size())return QVariant();
+
+               if(vn=="PRICE"){
+                       return tickets[it].price().value();
+               }else
+               if(vn=="ID")return tickets[it].ticketid().value();else
+               if(vn=="TITLE")return tickets[it].event().title().value();else
+               if(vn=="ARTIST")return tickets[it].event().artist().value().name().value();else
+               if(vn=="DATE"){
+                       return tickets[it].event().start().value();
+               }else
+               if(vn=="STARTTIME"){
+                       return tickets[it].event().start().value();
+               }else
+               if(vn=="ENDTIME"){
+                       return tickets[it].event().end().value();
+               }else
+               if(vn=="ROOM")return tickets[it].event().room().value();
+       }else if(loopname=="ACCTICKETS"){
+               QList<TickInfo> &tickets=printBuffer.tickinfo;
+               if(it<0 || it>=tickets.size())return QVariant();
+
+               if(vn=="PRICE"){
+                       return tickets[it].proto.price().value();
+               }else
+               if(vn=="FULLPRICE"){
+                       return tickets[it].proto.price().value()*tickets[it].amount;
+               }else
+               if(vn=="TITLE")return tickets[it].proto.event().title().value();else
+               if(vn=="ARTIST")return tickets[it].proto.event().artist().value().name().value();else
+               if(vn=="DATE"){
+                       return tickets[it].proto.event().start().value();
+               }else
+               if(vn=="STARTTIME"){
+                       return tickets[it].proto.event().start().value();
+               }else
+               if(vn=="ENDTIME"){
+                       return tickets[it].proto.event().end().value();
+               }else
+               if(vn=="ROOM")return tickets[it].proto.event().room().value();else
+               if(vn=="AMOUNT"){
+                       return tickets[it].amount;
+               }
+       }else if(loopname=="VOUCHERS"){
+               if(it<0 || it>=printBuffer.vouchers.size())return QVariant();
+
+               if(vn=="PRICE"){
+                       return printBuffer.vouchers[it].price().value();
+               }else
+               if(vn=="VALUE"){
+                       return printBuffer.vouchers[it].value().value();
+               }else
+               if(vn=="ID")return printBuffer.vouchers[it].voucherid().value();
+       }else if(loopname=="ADDRESSLINES"){
+               QStringList lst=m_order.fullInvoiceAddress().split("\n");
+               if(it<0 || it>=lst.size())return QVariant();
+               return lst[it];
+       }else{
+               qDebug()<<"Warning: requested variable"<<vn<<"from unknown loop"<<loopname;
+               return QVariant();
+       }
+       qDebug()<<"Warning: requested unknown variable"<<vn<<"from loop"<<loopname;
+       return QVariant();
+}
+
diff --git a/commonlib/templates/billrender.h b/commonlib/templates/billrender.h
new file mode 100644 (file)
index 0000000..6a7a0db
--- /dev/null
@@ -0,0 +1,59 @@
+//
+// C++ Interface: order bill renderer
+//
+// Description:
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2016
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#ifndef MAGICSMOKE_BILLRENDER_H
+#define MAGICSMOKE_BILLRENDER_H
+
+#include <QMap>
+
+#include "odtrender.h"
+
+#include "MOOrder"
+#include "MOShipping"
+
+/**displays an order and allows the user to execute several commands on it*/
+class MAGICSMOKE_COMMON_EXPORT MBillRenderer:public MOdtRenderer
+{
+       public:
+               /**creates the order renderer*/
+               MBillRenderer(const MOOrder&,const MTemplate&);
+
+       protected:
+               /**callback for bill generator: variables; see MOdtSignalRenderer for details*/
+               QVariant getVariable(QString)override;
+               /**callback for bill generator: loops; see MOdtSignalRenderer for details*/
+               int getLoopIterations(QString loopname)override;
+               /**callback to set values of a specific loop iteration*/
+               void setLoopIteration(QString loopname,int iteration)override;
+               /**callback for bill generator: loop variables; see MOdtSignalRenderer for details*/
+               QVariant getLoopVariable(QString,int,QString);
+
+       private:
+               MOOrder m_order;
+               QMap<QString,int>m_loopiter;
+
+               //printing buffer
+               struct TickInfo{
+                       TickInfo(const MOTicket&t):proto(t){amount=1;}
+                       TickInfo(const TickInfo&t):proto(t.proto){amount=t.amount;}
+                       TickInfo(){amount=0;}
+                       MOTicket proto;
+                       int amount;
+               };
+               struct PrintBuffer{
+                       QList<MOTicket> tickets;
+                       QList<MOVoucher> vouchers;
+                       QList<TickInfo> tickinfo;
+               }printBuffer;
+};
+
+#endif
index fcd5ee8..a92d485 100644 (file)
@@ -193,10 +193,10 @@ void MOdtRenderer::renderToFile(QString file)
        }
 }
 
-void MOdtRenderer::renderToFile(QFile &file)
+void MOdtRenderer::renderToFile(QFile &file,bool preventopen)
 {
        if(d->renderToFile(file))
-               if(QSettings().value("doOpenODFs",false).toBool())
+               if(!preventopen && QSettings().value("doOpenODFs",false).toBool())
                        openOfficeFile(file.fileName());
 }
 bool MOdtRendererPrivate::renderToFile(QFile &file)
@@ -226,6 +226,25 @@ void MOdtRenderer::renderToPrinter()
        QFile(tname).remove();
 }
 
+QString MOdtRenderer::renderToPdf()
+{
+       //generate temporary file
+       QTemporaryFile tfile;
+       tfile.setAutoRemove(false);//we don't want it to be auto-deleted on close()
+       tfile.setFileTemplate(QDir::tempPath()+"/msmoke_XXXXXX."+d->extension);
+       tfile.open();
+       QString tname=tfile.fileName();
+       //render
+       d->renderToFile(tfile);
+       tfile.close();
+       //call ooffice and print
+       QString pdf=convertOfficeFilePdf(tname);
+       //remove temporary file
+       QFile(tname).remove();
+
+       return pdf;
+}
+
 void MOdtRendererPrivate::render(QIODevice*out)
 {
        //sanity check
index 25ba593..4a0bc88 100644 (file)
@@ -38,10 +38,13 @@ class MAGICSMOKE_COMMON_EXPORT MOdtRenderer
                virtual void renderToFile(QString file);
                
                /**starts the internal rendering routine and outputs to file*/
-               virtual void renderToFile(QFile& file);
+               virtual void renderToFile(QFile& file,bool preventOpen=false);
                
                /**starts the internal rendering routine and outputs to printer (calls OpenOffice to print)*/
                virtual void renderToPrinter();
+
+               ///like renderToPrinter, but renders to PDF, returns the PDF file name
+               virtual QString renderToPdf();
                
                ///helper routine: converts a V1 template to V2
                ///you have to make sure to not do a double conversion on an already converted document
index a578406..c82faa0 100644 (file)
@@ -18,6 +18,7 @@
 #include <QComboBox>
 #include <QCoreApplication>
 #include <QDebug>
+#include <QDir>
 #include <QFileDialog>
 #include <QFileInfo>
 #include <QGroupBox>
@@ -100,6 +101,23 @@ void printOfficeFile(QString fname)
        }
 }
 
+QString convertOfficeFilePdf(QString fname)
+{
+       QFileInfo finf(fname);
+       //calculate parameters
+       QStringList r;
+       r<<"--headless"<<"--convert-to"<<"pdf"<<"--outdir"<<finf.dir().absolutePath();
+       r<<fname;
+       //print and wait for finish
+       QProcess proc;
+       proc.start(getofficepath(),r);
+       proc.waitForFinished();
+       //calculate new name
+       QString pname=finf.dir().absoluteFilePath(finf.completeBaseName()+".pdf");
+       if(QFile::exists(pname))return pname;
+       else return QString();
+}
+
 MOfficeConfig::MOfficeConfig(QWidget*par)
        :QDialog(par)
 {
index 3af1de7..5556228 100644 (file)
@@ -21,6 +21,10 @@ void openOfficeFile(QString fname);
 /**calls OpenOffice.org to print an ODF file*/
 void printOfficeFile(QString fname);
 
+///calls OpenOffice.org to convert an ODF file to PDF;
+///returns the PDF file name on success, empty string on failure
+QString convertOfficeFilePdf(QString fname);
+
 #include <QDialog>
 
 #include "commonexport.h"
index 6cc1d4f..e14acdc 100644 (file)
@@ -2,12 +2,14 @@ HEADERS += \
        $$PWD/odtrender.h \
        $$PWD/ticketrender.h \
        $$PWD/office.h \
-       $$PWD/labeldlg.h
+       $$PWD/labeldlg.h \
+       $$PWD/billrender.h
 
 SOURCES += \
        $$PWD/odtrender.cpp \
        $$PWD/ticketrender.cpp \
        $$PWD/office.cpp \
-       $$PWD/labeldlg.cpp
+       $$PWD/labeldlg.cpp \
+       $$PWD/billrender.cpp
 
 INCLUDEPATH += $$PWD
index 0109320..a0fae5a 100644 (file)
@@ -51,6 +51,7 @@ class MLabelRendererPrivate
                
                //called by MLabelRenderer
                QSizeF labelSize(const QPaintDevice&);
+               QSizeF labelSizeMM();
                
        private:
                MLabelRenderer*parent;
@@ -546,6 +547,19 @@ QSizeF MLabelRendererPrivate::labelSize(const QPaintDevice&dev)
        return tonatural(dev,tsize);
 }
 
+QSizeF MLabelRenderer::labelSizeMM()
+{
+       return d->labelSizeMM();
+}
+
+QSizeF MLabelRendererPrivate::labelSizeMM()
+{
+       qreal fac;
+       if(unit=="mm")fac=1.0;else fac=25.4;
+       return QSizeF(tsize.width()*fac, tsize.height()*fac);
+}
+
+
 MLabel::MLabel(){}
 MLabel::~MLabel(){}
 
index 5e2a408..0bf4a24 100644 (file)
@@ -54,6 +54,9 @@ class MAGICSMOKE_COMMON_EXPORT MLabelRenderer
                
                /**returns the size of the ticket in the coordinates of the specified paint device (used for preview)*/
                virtual QSizeF labelSize(const QPaintDevice&);
+
+               ///returns the size of the label in Millimeters
+               virtual QSizeF labelSizeMM();
                
        protected:
                friend class MLabelRendererPrivate;
deleted file mode 100644 (file)
index e4264a98e577c1747e3a2efd19abe8a364c7f500..0000000000000000000000000000000000000000
Binary files a/doc/logo.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..ca64a23b0588d5484a3ddbfade8e96f9b86e5c1f
--- /dev/null
@@ -0,0 +1 @@
+../icons/logo/logo.png
\ No newline at end of file
diff --git a/icons/arrows/arrowdiag.png b/icons/arrows/arrowdiag.png
new file mode 100644 (file)
index 0000000..8844a7f
Binary files /dev/null and b/icons/arrows/arrowdiag.png differ
diff --git a/icons/arrows/arrowdown.png b/icons/arrows/arrowdown.png
new file mode 100644 (file)
index 0000000..683c465
Binary files /dev/null and b/icons/arrows/arrowdown.png differ
diff --git a/icons/arrows/arrowleft.png b/icons/arrows/arrowleft.png
new file mode 100644 (file)
index 0000000..abe8720
Binary files /dev/null and b/icons/arrows/arrowleft.png differ
diff --git a/icons/arrows/arrowright.png b/icons/arrows/arrowright.png
new file mode 100644 (file)
index 0000000..8a62bf7
Binary files /dev/null and b/icons/arrows/arrowright.png differ
diff --git a/icons/arrows/arrowup.png b/icons/arrows/arrowup.png
new file mode 100644 (file)
index 0000000..cfb4cdb
Binary files /dev/null and b/icons/arrows/arrowup.png differ
diff --git a/icons/logo/icon.png b/icons/logo/icon.png
new file mode 100644 (file)
index 0000000..a5b3bbf
Binary files /dev/null and b/icons/logo/icon.png differ
similarity index 100%
rename from src/images/icon.xcf
rename to icons/logo/icon.xcf
diff --git a/icons/logo/logo.png b/icons/logo/logo.png
new file mode 100644 (file)
index 0000000..e4264a9
Binary files /dev/null and b/icons/logo/logo.png differ
similarity index 100%
rename from doc/logo.xcf
rename to icons/logo/logo.xcf
diff --git a/icons/misc/cancel.png b/icons/misc/cancel.png
new file mode 100644 (file)
index 0000000..fd285bc
Binary files /dev/null and b/icons/misc/cancel.png differ
diff --git a/icons/misc/done.png b/icons/misc/done.png
new file mode 100644 (file)
index 0000000..dbfb948
Binary files /dev/null and b/icons/misc/done.png differ
diff --git a/icons/misc/next.png b/icons/misc/next.png
new file mode 100644 (file)
index 0000000..7700d6f
Binary files /dev/null and b/icons/misc/next.png differ
diff --git a/icons/misc/prev.png b/icons/misc/prev.png
new file mode 100644 (file)
index 0000000..99dc873
Binary files /dev/null and b/icons/misc/prev.png differ
diff --git a/icons/misc/separator.png b/icons/misc/separator.png
new file mode 100644 (file)
index 0000000..ea92843
Binary files /dev/null and b/icons/misc/separator.png differ
diff --git a/icons/oxygen/COPYING b/icons/oxygen/COPYING
new file mode 100644 (file)
index 0000000..264c1dc
--- /dev/null
@@ -0,0 +1,52 @@
+The Oxygen Icon Theme
+    Copyright (C) 2007 Nuno Pinheiro <nuno@oxygen-icons.org>
+    Copyright (C) 2007 David Vignoni <david@icon-king.com>
+    Copyright (C) 2007 David Miller <miller@oxygen-icons.org>
+    Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
+    Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
+    Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
+    
+
+and others
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 3 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+Clarification:
+
+  The GNU Lesser General Public License or LGPL is written for
+  software libraries in the first place. We expressly want the LGPL to
+  be valid for this artwork library too.
+
+  KDE Oxygen theme icons is a special kind of software library, it is an
+  artwork library, it's elements can be used in a Graphical User Interface, or
+  GUI.
+
+  Source code, for this library means:
+   - where they exist, SVG;
+   - otherwise, if applicable, the multi-layered formats xcf or psd, or
+  otherwise png.
+
+  The LGPL in some sections obliges you to make the files carry
+  notices. With images this is in some cases impossible or hardly useful.
+
+  With this library a notice is placed at a prominent place in the directory
+  containing the elements. You may follow this practice.
+
+  The exception in section 5 of the GNU Lesser General Public License covers
+  the use of elements of this art library in a GUI.
+
+  kde-artists [at] kde.org
+
+-----
+see doc/copying-lgpl.html for license details
diff --git a/icons/oxygen/dialog-cancel.png b/icons/oxygen/dialog-cancel.png
new file mode 100644 (file)
index 0000000..6ce7961
Binary files /dev/null and b/icons/oxygen/dialog-cancel.png differ
diff --git a/icons/oxygen/dialog-cancel.svgz b/icons/oxygen/dialog-cancel.svgz
new file mode 100644 (file)
index 0000000..3f4c6aa
Binary files /dev/null and b/icons/oxygen/dialog-cancel.svgz differ
diff --git a/icons/oxygen/emblem-important.png b/icons/oxygen/emblem-important.png
new file mode 100644 (file)
index 0000000..2adca02
Binary files /dev/null and b/icons/oxygen/emblem-important.png differ
diff --git a/icons/oxygen/emblem-important.svgz b/icons/oxygen/emblem-important.svgz
new file mode 100644 (file)
index 0000000..b1d5dc0
Binary files /dev/null and b/icons/oxygen/emblem-important.svgz differ
diff --git a/icons/oxygen/favorites.png b/icons/oxygen/favorites.png
new file mode 100644 (file)
index 0000000..2dc1f23
Binary files /dev/null and b/icons/oxygen/favorites.png differ
diff --git a/icons/oxygen/favorites.svgz b/icons/oxygen/favorites.svgz
new file mode 100644 (file)
index 0000000..fd7d38b
Binary files /dev/null and b/icons/oxygen/favorites.svgz differ
diff --git a/icons/oxygen/hidef-preferences-system.svgz b/icons/oxygen/hidef-preferences-system.svgz
new file mode 100644 (file)
index 0000000..7277002
Binary files /dev/null and b/icons/oxygen/hidef-preferences-system.svgz differ
diff --git a/icons/oxygen/preferences-system.png b/icons/oxygen/preferences-system.png
new file mode 100644 (file)
index 0000000..75be44e
Binary files /dev/null and b/icons/oxygen/preferences-system.png differ
diff --git a/icons/oxygen/preferences-system.svgz b/icons/oxygen/preferences-system.svgz
new file mode 100644 (file)
index 0000000..b58b30e
Binary files /dev/null and b/icons/oxygen/preferences-system.svgz differ
diff --git a/icons/oxygen/printer.png b/icons/oxygen/printer.png
new file mode 100644 (file)
index 0000000..6e110de
Binary files /dev/null and b/icons/oxygen/printer.png differ
diff --git a/icons/oxygen/printer.svgz b/icons/oxygen/printer.svgz
new file mode 100644 (file)
index 0000000..fd862ee
Binary files /dev/null and b/icons/oxygen/printer.svgz differ
diff --git a/icons/wikipedia/README b/icons/wikipedia/README
new file mode 100644 (file)
index 0000000..98e489a
--- /dev/null
@@ -0,0 +1 @@
+Images in this folder were pulled from WikiPedia and are under Public Domain.
diff --git a/icons/wikipedia/de.png b/icons/wikipedia/de.png
new file mode 100644 (file)
index 0000000..8482b19
Binary files /dev/null and b/icons/wikipedia/de.png differ
diff --git a/icons/wikipedia/en.png b/icons/wikipedia/en.png
new file mode 100644 (file)
index 0000000..63a8dd7
Binary files /dev/null and b/icons/wikipedia/en.png differ
index 8b26a3e..79a57d7 100644 (file)
@@ -41,6 +41,7 @@ class MProgressWrapperDebug:public MProgressWrapper
                 virtual void setLabelText(QString l){label=l;}
                 virtual void setCancelButtonText(QString){}
                 virtual void cancel(){}
+                virtual void hide(){}
                 virtual void setRange(int mi,int ma){
                         min=mi;max=ma;
                         if(min>max)min=max;
index 56fbf9c..2237e83 100644 (file)
@@ -45,6 +45,7 @@ class MSIFACE_EXPORT MProgressWrapper:public QObject
                 virtual void cancel()=0;
                 virtual void setRange(int,int)=0;
                 virtual void setValue(int)=0;
+               virtual void hide()=0;
 };
 
 #endif
index fef5acd..aa6222c 100644 (file)
@@ -204,6 +204,8 @@ void MTemplateStore::updateTemplates(bool force)
                set.setValue(s.filename()+"/description",s.description().value());
                set.setValue(s.filename()+"/flags",s.flags().value());
        }
+
+       progress->hide();
 }
 
 bool MTemplateStore::retrieveFile(QString dname,QString fn)
@@ -386,6 +388,8 @@ MTemplate::Type MTemplate::type()const
        if(x=="zip")return ZipTemplate;
        //HTML?
        if(x=="html")return HtmlTemplate;
+       //MIME?
+       if(x=="mime")return MimeTemplate;
        //hmm, unknown one
        return UnknownTemplate;
 }
@@ -398,7 +402,7 @@ static QStringList tempBaseNames;
 QStringList MTemplate::legalBaseNames()
 {
        if(tempBaseNames.size()==0){
-               tempBaseNames<<"ticket"<<"voucher"<<"bill"<<"eventsummary"<<"scripts"<<"eventview";
+               tempBaseNames <<"ticket" <<"voucher" <<"bill" <<"eventsummary" <<"scripts" <<"eventview" <<"printathome";
        }
        return tempBaseNames;
 }
@@ -414,6 +418,8 @@ MTemplate::Types MTemplate::legalTypes(QString bn)
                return ZipTemplate;
        if(bn=="eventview")
                return HtmlTemplate;
+       if(bn=="printathome")
+               return MimeTemplate;
        return UnknownTemplate;
 }
 
@@ -429,6 +435,8 @@ QStringList MTemplate::legalSuffixes(QString bn)
                        return QStringList()<<"zip";
                case HtmlTemplate:
                        return QStringList()<<"html";
+               case MimeTemplate:
+                       return QStringList()<<"mime";
                default:
                        return QStringList();
        }
index 9e3f410..1c5b5d2 100644 (file)
@@ -106,7 +106,9 @@ class MSIFACE_EXPORT MTemplate
                        /**Script-Zip template*/
                        ZipTemplate=0x200,
                        ///HTML patterns
-                       HtmlTemplate=0x400
+                       HtmlTemplate=0x400,
+                       ///MIME patterns
+                       MimeTemplate=0x800
                };
                Q_DECLARE_FLAGS(Types,Type)
                
@@ -180,13 +182,13 @@ class MSIFACE_EXPORT MTemplateStore
                \param useFirst if true: use the first match regardless of whether there are more candidates
                */
                MTemplate getTemplate(QString base,bool useFirst=false);
-               /**returns a specific template by its full name, opens a template choice dialog if necessary
-               \param full the base name of the template to retrieve (eg. "ticket.xtt,1")*/
+               /**returns a specific template by its full name, returns an invalid template if it does not exist
+               \param full the full name of the template to retrieve (eg. "ticket.xtt,1")*/
                MTemplate getTemplateByFile(QString full);
                
                /**returns all templates (for MTemplateEditor)*/
                QList<MTemplate> allTemplates();
-               
+
        private:
                QString profileid;
 };
diff --git a/pack b/pack
index c3ce038..2802df2 160000 (submodule)
--- a/pack
+++ b/pack
@@ -1 +1 @@
-Subproject commit c3ce03861e7e94ee5a87b8cb506b120af4d74348
+Subproject commit 2802df28960dcde2a86a78a154cdcb6590152856
diff --git a/printathome/README.icons b/printathome/README.icons
new file mode 100644 (file)
index 0000000..3831ec5
--- /dev/null
@@ -0,0 +1,10 @@
+README for Print@Home Icons
+=============================
+
+The basic MagicSmoke Icon is part of the MagicSmoke project.
+
+The Icon Overlays have been copied from the KDE Oxygen Icon Theme.
+
+You can download them from: ftp://ftp.kde.org
+
+These Icons are licensed under GNU LGPL version 3 or any newer.
index 5ade69e..f54b015 100644 (file)
@@ -11,6 +11,7 @@
 //
 
 #include "clientcfg.h"
+#include "pah.h"
 
 #include "scli.h"
 
@@ -23,8 +24,9 @@
 #include <QLineEdit>
 #include <QSettings>
 
-#define GROUP "PrintAtHome/Client"
+#define GROUP PRINTATHOME_SETTINGSGROUP "/Client"
 #define TIMER GROUP "/timermin"
+#define ORDERAGE GROUP "/orderage"
 #define SETPROFILE GROUP "/setprofile"
 #define PROFILE GROUP "/profile"
 #define SETUSER GROUP "/setuser"
@@ -45,6 +47,10 @@ MPClientConfig::MPClientConfig(QWidget* parent)
        fl->addRow(tr("Check Interval in Minutes:"),mtimer=new QSpinBox);
        mtimer->setRange(1,10080);
        mtimer->setValue(timerInMinutes());
+       //age of orders
+       fl->addRow(tr("Maximum Age of Orders in Days:"),morderage=new QSpinBox);
+       morderage->setRange(0,999);
+       morderage->setValue(maximumAgeOfOrders());
        //profile
        fl->addRow("",msetprofile=new QCheckBox(tr("Preselect Profile")));
        fl->addRow(tr("Profile:"),mprofile=new QComboBox);
@@ -116,6 +122,7 @@ void MPClientConfig::save()
        QSettings set;
        set.setValue(SETPROFILE,msetprofile->isChecked());
        set.setValue(TIMER,mtimer->value());
+       set.setValue(ORDERAGE,morderage->value());
        set.setValue(PROFILE,mprofile->currentData());
        set.setValue(SETUSER,msetuser->isChecked());
        set.setValue(USERNAME,musername->text());
@@ -133,6 +140,12 @@ int MPClientConfig::timerInMinutes()
        return QSettings().value(TIMER,60).toInt();
 }
 
+int MPClientConfig::maximumAgeOfOrders()
+{
+       return QSettings().value(ORDERAGE,7).toInt();
+}
+
+
 QString MPClientConfig::preselectedProfileId()
 {
        return QSettings().value(PROFILE).toString();
index 4ab0a92..9929d13 100644 (file)
@@ -28,6 +28,7 @@ public:
        MPClientConfig(QWidget*parent=nullptr);
        
        static int timerInMinutes();
+       static int maximumAgeOfOrders();
        static bool preselectProfile();
        static QString preselectedProfileId();
        static bool preselectUser();
@@ -41,7 +42,7 @@ public slots:
        void save();
        
 private:
-       QSpinBox*mtimer;
+       QSpinBox*mtimer,*morderage;
        QCheckBox*msetprofile,*msetuser,*mautologin;
        QLineEdit*musername,*mpassword;
        QComboBox*mprofile;
diff --git a/printathome/examplemail.mime b/printathome/examplemail.mime
new file mode 100644 (file)
index 0000000..b4cfa23
--- /dev/null
@@ -0,0 +1,298 @@
+{#
+This template contains the mail sent to a customer
+when an order for this customer is executed by print@home.
+
+The first non-empty line contains the subject,
+subsequent lines contain the headers, then an empty line
+and after that the body of the mail.
+#}
+Order {{order.orderid}} - Your Print@Home Tickets are Ready
+From: no-reply@localhost
+Cc: {{cc|implode(", ")}}
+Bcc: info@localhost
+Content-Type: multipart/mixed; boundary="------boundary-aYAmoTT04MIpvxx"
+MIME-Version: 1.0
+
+ This is a multi-part message in MIME format.
+
+--------boundary-aYAmoTT04MIpvxx
+Content-Type: multipart/alternative; boundary="------boundary-aYAmoTT04MIpviI"
+Content-Transfer-Encoding: 8bit
+
+ This is a multi-part message in MIME format.
+
+--------boundary-aYAmoTT04MIpviI
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+Hello {{customer.title}} {{customer.name}}!
+
+Your order number {{order.orderid}} has been generated.
+
+total price : {{order.totalprice|asMoney}}
+
+
+Your documents are:
+{% for doc in docs %}
+{{doc.fileid}} {{doc.filename}}
+{% endfor %}
+
+
+    with best regards,
+    your MagicSmoke team
+
+--------boundary-aYAmoTT04MIpviI
+Content-Type: multipart/related; boundary="iKETDhtPafV07ewA9JlUOMitE"
+Content-Transfer-Encoding: 8bit
+
+--iKETDhtPafV07ewA9JlUOMitE
+Content-Type: text/html; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+
+<h2>Hello {{customer.title}} {{customer.name}}!</h2>
+
+<p>Your order number {{order.orderid}} has been generated.</p>
+
+<p>Your documents are:
+<ul>
+{% for doc in docs %}
+<li>{{doc.fileid}} {{doc.filename}}</li>
+{% endfor %}
+</ul></p>
+
+<p></p>
+<p></p>
+<p></p>
+
+<img src="cid:logo_png" align="right"/>
+ &nbsp; with best regards,<br/>
+ &nbsp; your MagicSmoke team
+
+--iKETDhtPafV07ewA9JlUOMitE
+Content-ID: <logo_png>
+Content-Transfer-Encoding: base64
+Content-Type: image/png; name="logo.png"
+
+iVBORw0KGgoAAAANSUhEUgAAAIAAAAAyCAYAAACUPNO1AAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
+WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1wYeEDgTc24NbgAAABN0RVh0Q29tbWVudABNYWdpYyBT
+bW9rZSu+IOAAACAASURBVHja7bx5lCRXfef7ubFHZGbt1dWburWD1JJaK5IQwoAlYFjkAR+GxYDB
+mOcBBAgwZjyYAWwfG5tnzGMYPPLYgAFjMAxgg0ASYB5aQBixSAhtrV7Ue+1VucR+731/3IjIrJYa
+PDPMmffemTgnT1YulbH8tu/v+/veEFt3/7YG0BqEJbAtG8uy0IDAQmtNlhd4nk8QhCilUVrjuh6e
+H+C5HkmaoJSm3+sRRRGtdkS/16Pf7zE1OU6vt45lQZYmKFnguQ6WDYPVPSTdwyiZowFLWGit0AjA
+AnT1ACHAsW0cx8Z1HWzLxrYtLMs8tNbYtoXjONi2Y75jWyilyfMCpRRaK4SozrV61lqTZTlFUVLK
+krKUKK3R2hwFgF3td3JinCgKCXwPz3OxbXMMAGUpkVJWzwqN+Y36vaIoybKMLMvJi6LZtzkujVIa
+IcDzXCbGx4haEZtmp9g8N8vszBR+tU8hzFG5jgOAZVkIIdBaU0pJmmbEccLqWpf1bo9ut0+a5qRZ
+xsrKGssra+R5Qb05lmVjHMCYXGkQCGzbhsoQge2SZzlSQdTqkCQJwnKxbQ8vaJFkEj9wiZMC128R
+hh16vYTxiRniNCaKxkiSPkoL2p0O05MtxqOCctM0cd+j2xvQ7Q7o9Xvkha72q6B2BcvCcWx83ycM
+/Ma4VuWsjuMYx3BqwzsIC2QpSdMcKSVKqcZQujKwUpqyLCmlrBxAopRuPheAEMI4vVKsrXfp9we4
+rovj2NU1MsdrjKBM2FgWtm0MU5Zm32VZkmU5pawcRGssy8LzXMY6bTqdFlOT40xPTdLptAiCgE2b
+ppkY79Butwh8vwkEbWICpRRSmd+T1bGXRUmSpkxOjjM/v0RZSNa7fbrdHkVRNg7UOIAQAiFMtA1/
+WGNZ4DguIHAsG8v2KEqJlBC1xilLheUEuF4L11e0Wi26/YwgHMMLW5RqkR3btnDo0FEUilIKZqan
+OOPUGcbbDmrwINHcNtrtFllWcODRgxw6Ms+x4/PEg8QYQBgjO45NKwpot1uMj3fwXBMJtmNjWSYj
+uK5jMoRtHDrLM9LURLZlmSito7EsS7SGoixQSlNUr6WU1X5FFRDDC22iOUUIges6WJYxtBgxeO2o
+xmlGM4NCKYVt24RhgO/7RFHAWKdNqxXRabfodFq02xHjYx3GxtqEYUgUBub7novruli2hVIKVf2e
+VNWzNA5WlpK8KHBcB9d1kVKR5TllWeI6DnlRsHnzLGVZUuQF/UGMY85TY9lWlYKpMoJASo3newjL
+xnEdikKSFZJWpwVZiWWHuF6LyakWURSwuNSjNTaDZVsIO2RlrWB8YhMrywuMj4+ze9dWdm5rYxcH
+8JwdaCy0kuRFzhn2DkAQBT4HDh6lP0iwbRvfd+m0W0xNTjA9NUGnHWFVF9mxHVzXrkqDQ52zi7wg
+SVwsK0ZKiVQSVT1AGuPLknIkcuoMUUeIEBZo1WTH0c9rJxGCJvsIQVOKhBBIqZrs5LqCMAxoRSHt
+dotOxxg9CAJ836PdigjDgHY7qowe4vsevueZjFadb13uhCsq5zKOm6Zpk6kc20E5GseRtKKQLZs3
+MdZpUxQlRVESJwm9fszq6jqe7+FkeYZjOzjCxbYw6VNYVeW1KEpJEPg4jo8XuCRJTporNs1tpz9I
+8YJxpmY8fN/mwKEJJqe30F1f55xzL+TYscOUssDxfC69aCc7tgaEaj+tzhjtdpt2y8dxbBaXVknT
+jDCMeGTfYeK0IOoP6HTaJio6HSYmxuiMtWlHIZZtYVuiMoLAri68Rpu0n6UIYSK3KIrK+MpEuVKU
+UiKUaMqASfucEPm6ybWjabN2hjoTwLBk2raF6zp4nlcZ3sF1HILAJwh8WlFIp9Oi1YqIwhDfN5Ht
++x5hEBCGPp7nYlmiOgLdZKU6+m3bxqrOHZwmussya5zSti2CwMe2LXzfY3ysQ54XJGlKt9unP0jI
+8wKtNY5AUBQFpZQ4jovnWRXQ8hDCRiOQChzh4PsRYTRJt5dQKof22CbcYJzpTS20dglafbaeMkev
+l3LK9llO3d4h7h5jy9xZtAJJW+xj29adjI+1cWxBHCdkecHszDRLy2ts37aVMAxBg+PatFotACbG
+O7TCAM+va6+FGgFaaF3VdkWOxi5NWXAccwGU0pRSUZvW1EyNEBKogeGw9tebqAxeA63RLFBHpOs4
+eJ5LEPjGkGGA57oEgYfr1gZ28VyPKAqMM/h+AyJN+bCbrFEUZYVNJFmWN45lMI6L4yiUYzdlTWs2
+4JYaIDq2ro7DJ8tysixnEMcsLC6zvt5lECcGA/hBAFqbOiIVcRzjuh4BFr7v4vshCBstbKSyaI+N
+05nYwvGFVS66+GwGicfE1BT92GJ6ruTSS+bI+wNmJlt4tmbmzHPw7Jz+4vcIwog4TkGrKv0FOK4B
+N2EYECcZ27dvY3ZuG/1+TJYO8DwD7kQDgDRSlsb4VTSjaaK8jlUhBK7nYuc5nucSKr+6sArXdSlL
+abKHbVGWcmh0IYZlgI04YFgeRGOU2vh1hEdR2Ly2LasBjCYr1MCxAnDS/FZRlBWKNyDNElazL6vq
+QEZLgue6JvtUiLB2Uq1NhyYrPJCmKWma0+316Xb7dHt94iRtOg7LsnDqmuV6Hr5lIr4sFXGSkGYl
+7Y5Fuz1B1BpH4ZAVmm2bJ5nbdjoHjw648ilbcVow5sOuc7ex+1y46pLTue8ekNk4raBkfv+32brz
+QmS8j1arTeD7OJ4PWuFZNpbjY9s27bKkLAuwPHrdLnHsI8uiylAlqqxAnCyr+qdR0kRwvZVSVa2b
+iSzXcY3RHRfbzqsTN9Hrey5FadJ3DfQYMbpWekOrpkdKQm2ouvWsoz+KQsLARLjruk23UpcHy7JQ
+2kS6lKIBcE19b05GjHQ/XuXcoilDxuiiKmuyuR71Q1bdRpplpsOpPq/LUllKLEvjyLJEWHZV7sxF
+CwIP2/EQlkOalcwvLDE5ZTG3ZQfjk9MUZcTm2Ul2nr2J9RjGNsPLngk3fR3iAubnwbZh56k+Kwtd
+ZrecisOAsc0X4tjg2AAKrTRKS7RSqKqFQktsUWJbJWWR0x8MjAOUBWUh0RgUPGoMRiJTKYWwhtjA
+81xzQcoS33cpCg+tNZ7nNmhaadW0ZkNULcnzvInOx9tGsYElrGHdd90GHJoUbjdOZ1miAtsapUCp
+kjwfOpfSymSACkCGod90FkJQGVZWGWyIW8pSIqsS5bnuhtICkOc5WZ5X3IHBL1ma45SyRCiFEDZS
+amxbYTkaFwvP8xgbG8cP2thOxJGj86z1FJddsYPOhIsdwhWXw+4zYTuw40zopzA9BbM7YW0ROp1x
+piZa2KLAEhIhNAIDzvKyaIiRNO4hy5g0TSmKgn5/wCAekCYJaZqRFzlSqqr2DmuwBVjV37XRAWxR
+ZQDXxfc8wqoW1oDI9MQGD4CuAKNBykmWkcQp/f6gcoiNbWHtKHlR4Dg2ZVk2UWhInwLLEo3hzN+G
+u3Adp4l2yxINSSSVavgKy7JwXBeravOcCmdoDVmeN85acw11616ndaU1tmUjPNEYXGtDiDl20mAP
+pZQBgXVNUkojlcaSmrLU5IXCySV5KYhaHttP2cn07Da0aPGNb/W4/u0dOrNwdsXbnXUabAMcYN9x
+aLcgCjxs4VLkFkWek+UZRZ6TpCnxYEC/O0+/36PIc0pp+tOilOSZYa+yvKDIjbMoKZuL59gWjisQ
+toXGcBmmlTWR4rrOSCqs2LiajCnlhqivDV+UJVmaYvVtlII0zZr91ddoNE3X/yuEoNcbNPvLwxA/
+zwkLA/o8z2scQSmN41iNE9fHqbRCqWFGcWwDZMMKXDq2TVGWJElGnudN3a8zirCsqgMw2aaOfoPj
+3IZvmJ2dYmFhGddzOH58Ecf3fcOo2TaW7SCEbR6WA8JBWC5aafr9Ad3BQeaXEqbnBE84dyuZgtPG
+wasO+uzquQ+cvhl6BawtQ68nyJKCJEko8pw4SYnjhDhOGcQeWdamKHKULMnSPkWWkqUDisI4gSyL
+KhIlYDzfqi5ewwK6bhXxw9SLFjiWBUKgMQ/Lsip2rqZpywZJl1ICVkUKqarPr/HBsMcfTekGvUvi
+OKEoCpIkw/cHBBUmaLdbVfbxq3bawStcA2wrMKmriJVCjaRu8D3TIjqOTSkl8SChP4irDGOcsj5X
+xxlGteu6aF3iOE7T3ga+D+MmQ8RxysT4GP1+jJNlKUJY5mENjW/ZLpbtYdngeh6O6+CHHYJ2Bz+I
+SFL44Y9BtGB6J2wdqY09oLcC/XWIexDHfdaP302SxCRJQhLHDOKYbrdPHMdVr64bQFMTM8ZIqvlb
+1+yEEFiWTV4oLKsgCHykBN8X2LZAStBaNXMAKcuR31QNdVpTwEVRkmY5WZqTpBl5VfdNT25R+nJD
++m8ir0LeNZBL05w0NUDT972K5cxptaKqTTTlqMYKozT2z8IXZWkcbDBISNIaxYvq/wQ1A+Y4Aqsq
+a8PfFIxCmJpLKEtpjsXzvOqEHCzbRggHYdlN9AvLQaMpioKs6KKtFjNzDrvPh9++Dr54BIoTDn4O
+CKZM9Fsu+EGbsc2X4yV9vDjGGSSIwQDCPvYgJs8zZJkboFeWSFmYqJc5djmgzHs1LK8GOqKp22Ah
+MGnV9xzaLdNRaK3pD2LieEBWpUwDmKj2oSqO3jCF5nc1nutAFOC5DlpFVVbYyAYaqlw0vXueF6Rp
+ShynFGWJlKbc1CjeRLnZX54XBiQ2HIBVOZpB+7ZtYdcDLqAoJGU5IEkykw1HuhylLLSlN5SmGqsM
+2cqq/hcG9+R50QzH8qIwGQAECAutRVUOXBzHx/U0fuiZAc7MFk49fZx+OsNqN+ZXr2tz/Y2w43R4
+0TZjn8MYMGgBR45Cuw1rqyCVZn29z9LB28gLhaRNVrokiawmZGll/AItZdUZaAQWlh0StEJAYVsG
+QFoCLAssYcpBu2Xar/GxFq0oxLIEcZJSFBKpuiRJhpJqiHW02mDQuucPAg+qTkIp1XSEozOCmjOo
+WzCT9g3zmOdFNVfQG1rR2hA1l2AynjGazg2OMK2obPiCsqxbxdjMNqqJpWgAr276frvKAFIqtCUQ
+ldPmuSkVtbMnScogTkgSQ8ClaY4jwIAny0zRHNfD80L8IKTVHkfjECcphw8fYnI6QouEuD/gbW8b
+49SzA84/G/YDZ2JQvzcLATC3CR55GPJUIrRkrBPgnvpkev0e66vHGcw/QNZbJ02KKk2rpoevL6BV
+ARstzLNVES++7xEEnkmrnkcY+rRbEX7g4bluRQuD53ugIc/Lapo5ZA9N3VeNY5iULpqa63h+Baas
+DZE02n7Wwxa32mdZShzHtNS+7zE21qbdihoyqD63sjTlqXbIRKfYtk1R+ESRqrCBCQ4hBFaV0ZSU
+VZYWTddSl7ayNOWkLg2jE8karBrsldLrx2SpySiO57vYtovjuLiej8CikCXr3XXWu32mpjexY8ck
+h49k3PeTfYxPbiYtbNpjk0yPBawtwQMJOKdCfxUmx2AQQ6cFs9MwHSqSzGLvIQvXDtAqRcsxHOdC
+xvOMlYWH6PfWSeI+ZalRSo7ULEPfOraoTkzguhZRaOprbXTf97CEQJaSuChJs4wkyZBSVkSMoJSS
+spr+1RmgJpIMBWuipWbx2u0I1zW/bYCy2JBmawO4qdMY1nEc0iyrOAGbKAqaaeZw7lCPo8vq/4aj
+4TwvTEucZgRh2GQRt2ohaxaxUKpB/YKNQ6mh4U0pqZ2llJJur8/q6jpLSyusd/usr3dxXNcQIqtH
+7qTIB6AVs6c/j+mpKZQWHLr/8xz8qWb7rl8jHgzwgwg/nGCiozi4t2Bp0WVuFnrHYdsErK9C0oXl
+BRhra46uOMyO5Yy1NJ0QjjjjOLaEYhHyHhPjbTzPJ2tPkCZ9siyhLPINEWIuhEAI3aR/MHigLArK
+wmSQoiyRpSRJM5SSJEmG49gEvotSPklCU/frmkyVyk2LZhzB81zAsGZhGBAGAa7rNOnXRHFJlhcb
+ZgSOYxOVIVqrhv07kTvQeigAGa3VsqKBy7IkjlOCJMWu8INjGx7A81zT31ct5XByqxsHsSyTMWrj
+C2Gcf9CPWV5Z4/j8IisrayYLZDkimtqtEYKos5WJLZcShBFJnDK/7yZs22PTmS9g6cBX0apk265X
+E7YmmJyaIysEW7fvwPXaPOnyNocPWOzavsyZp0+TZ5pWZAzlORo0bJ2WZLkkz1LW1rscWchYXznC
+oLda1aWUPM/JK6BS5BllaRB8PZ62LAvXNTP1MDTpvzaAlEbJo5SiLMqmjtfgJ01zsixr6rSsRSCl
+GRPXOgHXdel0WkxOjDMxMUYUBs3gZsiXqEYBVJeBWvFTdxX1vL52lg3dTFNK9AZqt1YJ1RnBHgGK
+Q82DiXTNxulljTXqbGNbdiMWieOEbrfPwtIyi4sr9AdJw3A6k9ueTBhNoDSsHbub/tqjOG5Ia/IM
+ssExDv3kY7TGtxJNPRHH1thCsrR4hB07trNw7CBBOMYD93Xo910e/d5H+LVXv9XQyDgINO6ES1nC
+oQXB5ikby4Ioiti+SUIRYDubse1FHMciz0wKdWxBKrShdAvLoHQpkbLAtkUFmhyEKBuGq+br617Y
+/G0TRS5CUFG7psuomcAsz0mS1IgmipKyNJx+FJn5fOAPBzh1BNdGHJ0JjGYHxynw/RrAyYpzFw3m
+GKWb62eTzTbSzaZbkdi2bpy1TvGj9d2yrIYJHRJKujnPJM2aYVCvN2AQJ5RlOSSctNbMH/gn8myA
+bTsE0TQaRXfxfizbYWz6LGQZ05l+IlqVdNeXmJqe48jhIwRBG1tIHt2XEoYOD93/IHv2zXPaKZMs
+LSXMTI2TZDkTYyFaaw7Na2YnXJTKabXH2HGKxfHjx/HsDlEUkKZJNcHKiBPXAJU0oyhLlNZYlYe7
+jk3gD+nRut83qd1EtJFbGTrbqUaqo21fnhekWU4SJ8SJmZrlRYFbAU0hhNlvNZptZFcVVw+MKHJk
+Mz/QWjWysxqgWcJCoUaAJA1/MBr9oyWhzg6mczB9SlmCEKqikMUGSnmUL+gPYpLEXMcsM6xrPpKZ
+NkjClg/fgSUEnhdh2S55uo5UJa4b4ngRebLCpjP+FUrmxFlGGLUZ9FdRWoAuGR8XLBxfYHJykv6g
+x579C0x0bFxbcfjIPGOdNlpBFHr4Hix3bTZPjZMmAyYmxvE8l+PHjtHr94lCvxI2piRxTJrlDAZG
+1VNfPNdx8XxTDwPfQ0pJfxDT7Q7oK2kQf140QxgDzuyGi7eEha44hCRJSVoRcVWChoZTG+YFhj0s
+mzbOsuwmYmuSqe4CRtvGUsrHCD9Hs4jW6rEahJHBVt2dnNjfa1070lDrWJeiusTlecEgThjEQ/HH
+422O43hYtocsE/I8RqDxvBbCslGqZHbntcgyp7tyD63p3UBJMkhxXJ+kzOivO/TWl/FdzerKMktL
+azywB87c0WFp4VGUPJUsz9m6ZZaiNGmq24etMx7pIMV3fSanZijKkjRJcG2B12kThYHpsdvRhtbQ
+rWbhnmfIlLwoCMMQx3YATVfV41XRsIZC1HXUbeq5UoowDEiSlLFOmzTNKlGoqhi9lH6/38wOyhEu
+wB6ZvQ8FpkOSqD7WOtXW5E0tIK2fh9FuPquJqkb92TyLDY6x8f+GWWOohFYNB3AiQfQYB9Bakmdd
+wMYSYLuRYcWwmdr+VMoypzv/PRy3he/ZDHorWLaPQOH5Af3eKmU+IE08sixj/6EFyjJBlTFlnhLn
+8yCW0FoxPbOZLDNz6G5fc9rWgKXVNdPK2AFr64vkRY7nmlpsWTZBEFT1jgYA1TN44xBOJQTV1RDI
+Jc/zRnHr+37jNDWIqtOm1powCCqlrmwiM89zer0Bnuuwtt6j1+sPgaYaSsKGGoKhxLxu7WrDj2oJ
+6pq9kerVG+xd8x+V1vikmeFE40MF/OqxthxS3iczPoAj5ZC1EsJGK4nt+ExsvZI8XmKw+hACQWfr
+hSRJnyJdxQtnKVRJEHjEcRetC/Ksj1aSLBnw4MMrrK+NMzvlog/vJRzfSdI7xqlnXcLU1AxaK1pR
+wL7DOadsbtFbX8Z22kxOz7Fv3yPM95fxXJdW5GM7Dq1WRCsKKxWw1Sh46zQfBD5am1643W41n9XD
+lvp79evRCzmKyoepvKTdNkLXMAwIAs+UiyQz9bQCnbXhh8phtZFkUhtbvhNlZaNAcnTGUMlBRr5z
+cicYsoxyiBsq6jcviqasnWyz3WD6PbbtVWIMheOGjM1dRrK+j6R7ALRibO5JCAGD1QcRwsF2I9AK
+2xakSVzp6jRrx3/MxKbz6a8dIU41692Y1fn7UNYUxw49wKC3jO2NE4UhUpY4rsMggU7bJUtjPC/C
+8wKOHTvG4SPH6HYHrKyuM+j3DelSMV2NLFoOJddUjOZQhGE1gona+Fa18MX01vZwoUkt3hhJl1Ip
+XMeumMbAyLs8tyopAmGJRrVTp+86vddZYtSphsMYfYIY9cSWkOq465ExGxXBI9qH2mHEyHHnRcFg
+YNq+OEmrCerJN9uLtr5HVytwHDeiM3MBg5X7yJMVk2L9cfzWHP2l+9C6xHJCZN7DCzoopVg/fjde
+tAlQrC/ez/jsOawe+x5+aytJkrA8v4fe2mFy1Wbh6MPE/XVa45tohS0sW2DZDhqHTjvA8wS24xNF
+LZYWFxkM+vQHMRqr6uFzZGlQsEG1hkbOsrwBQDXq7Xb79PoDBoOYfn9APDBov5QleV6a8YcQGzSA
+o2sAjJPJDem75gIsyyRnx3GwbLEBsNX1elir2aAfODGFD49h+NqwjnaTver2z3Ed49y10MS2sKug
+ENZwFdQgTuj3DYg2vidO+nC0luYPNySaPJvB6v2URVJFlEM0+QTi1T2UxcCgXyUpsmVaY1soi5I8
+WUarAlmY31FljiwSs9xLSpR2WVo8TmlvJV2fp9vrkxQuVz/1GZyyfRtR4FCUkJUOnchj3HGx7W1c
+eNFl3HPPDzh+/Bj9Xh9Zze17/ZixTot2u2XWD9WpGCgLo8wZDBLSLKs492Hb08i2XZcoCky/H9Rz
+eruZFGpNJVHbaLxabiV9I1+TsiTPTWdhWXWXIClLKjbQeoye8ERlcZ3iR6O8XvFkRC5DRrGJfGE1
+ztV0BpwoDlX8SzYHBLYb4He2k6ztQZZpdVA2fms7ae8QebJkRBVaUCQLeK2tSGnqi5IFWhaouqbK
+HKVKZJlXJ27EjL3F+xDCZnl5mfzH/wTC4+qrr8J15vA9hzBok5cQeBFTk0ZiUlRLu5aW5xvFrOu6
+1cVXDX1b19ha3l6rZuo5vRk0DVF8vYQsikKiyCzYCHwf1zNg0aRNQZHnTVSlqan/SVKzfUUDChlZ
+QjYkjPQJFPDGVq8edG00vDADOcfGdtxGQVQbv9Yejg6vRgdTytaN/u/n1f6h8sjxcf0Jst5hpMya
+VTG2G4CW5PH8cKGkBlkMsN0WssyJ1/chhIVSRZNqyjJHIMji49hOy/yTEMgyw4tmkUnK+toq9979
+VbIs44orLmf3ru2sLCfIsTHcyRYah/HxDnObt3LeBRfx3e/cydraEnleEAYBUhqWzfdcI+0qS2Ow
+CvQYhi+rBkBlo/4xqp9h327bFoHvE4Z+lQmMhKuWaYlqpFoUJb3+wLCGWU5RFs0krlbnNiKTyuHq
+CdyonLw25BCYWk2LWjtBs8TN9TZ0GifrAmpnqAkro4FIf2bvv8EBLMejSFeRMm9WuViOhxvMkg2O
+omRhlkkJG1VNsAQaKQuKZNnwBbKkLIxoQ5YZCEHWP4zf3tEsmFSqoEhXje5AlfR76zx47zcpyxyt
+LuWCc7aTehaljLABhUMrCtm27RQuvexK7rzj26yuLJFFRiaW5wVjY50qdZt0XK+NMyLSSk5elQGN
+uUBFZVCDkMuKPzdlwfddvEp/byjWoe6v3mftZDW4sqtlaY8la4aCjNEU7lTrGUfBauMU1vC9uv/3
+ffcxJaNe0dywlMqc18rqOqur6wzitOIeRp3lJDyALFKUKhv4KWwH2wnJ4+ONUzTLpLTCcVtoVVLk
+PZQssGxD7RbJkjG0LKvnAln0RrxQGIWP41flRNLvrbL3oe+jtcRzFOefcxrdbo9Op4VtWYRRB9f1
+qtbman78w++zurJkNHhJgm1bRFFUKWqM0agWWpRlSa6hlGVDu8rKcKNOMlqTLcuMUGvJlm0NhZwN
+tz9SSgTDhaJmNiGwLKdabMuGYU/FulUtq25kW/ZIN1KXEUNDy0rMQeNglrA2KHuSJCGp6HLDhprF
+H6PLv39uBlCyGKlNRhiiZGYiGQ31yVRyLCeYMVr2+DhKGQeQeY8yXwc0ebpUgZ0SLfNmjb9p30w2
+EVT9sID++jz79twDKiXyNaejieM+s7PThL6HH/hMT7ZpR2ezeXacPQ8/RK+3huNYGyKuBndhYNRB
+vX4fuzvAEhCnGWpEADraSjWEjRzqEIQA23EaHcIomKujvnaUWs411O6Lx8ixRqeIdeTWIo5ayJoX
+YsMqoVEmz7aN6lcrRZ7XeoeUfj9uHCDN8kpYc7Laf2Lp0DUTWEWAZWNZTgWw8qH2rupR7/3+F5mZ
+maTdMtTs6lqX0574NDSgZIZWks1zs+z96d837RLAkaMLnHPxdc0BKFVi2W6zrEmpgv7aUfbvs/jK
+V2Oe/cync+aZZ7C4oJicnCQKfbygg+cHKA1FUXLvvfdw2s5tvPTFz+W7d/2Im27+NpYVYrt2ZRSz
+2DIKQ/r9AWvrXbqVbNtM2Kxmgiel5ClXXcorXvorTE9NEIYBR48t8LG/+TyveuULmZ9f5nfe+SfN
+vL9eC+j7/uMqb4bAb0gqFUVBHKfESUqSmHUPRrJtlom7jsOX/v4/Mzc301y3cy589pA9tC10hWNq
+aXtetcCjg6f/tk3UXUB1LxDLNiuEVImWpk82B2Cj0Zx36fMZG5vk6N5vkqQZkxNjPOdZT+OmW+9C
+LHjLBgAADaVJREFUFgO0krzkRc9laXmNuU3TAPzgR/fzjOf8JqOhpZVEV6IGpRVCG3zQWzvC/gMW
+t9x8MzzrWs488yz6/QGnn35q1RYZqdrE1Aw7Tz2Tyy97Ar7n8eQrLuaD/+lv8D1zB40wCBgb6zA5
+MUYUhlXaDWi3+nR7A3r9AVlaq3Yk1z7jKn7nra/lbf/uj7ntzruRUvG0q5/Ee9/1JsbHOnxj4U4j
+3KxW8XqeWeTpVat/Ntw/QI0SU8b4SZLS6w8qBXTSpPDh+NjGtmwuuuI6vvG1T3DuE88E4Nj84gax
+54kDpROHSCeP9J/bBtKsB1CqRKty49yiWoQphCBOzWffvv1unn3tVbz8Zb/CTbfeZXp+rXjly67j
+7/7+Jm64/pWNKNHc8qUqAwiUNg4mmjuTSNACRU5v7RCPlH30LbfybODss89mbW2dmZlpNBZeEKHX
+1tmydRv7D68wPT3Ol7/6LQ4dnm9IG9u2K02+uZWLH3jMTk9WN2Bokxc5/d6AXt9I03/95S9kaXmV
+H93zAFEYUJaSO777A65/y+/zsRvfZ2Rivm/W/IV+tYw7MDdsqPBBPRHMshxLyAZb5HlBXKXqODb1
+ehTImcFT2TCE3W6/MUyW5RvQ/78E0T8W7IkT3nusczhmIYhVrbY16/SaGyQ0O9Zm3aDbBuDWb9zB
+s6+9imdd8xRmpidYWl7jwguewLnnnMkrf/N3Gwcwdx1RbBCmYwCgwGABzdDJFAWD/ip7Hk6rmzNo
+zjnnXGwbLtq1jdkpn5VtAffvWSZNEj77pe9y6623URSSPDOYpZQlK1I1EzjPc2m3W0xNjrF1yyYm
+xjts374FKSW9/oBNs1PYts1vv/k13PrN29nzyEHSLGPvvoP87Wf+gVN3bm/EnUHTKnoVMGOk/TPC
+jzwvKiVQvqEeG8f0kUo29y4aysHkY4w8Wk7++zd90teiWoHsCMsyRlFlZfxqVFlFrOkAQFhW8wO3
+fOMO4B3Yts2Lf/WZfPjGz/Dylzyfu/75Hh58eO/GHVYnEAQ+b3vzq3nB86+h1QoZH+uwsLjM5794
+M+/7P2802UJJXvxvnsdrX/0izjpzJ45jWkGlNMeXEorSRWvBk3ZvxnMtrrn6DN742udy7fNfx959
+exnvtHjT61/BM6+5quLRLW7++u28+w/+I4cOH+Pe+x4mDHwuOO8JPOmy8zltx3YWl1bYPDfL857z
+dJ73nKeT5wUPPryP2++8my99+esATE6M8+E/fzeTk+PNmd34V5/hJS96Lp1Om4OHjvDxT32BX3vx
+dZxx+g7iJOXr37yTj9z4yWaWsHluhuv/7Sv4pasvRypFnuf8/X/9Kh/88MeJ46S6J9LG7fiB7zAx
+PtY4wsFDx3jC7mcCcMbpO/iD/3ADV115MbZtc+zYAn/2oY/ymc/ddNKFrI/LJbRmLtbR9G4dTp6v
+w8nzqsf5Opy6QEdTu5vn9qbL9NiWq7TWWkfTu/WDD+3VWmv9k/se1pPbrtRLy6v6dW96j46mL9T1
+9p3v/lC3Zy/VrdlL9F/+9We11lrvP3BIBxPn6ouv/JXmex/40Ed1MLFLv+0df9y8d+XTX6Z3PuEa
+vb7e01prnSS5vvX2A/qff7Kqf3h/V8dJ2Xz399//af2Cl75VHzu+qLXW+nff9Wc6nLpAf/bzX9Va
+a/1XH/+cbs1cqMOp3Tqc2q07my7WZ+z6Zf2rL3uj/ptPfUHneaEfbzt2fEG//d//iX7DDe/RN7z9
+D/XRYwvNZ8fnF3V/EDevFxaWdZKkOk2z5r0/+OP/pH/p2pfqF7749Xp5ZU1rrfXr3vQf9I6zrtYf
+/otPaq21vuuff6RnT7lMd2Z36+/c9cPmf/2JXfqMXb+stdb6W7fdpc86/xrtT+zS/sQuveuS5+jV
+tXUtpdQXXnGd3rTjCr221tVaa/3q3/p3zff8iV06nDpfh1Pn62j6guYRTp2vg8nztD+xS1vDEebj
+6NLqCK56Xl21jFopbv76bQCct+ss3vHW1xCFAZ/74i0npPu6xiumpiYAaLfNXT8eePCR5juXXXI+
+oHnec57evLfnkf0srazyyN6DAPi+QzwY0F1fJ8sL5AjqPeecc/nDd9/A5rkZ1td7/F8f+SRKaa57
+7jMA2L5tc8UIyqY2Hz4yz9duuY23vOOPefErbuAHP7qPNMs2HPvmuVl+45W/ysz0BO1WRJ7nzWdf
+vumfeP8H/kvzemyszTve+Sf8xX/5dPPeaTu3IaXija9/JVOT4yyvrHHL129Da82nP/uPAFx+2YW8
+6uUvbO50Um++7/GxG9/HX370szz3Bf8HBw8daz77w3ffwMT4GD+9fw8PPLiX9W6PPXsPAPDbb37N
+Bnp5eDMK2TxGW1pHa4VWJxkZ6rqHtxCWg6qIIa0Vt3z9dt78hlcB8Dtv+Q0+/dkv0+sNKsn20IFM
+O2nx+hvey+e+cDNra+v8xq+/iMuftHs4pKkA4S1fv42nPfVyAF7+kufz+S/ewumnnwLA/Q/u4777
+7mPXrl1VyzXW/P/01CRnn2FWJx4+utCshnntG97FU59yKR/5y08/bh2UUnHggW+x84lP59t3fI+z
+zzyNa5/xZC679AKedMkFzM3NcPppp9DtDZolVfV2x3fu3jBoOnDwCAcOHsH13Oa9QpZ0e30uv8yc
+66HDxyoquWTv/oPN9375GVfx4f/8qQ0V+xN/9adcfdWlppuJAnq9QfPZNU+/CoCzzjyVe//5ywgh
+mKwcbGZmqlmEcuIo+vG7AK1GIf9jjF8TQYbvH0qb7vzuDxgMElqtECEEn/jbL7JBIbEBzGhO2baZ
+N73+FTz5iov4j3/xCd73/ht55ctesAEcfvDDH2fz5lne/IZX8UfvfQtve/OrOT6/yD985Zt84EMf
+p9Uew7IEF+zeXQkgTBsWRVHj7fXiy6Io+dwXbuFzX7j5Z4KkTqfFb77qRXzoI5/gR/c8wI/vfQDb
+tpmbm+bhe24FBN/57g/wPJcsG2aA4/NLjWTdoPaM5eU1tmyOm/fyrGBtrdsoOpRUzYy+biHrpeAn
+Gqq+L+B5u87mbz/2Z7zgxW9owGJ9c6p773uIpz3r5RsGS6O6gxOHUI+3WaNGPfHimIGIAVNaK6LI
+HJTtOOR5yf992/eM9z96hNvv/P7j/07lAJ/95J/z5Csu4sGH9vE7//5PWVxa3jAgEULwrt+9vskq
+T9z9TM4+/1ouufKFvP7N7+WRRw7w0IMP8eWvfJmf3vfTEZ0dLC+vsLpuLvyWzTMIMRRYbJqd4hN/
+9Sc/sz/+vXe8jjNOO6U5XCkVS0trFEXJ939wL4ePHOfwkePkxTADLCwusbC4Moz2omRhcdkYvHaK
+PKc/iLn9DnNttm2dMyROXnDK9i3N97717bsec0z/+sWvbwDdM3/5KXzw/e9sANyd3/1hAwTrbgRg
+65Y5Pnbj+5pZxb+kg7BOFhlND9q0kopXvPQ6AHadcxYAN3/jDgA+9Xf/2OwsioLmV+oVNmiY2zRT
+3QzKrJF/wXXPar63Zcss1//bl/PkKy5q3nv04duIV35Cf+lHHN9/B9/82sfZsWMLDz34EF/60hfI
+83Qk+lK+cvNd5HlBqxXyljf+OqCJooCPfPDd/OBHP/2ZJEmrFXLzP36Uq668uLnIb7/hNdi2xbve
+++d0ewNW17oblnEvL6+yuro+jDip6Hb7zW1Y6t9J04x3/N6fsrbeZW5uhmc/86kA/NZrXgLAPT95
+oMENQeA3/+u6Du98zwea16999b/hzW/4dSzL4r1/9GHyvGB6aoK33/AaylLSbrX467/4I26/8/v/
+ba1jMHGuHj526XDyPB1M7NLBxLk6nDxPR9MX6tbMxXpxaaVBqP3+QIeT5+mzL3iWVkrpJ+z+V9qf
+OFfv3X9Id7v9DUj64KGjOpy6QL/mde/UR47Oa621fvChvfpTf/cP+vq3vEcfO76g+/2BfuNbf19f
+ePnz9aMHj+iTbffe95AOJ89vfmd4PIm+8ukv10971q/pm772Lb2+3tPHji/qn96/R//eez+og8kL
+tD9x/sjjvOqxSxdFoZ/zr1+r33DDe/VP79+jDx0+po8cndff+NZ39FOveUlzbQ4dPrZhn/PzS3p+
+fql5XRSFfuo1L9Vr693mvbIs9ee/eLMOJs7Vuy5+tv7M576iFxaX9ZGj8/rAo4f1n37gL/XM9kt1
+MHGuPnjo6IbfX1hcbrqaelNKNaj+6mtepm++9Tbd6w30kaPz+u4f/kT/1vXv2tAB/EseIpg4V5+M
+OTI3i7CHWaFaR1/f2FkIu1E5S1UADoIS2/FRskRpWd3ezR65ye1QA6eHd+NDIPjaP/w1T3vq5bzj
+997P1279dgUQLa6+6jI+9GfvIklSprdfbm4zF4bs3LGDU045hUOHDvHoo4+SJPHj0B7Wz4h+/XPY
+8pOrah5P5jW6cvh/jMCp75L8P39zTn5RRMMWjS5g0BvkyHWG1487/txICP08tkpz6cXnA7Bv/0Ee
+3rO/uZiXXXIBAB/9xH9tji1NEvbu3cv+/fubRZ1Dzf3IjR5RI3ce/8Vd1Mdj7U72+r9vEydh8n6x
+jnFCBvhfuz31KU/i7W/5TS7cfW4zSLEsiwOPHubTn/0yn/z0l35BF/f//ZvG+jl0rvj/nwP87+3x
+HOBkpeoXkw2s/32p/7+wiccx9i8mA/w/VEUEq97e4c0AAAAASUVORK5CYII=
+
+--iKETDhtPafV07ewA9JlUOMitE--
+
+--------boundary-aYAmoTT04MIpviI--
+
+{% for doc in docs %}
+--------boundary-aYAmoTT04MIpvxx
+Content-Transfer-Encoding: base64
+Content-Type: application/pdf; name="{{doc.filename}}"
+
+{{doc.content|base64}}
+
+{% endfor %}
+--------boundary-aYAmoTT04MIpvxx--
index 2eba23c..4b76816 100644 (file)
@@ -7,5 +7,6 @@ See COPYING.GPL for details. -->
         <file>icon-print.png</file>
         <file>icon-block.png</file>
         <file>icon-active.png</file>
+       <file>icon-problem.png</file>
     </qresource>
 </RCC>
index 48427be..c9fa6b0 100644 (file)
Binary files a/printathome/icon-print.xcf and b/printathome/icon-print.xcf differ
diff --git a/printathome/icon-problem.png b/printathome/icon-problem.png
new file mode 100644 (file)
index 0000000..fb4bf7b
Binary files /dev/null and b/printathome/icon-problem.png differ
index 04cc513..f7e9a94 100644 (file)
 #include "mapplication.h"
 #include "msinterface.h"
 #include "scli.h"
-#include <WTransaction>
+#include "WTransaction"
+#include "MTGetOrderList"
 
 #include "pah.h"
 #include "clientcfg.h"
 #include "servercfg.h"
+#include "printrun.h"
 
+#include <QAction>
+#include <QDateTime>
 #include <QIcon>
 #include <QMenu>
-#include <QAction>
+#include <QMessageBox>
+#include <QSettings>
+#include <QTimer>
+
+static PrintIcon*piInstance=nullptr;
+
+#define PRINTACTIVESETTING PRINTATHOME_SETTINGSGROUP "/isActive"
 
 PrintIcon::PrintIcon()
 {
        setPrintMode(PrintMode::Blocked);
        
        QMenu*ctx=new QMenu;
-       ctx->addAction(tr("Start &Printing now"));
+       ctx->addAction(tr("Start &Printing now"),this,SIGNAL(startPrinting()));
+       QAction *a=ctx->addAction(tr("Printing &Active"));
+       a->setCheckable(true);
+       a->setChecked(misactive=QSettings().value(PRINTACTIVESETTING,true).toBool());
+       connect(a,SIGNAL(toggled(bool)),this,SLOT(activatePrint(bool)));
        ctx->addSeparator();
        ctx->addAction(tr("&Client Configuration..."),this,SLOT(clientConfig()));
        ctx->addAction(tr("&Server Configuration..."),this,SLOT(serverConfig()));
@@ -37,43 +51,161 @@ PrintIcon::PrintIcon()
        setContextMenu(ctx);
        
        show();
+
+       piInstance=this;
+}
+
+PrintIcon* PrintIcon::instance()
+{
+       return piInstance;
 }
 
+
 void PrintIcon::setPrintMode(PrintMode m)
 {
-       QString mode;
        switch(m){
                case PrintMode::Normal:
                        setIcon(QIcon(":/icon-print.png"));
-                       mode=tr("Ready");
+                       mmode=tr("Ready");
                        break;
                case PrintMode::Blocked:
                        setIcon(QIcon(":/icon-block.png"));
-                       mode=tr("Blocked");
+                       mmode=tr("Blocked");
                        break;
                case PrintMode::Printing:
                        setIcon(QIcon(":/icon-active.png"));
-                       mode=tr("Printing...");
+                       mmode=tr("Printing...");
+                       break;
+               case PrintMode::Problem:
+                       setIcon(QIcon(":/icon-problem.png"));
+                       mmode=tr("Configuration Problems!");
                        break;
        }
+       toolTipUpdate();
+}
+
+void PrintIcon::toolTipUpdate()
+{
        QString user=tr("(not logged in)"),host;
        if(req){
                user=req->currentUser();
                host=req->profileName();
        }
-       setToolTip(tr("MagicSmoke Print@Home: %1 - logged in as %2 at %3").arg(mode).arg(user).arg(host));
+       setToolTip(tr("MagicSmoke Print@Home: %1\nlogged in as %2 at %3\nNext print: %4")
+               .arg(mmode)
+               .arg(user).arg(host)
+               .arg(mnexttime)
+       );
+}
+
+void PrintIcon::setNextTime(QString s)
+{
+       mnexttime=s;
+       toolTipUpdate();
+}
+
+void PrintIcon::activatePrint(bool a)
+{
+       misactive=a;
+       emit printActivated(a);
 }
 
 void PrintIcon::clientConfig()
 {
        MPClientConfig cc;
-       cc.exec();
+       if(cc.exec()==QDialog::Accepted)
+               emit settingsChanged();
 }
 
 void PrintIcon::serverConfig()
 {
        MPServerConfig sc;
-       sc.exec();
+       if(sc.exec()==QDialog::Accepted)
+               emit settingsChanged();
+}
+
+
+PrintScheduler::PrintScheduler()
+{
+       //set icon to active mode
+       PrintIcon *pi=PrintIcon::instance();
+       pi->setPrintMode(pi->printIsActive()?PrintIcon::PrintMode::Normal:PrintIcon::PrintMode::Blocked);
+
+       //create timer
+       mtimer=new QTimer;
+       mtimer->setSingleShot(false);
+       connect(mtimer,SIGNAL(timeout()),this,SLOT(startPrinting()));
+
+       //connect signals
+       connect(pi,SIGNAL(printActivate(bool)),this,SLOT(activatePrint(bool)));
+       connect(pi,SIGNAL(settingsChanged()),this,SLOT(reinit()));
+       connect(pi,SIGNAL(startPrinting()),this,SLOT(startPrinting()));
+
+       //init
+       reinit();
+}
+
+void PrintScheduler::activatePrint(bool a)
+{
+       if(a!=mtimer->isActive())
+               reinit();
+}
+
+void PrintScheduler::reinit()
+{
+       PrintIcon*pi=PrintIcon::instance();
+       //check and change timer interval
+       const int interval=MPClientConfig::timerInMinutes()*60000;
+       if(interval!=mtimer->interval())
+               mtimer->setInterval(interval);
+
+       //validate server config
+       if(!MPServerConfig::validateConfig()){
+               pi->setNextTime(tr("not running"));
+               pi->setPrintMode(PrintIcon::PrintMode::Problem);
+               mtimer->stop();
+               return;
+       }
+
+       //check whether the user blocked it
+       if(!PrintIcon::instance()->printIsActive()){
+               pi->setNextTime(tr("not running"));
+               PrintIcon::instance()->setPrintMode(PrintIcon::PrintMode::Blocked);
+               mtimer->stop();
+               return;
+       }
+
+       //make sure timer is running
+       if(!mtimer->isActive())mtimer->start();
+       pi->setNextTime(QDateTime::currentDateTime().addMSecs(mtimer->remainingTime()).time().toString());
+       PrintIcon::instance()->setPrintMode(PrintIcon::PrintMode::Normal);
+}
+
+void PrintScheduler::startPrinting()
+{
+       //sanity check
+       if(!MPServerConfig::validateConfig()){
+               QMessageBox::warning(nullptr,tr("Warning"),tr("Cannot execute Print@Home: the server configuration is not consistent!"));
+               return;
+       }
+
+       //take care of logistics
+       static bool isprinting=false;
+       if(isprinting){
+               qDebug()<<"Printing is already active. Ignoring.";
+               return;
+       }
+       qDebug()<<"Starting to print now...";
+       isprinting=true;
+       PrintIcon*pi=PrintIcon::instance();
+       pi->setPrintMode(PrintIcon::PrintMode::Printing);
+
+       // print
+       PrintingRun().exec();
+
+       // done
+       pi->setPrintMode(pi->printIsActive()?PrintIcon::PrintMode::Normal:PrintIcon::PrintMode::Blocked);
+       isprinting=false;
 }
 
 
@@ -100,12 +232,14 @@ int main(int argc,char**argv)
                ms->loginSession(sc.currentUsername(), sc.currentSessionId());
                ms->initialize();
                QObject::connect(&sc,SIGNAL(sessionIdChanged(QString)),ms,SLOT(setSessionId(QString)));
-               pi.setPrintMode(PrintIcon::PrintMode::Normal);
        }else{
                qDebug()<<"Unable to get session. Giving up.";
                return 1;
        }
 
+       //activate scheduler
+       PrintScheduler ps;
+
        //event loop
        return app.exec();
 }
index e47ce53..172c052 100644 (file)
 
 #include <QSystemTrayIcon>
 
+#include "MOOrderInfo"
+#include "MOCustomerInfo"
+
+#define PRINTATHOME_SETTINGSGROUP "PrintAtHome"
+
+class QTimer;
+
+///This represents the icon in the system tray and the main UI of the Print at Home client
 class PrintIcon:public QSystemTrayIcon
 {
        Q_OBJECT
 public:
        PrintIcon();
+
+       ///returns the Icon instance
+       static PrintIcon* instance();
        
        enum class PrintMode{
                ///Normal operation, waiting for events
                Normal,
                ///Actively printing
                Printing,
-               ///operation blocked, no connection
-               Blocked
+               ///operation blocked, no connection or printing deactivated
+               Blocked,
+               ///operation not possible, problem with settings
+               Problem
        };
+
+       ///returns true if the "Activate Printing" menu item is ticked
+       bool printIsActive(){return misactive;}
+
 public slots:
+       ///sets the icon variant
        void setPrintMode(PrintMode);
+       ///tells the tool tio when we will print next time
+       void setNextTime(QString s);
+
+private slots:
+       ///opens the client configuration dialog
        void clientConfig();
+       ///opens the server configuration dialog
        void serverConfig();
+
+       ///callback for the menu
+       void activatePrint(bool);
+       ///updates the tool tip text with current settings
+       void toolTipUpdate();
+
+signals:
+       ///emitted when the "Activate Printing" menu item is changed
+       void printActivated(bool);
+       ///emitted whenever settings have changed
+       void settingsChanged();
+       ///emitted when the user wants to start printing immediately
+       void startPrinting();
+
+private:
+       bool misactive=false;
+       QString mmode,mnexttime;
+};
+
+///Central scheduler class for the Print at Home client.
+class PrintScheduler:public QObject
+{
+       Q_OBJECT
+public:
+       PrintScheduler();
+public slots:
+       ///initiates and controls the print job.
+       void startPrinting();
+       ///call back for the menu item - affects the timer
+       void activatePrint(bool);
+       ///re-initializes the scheduler with current settings
+       void reinit();
+private:
+       QTimer*mtimer;
 };
 
 #endif
index e55a7ca..8fa663a 100644 (file)
@@ -7,8 +7,8 @@ include(../iface/iface.pri)
 include(../commonlib/commonlib.pri)
 
 #sources
-SOURCES += pah.cpp clientcfg.cpp servercfg.cpp
-HEADERS += pah.h clientcfg.h servercfg.h
+SOURCES += pah.cpp clientcfg.cpp servercfg.cpp printrun.cpp
+HEADERS += pah.h clientcfg.h servercfg.h printrun.h
 
 RESOURCES += files.qrc
 
@@ -17,4 +17,4 @@ INCLUDEPATH += .
 DEPENDPATH += $$INCLUDEPATH
 
 #make sure the correct Qt DLLs are used
-QT += network gui widgets
+QT += network gui widgets printsupport
diff --git a/printathome/printrun.cpp b/printathome/printrun.cpp
new file mode 100644 (file)
index 0000000..9e7fe07
--- /dev/null
@@ -0,0 +1,188 @@
+//
+// C++ Implementation: print @ home run
+//
+// Description: Print runner
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2016
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#include "mapplication.h"
+#include "msinterface.h"
+#include "MOOrder"
+#include "MOOrderDocument"
+#include "MTGetCustomer"
+#include "MTGetOrder"
+#include "MTGetOrderList"
+#include "MTSetOrderDocument"
+#include "WTransaction"
+#include "MTSendCustomerMail"
+#include "MTChangeCustomerMail"
+
+#include "ticketrender.h"
+#include "odtrender.h"
+#include "billrender.h"
+#include "stick.h"
+
+#include "pah.h"
+#include "printrun.h"
+#include "servercfg.h"
+#include "clientcfg.h"
+
+#include <QDateTime>
+#include <QDebug>
+#include <QDir>
+#include <QPainter>
+#include <QPrinter>
+#include <QStringList>
+#include <QTemporaryFile>
+#include <QVariant>
+
+PrintingRun::PrintingRun()
+{
+       // get templates
+       MTemplateStore *ts=req->templateStore();
+       ticket=ts->getTemplateByFile(MPServerConfig::ticketTemplateName());
+       voucher=ts->getTemplateByFile(MPServerConfig::voucherTemplateName());
+       invoice=ts->getTemplateByFile(MPServerConfig::billTemplateName());
+       mail=ts->getTemplateByFile(MPServerConfig::mailTemplateName());
+
+       mticketname=MPServerConfig::ticketFileName()+".pdf";
+       mvouchername=MPServerConfig::voucherFileName()+".pdf";
+       minvoicename=MPServerConfig::billFileName()+".pdf";
+
+       // get orders
+       const int age=MPClientConfig::maximumAgeOfOrders();
+       const qint64 oldest=age<=0?0:QDateTime::currentDateTime().addDays(-age).toTime_t();
+       MTGetOrderList olt=req->queryGetOrderList(oldest);
+       QList<MOOrderInfo> olist=olt.getorders();
+       mcustomers=olt.getcustomers();
+
+       // filter orders
+       const QList<int>shipids=MPServerConfig::shippingIds();
+       for(const MOOrderInfo&ord:olist)
+               if(ord.isPlaced() && shipids.contains(ord.shippingtypeid()))
+                       morders.append(ord);
+       qDebug()<<"Processing"<<morders.size()<<"orders of"<<olist.size()<<"since"<<age<<"days";
+}
+
+void PrintingRun::exec()
+{
+       for(const MOOrderInfo&ord:morders)
+               printOneOrder(ord);
+       qDebug()<<"Finished Print Run.";
+}
+
+void PrintingRun::printOneOrder(const MOOrderInfo&orderinfo)
+{
+       qDebug()<<"Printing order"<<orderinfo.orderid();
+       //get order details
+       //TODO: mark as shipped instead
+       MOOrder order=req->queryGetOrder(orderinfo.orderid()).getorder();
+
+       QList<MOOrderDocument> doclist;
+
+       //generate tickets
+       if(order.tickets().size()>0){
+               QTemporaryFile tmp(QDir::tempPath()+"/mspah-XXXXXX.pdf");
+               tmp.open();tmp.close();
+               QPrinter prn(QPrinter::HighResolution);
+               prn.setOutputFileName(tmp.fileName());
+               MTicketRenderer render(ticket);
+               prn.setPageSizeMM(render.labelSizeMM());
+               QPainter*paint=new QPainter(&prn);
+               bool np=false;
+               for(MOTicket tick:order.tickets()){
+                       //do we know the event?
+                       if(mevents.contains(tick.eventid()))
+                               tick.setEvent(mevents[tick.eventid()]);
+                       else
+                               //this automatically retrieves the event
+                               if(tick.event().isValid())
+                                       mevents.insert(tick.eventid(),tick.event());
+                       //print the ticket
+                       if(np)prn.newPage();
+                       else np=true;
+                       render.render(tick,prn,paint);
+               }
+               delete paint;
+               uploadFile(order.orderid(),tmp.fileName(),mticketname,doclist);
+       }
+
+       //generate vouchers
+       if(order.vouchers().size()>0){
+               QTemporaryFile tmp(QDir::tempPath()+"/mspah-XXXXXX.pdf");
+               tmp.open();tmp.close();
+               QPrinter prn(QPrinter::HighResolution);
+               prn.setOutputFileName(tmp.fileName());
+               MVoucherRenderer render(voucher);
+               prn.setPageSizeMM(render.labelSizeMM());
+               QPainter*paint=new QPainter(&prn);
+               bool np=false;
+               for(MOVoucher vouc:order.vouchers()){
+                       //print the voucher
+                       if(np)prn.newPage();
+                       else np=true;
+                       render.render(vouc,prn,paint);
+               }
+               delete paint;
+               uploadFile(order.orderid(),tmp.fileName(),mvouchername,doclist);
+       }
+
+       //generate bill
+       MBillRenderer orender(order,invoice);
+       QString pname=orender.renderToPdf();
+       qDebug()<<"rendered bill to"<<pname;
+       uploadFile(order.orderid(),pname,minvoicename,doclist);
+
+       //send mail
+       sendMail(order,doclist);
+}
+
+void PrintingRun::uploadFile(int orderid,QString localfile,QString remotefile,QList<MOOrderDocument>&doclist)
+{
+       MOOrderDocument doc;
+       doc.setorderid(orderid);
+       doc.setfilename(remotefile);
+       QFile fd(localfile);
+       fd.open(QIODevice::ReadOnly);
+       doc.setcontent(fd.readAll());
+       fd.close();
+       doc.setvisible(true);
+       MTSetOrderDocument trans=MTSetOrderDocument::query(doc);
+       doclist.append(doc);
+}
+
+void PrintingRun::sendMail(const MOOrder&order,const QList<MOOrderDocument>&doclist)
+{
+       //get customer details
+       MOCustomer cust;
+       if(mcustomerdetails.contains(order.customerid()))
+               cust=mcustomerdetails[order.customerid()];
+       else
+               cust=MTGetCustomer::query(order.customerid()).getcustomer();
+       QStringList cc;
+       const QList<int>contacttypes=MPServerConfig::mailContactTypeIds();
+       for(auto co:cust.contacts())
+               if(contacttypes.contains(co.contactid()))
+                       cc<<co.contact();
+       //feed renderer
+       MStickRenderer render;
+       render.setProperty("order",QVariant::fromValue(order));
+       render.setProperty("docs",QVariant::fromValue(doclist));
+       render.setProperty("customer",QVariant::fromValue(cust));
+       render.setProperty("cc",cc);
+       render.setTemplateFile(mail.cacheFileName());
+       //execute
+       QString mymail=render.render();
+       //make sure customer has a mail address configured
+       if(cust.email().isNull() || cust.email().value().isEmpty()){
+               if(cc.size()>0)
+                       MTChangeCustomerMail::query(cust.customerid(),cc[0]);
+       }
+       //send mail
+       MTSendCustomerMail::query(order.customerid(),mymail);
+}
diff --git a/printathome/printrun.h b/printathome/printrun.h
new file mode 100644 (file)
index 0000000..f59846e
--- /dev/null
@@ -0,0 +1,53 @@
+//
+// C++ Interface: print @ home run
+//
+// Description:
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2016
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#ifndef PAH_RUN_H
+#define PAH_RUN_H
+
+#include <QObject>
+#include <QMap>
+
+#include "MOOrderInfo"
+#include "MOCustomerInfo"
+#include "MOCustomer"
+#include "MOEvent"
+
+#include "templates/templates.h"
+
+class MOOrderDocument;
+class MOOrder;
+
+///Represents a single printing run with multiple orders to be printed.
+class PrintingRun:public QObject
+{
+       Q_OBJECT
+public:
+       ///retrieves all basic information for open orders
+       PrintingRun();
+
+public slots:
+       ///executes the actual print run
+       void exec();
+private:
+       QList<MOOrderInfo>morders;
+       QList<MOCustomerInfo>mcustomers;
+       QMap<int,MOCustomer>mcustomerdetails;
+       MTemplate ticket,voucher,invoice,mail;
+       QString mticketname,mvouchername,minvoicename;
+       QMap<int,MOEvent>mevents;
+
+       void printOneOrder(const MOOrderInfo&);
+       void uploadFile(int orderid,QString localfile,QString remotefile,QList<MOOrderDocument>&doclist);
+       void sendMail(const MOOrder&,const QList<MOOrderDocument>&);
+};
+
+#endif
index bf92f50..6353061 100644 (file)
 #include "templates.h"
 
 #include "MTGetAllContactTypes"
+#include "MTGetAllShipping"
 #include "MTGetPrintAtHomeSettings"
 #include "MTSetPrintAtHomeSettings"
 
-#include <QFormLayout>
 #include <QBoxLayout>
 #include <QCheckBox>
-#include <QPushButton>
 #include <QComboBox>
-#include <QSpinBox>
-#include <QLineEdit>
-#include <QSettings>
+#include <QCoreApplication>
 #include <QDebug>
+#include <QFormLayout>
+#include <QLabel>
+#include <QLineEdit>
 #include <QMessageBox>
+#include <QPushButton>
+#include <QSettings>
+#include <QSpinBox>
 
 #define TICKETCS "TicketChecksum"
 #define VOUCHERCS "VoucherChecksum"
 #define INVOICECS "InvoiceChecksum"
+#define MAILCS "MailChecksum"
 #define MAILCONTACTS "MailContactTypes"
+#define SHIPPING "ShippingTypes"
+#define TICKETNM "TicketName"
+#define VOUCHERNM "VoucherName"
+#define INVOICENM "InvoiceName"
 
 //seconds between forced re-inits
 #define INIT_TIMER 5*60
 QList<MOContactType> MPServerConfig::mcontacttypes;
 QDateTime MPServerConfig::mlastinit;
 QMap<QString,QString> MPServerConfig::msettings;
+QList<MOShipping> MPServerConfig::mshiptypes;
+
+//helpers to store sets of checksum:name pairs
+static inline QString strpair2str(const QPair<QString,QString>&pair)
+{
+       QByteArray a;
+       QDataStream stream(&a,QIODevice::WriteOnly);
+       stream<<pair;
+       return QString::fromLatin1(a.toBase64());
+}
+
+static inline QString strpair2str(const QString&checksum,const QString&filename)
+{
+       return strpair2str(QPair<QString,QString>(checksum,filename));
+}
+
+static inline QPair<QString,QString> str2strpair(const QString& str)
+{
+       QByteArray a(QByteArray::fromBase64(str.toLatin1()));
+       QDataStream stream(a);
+       QPair<QString,QString>pair;
+       stream>>pair;
+       return pair;
+}
+
 
 
 MPServerConfig::MPServerConfig(QWidget* parent)
 :QDialog(parent)
 {
        setWindowTitle(tr("Print@Home Server Configuration"));
-       QVBoxLayout*vl,*vl2;
+       QVBoxLayout*vl,*vl2,*vl3;
+       QHBoxLayout*hl;
        setLayout(vl=new QVBoxLayout);
        QFormLayout*fl;
        vl->addLayout(fl=new QFormLayout);
        //template for tickets
        fl->addRow(tr("Ticket Template:"),mticket=new QComboBox);
+       fl->addRow(tr("Ticket File Name:"),hl=new QHBoxLayout);
+       hl->addWidget(mticketn=new QLineEdit(ticketFileName()),1);
+       hl->addWidget(new QLabel(".pdf"),0);
        //template for vouchers
        fl->addRow(tr("Voucher Template:"),mvoucher=new QComboBox);
+       fl->addRow(tr("Voucher File Name:"),hl=new QHBoxLayout);
+       hl->addWidget(mvouchern=new QLineEdit(voucherFileName()),1);
+       hl->addWidget(new QLabel(".pdf"),0);
        //template for invoice
        fl->addRow(tr("Invoice Template:"),minvoice=new QComboBox);
+       fl->addRow(tr("Invoice File Name:"),hl=new QHBoxLayout);
+       hl->addWidget(minvoicen=new QLineEdit(billFileName()),1);
+       hl->addWidget(new QLabel(".pdf"),0);
+       //template for mail
+       fl->addRow(tr("Mail Template:"),mmail=new QComboBox);
        //contacts
-       fl->addRow(tr("eMail Contacts:"),vl2=new QVBoxLayout);
+       QFrame*frm;
+       fl->addRow(tr("eMail Contacts:"),frm=new QFrame);
+       frm->setFrameStyle(QFrame::StyledPanel|QFrame::Plain);
+       frm->setLayout(vl2=new QVBoxLayout);
+       //shipping types
+       fl->addRow(tr("Shipping Types:"),frm=new QFrame);
+       frm->setFrameStyle(QFrame::StyledPanel|QFrame::Plain);
+       frm->setLayout(vl3=new QVBoxLayout);
        //get settings
        init();
        //update templates
        QList<MTemplate>tpl=req->templateStore()->allTemplates();
-       int tp=-1,vp=-1,ip=-1;
-       const QString tc=ticketTemplateCS(),vc=voucherTemplateCS(),ic=invoiceTemplateCS();
+       int tp=-1,vp=-1,ip=-1,mp=-1;
+       const QPair<QString,QString> tc=ticketTemplateCS(), vc=voucherTemplateCS(), ic=invoiceTemplateCS(), mc=mailTemplateCS();
        qDebug()<<"DEFAULTS tick"<<tc<<", vouch"<<vc<<", inv"<<ic;
        for(MTemplate tmp:tpl){
                qDebug()<<"JFF"<<tmp.baseName()<<tmp.description()<<tmp.checksum()<<tmp.completeFileName();
                if(tmp.baseName()=="ticket"){
-                       mticket->addItem(tmp.description(),tmp.checksum());
-                       if(tc==tmp.checksum())tp=mticket->count()-1;
+                       mticket->addItem(tmp.description(), strpair2str(tmp.checksum(),tmp.completeFileName()));
+                       if(tc.first==tmp.checksum()&&tc.second==tmp.completeFileName())
+                               tp=mticket->count()-1;
                }
-               if(tmp.baseName()=="voucher"){
-                       mvoucher->addItem(tmp.description(),tmp.checksum());
-                       if(vc==tmp.checksum())vp=mvoucher->count()-1;
+               else if(tmp.baseName()=="voucher"){
+                       mvoucher->addItem(tmp.description(), strpair2str(tmp.checksum(),tmp.completeFileName()));
+                       if(vc.first==tmp.checksum()&&vc.second==tmp.completeFileName())
+                               vp=mvoucher->count()-1;
                }
-               if(tmp.baseName()=="bill"){
-                       minvoice->addItem(tmp.description(),tmp.checksum());
-                       if(ic==tmp.checksum())ip=minvoice->count()-1;
+               else if(tmp.baseName()=="bill"){
+                       minvoice->addItem(tmp.description(), strpair2str(tmp.checksum(),tmp.completeFileName()));
+                       if(ic.first==tmp.checksum(),ic.second==tmp.completeFileName())
+                               ip=minvoice->count()-1;
+               }
+               else if(tmp.baseName()=="printathome"){
+                       mmail->addItem(tmp.description(), strpair2str(tmp.checksum(),tmp.completeFileName()));
+                       if(mc.first==tmp.checksum(),mc.second==tmp.completeFileName())
+                               mp=mmail->count()-1;
                }
        }
+       qDebug()<<"tp"<<tp<<"vp"<<vp<<"ip"<<ip<<"mp"<<mp;
        mticket->setCurrentIndex(tp);
        mvoucher->setCurrentIndex(vp);
        minvoice->setCurrentIndex(ip);
+       mmail->setCurrentIndex(mp);
        //update contacts
        QList<int> mcont=mailContactTypeIds();
-       qDebug()<<"DEFAULT contacts"<<mcont;
+       qDebug()<<"DEFAULT contacts"<<mcont<<mailContactTypes();
        for(auto ctc:mcontacttypes){
                const QString cn=ctc.contacttype();
                QCheckBox*cb=new QCheckBox(cn);
@@ -94,9 +156,18 @@ MPServerConfig::MPServerConfig(QWidget* parent)
                vl2->addWidget(cb);
                mcontacts.insert(ctc.contacttypeid(),cb);
        }
-       
+       //update shipping types
+       QList<int> mship=shippingIds();
+       qDebug()<<"DEFAULT shipping types"<<mship;
+       for(auto ctc:mshiptypes){
+               const QString cn=ctc.description();
+               QCheckBox*cb=new QCheckBox(cn);
+               cb->setChecked(mship.contains(ctc.shipid()));
+               vl3->addWidget(cb);
+               mshipping.insert(ctc.shipid(),cb);
+       }
+
        vl->addSpacing(15);
-       QHBoxLayout*hl;
        vl->addLayout(hl=new QHBoxLayout);
        hl->addStretch(1);
        QPushButton*p;
@@ -120,6 +191,7 @@ void MPServerConfig::init(bool force)
        qDebug()<<"NEW-SETTINGS"<<msettings;
        
        mcontacttypes=req->queryGetAllContactTypes().gettypes();
+       mshiptypes=req->queryGetAllShipping().getshipping();
        
        mlastinit=QDateTime::currentDateTime();
 }
@@ -131,11 +203,20 @@ void MPServerConfig::save()
        settings.append(MOKeyValuePair(TICKETCS,mticket->currentData().toString()));
        settings.append(MOKeyValuePair(VOUCHERCS,mvoucher->currentData().toString()));
        settings.append(MOKeyValuePair(INVOICECS,minvoice->currentData().toString()));
+       settings.append(MOKeyValuePair(MAILCS,mmail->currentData().toString()));
+       settings.append(MOKeyValuePair(TICKETNM,mticketn->text()));
+       settings.append(MOKeyValuePair(VOUCHERNM,mvouchern->text()));
+       settings.append(MOKeyValuePair(INVOICENM,minvoicen->text()));
        QString mct;
        for(int cid:mcontacts.keys())
                if(mcontacts[cid]->isChecked())
                        mct+=QString("%1 ").arg(cid);
-       settings.append(MOKeyValuePair(MAILCONTACTS,mct));
+       settings.append(MOKeyValuePair(MAILCONTACTS,mct.trimmed()));
+       mct.clear();
+       for(int sid:mshipping.keys())
+               if(mshipping[sid]->isChecked())
+                       mct+=QString("%1 ").arg(sid);
+       settings.append(MOKeyValuePair(SHIPPING,mct.trimmed()));
        qDebug()<<"SAVING"<<toMap(settings);
        
        //save
@@ -147,23 +228,28 @@ void MPServerConfig::save()
        init(true);
 }
 
-// TODO: THIS SHOULD REALLY BE STORED ON THE SERVER AS CONFIG VALUE!!!!!!
-QString MPServerConfig::ticketTemplateCS()
+QPair<QString,QString> MPServerConfig::ticketTemplateCS()
 {
        init();
-       return msettings.value(TICKETCS);
+       return str2strpair(msettings.value(TICKETCS));
 }
 
-QString MPServerConfig::voucherTemplateCS()
+QPair<QString,QString> MPServerConfig::voucherTemplateCS()
 {
        init();
-       return msettings.value(VOUCHERCS);
+       return str2strpair(msettings.value(VOUCHERCS));
 }
 
-QString MPServerConfig::invoiceTemplateCS()
+QPair<QString,QString> MPServerConfig::invoiceTemplateCS()
 {
        init();
-       return msettings.value(INVOICECS);
+       return str2strpair(msettings.value(INVOICECS));
+}
+
+QPair<QString,QString> MPServerConfig::mailTemplateCS()
+{
+       init();
+       return str2strpair(msettings.value(MAILCS));
 }
 
 QStringList MPServerConfig::mailContactTypes()
@@ -184,3 +270,72 @@ QList<int> MPServerConfig::mailContactTypeIds()
                ret.append(s.toInt());
        return ret;
 }
+
+QList<int> MPServerConfig::shippingIds()
+{
+       init();
+       QList<int>ret;
+       for(auto s:msettings.value(SHIPPING).split(' ',QString::SkipEmptyParts))
+               ret.append(s.toInt());
+       return ret;
+}
+
+QString MPServerConfig::ticketFileName()
+{
+       return msettings.value(TICKETNM,QCoreApplication::translate("MPServerConfig","tickets","file name"));
+}
+
+QString MPServerConfig::voucherFileName()
+{
+       return msettings.value(VOUCHERNM,QCoreApplication::translate("MPServerConfig","vouchers","file name"));
+}
+
+QString MPServerConfig::billFileName()
+{
+       return msettings.value(INVOICENM,QCoreApplication::translate("MPServerConfig","invoice","file name"));
+}
+
+bool MPServerConfig::validateConfig()
+{
+       init();
+       //check we even have a config
+       QPair<QString,QString>
+               tc=ticketTemplateCS(),
+               vc=voucherTemplateCS(),
+               ic=invoiceTemplateCS(),
+               mc=mailTemplateCS();
+       if(tc.first.isEmpty()||vc.first.isEmpty()||ic.first.isEmpty())
+               return false;
+       //validate templates exist
+       MTemplateStore*ts=req->templateStore();
+       MTemplate tmp=ts->getTemplateByFile(tc.second);
+       if(!tmp.isValid()||tmp.checksum()!=tc.first)return false;
+       tmp=ts->getTemplateByFile(vc.second);
+       if(!tmp.isValid()||tmp.checksum()!=vc.first)return false;
+       tmp=ts->getTemplateByFile(ic.second);
+       if(!tmp.isValid()||tmp.checksum()!=ic.first)return false;
+       tmp=ts->getTemplateByFile(mc.second);
+       if(!tmp.isValid()||tmp.checksum()!=mc.first)return false;
+       //validate contacts exist
+       for(int id:mailContactTypeIds()){
+               bool found=false;
+               for(const MOContactType &ct:mcontacttypes)
+                       if(ct.contacttypeid()==id){
+                               found=true;
+                               break;
+                       }
+               if(!found)return false;
+       }
+       //validate shipping types
+       for(int id:shippingIds()){
+               bool found=false;
+               for(const MOShipping&st:mshiptypes)
+                       if(st.shipid()==id){
+                               found=true;
+                               break;
+                       }
+               if(!found)return false;
+       }
+       //done
+       return true;
+}
index da31ad7..5e206df 100644 (file)
 #include <QMap>
 #include <QList>
 #include <QDateTime>
+#include <QPair>
 
 #include "MOContactType"
+#include "MOShipping"
 
 class QCheckBox;
 class QLineEdit;
@@ -31,21 +33,48 @@ class MPServerConfig:public QDialog
 public:
        MPServerConfig(QWidget*parent=nullptr);
        
+       ///returns all contact types that are mail addresses
        static QStringList mailContactTypes();
+       ///returns the IDs of all contact types that are mail addresses
        static QList<int> mailContactTypeIds();
+
+       ///returns the IDs of shipping types used for PrintAtHome
+       static QList<int> shippingIds();
+
+       ///returns true if the server configuration is consistent
+       static bool validateConfig();
+
+       ///returns the template name for tickets
+       static QString ticketTemplateName(){return ticketTemplateCS().second;}
+       ///returns the template name for vouchers
+       static QString voucherTemplateName(){return voucherTemplateCS().second;}
+       ///returns the template name for bills
+       static QString billTemplateName(){return invoiceTemplateCS().second;}
+       ///returns the template name for mails
+       static QString mailTemplateName(){return mailTemplateCS().second;}
+
+       ///returns the name under which the ticket PDF should be stored on the server
+       static QString ticketFileName();
+       ///returns the name under which the voucher PDF should be stored on the server
+       static QString voucherFileName();
+       ///returns the name under which the bill/invoice PDF should be stored on the server
+       static QString billFileName();
 public slots:
        void save();
        
 private:
-       QComboBox*mticket,*mvoucher,*minvoice;
-       QMap<int,QCheckBox*>mcontacts;
+       QComboBox*mticket,*mvoucher,*minvoice,*mmail;
+       QLineEdit*mticketn,*mvouchern,*minvoicen;
+       QMap<int,QCheckBox*>mcontacts,mshipping;
        static QMap<QString,QString>msettings;
        static QList<MOContactType>mcontacttypes;
+       static QList<MOShipping>mshiptypes;
        static QDateTime mlastinit;
                
-       static QString ticketTemplateCS();
-       static QString voucherTemplateCS();
-       static QString invoiceTemplateCS();
+       static QPair<QString,QString> ticketTemplateCS();
+       static QPair<QString,QString> voucherTemplateCS();
+       static QPair<QString,QString> invoiceTemplateCS();
+       static QPair<QString,QString> mailTemplateCS();
        static void init(bool force=false);
 };
 
index cb48922..40bb9f5 100644 (file)
@@ -21,6 +21,7 @@
 #include "payedit.h"
 #include "templates.h"
 #include "ticketrender.h"
+#include "billrender.h"
 
 #include "MOEvent"
 #include "MTCancelOrder"
@@ -447,13 +448,8 @@ void MOrderWindow::printBill()
                updateData();
        }
        //print bill
-       initPrintBuffer();
-       MOdtSignalRenderer rend(tf);
-       connect(&rend,SIGNAL(getVariable(QString,QVariant&)), this,SLOT(getVariable(QString,QVariant&)));
-       connect(&rend,SIGNAL(getLoopIterations(QString,int&)), this,SLOT(getLoopIterations(QString,int&)));
-       connect(&rend,SIGNAL(setLoopIteration(QString,int)), this,SLOT(setLoopIteration(QString,int)));
+       MBillRenderer rend(m_order, tf);
        rend.renderToPrinter();
-       donePrintBuffer();
 }
 
 void MOrderWindow::saveBill()
@@ -492,203 +488,8 @@ void MOrderWindow::saveBill()
                updateData();
        }
        //render bill
-       initPrintBuffer();
-       MOdtSignalRenderer rend(tf);
-       connect(&rend,SIGNAL(getVariable(QString,QVariant&)), this,SLOT(getVariable(QString,QVariant&)));
-       connect(&rend,SIGNAL(getLoopIterations(QString,int&)), this,SLOT(getLoopIterations(QString,int&)));
-       connect(&rend,SIGNAL(setLoopIteration(QString,int)), this,SLOT(setLoopIteration(QString,int)));
+       MBillRenderer rend(m_order, tf);
        rend.renderToFile(fname);
-       donePrintBuffer();
-}
-
-void MOrderWindow::getVariable(QString vn,QVariant&value)
-{
-       if(vn=="ORDERDATE"){
-               value=m_order.ordertime().value();
-       }else
-       if(vn=="ORDERDATETIME"){
-               value=m_order.ordertime().value();
-       }else
-       if(vn=="SENTDATE"){
-               value=m_order.senttime().value();
-       }else
-       if(vn=="SENTDATETIME"){
-               value=m_order.senttime().value();
-       }else
-       if(vn=="CUSTOMERID")value=QString::number(m_order.customerid());else
-       if(vn=="ORDERID")value=QString::number(m_order.orderid());else
-       if(vn=="ADDRESS")value=m_order.fullInvoiceAddress();else
-       if(vn=="FULLADDRESS")value=m_order.fullInvoiceAddress();else
-       if(vn=="NAME")value=m_order.customer().value().fullName();else
-       if(vn=="DELIVERYADDRESS")value=m_order.fullDeliveryAddress();else
-       if(vn=="FINALADDRESS")value=m_order.fullDeliveryAddress();else
-       if(vn=="TOTALPRICE"){
-               value=m_order.totalprice().value();
-       }else
-       if(vn=="AMOUNTPAID"){
-               value=m_order.amountpaid().value();
-       }else
-       if(vn=="SELLER")value=m_order.soldby().value();else
-       if(vn=="COMMENT")value=m_order.comments().value();else
-       if(vn=="AMOUNTTOPAY"){
-               value=m_order.amountToPay();
-       }else
-       if(vn=="AMOUNTTOREFUND"){
-               value=m_order.amountToRefund();
-       }else
-       if(vn=="TICKETS"){
-               value=printBuffer.tickets.size();
-       }else
-       if(vn=="ACCTICKETS"){
-               value=printBuffer.tickinfo.size();
-       }else
-       if(vn=="VOUCHERS"){
-               value=printBuffer.vouchers.size();
-       }else
-       if(vn=="ADDRESSLINES"){
-               value=m_order.fullInvoiceAddress().split("\n").size();
-       }else
-       if(vn=="SHIPPING")value=m_order.shippingtype().value().description().value();else
-       if(vn=="SHIPPINGPRICE"){
-               value=m_order.shippingtype().value().cost().value();
-       }else{
-               if(vn.contains(':')){
-                       QStringList sl=vn.split(':');
-                       int it=-1;
-                       if(m_loopiter.contains(sl[0]))it=m_loopiter[sl[0]];
-                       getLoopVariable(sl[0],it,sl[1],value);
-               }
-       }
-//     qDebug()<<"got variable"<<vn<<"value"<<value;
-}
-
-void MOrderWindow::getLoopIterations(QString loopname,int&iterations)
-{
-       if(loopname=="TICKETS")iterations=printBuffer.tickets.size();
-       if(loopname=="ACCTICKETS")iterations=printBuffer.tickinfo.size();
-       if(loopname=="VOUCHERS")iterations=printBuffer.vouchers.size();
-       if(loopname=="ADDRESSLINES")iterations=m_order.fullInvoiceAddress().split("\n").size();
-//     qDebug()<<"loop"<<loopname<<"has"<<iterations<<"iterations";
-}
-
-void MOrderWindow::setLoopIteration(QString loopname, int iteration)
-{
-//     qDebug()<<"setting loop iter"<<loopname<<iteration;
-       int max=-1;
-       if(iteration<0)return;
-       getLoopIterations(loopname,max);
-       if(iteration>=max)return;
-       m_loopiter.insert(loopname,iteration);
-}
-
-
-void MOrderWindow::getLoopVariable(QString loopname,int it,QString vn,QVariant&value)
-{
-       if(loopname=="TICKETS"){
-               QList<MOTicket> &tickets=printBuffer.tickets;
-               if(it<0 || it>=tickets.size())return;
-               
-               if(vn=="PRICE"){
-                       value=tickets[it].price().value();
-               }else
-               if(vn=="ID")value=tickets[it].ticketid().value();else
-               if(vn=="TITLE")value=tickets[it].event().title().value();else
-               if(vn=="ARTIST")value=tickets[it].event().artist().value().name().value();else
-               if(vn=="DATE"){
-                       value=tickets[it].event().start().value();
-               }else
-               if(vn=="STARTTIME"){
-                       value=tickets[it].event().start().value();
-               }else
-               if(vn=="ENDTIME"){
-                       value=tickets[it].event().end().value();
-               }else
-               if(vn=="ROOM")value=tickets[it].event().room().value();
-       }else if(loopname=="ACCTICKETS"){
-               QList<TickInfo> &tickets=printBuffer.tickinfo;
-               if(it<0 || it>=tickets.size())return;
-               
-               if(vn=="PRICE"){
-                       value=tickets[it].proto.price().value();
-               }else
-               if(vn=="FULLPRICE"){
-                       value=tickets[it].proto.price().value()*tickets[it].amount;
-               }else
-               if(vn=="TITLE")value=tickets[it].proto.event().title().value();else
-               if(vn=="ARTIST")value=tickets[it].proto.event().artist().value().name().value();else
-               if(vn=="DATE"){
-                       value=tickets[it].proto.event().start().value();
-               }else
-               if(vn=="STARTTIME"){
-                       value=tickets[it].proto.event().start().value();
-               }else
-               if(vn=="ENDTIME"){
-                       value=tickets[it].proto.event().end().value();
-               }else
-               if(vn=="ROOM")value=tickets[it].proto.event().room().value();else
-               if(vn=="AMOUNT"){
-                       value=tickets[it].amount;
-               }
-       }else if(loopname=="VOUCHERS"){
-               if(it<0 || it>=printBuffer.vouchers.size())return;
-               
-               if(vn=="PRICE"){
-                       value=printBuffer.vouchers[it].price().value();
-               }else
-               if(vn=="VALUE"){
-                       value=printBuffer.vouchers[it].value().value();
-               }else
-               if(vn=="ID")value=printBuffer.vouchers[it].voucherid().value();
-       }else if(loopname=="ADDRESSLINES"){
-               QStringList lst=m_order.fullInvoiceAddress().split("\n");
-               if(it<0 || it>=lst.size())return;
-               value=lst[it];
-       }else
-               return /*empty handed*/;
-}
-
-void MOrderWindow::donePrintBuffer()
-{
-       printBuffer.tickets.clear();
-       printBuffer.vouchers.clear();
-       printBuffer.tickinfo.clear();
-}
-
-static inline bool compare(const MOTicket&a,const MOTicket&b)
-{
-       if(a.eventid()!=b.eventid())return false;
-       if(a.price()!=b.price())return false;
-       return true;
-}
-
-void MOrderWindow::initPrintBuffer()
-{
-       //clear
-       donePrintBuffer();
-       //get tickets (only valid ones, only those that are to be paid)
-       QList<MOTicket>tlst=m_order.tickets();
-       for(int i=0;i<tlst.size();i++)
-               if(!tlst[i].ticketid().isNull() && (tlst[i].status()&MOTicket::MaskPay)!=0){
-                       printBuffer.tickets.append(tlst[i]);
-                }
-       //accumulated view on tickets
-       for(int i=0;i<printBuffer.tickets.size();i++){
-               MOTicket t=printBuffer.tickets[i];
-               bool found=false;
-               for(int j=0;j<printBuffer.tickinfo.size();j++){
-                       if(compare(printBuffer.tickinfo[j].proto,t)){
-                               found=true;
-                               printBuffer.tickinfo[j].amount++;
-                               break;
-                       }
-               }
-               if(!found)printBuffer.tickinfo.append(t);
-       }
-       //get valid vouchers
-       QList<MOVoucher>vlst=m_order.vouchers();
-       for(int i=0;i<vlst.size();i++)
-               if(vlst[i].price()>0||vlst[i].value()>0)
-                       printBuffer.vouchers.append(vlst[i]);
 }
 
 void MOrderWindow::payment()
index 916949e..757f05f 100644 (file)
@@ -60,14 +60,6 @@ class MOrderWindow:public QMainWindow
                void printBill();
                /**save the bill as file*/
                void saveBill();
-               /**callback for bill generator: variables; see MOdtSignalRenderer for details*/
-               void getVariable(QString,QVariant&);
-               /**callback for bill generator: loops; see MOdtSignalRenderer for details*/
-               void getLoopIterations(QString loopname,int&iterations);
-               /**callback to set values of a specific loop iteration*/
-               void setLoopIteration(QString loopname,int iteration);
-               /**callback for bill generator: loop variables; see MOdtSignalRenderer for details*/
-               void getLoopVariable(QString,int,QString,QVariant&);
 
                /**received payment*/
                void payment();
@@ -125,22 +117,6 @@ class MOrderWindow:public QMainWindow
                QAction*m_res2order,*m_cancel,*m_ship,*m_pay,*m_payv,*m_refund;
                QMap<QString,int>m_loopiter;
                QString m_testTemplate;
-               
-               //printing buffer
-               struct TickInfo{
-                       TickInfo(const MOTicket&t):proto(t){amount=1;}
-                       TickInfo(const TickInfo&t):proto(t.proto){amount=t.amount;}
-                       TickInfo(){amount=0;}
-                       MOTicket proto;
-                       int amount;
-               };
-               struct PrintBuffer{
-                       QList<MOTicket> tickets;
-                       QList<MOVoucher> vouchers;
-                       QList<TickInfo> tickinfo;
-               }printBuffer;
-               void initPrintBuffer();
-               void donePrintBuffer();
 };
 
 class MTicketRenderer;
deleted file mode 100644 (file)
index 8844a7f1a4781d28a1897eef2908d762a221901e..0000000000000000000000000000000000000000
Binary files a/src/images/arrowdiag.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..36757df914bcb27e7eee5a8d968895193816a7b5
--- /dev/null
@@ -0,0 +1 @@
+../../icons/arrows/arrowdiag.png
\ No newline at end of file
deleted file mode 100644 (file)
index 683c465cea3449012360934c3b8cb835f206c424..0000000000000000000000000000000000000000
Binary files a/src/images/arrowdown.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..9bffeb0b856b4e24827702e603a6752f8cbaa1e6
--- /dev/null
@@ -0,0 +1 @@
+../../icons/arrows/arrowdown.png
\ No newline at end of file
deleted file mode 100644 (file)
index abe8720be35196a4e25f7e1d53b26fee76aa7a29..0000000000000000000000000000000000000000
Binary files a/src/images/arrowleft.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..28a2bc5b7151c7068049386374a8d3112b641803
--- /dev/null
@@ -0,0 +1 @@
+../../icons/arrows/arrowleft.png
\ No newline at end of file
deleted file mode 100644 (file)
index 8a62bf75f78dd52d84586f9f1d1e8db84f275c70..0000000000000000000000000000000000000000
Binary files a/src/images/arrowright.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..748f8efa2b1f7e0f03a65fe35a3bbdb85a8aa1b0
--- /dev/null
@@ -0,0 +1 @@
+../../icons/arrows/arrowright.png
\ No newline at end of file
deleted file mode 100644 (file)
index cfb4cdb309cb71574f98a9d6bee98902a54e5e9e..0000000000000000000000000000000000000000
Binary files a/src/images/arrowup.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..34e600e4c7ffa8b1b8a19b282262137576adf6ca
--- /dev/null
@@ -0,0 +1 @@
+../../icons/arrows/arrowup.png
\ No newline at end of file
deleted file mode 100644 (file)
index fd285bc5c0b0342ebbac2dd5339e191d1ef18ef0..0000000000000000000000000000000000000000
Binary files a/src/images/cancel.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..e95d009f674d76399854262deaa3af8d3baa982c
--- /dev/null
@@ -0,0 +1 @@
+../../icons/misc/cancel.png
\ No newline at end of file
deleted file mode 100644 (file)
index dbfb948ab86404ef63eb16f7540419e4d6150737..0000000000000000000000000000000000000000
Binary files a/src/images/done.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..c115da7ac6a06bb08ba7e1bfb46feb8697663e8f
--- /dev/null
@@ -0,0 +1 @@
+../../icons/misc/done.png
\ No newline at end of file
deleted file mode 100644 (file)
index a5b3bbf74b2456e291c14d9c74e7620e662bb033..0000000000000000000000000000000000000000
Binary files a/src/images/icon.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..72d2d121ec7cd467816b96adb4073278a334df29
--- /dev/null
@@ -0,0 +1 @@
+../../icons/logo/icon.png
\ No newline at end of file
deleted file mode 100644 (file)
index 7700d6fce6ba1d328b435fb7117cd63fb760cca0..0000000000000000000000000000000000000000
Binary files a/src/images/next.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..be7a3876cfab4db50574108906602d32491caee0
--- /dev/null
@@ -0,0 +1 @@
+../../icons/misc/next.png
\ No newline at end of file
deleted file mode 100644 (file)
index 99dc8733c731b2f02c473f8fdfe652357dc86a1b..0000000000000000000000000000000000000000
Binary files a/src/images/prev.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..dcc8316560d7b3a30fa2507e780bdebc9b4ef24e
--- /dev/null
@@ -0,0 +1 @@
+../../icons/misc/prev.png
\ No newline at end of file
deleted file mode 100644 (file)
index ea928431d20d5005a7d8df12210c27ebec978a92..0000000000000000000000000000000000000000
Binary files a/src/images/separator.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..0ff77effdb2b32e346381923c745067cce8c281a
--- /dev/null
@@ -0,0 +1 @@
+../../icons/misc/separator.png
\ No newline at end of file
index 015ced2..75a55b7 100644 (file)
                <Property name="amountdue" type="int">amount that needs to be paid, negative if too much has been paid</Property>
                <Property name="totalprice" type="int">total price for this order (including shipping and all items)</Property>
                <Property name="shippingcosts" type="int">costs for shipping</Property>
-               
+               <Property name="shippingtypeid" type="int"/>
+
                <Property name="ordertime" type="int64"/>
                <Property name="senttime" type="int64"/>
                <!-- etc.pp. -->
                        <Map column="senttime"/>
                        <Map column="amountpaid"/>
                        <Map column="shippingcosts"/>
-                       
+                       <Map column="shippingtype" property="shippingtypeid"/>
+
                        <Map property="totalprice">
                                <Call lang="php" method="$data->getTotalPriceFromDB()"/>
                        </Map>
                        <Map column="flags"/>
                </Mapping>
        </Class>
+
+       <Class name="OrderDocument">
+               <Doc>This class represents a document stored for a specific order.</Doc>
+               <Property name="fileid" type="int64">Informational: internal file ID</Property>
+               <Property name="orderid" type="int64">The order it belongs to.</Property>
+               <Property name="filename" type="string">The file name of the document.</Property>
+               <Property name="mtime" type="int64">Modification Time</Property>
+               <Property name="rtime" type="int64">Web Retrieval Time</Property>
+               <Property name="content" type="blob">File Content</Property>
+               <Property name="visible" type="bool">True if the file is visible for the customer</Property>
+               <Mapping table="orderdocuments">
+                       <Map column="fileid"/>
+                       <Map column="orderid"/>
+                       <Map column="filename"/>
+                       <Map column="mtime"/>
+                       <Map column="rtime"/>
+                       <Map column="content"/>
+                       <Map column="visible"/>
+               </Mapping>
+       </Class>
+       <Class name="OrderDocumentInfo">
+               <Doc>
+                       This class represents a document stored for a specific order without the
+                       actual content blob.
+               </Doc>
+               <Property name="fileid" type="int64">Informational: internal file ID</Property>
+               <Property name="orderid" type="int64">The order it belongs to.</Property>
+               <Property name="filename" type="string">The file name of the document.</Property>
+               <Property name="mtime" type="int64">Modification Time</Property>
+               <Property name="rtime" type="int64">Web Retrieval Time</Property>
+               <Property name="visible" type="bool">True if the file is visible for the customer</Property>
+               <Mapping table="orderdocuments">
+                       <Map column="fileid"/>
+                       <Map column="orderid"/>
+                       <Map column="filename"/>
+                       <Map column="mtime"/>
+                       <Map column="rtime"/>
+                       <Map column="visible"/>
+               </Mapping>
+       </Class>
 </Wolf>
index ab555c6..20f3bd1 100644 (file)
                <Input>
                        <Var name="orderid" type="int"/>
                </Input>
-               <Call lang="php" method="WOOrder::getOrderDocuments($this);"/>
+               <Call lang="php" method="WOOrder::getOrderDocumentNames($this);"/>
                <Output>
-                       <Var name="filename" type="List:string"/>
+                       <Var name="documentinfo" type="List:OrderDocumentInfo"/>
                </Output>
        </Transaction>
        <Transaction name="GetOrderDocument" update="no">
                </Input>
                <Call lang="php" method="WOOrder::getOrderDocument($this);"/>
                <Output>
-                       <Var name="content" type="blob"/>
-                       <Var name="visible" type="bool"/>
-                       <Var name="mtime" type="int64"/>
-                       <Var name="rtime" type="int64"/>
+                       <Var name="document" type="OrderDocument"/>
                </Output>
        </Transaction>
        <Transaction name="SetOrderDocument" update="yes">
                <Doc>Stores a document associated with an order. Overwrites the document if it already exists, creates it otherwise.</Doc>
                <Input>
-                       <Var name="orderid" type="int"/>
-                       <Var name="filename" type="string"/>
-                       <Var name="content" type="blob"/>
-                       <Var name="visible" type="bool"/>
+                       <Var name="document" type="OrderDocument">
+                               The document to be set.
+                               Hint: the fileid is ignored, matching happens through orderid and filename.
+                       </Var>
                </Input>
-               <Call lang="php" method="WOOrder::getOrderDocument($this);"/>
+               <Call lang="php" method="WOOrder::setOrderDocument($this);"/>
                <Output/>
        </Transaction>
        <Transaction name="DeleteOrderDocument" update="yes">
                        <Var name="orderid" type="int"/>
                        <Var name="filename" type="string"/>
                </Input>
-               <Call lang="php" method="WOOrder::getOrderDocument($this);"/>
+               <Call lang="php" method="WOOrder::deleteOrderDocument($this);"/><!-- TODO -->
                <Output/>
        </Transaction>
 
                <Doc>Sends an eMail to a customer. Usually to update the customer about Order data.</Doc>
                <Input>
                        <Var name="customerid" type="int">ID of the customer. Must be given! The mail address associated with the account is used.</Var>
-                       <Var name="cc" type="List:string">Additional Cc addresses - usually extracted from the customer contact information.</Var>
-                       <Var name="content" type="blob">Content of the eMail.</Var>
+                       <Var name="content" type="string">Content of the eMail.</Var>
                </Input>
-               <Call lang="php" method=";"/><!-- TODO -->
+               <Call lang="php" method="WOCustomer::sendMail($this);"/>
                <Output/>
        </Transaction>  
        
        <Transaction name="GetPrintAtHomeSettings" update="no">
                <Doc>Gets all settings pertaining to Print@Home</Doc>
                <Input/>
-               <Call lang="php" method="WOOrder::getPrintAtHomeSettings($this);"/><!-- TODO -->
+               <Call lang="php" method="WOOrder::getPrintAtHomeSettings($this);"/>
                <Output>
                        <Var name="settings" type="List:KeyValuePair"/>
                </Output>
                <Input>
                        <Var name="settings" type="List:KeyValuePair"/>
                </Input>
-               <Call lang="php" method="WOOrder::setPrintAtHomeSettings($this);"/><!-- TODO -->
+               <Call lang="php" method="WOOrder::setPrintAtHomeSettings($this);"/>
                <Output/>
        </Transaction>
 </Wolf>
index ea3e67a..bc5b1b7 100644 (file)
@@ -13,7 +13,7 @@
 
 
 //create engine: server-host, user, password
-$db = new MysqlEngine("localhost","smoke","");
+$db = new MysqlEngine("192.168.1.5","smoke","");
 //set database name
 //$db->setDbName("smoke2");
 $db->setDbName("DB396352");
index fc6c29f..5bce397 100644 (file)
@@ -5,4 +5,8 @@ The flags (de.png, en.png) were originally pulled from Wikipedia, where they
 were listed as being in the public domain.
 
 The Logo (logo.png) is under the same license as the rest of the documentation,
-see the global README file for details.
\ No newline at end of file
+see the global README file for details.
+
+The maintenance.png Icon was copied from the KDE Oxygen Icon Theme and is
+distributed under the GNU LGPL v3 or any newer. Its original file name was
+preferences-system.png.
deleted file mode 100644 (file)
index 683c465cea3449012360934c3b8cb835f206c424..0000000000000000000000000000000000000000
Binary files a/www/images/arrowdown.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..9bffeb0b856b4e24827702e603a6752f8cbaa1e6
--- /dev/null
@@ -0,0 +1 @@
+../../icons/arrows/arrowdown.png
\ No newline at end of file
deleted file mode 100644 (file)
index 8482b19dc9f8fa5c85b326ec6b25576b86dbdfc6..0000000000000000000000000000000000000000
Binary files a/www/images/de.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..fc7fdda1135731bbdfeba90af7e5ad6bb99770d5
--- /dev/null
@@ -0,0 +1 @@
+../../icons/wikipedia/de.png
\ No newline at end of file
deleted file mode 100644 (file)
index 63a8dd7004ca7d2174caf4ca3d1d810cef2640b7..0000000000000000000000000000000000000000
Binary files a/www/images/en.png and /dev/null differ
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..7a7bb5b178f3049091bc4d3b514745d79ddc349a
--- /dev/null
@@ -0,0 +1 @@
+../../icons/wikipedia/en.png
\ No newline at end of file
diff --git a/www/images/maintenance.png b/www/images/maintenance.png
new file mode 120000 (symlink)
index 0000000..d636f71
--- /dev/null
@@ -0,0 +1 @@
+../../icons/oxygen/preferences-system.png
\ No newline at end of file
index 5c1b2c1..23e7989 100644 (file)
@@ -593,6 +593,7 @@ class WOCustomer extends WOCustomerAbstract
                redirectHome(array("mode"=>"checkout"));
        }
        
+       ///callback for the GetCreateCustomerHints transaction
        static public function createHints($trans)
        {
                $trans->setcontacttypes( WOContactType::fromTableArraycontacttype( WTcontacttype::selectFromDB()));
@@ -616,6 +617,36 @@ class WOCustomer extends WOCustomerAbstract
                        $trans->setstates($rs);
                }
        }
+
+       ///callback for the SendCustomerMail transaction
+       static public function sendMail($trans)
+       {
+               //get customer basics
+               $ct=WTcustomer::getFromDB($trans->getcustomerid());
+               if(!is_a($ct,"WTcustomer")){
+                       return;
+               }
+               if(!isEmail($ct->email)){
+                       return;
+               }
+               //parse mail
+               $page=explode("\n",trim($trans->getcontent()));
+               if(count($page)<2)return;
+               $subject=array_shift($page);
+               $mode=0;
+               $mailtext="";$mailheader="";
+               foreach($page as $line){
+                       if($mode==0){
+                               if(trim($line)=="")$mode=1;
+                               else $mailheader.=$line."\n";
+                       }else{
+                               $mailtext.=$line."\n";
+                       }
+               }
+               //send mail
+               mail($ct->email,$subject,$mailtext,$mailheader);
+
+       }
 };
 
 //eof
index cfcb061..64fb6d7 100644 (file)
@@ -1007,11 +1007,12 @@ class WOOrder extends WOOrderAbstract
                $res=array();
                $cfg=WTconfig::selectFromDB();
                foreach($cfg as $c) {
-                       if(strncmp($c->ckey,"printathome:",12)==0)
-                               $r=new WOKeyValuePair();
+                       if(strncmp($c->ckey,"printathome:",12)==0){
+                               $r=new WOKeyValuePair;
                                $r->setkey(substr($c->ckey,12));
                                $r->setvalue($c->cval);
                                $res[]=$r;
+                       }
                }
                $trans->setsettings($res);
                
@@ -1030,6 +1031,44 @@ class WOOrder extends WOOrderAbstract
                }
                
        }
+
+       static public function getOrderDocumentNames($trans)
+       {
+               global $db;
+               $q="orderid=".$db->escapeInt($trans->getorderid());
+               $trans->setdocumentinfo(WOOrderDocumentInfo::fromTableArrayorderdocuments(
+                       WTorderdocuments::selectFromDB($q)));
+       }
+
+       static public function getOrderDocument($trans)
+       {
+               global $db;
+               $q="orderid=".$db->escapeInt($trans->getorderid())." AND filename=".
+                       $db->escapeString($trans->getfilename());
+               $tab=WTorderdocuments::selectFromDB($q);
+               if(count($tab)>0)
+                       $trans->setdocument(WOOrderDocument::fromTableorderdocuments($tab[0]));
+       }
+
+       static public function setOrderDocument($trans)
+       {
+               global $db;
+               $doc=$trans->getdocument();
+               $q="orderid=".$db->escapeInt($doc->getorderid())." AND filename=".
+                       $db->escapeString($doc->getfilename());
+               $tab=WTorderdocuments::selectFromDB($q);
+               if(count($tab)>0)
+                       $nrow=$tab[0];
+               else
+                       $nrow=WTorderdocuments::newRow(array(
+                               "orderid"=>$doc->getorderid(),
+                               "filename"=>$doc->getfilename()
+                               ));
+               $nrow->content=$doc->getcontent();
+               $nrow->mtime=time();
+               $nrow->visible=$doc->getvisible();
+               $nrow->insertOrUpdate();
+       }
 };
 
 
index 660744b..f903fcc 100644 (file)
@@ -4,6 +4,12 @@
 // protected under the GNU AGPL version 3 or at your option any newer
 // see COPYING.AGPL
 
+//bail out during maintenance
+if(file_exists("maintenance.php")){
+       include("maintenance.php");
+       die("<!-- Maintenance! -->");
+}
+
 //basics
 include('inc/loader.php');
 include('inc/loader_nonadmin.php');
index ce642d1..f9ec7ee 100644 (file)
@@ -3,6 +3,10 @@
 // protected under the GNU AGPL version 3 or at your option any newer
 // see COPYING.AGPL
 
+//check for maintenance mode
+if(file_exists("maintenance.php"))
+       die("<!-- Sorry, Down For Maintenance! -->");
+
 //fix content-type to something that is not manipulated by proxies
 header("Content-Type: application/x-MagicSmoke");
 
diff --git a/www/maintenance.php.template b/www/maintenance.php.template
new file mode 100644 (file)
index 0000000..66be0f3
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+ /* RENAME THIS FILE TO maintenance.php IF YOU WANT MAGICSMOKE TO STOP ANSWERING.
+  * THIS WILL DISABLE BOTH THE USER/CUSTOMER FACING WEB SITE AS WELL AS THE MACHINE INTERFACE.
+  * THE ADMINISTRATIVE PAGE admin.php IS UNAFFECTED.
+  * THE HTML CODE BELOW IS SHOWN ON THE USER/CUSTOMER FACING WEB SITE INSTEAD OF THE USUAL
+  * RENDERING OF MAGICSMOKE DATA.
+  */
+?>
+<html>
+<title>Maintenance</title>
+<body>
+<img src="images/maintenance.png" align="left"/>
+<img src="images/maintenance.png" align="right"/>
+<h1>Sorry, down for Maintenance</h1>
+
+<p>We are currently executing important maintenance tasks to make your experience as good as possible.
+We are trying our best to make this maintenance window as short as possible.</p>
+
+<p><b>Please return in one or two hours.</b></p>
+
+<h1>Wir f&uuml;hren gerade Wartungsarbeiten durch</h1>
+
+<p>...damit unsere Webseite so gut wie m&ouml;glich f&uuml;r Sie funktioniert. Wir versuchen unser
+bestes den Ausfall so kurz wie m&ouml;glich zu gestalten.</p>
+
+<p><b>Bitte kommen Sie in ein bis zwei Stunden wieder.</b></p>
+
+</body>
+</html>
index 2a8c342..a5b4f81 100644 (file)
@@ -6,8 +6,9 @@ The first non-empty line contains the subject,
 subsequent lines contain the headers, then an empty line
 and after that the body of the mail.
 #}
-Order {{order.orderid}} Details at MagicSmoke.silmor.de 
-From: no-reply@localdomain.com
+Order {{order.orderid}} Details at MagicSmoke.silmor.de
+From: no-reply@localhost
+Bcc: info@localhost
 Content-Type: multipart/alternative; boundary="------boundary-aYAmoTT04MIpviI"
 MIME-Version: 1.0