From: Konrad Rosenbaum Date: Wed, 21 Dec 2016 15:12:23 +0000 (+0100) Subject: print @ Home client side and main server complete, web iface todo X-Git-Url: http://git.silmor.de/gitweb/?a=commitdiff_plain;h=c2374a46924944dffdd4ed6cdb2d62677c48c428;p=web%2Fkonrad%2Fsmoke.git print @ Home client side and main server complete, web iface todo Change-Id: Ib8f904276ac96643e5d4ca12bc16fad7aaf27ff8 --- diff --git a/.gitignore b/.gitignore index dd8d391..6df853e 100644 --- a/.gitignore +++ b/.gitignore @@ -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-* diff --git a/commonlib/mapplication.cpp b/commonlib/mapplication.cpp index 7a47193..e9f8aa1 100644 --- a/commonlib/mapplication.cpp +++ b/commonlib/mapplication.cpp @@ -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); diff --git a/commonlib/stick/stick.cpp b/commonlib/stick/stick.cpp index 223730a..ab83d26 100644 --- a/commonlib/stick/stick.cpp +++ b/commonlib/stick/stick.cpp @@ -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()); }); - engine.setFunction("xml",[](const QList&args,ELAM::Engine&)->QVariant{ + engine.setFunction("raw",[](const QList&args,ELAM::Engine&)->QVariant{ if(args.size()!=1||!args[0].canConvert()) return ELAM::Exception(ELAM::Exception::ArgumentListError,"expecting 1 string argument"); return args[0]; }); + //MIME + engine.setFunction("base64",[](const QList&args,ELAM::Engine&)->QVariant{ + if(args.size()!=1||!args[0].canConvert()) + 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&args,ELAM::Engine&)->QVariant{ + if(args.size()!=2||!args[0].canConvert()||!args[1].canConvert()) + 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&args,ELAM::Engine&)->QVariant{ if(args.size()!=1) diff --git a/commonlib/stick/stick.h b/commonlib/stick/stick.h index 3215751..1076937 100644 --- a/commonlib/stick/stick.h +++ b/commonlib/stick/stick.h @@ -23,28 +23,40 @@ 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(QMapproperties); + ///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: QMapmproperties; 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 index 0000000..fb35cd5 --- /dev/null +++ b/commonlib/templates/billrender.cpp @@ -0,0 +1,206 @@ +// +// C++ Implementation: bill odtrender +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2016 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#include "billrender.h" +#include +#include + +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) + QListtlst=m_order.tickets(); + for(int i=0;ivlst=m_order.vouchers(); + for(int i=0;i0||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"<=max)return; + m_loopiter.insert(loopname,iteration); +} + + +QVariant MBillRenderer::getLoopVariable(QString loopname,int it,QString vn) +{ + if(loopname=="TICKETS"){ + QList &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 &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"<, (C) 2016 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#ifndef MAGICSMOKE_BILLRENDER_H +#define MAGICSMOKE_BILLRENDER_H + +#include + +#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; + QMapm_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 tickets; + QList vouchers; + QList tickinfo; + }printBuffer; +}; + +#endif diff --git a/commonlib/templates/odtrender.cpp b/commonlib/templates/odtrender.cpp index fcd5ee8..a92d485 100644 --- a/commonlib/templates/odtrender.cpp +++ b/commonlib/templates/odtrender.cpp @@ -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 diff --git a/commonlib/templates/odtrender.h b/commonlib/templates/odtrender.h index 25ba593..4a0bc88 100644 --- a/commonlib/templates/odtrender.h +++ b/commonlib/templates/odtrender.h @@ -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 diff --git a/commonlib/templates/office.cpp b/commonlib/templates/office.cpp index a578406..c82faa0 100644 --- a/commonlib/templates/office.cpp +++ b/commonlib/templates/office.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -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"< #include "commonexport.h" diff --git a/commonlib/templates/templates.pri b/commonlib/templates/templates.pri index 6cc1d4f..e14acdc 100644 --- a/commonlib/templates/templates.pri +++ b/commonlib/templates/templates.pri @@ -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 diff --git a/commonlib/templates/ticketrender.cpp b/commonlib/templates/ticketrender.cpp index 0109320..a0fae5a 100644 --- a/commonlib/templates/ticketrender.cpp +++ b/commonlib/templates/ticketrender.cpp @@ -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(){} diff --git a/commonlib/templates/ticketrender.h b/commonlib/templates/ticketrender.h index 5e2a408..0bf4a24 100644 --- a/commonlib/templates/ticketrender.h +++ b/commonlib/templates/ticketrender.h @@ -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; diff --git a/doc/logo.png b/doc/logo.png deleted file mode 100644 index e4264a9..0000000 Binary files a/doc/logo.png and /dev/null differ diff --git a/doc/logo.png b/doc/logo.png new file mode 120000 index 0000000..ca64a23 --- /dev/null +++ b/doc/logo.png @@ -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 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 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 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 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 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 index 0000000..a5b3bbf Binary files /dev/null and b/icons/logo/icon.png differ diff --git a/src/images/icon.xcf b/icons/logo/icon.xcf 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 index 0000000..e4264a9 Binary files /dev/null and b/icons/logo/logo.png differ diff --git a/doc/logo.xcf b/icons/logo/logo.xcf 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 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 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 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 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 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 index 0000000..264c1dc --- /dev/null +++ b/icons/oxygen/COPYING @@ -0,0 +1,52 @@ +The Oxygen Icon Theme + Copyright (C) 2007 Nuno Pinheiro + Copyright (C) 2007 David Vignoni + Copyright (C) 2007 David Miller + Copyright (C) 2007 Johann Ollivier Lapeyre + Copyright (C) 2007 Kenneth Wimer + Copyright (C) 2007 Riccardo Iaconelli + + +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 . + +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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..98e489a --- /dev/null +++ b/icons/wikipedia/README @@ -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 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 index 0000000..63a8dd7 Binary files /dev/null and b/icons/wikipedia/en.png differ diff --git a/iface/misc/boxwrapper.cpp b/iface/misc/boxwrapper.cpp index 8b26a3e..79a57d7 100644 --- a/iface/misc/boxwrapper.cpp +++ b/iface/misc/boxwrapper.cpp @@ -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; diff --git a/iface/misc/boxwrapper.h b/iface/misc/boxwrapper.h index 56fbf9c..2237e83 100644 --- a/iface/misc/boxwrapper.h +++ b/iface/misc/boxwrapper.h @@ -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 diff --git a/iface/templates/templates.cpp b/iface/templates/templates.cpp index fef5acd..aa6222c 100644 --- a/iface/templates/templates.cpp +++ b/iface/templates/templates.cpp @@ -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(); } diff --git a/iface/templates/templates.h b/iface/templates/templates.h index 9e3f410..1c5b5d2 100644 --- a/iface/templates/templates.h +++ b/iface/templates/templates.h @@ -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 allTemplates(); - + private: QString profileid; }; diff --git a/pack b/pack index c3ce038..2802df2 160000 --- 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 index 0000000..3831ec5 --- /dev/null +++ b/printathome/README.icons @@ -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. diff --git a/printathome/clientcfg.cpp b/printathome/clientcfg.cpp index 5ade69e..f54b015 100644 --- a/printathome/clientcfg.cpp +++ b/printathome/clientcfg.cpp @@ -11,6 +11,7 @@ // #include "clientcfg.h" +#include "pah.h" #include "scli.h" @@ -23,8 +24,9 @@ #include #include -#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(); diff --git a/printathome/clientcfg.h b/printathome/clientcfg.h index 4ab0a92..9929d13 100644 --- a/printathome/clientcfg.h +++ b/printathome/clientcfg.h @@ -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 index 0000000..b4cfa23 --- /dev/null +++ b/printathome/examplemail.mime @@ -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 + + +

Hello {{customer.title}} {{customer.name}}!

+ +

Your order number {{order.orderid}} has been generated.

+ +

Your documents are: +

    +{% for doc in docs %} +
  • {{doc.fileid}} {{doc.filename}}
  • +{% endfor %} +

+ +

+

+

+ + +   with best regards,
+   your MagicSmoke team + +--iKETDhtPafV07ewA9JlUOMitE +Content-ID: +Content-Transfer-Encoding: base64 +Content-Type: image/png; name="logo.png" + +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 + +--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-- diff --git a/printathome/files.qrc b/printathome/files.qrc index 2eba23c..4b76816 100644 --- a/printathome/files.qrc +++ b/printathome/files.qrc @@ -7,5 +7,6 @@ See COPYING.GPL for details. --> icon-print.png icon-block.png icon-active.png + icon-problem.png diff --git a/printathome/icon-print.xcf b/printathome/icon-print.xcf index 48427be..c9fa6b0 100644 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 index 0000000..fb4bf7b Binary files /dev/null and b/printathome/icon-problem.png differ diff --git a/printathome/pah.cpp b/printathome/pah.cpp index 04cc513..f7e9a94 100644 --- a/printathome/pah.cpp +++ b/printathome/pah.cpp @@ -13,22 +13,36 @@ #include "mapplication.h" #include "msinterface.h" #include "scli.h" -#include +#include "WTransaction" +#include "MTGetOrderList" #include "pah.h" #include "clientcfg.h" #include "servercfg.h" +#include "printrun.h" +#include +#include #include #include -#include +#include +#include +#include + +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(); } diff --git a/printathome/pah.h b/printathome/pah.h index e47ce53..172c052 100644 --- a/printathome/pah.h +++ b/printathome/pah.h @@ -15,24 +15,82 @@ #include +#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 diff --git a/printathome/printathome.pro b/printathome/printathome.pro index e55a7ca..8fa663a 100644 --- a/printathome/printathome.pro +++ b/printathome/printathome.pro @@ -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 index 0000000..9e7fe07 --- /dev/null +++ b/printathome/printrun.cpp @@ -0,0 +1,188 @@ +// +// C++ Implementation: print @ home run +// +// Description: Print runner +// +// +// Author: Konrad Rosenbaum , (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 +#include +#include +#include +#include +#include +#include +#include + +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 olist=olt.getorders(); + mcustomers=olt.getcustomers(); + + // filter orders + const QListshipids=MPServerConfig::shippingIds(); + for(const MOOrderInfo&ord:olist) + if(ord.isPlaced() && shipids.contains(ord.shippingtypeid())) + morders.append(ord); + qDebug()<<"Processing"<queryGetOrder(orderinfo.orderid()).getorder(); + + QList 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"<&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&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 QListcontacttypes=MPServerConfig::mailContactTypeIds(); + for(auto co:cust.contacts()) + if(contacttypes.contains(co.contactid())) + cc<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 index 0000000..f59846e --- /dev/null +++ b/printathome/printrun.h @@ -0,0 +1,53 @@ +// +// C++ Interface: print @ home run +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2016 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#ifndef PAH_RUN_H +#define PAH_RUN_H + +#include +#include + +#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: + QListmorders; + QListmcustomers; + QMapmcustomerdetails; + MTemplate ticket,voucher,invoice,mail; + QString mticketname,mvouchername,minvoicename; + QMapmevents; + + void printOneOrder(const MOOrderInfo&); + void uploadFile(int orderid,QString localfile,QString remotefile,QList&doclist); + void sendMail(const MOOrder&,const QList&); +}; + +#endif diff --git a/printathome/servercfg.cpp b/printathome/servercfg.cpp index bf92f50..6353061 100644 --- a/printathome/servercfg.cpp +++ b/printathome/servercfg.cpp @@ -16,24 +16,32 @@ #include "templates.h" #include "MTGetAllContactTypes" +#include "MTGetAllShipping" #include "MTGetPrintAtHomeSettings" #include "MTSetPrintAtHomeSettings" -#include #include #include -#include #include -#include -#include -#include +#include #include +#include +#include +#include #include +#include +#include +#include #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 @@ -41,52 +49,106 @@ QList MPServerConfig::mcontacttypes; QDateTime MPServerConfig::mlastinit; QMap MPServerConfig::msettings; +QList MPServerConfig::mshiptypes; + +//helpers to store sets of checksum:name pairs +static inline QString strpair2str(const QPair&pair) +{ + QByteArray a; + QDataStream stream(&a,QIODevice::WriteOnly); + stream<(checksum,filename)); +} + +static inline QPair str2strpair(const QString& str) +{ + QByteArray a(QByteArray::fromBase64(str.toLatin1())); + QDataStream stream(a); + QPairpair; + 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 QListtpl=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 tc=ticketTemplateCS(), vc=voucherTemplateCS(), ic=invoiceTemplateCS(), mc=mailTemplateCS(); qDebug()<<"DEFAULTS tick"<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"<setCurrentIndex(tp); mvoucher->setCurrentIndex(vp); minvoice->setCurrentIndex(ip); + mmail->setCurrentIndex(mp); //update contacts QList mcont=mailContactTypeIds(); - qDebug()<<"DEFAULT contacts"<addWidget(cb); mcontacts.insert(ctc.contacttypeid(),cb); } - + //update shipping types + QList mship=shippingIds(); + qDebug()<<"DEFAULT shipping types"<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"<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"< MPServerConfig::ticketTemplateCS() { init(); - return msettings.value(TICKETCS); + return str2strpair(msettings.value(TICKETCS)); } -QString MPServerConfig::voucherTemplateCS() +QPair MPServerConfig::voucherTemplateCS() { init(); - return msettings.value(VOUCHERCS); + return str2strpair(msettings.value(VOUCHERCS)); } -QString MPServerConfig::invoiceTemplateCS() +QPair MPServerConfig::invoiceTemplateCS() { init(); - return msettings.value(INVOICECS); + return str2strpair(msettings.value(INVOICECS)); +} + +QPair MPServerConfig::mailTemplateCS() +{ + init(); + return str2strpair(msettings.value(MAILCS)); } QStringList MPServerConfig::mailContactTypes() @@ -184,3 +270,72 @@ QList MPServerConfig::mailContactTypeIds() ret.append(s.toInt()); return ret; } + +QList MPServerConfig::shippingIds() +{ + init(); + QListret; + 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 + 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; +} diff --git a/printathome/servercfg.h b/printathome/servercfg.h index da31ad7..5e206df 100644 --- a/printathome/servercfg.h +++ b/printathome/servercfg.h @@ -17,8 +17,10 @@ #include #include #include +#include #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 mailContactTypeIds(); + + ///returns the IDs of shipping types used for PrintAtHome + static QList 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; - QMapmcontacts; + QComboBox*mticket,*mvoucher,*minvoice,*mmail; + QLineEdit*mticketn,*mvouchern,*minvoicen; + QMapmcontacts,mshipping; static QMapmsettings; static QListmcontacttypes; + static QListmshiptypes; static QDateTime mlastinit; - static QString ticketTemplateCS(); - static QString voucherTemplateCS(); - static QString invoiceTemplateCS(); + static QPair ticketTemplateCS(); + static QPair voucherTemplateCS(); + static QPair invoiceTemplateCS(); + static QPair mailTemplateCS(); static void init(bool force=false); }; diff --git a/src/dialogs/orderwin.cpp b/src/dialogs/orderwin.cpp index cb48922..40bb9f5 100644 --- a/src/dialogs/orderwin.cpp +++ b/src/dialogs/orderwin.cpp @@ -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"<=max)return; - m_loopiter.insert(loopname,iteration); -} - - -void MOrderWindow::getLoopVariable(QString loopname,int it,QString vn,QVariant&value) -{ - if(loopname=="TICKETS"){ - QList &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 &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) - QListtlst=m_order.tickets(); - for(int i=0;ivlst=m_order.vouchers(); - for(int i=0;i0||vlst[i].value()>0) - printBuffer.vouchers.append(vlst[i]); } void MOrderWindow::payment() diff --git a/src/dialogs/orderwin.h b/src/dialogs/orderwin.h index 916949e..757f05f 100644 --- a/src/dialogs/orderwin.h +++ b/src/dialogs/orderwin.h @@ -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; QMapm_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 tickets; - QList vouchers; - QList tickinfo; - }printBuffer; - void initPrintBuffer(); - void donePrintBuffer(); }; class MTicketRenderer; diff --git a/src/images/arrowdiag.png b/src/images/arrowdiag.png deleted file mode 100644 index 8844a7f..0000000 Binary files a/src/images/arrowdiag.png and /dev/null differ diff --git a/src/images/arrowdiag.png b/src/images/arrowdiag.png new file mode 120000 index 0000000..36757df --- /dev/null +++ b/src/images/arrowdiag.png @@ -0,0 +1 @@ +../../icons/arrows/arrowdiag.png \ No newline at end of file diff --git a/src/images/arrowdown.png b/src/images/arrowdown.png deleted file mode 100644 index 683c465..0000000 Binary files a/src/images/arrowdown.png and /dev/null differ diff --git a/src/images/arrowdown.png b/src/images/arrowdown.png new file mode 120000 index 0000000..9bffeb0 --- /dev/null +++ b/src/images/arrowdown.png @@ -0,0 +1 @@ +../../icons/arrows/arrowdown.png \ No newline at end of file diff --git a/src/images/arrowleft.png b/src/images/arrowleft.png deleted file mode 100644 index abe8720..0000000 Binary files a/src/images/arrowleft.png and /dev/null differ diff --git a/src/images/arrowleft.png b/src/images/arrowleft.png new file mode 120000 index 0000000..28a2bc5 --- /dev/null +++ b/src/images/arrowleft.png @@ -0,0 +1 @@ +../../icons/arrows/arrowleft.png \ No newline at end of file diff --git a/src/images/arrowright.png b/src/images/arrowright.png deleted file mode 100644 index 8a62bf7..0000000 Binary files a/src/images/arrowright.png and /dev/null differ diff --git a/src/images/arrowright.png b/src/images/arrowright.png new file mode 120000 index 0000000..748f8ef --- /dev/null +++ b/src/images/arrowright.png @@ -0,0 +1 @@ +../../icons/arrows/arrowright.png \ No newline at end of file diff --git a/src/images/arrowup.png b/src/images/arrowup.png deleted file mode 100644 index cfb4cdb..0000000 Binary files a/src/images/arrowup.png and /dev/null differ diff --git a/src/images/arrowup.png b/src/images/arrowup.png new file mode 120000 index 0000000..34e600e --- /dev/null +++ b/src/images/arrowup.png @@ -0,0 +1 @@ +../../icons/arrows/arrowup.png \ No newline at end of file diff --git a/src/images/cancel.png b/src/images/cancel.png deleted file mode 100644 index fd285bc..0000000 Binary files a/src/images/cancel.png and /dev/null differ diff --git a/src/images/cancel.png b/src/images/cancel.png new file mode 120000 index 0000000..e95d009 --- /dev/null +++ b/src/images/cancel.png @@ -0,0 +1 @@ +../../icons/misc/cancel.png \ No newline at end of file diff --git a/src/images/done.png b/src/images/done.png deleted file mode 100644 index dbfb948..0000000 Binary files a/src/images/done.png and /dev/null differ diff --git a/src/images/done.png b/src/images/done.png new file mode 120000 index 0000000..c115da7 --- /dev/null +++ b/src/images/done.png @@ -0,0 +1 @@ +../../icons/misc/done.png \ No newline at end of file diff --git a/src/images/icon.png b/src/images/icon.png deleted file mode 100644 index a5b3bbf..0000000 Binary files a/src/images/icon.png and /dev/null differ diff --git a/src/images/icon.png b/src/images/icon.png new file mode 120000 index 0000000..72d2d12 --- /dev/null +++ b/src/images/icon.png @@ -0,0 +1 @@ +../../icons/logo/icon.png \ No newline at end of file diff --git a/src/images/next.png b/src/images/next.png deleted file mode 100644 index 7700d6f..0000000 Binary files a/src/images/next.png and /dev/null differ diff --git a/src/images/next.png b/src/images/next.png new file mode 120000 index 0000000..be7a387 --- /dev/null +++ b/src/images/next.png @@ -0,0 +1 @@ +../../icons/misc/next.png \ No newline at end of file diff --git a/src/images/prev.png b/src/images/prev.png deleted file mode 100644 index 99dc873..0000000 Binary files a/src/images/prev.png and /dev/null differ diff --git a/src/images/prev.png b/src/images/prev.png new file mode 120000 index 0000000..dcc8316 --- /dev/null +++ b/src/images/prev.png @@ -0,0 +1 @@ +../../icons/misc/prev.png \ No newline at end of file diff --git a/src/images/separator.png b/src/images/separator.png deleted file mode 100644 index ea92843..0000000 Binary files a/src/images/separator.png and /dev/null differ diff --git a/src/images/separator.png b/src/images/separator.png new file mode 120000 index 0000000..0ff77ef --- /dev/null +++ b/src/images/separator.png @@ -0,0 +1 @@ +../../icons/misc/separator.png \ No newline at end of file diff --git a/wob/classes/order.wolf b/wob/classes/order.wolf index 015ced2..75a55b7 100644 --- a/wob/classes/order.wolf +++ b/wob/classes/order.wolf @@ -259,7 +259,8 @@ amount that needs to be paid, negative if too much has been paid total price for this order (including shipping and all items) costs for shipping - + + @@ -272,7 +273,8 @@ - + + @@ -365,4 +367,44 @@ + + + This class represents a document stored for a specific order. + Informational: internal file ID + The order it belongs to. + The file name of the document. + Modification Time + Web Retrieval Time + File Content + True if the file is visible for the customer + + + + + + + + + + + + + This class represents a document stored for a specific order without the + actual content blob. + + Informational: internal file ID + The order it belongs to. + The file name of the document. + Modification Time + Web Retrieval Time + True if the file is visible for the customer + + + + + + + + + diff --git a/wob/transact/order.wolf b/wob/transact/order.wolf index ab555c6..20f3bd1 100644 --- a/wob/transact/order.wolf +++ b/wob/transact/order.wolf @@ -484,9 +484,9 @@ - + - + @@ -497,21 +497,18 @@ - - - - + Stores a document associated with an order. Overwrites the document if it already exists, creates it otherwise. - - - - + + The document to be set. + Hint: the fileid is ignored, matching happens through orderid and filename. + - + @@ -520,7 +517,7 @@ - + @@ -528,17 +525,16 @@ Sends an eMail to a customer. Usually to update the customer about Order data. ID of the customer. Must be given! The mail address associated with the account is used. - Additional Cc addresses - usually extracted from the customer contact information. - Content of the eMail. + Content of the eMail. - + Gets all settings pertaining to Print@Home - + @@ -548,7 +544,7 @@ - + diff --git a/www/config.php.template b/www/config.php.template index ea3e67a..bc5b1b7 100644 --- a/www/config.php.template +++ b/www/config.php.template @@ -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"); diff --git a/www/images/README b/www/images/README index fc6c29f..5bce397 100644 --- a/www/images/README +++ b/www/images/README @@ -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. diff --git a/www/images/arrowdown.png b/www/images/arrowdown.png deleted file mode 100644 index 683c465..0000000 Binary files a/www/images/arrowdown.png and /dev/null differ diff --git a/www/images/arrowdown.png b/www/images/arrowdown.png new file mode 120000 index 0000000..9bffeb0 --- /dev/null +++ b/www/images/arrowdown.png @@ -0,0 +1 @@ +../../icons/arrows/arrowdown.png \ No newline at end of file diff --git a/www/images/de.png b/www/images/de.png deleted file mode 100644 index 8482b19..0000000 Binary files a/www/images/de.png and /dev/null differ diff --git a/www/images/de.png b/www/images/de.png new file mode 120000 index 0000000..fc7fdda --- /dev/null +++ b/www/images/de.png @@ -0,0 +1 @@ +../../icons/wikipedia/de.png \ No newline at end of file diff --git a/www/images/en.png b/www/images/en.png deleted file mode 100644 index 63a8dd7..0000000 Binary files a/www/images/en.png and /dev/null differ diff --git a/www/images/en.png b/www/images/en.png new file mode 120000 index 0000000..7a7bb5b --- /dev/null +++ b/www/images/en.png @@ -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 index 0000000..d636f71 --- /dev/null +++ b/www/images/maintenance.png @@ -0,0 +1 @@ +../../icons/oxygen/preferences-system.png \ No newline at end of file diff --git a/www/inc/wext/customer.php b/www/inc/wext/customer.php index 5c1b2c1..23e7989 100644 --- a/www/inc/wext/customer.php +++ b/www/inc/wext/customer.php @@ -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 diff --git a/www/inc/wext/order.php b/www/inc/wext/order.php index cfcb061..64fb6d7 100644 --- a/www/inc/wext/order.php +++ b/www/inc/wext/order.php @@ -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(); + } }; diff --git a/www/index.php b/www/index.php index 660744b..f903fcc 100644 --- a/www/index.php +++ b/www/index.php @@ -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(""); +} + //basics include('inc/loader.php'); include('inc/loader_nonadmin.php'); diff --git a/www/machine.php b/www/machine.php index ce642d1..f9ec7ee 100644 --- a/www/machine.php +++ b/www/machine.php @@ -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(""); + //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 index 0000000..66be0f3 --- /dev/null +++ b/www/maintenance.php.template @@ -0,0 +1,29 @@ + + +Maintenance + + + +

Sorry, down for Maintenance

+ +

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.

+ +

Please return in one or two hours.

+ +

Wir führen gerade Wartungsarbeiten durch

+ +

...damit unsere Webseite so gut wie möglich für Sie funktioniert. Wir versuchen unser +bestes den Ausfall so kurz wie möglich zu gestalten.

+ +

Bitte kommen Sie in ein bis zwei Stunden wieder.

+ + + diff --git a/www/template/en/ordermail.txt b/www/template/en/ordermail.txt index 2a8c342..a5b4f81 100644 --- a/www/template/en/ordermail.txt +++ b/www/template/en/ordermail.txt @@ -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