From: Konrad Rosenbaum Date: Thu, 29 Dec 2016 15:57:29 +0000 (+0100) Subject: basic coupon handling in client display X-Git-Url: http://git.silmor.de/gitweb/?a=commitdiff_plain;h=db82f71488d254802f1f4f773ff23afa6a4870bf;p=web%2Fkonrad%2Fsmoke.git basic coupon handling in client display Change-Id: I1937c81efa9568fc2fad8a72f612b295d989942f --- diff --git a/doc/seatplans.txt b/doc/seatplans.txt index c3193e2..210c429 100644 --- a/doc/seatplans.txt +++ b/doc/seatplans.txt @@ -1,14 +1,17 @@ Seat-Plans =========== -Specification Version (YYYYnn): 201501 +Specification Version (YYYYnn): 201601 Server Side Minimum: --------------------- - + + + + In this simplest form there are two kinds of seats: "Premium" and "Cheapo". @@ -17,19 +20,49 @@ In both cases seats are unnumbered (i.e. audience members can chose freely). defines the complete plan version - version number of this spec, it is generally assumed that newer versions are backwards compatible - defines one group of seats + defines one physical group of seats capacity - specifies how many seats there are in this group id - automatically generated ID for groups, must only contain letters and digits, max. 8 chars, no spaces name - human readable name for the group numbered - "yes": the seats have numbers, "no" audience members can freely chose - price - space separated list of pricecategory IDs for price levels that can be - sold in this group, if missing: all categories of the event - + price - space separated list of pricecategory IDs or abbreviations for price + levels that can be sold in this group, if missing: all categories of the event + defines a virtual group of seats that can span multiple groups + -> VGroups define upper limits for combinations of categories + -> e.g. VIP guests could get cheaper tickets, with a VIP category with different + price in each group, say there can be 4 VIP tickets total, but since VIPs can + freely chose between groupstotal there must be a capacity of 4 in each group + and another limit (the VGroup) that prevents purchase of more than 4 in total + -> VGroup cannot have sub-elements + name - human readable name for the group + capacity - number of seats in this group + price - the price categories restricted by this VGroup + defines a default for a price category + -> those are offered to be automatically generated when the seat plan is assigned to + an event + capacity - the automatically entered amount of seats for this category + price - a list of price category IDs or abbreviations that this setting applies to + cost - the automatically assigned cost, this attribute can have one or two items + separated by a space: + - first - either a numeric value of the price in cents or the keyword "calc", which + means to use the stored formula to pre-calculate a price + - second - the optional keyword "ask" to ask for confirmation from the user + => defaults are generated in the order they are listed in the seat plan, so the order + can be used to modify calculated values; each line is calculated at once + +The capacity attribute can have positive numeric values (absolute number of seats) or +one of the following values: + maxcat - the maximum allowed by any of the used categories + mincat - the minimum allowed by any of the used categories + sumcat - the sum of all categories + auto - automatically counts the rows and seats defined in the group + -> sumcat is the default if there are no Row definitions + -> auto is the default if there are Row definitions To number seats by row add Row-elements to the Group tag: - + @@ -87,7 +120,7 @@ Graphical Seat Plans This part is never evaluated by the server, only by displays: - + @@ -158,7 +191,7 @@ The background of the seat plan can be defined in an additional element: - base64-encoded data + base64-encoded data Attributes: @@ -170,7 +203,7 @@ Attributes: Background:text/fontsize - font size in relative points of the seat plan, automatically scaled Background:text/content - textual content Background:image/image - link to an Image tag - Image - contains the actual image data base64 encoded + Image/Data - contains the actual image data base64 encoded Image/name - link name of the image, usually derived from the original file name Specifying Seats in Orders diff --git a/doc/seatplans.xsd b/doc/seatplans.xsd index f1e1bda..d4da099 100644 --- a/doc/seatplans.xsd +++ b/doc/seatplans.xsd @@ -1,6 +1,6 @@ @@ -10,6 +10,7 @@ + @@ -18,20 +19,32 @@ - A version number for the file format. It always consists of 4-digit year plus a 2-digit counter. + + + A version number for the file format. It always consists of 4-digit year plus a 2-digit counter. + + - Two unsigned integers noting Width and Height. + + + Two unsigned integers noting Width and Height. + + - Two unsigned integers noting top left X,Y and Width,Height. + + + Two unsigned integers noting top left X,Y and Width,Height. + + diff --git a/iface/wext/MOEventSaleInfo b/iface/wext/MOEventSaleInfo new file mode 100644 index 0000000..132d246 --- /dev/null +++ b/iface/wext/MOEventSaleInfo @@ -0,0 +1 @@ +#include"eventsaleinfo.h" diff --git a/iface/wext/MOSeatPlanDefPrice b/iface/wext/MOSeatPlanDefPrice new file mode 100644 index 0000000..79dda50 --- /dev/null +++ b/iface/wext/MOSeatPlanDefPrice @@ -0,0 +1 @@ +#include"seatplanobj.h" diff --git a/iface/wext/MOSeatPlanGroup b/iface/wext/MOSeatPlanGroup new file mode 100644 index 0000000..79dda50 --- /dev/null +++ b/iface/wext/MOSeatPlanGroup @@ -0,0 +1 @@ +#include"seatplanobj.h" diff --git a/iface/wext/MOSeatPlanVGroup b/iface/wext/MOSeatPlanVGroup new file mode 100644 index 0000000..79dda50 --- /dev/null +++ b/iface/wext/MOSeatPlanVGroup @@ -0,0 +1 @@ +#include"seatplanobj.h" diff --git a/iface/wext/eventsaleinfo.cpp b/iface/wext/eventsaleinfo.cpp new file mode 100644 index 0000000..9d0e74e --- /dev/null +++ b/iface/wext/eventsaleinfo.cpp @@ -0,0 +1,50 @@ +// +// C++ Implementation: MOOrder +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2009-2011 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#include "eventsaleinfo.h" + +#include "MOSeatPlan" +#include "MOSeatPlanGroup" +#include "MOSeatPlanVGroup" + +#include + +static int mymeta= + qRegisterMetaType()+ + qRegisterMetaType >()+ + qRegisterMetaType >(); +static bool mocv= + QMetaType::registerConverter,MOEventSaleInfo>([](const Nullable&n){return n.value();})| + QMetaType::registerConverter,QVariantList>([](const QList&n){QVariantList r;for(auto v:n)r<grp; + for(auto g:plan.Group()){ + g.adjust(*this); + grp.append(g); + } + plan.setGroup(grp); + QListvgrp; + for(auto g:plan.VGroup()){ + g.adjust(*this); + vgrp.append(g); + } + plan.setVGroup(vgrp); + return plan; +} diff --git a/iface/wext/eventsaleinfo.h b/iface/wext/eventsaleinfo.h new file mode 100644 index 0000000..c471d7a --- /dev/null +++ b/iface/wext/eventsaleinfo.h @@ -0,0 +1,48 @@ +// +// C++ Interface: unabstract +// +// Description: removes abstract flag from classes that only need to be abstract in PHP +// +// +// Author: Konrad Rosenbaum , (C) 2009-2011 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#ifndef MAGICSMOKE_MOEVENTSALEINFO_H +#define MAGICSMOKE_MOEVENTSALEINFO_H + +#include "MOEventSaleInfoAbstract" +#include "MOSeatPlan" + +#ifndef MSIFACE_EXPORT +#define MSIFACE_EXPORT Q_DECL_IMPORT +#endif + + + +//class MOSeatPlan; +/**this class represents a seat group*/ +class MSIFACE_EXPORT MOEventSaleInfo:public MOEventSaleInfoAbstract +{ + Q_GADGET + WOBJECT(MOEventSaleInfo) + Q_PROPERTY(bool isValid READ isValid) + Q_PROPERTY(MOSeatPlan seatplan READ seatplan) + public: + /**returns whether the object is valid (it comes from the DB and it has been understood by the parser)*/ + bool isValid()const{return !eventid().isNull();} + + ///returns the parsed seat plan without sale info worked in + MOSeatPlan seatplan()const; + + ///returns the parsed seat plan with all the ticket info merged in + MOSeatPlan seatplanAdjusted()const; +}; + +Q_DECLARE_METATYPE(MOEventSaleInfo) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(Nullable) + +#endif diff --git a/iface/wext/seatplanobj.cpp b/iface/wext/seatplanobj.cpp new file mode 100644 index 0000000..b318a94 --- /dev/null +++ b/iface/wext/seatplanobj.cpp @@ -0,0 +1,104 @@ +// +// C++ Implementation: MOOrder +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2009-2011 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#include "seatplanobj.h" + +#include "MOEventSaleInfo" +#include "MOEventPrice" + +#include + +#include + +static int mymeta= + qRegisterMetaType()+ + qRegisterMetaType >()+ + qRegisterMetaType >()+ + qRegisterMetaType()+ + qRegisterMetaType >()+ + qRegisterMetaType >()+ + qRegisterMetaType()+ + qRegisterMetaType >()+ + qRegisterMetaType >(); +static bool mocv= + QMetaType::registerConverter,MOSeatPlanDefPrice>([](const Nullable&n){return n.value();})| + QMetaType::registerConverter,QVariantList>([](const QList&n){QVariantList r;for(auto v:n)r<,MOSeatPlanGroup>([](const Nullable&n){return n.value();})| + QMetaType::registerConverter,QVariantList>([](const QList&n){QVariantList r;for(auto v:n)r<,MOSeatPlanVGroup>([](const Nullable&n){return n.value();})| + QMetaType::registerConverter,QVariantList>([](const QList&n){QVariantList r;for(auto v:n)r< getcategories(const MOEventSaleInfo&ei,QString spec) +{ + const QStringList specl=spec.split(' ',QString::SkipEmptyParts); + QListprc; + QListids; + QStringList abbrs; + for(QString sp:specl){ + bool isint=false; + const int id=sp.toInt(&isint); + if(isint)ids<::max(),sum=0; + QListcats=getcategories(ei,price()); + if(cats.size()==0)max=min=0; + for(const MOEventPrice&c:cats){ + if(c.maxavailable()>max)max=c.maxavailable(); + if(c.maxavailable()il; + for(QString c:s.split(' ',QString::SkipEmptyParts)) + il<, (C) 2009-2011 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#ifndef MAGICSMOKE_MOSEATPLANOBJ_H +#define MAGICSMOKE_MOSEATPLANOBJ_H + +#include "MOSeatPlanDefPriceAbstract" +#include "MOSeatPlanGroupAbstract" +#include "MOSeatPlanVGroupAbstract" +#include "misc.h" + +#include + +#ifndef MSIFACE_EXPORT +#define MSIFACE_EXPORT Q_DECL_IMPORT +#endif + +class MOEventSaleInfo; + +/**this class represents a default price category definition*/ +class MSIFACE_EXPORT MOSeatPlanDefPrice:public MOSeatPlanDefPriceAbstract +{ + Q_GADGET + WOBJECT(MOSeatPlanDefPrice) + Q_PROPERTY(bool isValid READ isValid) + public: + /**returns whether the object is valid (it comes from the DB and it has been understood by the parser)*/ + bool isValid()const{return !capacity().isNull() && !price().isNull() && !cost().isNull();} +}; + +Q_DECLARE_METATYPE(MOSeatPlanDefPrice) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(Nullable) + + +/**this class represents a seat group*/ +class MSIFACE_EXPORT MOSeatPlanGroup:public MOSeatPlanGroupAbstract +{ + Q_GADGET + WOBJECT(MOSeatPlanGroup) + Q_PROPERTY(bool isValid READ isValid) + Q_PROPERTY(int capacityNum READ capacityNum) + Q_PROPERTY(QRect geometry READ geometry) + Q_PROPERTY(QList categoryIds READ categoryIds) + Q_PROPERTY(QStringList categoryAbbreviations READ categoryAbbreviations) + Q_PROPERTY(int numReservedTickets READ numReservedTickets) + Q_PROPERTY(int numOrderedTickets READ numOrderedTickets) + Q_PROPERTY(int numUsedTickets READ numUsedTickets) + Q_PROPERTY(int numCancelledTickets READ numCancelledTickets) + Q_PROPERTY(int numBlockedSeats READ numBlockedSeats) + public: + /**returns whether the object is valid (it comes from the DB and it has been understood by the parser)*/ + bool isValid()const{return !capacity().isNull() && !price().isNull() && !id().isNull();} + + ///numeric capacity + int capacityNum()const{return mcapacity;} + + ///geometry as a rect + QRect geometry()const; + + ///IDs of categories in this group + QList categoryIds()const{return mcategoryids;} + + ///Abbreviations of categories in this group + QStringList categoryAbbreviations()const{return mcategoryabbr;} + + ///number of reserved tickets + int numReservedTickets()const{return mreserve;} + + ///number of ordered (not yet used) tickets + int numOrderedTickets()const{return mordered;} + + ///number of used tickets + int numUsedTickets()const{return mused;} + + ///number of cancelled tickets + int numCancelledTickets()const{return mcancelled;} + + ///number of blocked seats (reserved, ordered, used tickets) + int numBlockedSeats()const{return mblocked;} + + private: + friend class MOEventSaleInfo; + void adjust(const MOEventSaleInfo&); + + QList mcategoryids; + QStringList mcategoryabbr; + int mreserve=0,mordered=0,mused=0,mcancelled=0,mblocked=0,mcapacity=0; +}; + +Q_DECLARE_METATYPE(MOSeatPlanGroup) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(Nullable) + + + +/**this class represents a seat group*/ +class MSIFACE_EXPORT MOSeatPlanVGroup:public MOSeatPlanVGroupAbstract +{ + Q_GADGET + WOBJECT(MOSeatPlanVGroup) + Q_PROPERTY(bool isValid READ isValid) + Q_PROPERTY(int capacityNum READ capacityNum) + Q_PROPERTY(QList categoryIds READ categoryIds) + Q_PROPERTY(QStringList categoryAbbreviations READ categoryAbbreviations) + Q_PROPERTY(int numReservedTickets READ numReservedTickets) + Q_PROPERTY(int numOrderedTickets READ numOrderedTickets) + Q_PROPERTY(int numUsedTickets READ numUsedTickets) + Q_PROPERTY(int numCancelledTickets READ numCancelledTickets) + Q_PROPERTY(int numBlockedSeats READ numBlockedSeats); + public: + /**returns whether the object is valid (it comes from the DB and it has been understood by the parser)*/ + bool isValid()const{return !capacity().isNull() && !price().isNull();} + + ///numeric capacity + int capacityNum()const{return mcapacity;} + + ///IDs of categories in this group + QList categoryIds()const{return mcategoryids;} + + ///Abbreviations of categories in this group + QStringList categoryAbbreviations()const{return mcategoryabbr;} + + ///number of reserved tickets + int numReservedTickets()const{return mreserve;} + + ///number of ordered (not yet used) tickets + int numOrderedTickets()const{return mordered;} + + ///number of used tickets + int numUsedTickets()const{return mused;} + + ///number of cancelled tickets + int numCancelledTickets()const{return mcancelled;} + + ///number of blocked seats (reserved, ordered, used tickets) + int numBlockedSeats()const{return mblocked;} + + private: + friend class MOEventSaleInfo; + void adjust(const MOEventSaleInfo&); + + QList mcategoryids; + QStringList mcategoryabbr; + int mreserve=0,mordered=0,mused=0,mcancelled=0,mblocked=0,mcapacity=0; +}; + +Q_DECLARE_METATYPE(MOSeatPlanVGroup) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(Nullable) + +#endif diff --git a/iface/wext/wext.pri b/iface/wext/wext.pri index fc013be..9fbbe7d 100644 --- a/iface/wext/wext.pri +++ b/iface/wext/wext.pri @@ -10,7 +10,9 @@ HEADERS += \ wext/event.h \ wext/customer.h \ wext/transaction.h \ - wext/keyvalue.h + wext/keyvalue.h \ + wext/seatplanobj.h \ + wext/eventsaleinfo.h SOURCES += \ wext/customerinfo.cpp \ @@ -19,4 +21,6 @@ SOURCES += \ wext/address.cpp \ wext/event.cpp \ wext/customer.cpp \ - wext/transaction.cpp + wext/transaction.cpp \ + wext/seatplanobj.cpp \ + wext/eventsaleinfo.cpp diff --git a/pack b/pack index 2802df2..c214783 160000 --- a/pack +++ b/pack @@ -1 +1 @@ -Subproject commit 2802df28960dcde2a86a78a154cdcb6590152856 +Subproject commit c214783152ecdbf1cf2b9715ab3328ec81716b2e diff --git a/src/mwin/carttab.cpp b/src/mwin/carttab.cpp index 60307a6..4b29258 100644 --- a/src/mwin/carttab.cpp +++ b/src/mwin/carttab.cpp @@ -20,16 +20,24 @@ #include "carttab.h" #include "MTGetAllShipping" +#include "MTGetCoupon" #include "MTGetEvent" #include "MTGetValidVoucherPrices" +#include "MTGetVoucher" #include "MTCreateOrder" #include "MTCreateReservation" +#include "MOCoupon" +#include "MOCouponRule" + #include #include #include #include +#include #include +#include +#include #include #include #include @@ -77,14 +85,25 @@ MCartTab::MCartTab(QString pk) connect(p,SIGNAL(clicked()),this,SLOT(cartRemoveItem())); vl2->addWidget(frm=new QFrame,0); frm->setFrameShape(QFrame::HLine); - vl2->addLayout(hl2=new QHBoxLayout,0); - hl2->addWidget(lab=new QLabel(tr("Total Price Sum:"))); + QFormLayout *fl; + vl2->addLayout(fl=new QFormLayout,0); + fl->addRow(lab=new QLabel(tr("Total Price Sum:")),price=new QLabel("0.0")); QFont fnt=lab->font(); fnt.setPointSizeF(fnt.pointSizeF()*1.5); lab->setFont(fnt); - hl2->addWidget(price=new QLabel("0.0")); price->setFont(fnt); - hl2->addStretch(1); + fl->addRow(tr("Coupon Code:"),hl2=new QHBoxLayout); + hl2->addWidget(coupon=new QLabel(tr("(none)","coupon code label")),1); + hl2->addWidget(p=new QPushButton(tr("Add Coupon...")),0); + connect(p,&QPushButton::clicked,this,[&](){changeVoucher("vadd:coupon");}); + connect(coupon,SIGNAL(linkActivated(const QString&)),this,SLOT(changeVoucher(QString))); + fl->addRow(tr("Pay with Vouchers:"),hl2=new QHBoxLayout); + hl2->addWidget(vouchers=new QLabel("..."),1); + hl2->addWidget(p=new QPushButton(tr("Add Voucher...")),0); + connect(p,&QPushButton::clicked,this,[&](){changeVoucher("vadd:voucher");}); + connect(vouchers,SIGNAL(linkActivated(const QString&)),this,SLOT(changeVoucher(QString))); + fl->addRow(tr("Remaining Sum:"),priceremain=new QLabel("0.0")); + //hl2->addStretch(1); hl->addWidget(frm=new QFrame,0); frm->setFrameShape(QFrame::VLine); @@ -181,11 +200,17 @@ void MCartTab::updateShipping() cartship->setToolTip(QString()); } +#define COL_AMOUNT 0 +#define COL_TITLE 1 +#define COL_START 2 +#define COL_PRICE 3 +#define COL_NOTES 4 + void MCartTab::initCart() { //clear cart cartmodel->clear(); - cartmodel->setHorizontalHeaderLabels(QStringList()<setHorizontalHeaderLabels(QStringList()<setText(""); @@ -199,6 +224,9 @@ void MCartTab::initCart() cartship->setCurrentIndex(0); //reset colors resetColor(); + //reset coupon and voucher labels + couponinfo=MOCoupon(); + vouchersforpay.clear(); //update price display updatePrice(); } @@ -278,9 +306,12 @@ static const int CART_VOUCHER=2; static const int CART_IDROLE=Qt::UserRole;//ticket id static const int CART_PRICEIDROLE=Qt::UserRole+3;//for tickets: price category id +static const int CART_ORIGPRICEIDROLE=Qt::UserRole+5;//for tickets: original price ID if it has been changed by a coupon +static const int CART_EVENTDATAROLE=Qt::UserRole+6;//for tickets: the full event static const int CART_PRICEROLE=Qt::UserRole+4;//voucher/ticket price static const int CART_VALUEROLE=Qt::UserRole+1;//voucher value static const int CART_TYPEROLE=Qt::UserRole+2;//which is it? ticket or voucher? +static const int CART_NOTECOPY=Qt::UserRole+7;//a copy of the original note field, in case it gets overwritten by an error void MCartTab::cartAddTicket() { @@ -343,7 +374,9 @@ void MCartTab::addTicketForEvent(qint64 id, qint64 prcid) cartmodel->setData(cartmodel->index(cr,0),id,CART_IDROLE); cartmodel->setData(cartmodel->index(cr,0),CART_TICKET,CART_TYPEROLE); cartmodel->setData(cartmodel->index(cr,0),ep[pcidx].pricecategoryid().value(),CART_PRICEIDROLE); + cartmodel->setData(cartmodel->index(cr,0),ep[pcidx].pricecategoryid().value(),CART_ORIGPRICEIDROLE); cartmodel->setData(cartmodel->index(cr,0),ep[pcidx].price().value(),CART_PRICEROLE); + cartmodel->setData(cartmodel->index(cr,0),QVariant::fromValue(ev),CART_EVENTDATAROLE); cartmodel->setData(cartmodel->index(cr,1),ev.title().value()); cartmodel->setData(cartmodel->index(cr,2),ev.startTimeString()); QString pcn=cent2str(ep[pcidx].price())+" ("+ep[pcidx].pricecategory().value().name().value()+")"; @@ -372,6 +405,19 @@ void MCartTab::eventOrderTicket(qint64 id,qint64 prcid) void MCartTab::cartAddVoucher() { + //check privileges + if(!req->hasRight(MInterface::PCreateOrder_CanPayVoucherWithVoucher)){ + bool vfound=false; + for(int r=0;rrowCount();r++) + if(cartmodel->data(cartmodel->index(r,0),CART_TYPEROLE).toInt()==CART_VOUCHER){ + vfound=true; + break; + } + if(vfound){ + QMessageBox::warning(this,tr("Warning"),tr("Cannot pay for vouchers with another voucher!")); + return; + } + } //create voucher selection dialog QDialog dlg; dlg.setWindowTitle(tr("Select Voucher")); @@ -554,6 +600,7 @@ void MCartTab::cartTableToOrder(MOCartOrder&cord) bool MCartTab::orderExecute(MOCartOrder&cord,MOOrder&ord,bool isreserve,bool issale) { + //TODO: add coupon and related info? if(isreserve){ MTCreateReservation co=req->queryCreateReservation(cord); if(co.hasError()){ @@ -563,7 +610,7 @@ bool MCartTab::orderExecute(MOCartOrder&cord,MOOrder&ord,bool isreserve,bool iss ord=co.getorder(); cord=co.getcart(); }else{ - MTCreateOrder co=req->queryCreateOrder(cord,issale); + MTCreateOrder co=req->queryCreateOrder(cord,issale,QStringList());//TODO: add vouchers if(co.hasError()){ QMessageBox::warning(this,tr("Warning"),tr("Error while creating order: %1").arg(co.errorString())); return false; @@ -603,31 +650,53 @@ void MCartTab::verifyOrderCustomer(MOCartOrder&cord) void MCartTab::verifyOrderTickets(const QList&ticks) { + //reset model + for(int i=0;irowCount();i++){ + if(cartmodel->data(cartmodel->index(i,0),CART_TYPEROLE).toInt() != CART_TICKET)continue; + for(int j=0;jcolumnCount();j++){ + QModelIndex idx=cartmodel->index(i,j); + cartmodel->setData(idx,QVariant(),Qt::BackgroundRole); + cartmodel->setData(idx,QVariant(),Qt::ToolTipRole); + } + QModelIndex idx=cartmodel->index(i,COL_NOTES); + cartmodel->setData(idx,cartmodel->data(idx,CART_NOTECOPY)); + } + //insert for(int i=0;i=cartmodel->rowCount())continue; - QModelIndex idx0=cartmodel->index(j,0); + const QModelIndex idx0=cartmodel->index(j,COL_AMOUNT); if(cartmodel->data(idx0,CART_TYPEROLE).toInt() != CART_TICKET)continue; - QModelIndex idx1=cartmodel->index(j,1); - cartmodel->setData(idx1,QColor(Qt::red),Qt::BackgroundRole); + const QModelIndex idx1=cartmodel->index(j,COL_TITLE); + const auto red=QColor(Qt::red); + cartmodel->setData(idx1,red,Qt::BackgroundRole); + const QModelIndex idxn=cartmodel->index(j,COL_NOTES); //check state, color it + QString tt; switch(ticks[i].status().value()){ case MOCartTicket::EventOver: - cartmodel->setData(idx1,tr("The event is already over, please remove this entry."),Qt::ToolTipRole); + tt=tr("The event is already over, please remove this entry."); + cartmodel->setData(idx1,tt,Qt::ToolTipRole); + cartmodel->setData(idxn,tt); break; case MOCartTicket::TooLate: - cartmodel->setData(idx1,tr("You cannot order tickets for this event anymore, ask a more privileged user."),Qt::ToolTipRole); + tt=tr("You cannot order tickets for this event anymore, ask a more privileged user."); + cartmodel->setData(idx1,tt,Qt::ToolTipRole); + cartmodel->setData(idxn,tt); break; - case MOCartTicket::Exhausted:{ - cartmodel->setData(idx0,QColor(Qt::red),Qt::BackgroundRole); - QString tt=tr("The event is (almost) sold out, there are %1 tickets left.").arg(ticks[i].maxamount()); + case MOCartTicket::Exhausted: + cartmodel->setData(idx0,red,Qt::BackgroundRole); + tt=tr("The event or category is (almost) sold out, there are %1 tickets left.").arg(ticks[i].maxamount()); cartmodel->setData(idx0,tt,Qt::ToolTipRole); cartmodel->setData(idx1,tt,Qt::ToolTipRole); - break;} + cartmodel->setData(idxn,tt); + break; case MOCartTicket::Invalid: - cartmodel->setData(idx1,tr("The event does not exist or there is another serious problem, please remove this entry."),Qt::ToolTipRole); + tt=tr("The event does not exist or there is another serious problem, please remove this entry."); + cartmodel->setData(idx1,tt,Qt::ToolTipRole); + cartmodel->setData(idxn,tt); break; default:break;//ignore all others } @@ -637,6 +706,18 @@ void MCartTab::verifyOrderTickets(const QList&ticks) void MCartTab::verifyOrderVouchers(const QList&voucs) { + //reset model + for(int i=0;irowCount();i++){ + if(cartmodel->data(cartmodel->index(i,0),CART_TYPEROLE).toInt() != CART_VOUCHER)continue; + for(int j=0;jcolumnCount();j++){ + QModelIndex idx=cartmodel->index(i,j); + cartmodel->setData(idx,QVariant(),Qt::BackgroundRole); + cartmodel->setData(idx,QVariant(),Qt::ToolTipRole); + } + QModelIndex idx=cartmodel->index(i,COL_NOTES); + cartmodel->setData(idx,cartmodel->data(idx,CART_NOTECOPY)); + } + //insert for(int i=0;i&voucs) if(cartmodel->data(idx0,CART_TYPEROLE).toInt() != CART_VOUCHER)continue; QModelIndex idx1=cartmodel->index(j,1); cartmodel->setData(idx1,QColor(Qt::red),Qt::BackgroundRole); + const QModelIndex idxn=cartmodel->index(j,COL_NOTES); //check state, color it + QString tt; switch(voucs[i].status().value()){ case MOCartVoucher::InvalidValue: - cartmodel->setData(idx1,tr("You do not have permission to create vouchers with this value, please remove it."),Qt::ToolTipRole); + tt=tr("You do not have permission to create vouchers with this value, please remove it."); + cartmodel->setData(idx1,tt,Qt::ToolTipRole); + cartmodel->setData(idxn,tt); break; case MOCartVoucher::InvalidPrice: - cartmodel->setData(idx1,tr("The price tag of this voucher is not valid, please remove and recreate it."),Qt::ToolTipRole); + tt=tr("The price tag of this voucher is not valid, please remove and recreate it."); + cartmodel->setData(idx1,tt,Qt::ToolTipRole); + cartmodel->setData(idxn,tt); break; default:break;//ignore all others } @@ -677,8 +764,194 @@ void MCartTab::updatePrice() } //get shipping prc+=cartship->itemData(cartship->currentIndex(),SHIPPING_PRICEROLE).toInt(); - //display + //display price price->setText(cent2str(prc)); + //format coupon + if(couponinfo.couponid().isNull()) + coupon->setText(tr("(none)","empty coupon code label")+ + QString(" %1").arg(tr("[Add Coupon]","link/label"))); + else + coupon->setText(QString("%1 (%2)").arg(couponinfo.couponid()).arg(couponinfo.description())); + //format vouchers + QString vt; + if(vouchersforpay.size()>0){ + //check for vouchers in cart + bool vfound=false; + for(int r=0;rrowCount();r++) + if(cartmodel->data(cartmodel->index(r,0),CART_TYPEROLE).toInt()==CART_VOUCHER){ + vfound=true; + break; + } + if(vfound) + vt+=""+tr("Warning: Paying for vouchers with older vouchers!")+"
"; + //render vouchers for payment + vt+=""; + for(auto vou:vouchersforpay){ + vt+=QString("") + .arg(vou.first).arg(cent2str(vou.second)).arg(tr("[remove]","remove voucher link/label")); + prc-=vou.second; + } + vt+="
%1   -  %2   %3

"; + } + vt+=""+tr("[Add Voucher]","link/label")+""; + vouchers->setText(vt); + //format remaining price + if(prc<0)prc=0; + priceremain->setText(cent2str(prc)); +} + +void MCartTab::changeVoucher ( QString url ) +{ + qDebug()<queryGetCoupon(cid).getcoupon(); + //verify and use + if(cp.couponid().isNull()){ + QMessageBox::warning(this,tr("Warning"),tr("'%1' is not a valid coupon.").arg(cid)); + return; + } + if(!req->checkFlags(cp.flags())){ + QMessageBox::warning(this,tr("Warning"),tr("Sorry, you are not allowed to use this coupon.")); + return; + } + couponinfo=cp; + applyCoupon(); + }else if(url=="vadd:voucher"){ + //sanity check + bool priv=req->hasRight(MInterface::PCreateOrder_CanPayVoucherWithVoucher); + bool vfound=false; + for(int r=0;rrowCount();r++) + if(cartmodel->data(cartmodel->index(r,0),CART_TYPEROLE).toInt()==CART_VOUCHER){ + vfound=true; + break; + } + if(!priv && vfound){ + QMessageBox::warning(this,tr("Warning"),tr("Cannot pay for vouchers with another voucher!")); + return; + } + //get voucher ID + const QString vid=QInputDialog::getText(this,tr("Enter Voucher ID"),tr("Please enter the voucher ID:")).trimmed().toUpper(); + if(vid.isEmpty())return; + for(auto v:vouchersforpay) + if(v.first==vid){ + QMessageBox::warning(this,tr("Warning"),tr("Voucher '%1' is already in list.").arg(vid)); + return; + } + //retrieve it and check + MTGetVoucher vou=req->queryGetVoucher(vid); + if(vou.getvoucher().isNull() || vou.getvoucher().value().voucherid().isNull()){ + QMessageBox::warning(this,tr("Warning"),tr("The voucher '%1' does not exist.").arg(vid)); + return; + } + if(vou.getvoucher().data().value().isNull()||vou.getvoucher().data().value().data()<=0){ + QMessageBox::warning(this,tr("Warning"),tr("The voucher '%1' does not have any value left.").arg(vid)); + return; + } + if(!vou.getispaidfor().value()){ + QMessageBox::warning(this,tr("Warning"),tr("The voucher '%1' cannot be used: it has not been paid yet.").arg(vid)); + return; + } + //add + vouchersforpay.append(QPair(vid,vou.getvoucher().data().value())); + }else if(url.startsWith("vremove:")){ + const QString vid=url.mid(8); + for(int i=0;i current implementation bails out if it encounters any of the above + + //sanity check + if(couponinfo.couponid().isNull())return; + //TODO: once removal of coupon gets implemented - do a reset instead of quitting + + //check for not implemented limits - TODO: implement instead + if(!couponinfo.amountuse().isNull() || !couponinfo.ticketsperorder().isNull()){ + coupon->setText(tr("Sorry, coupon limits are not implemented yet.")); + return; + } + for(auto rule:couponinfo.rules()) + if(!rule.maxuse().isNull() || !rule.needcategory().isNull() || !rule.needtickets().isNull()){ + coupon->setText(tr("Sorry, coupon rule limits are not implemented yet.")); + return; + } + + //go through order and reset tickets + //TODO: count and accumulate + for(int i=0;irowCount();i++){ + QModelIndex idx0=cartmodel->index(i,0); + if(cartmodel->data(idx0,CART_TYPEROLE).toInt()!=CART_TICKET)continue; + MOEvent ev=cartmodel->data(idx0,CART_EVENTDATAROLE).value(); + //QModelIndex idxp=cartmodel->index(i,COL_PRICE); + int pid=cartmodel->data(idx0,CART_ORIGPRICEIDROLE).toInt(); + MOEventPrice ep; + for(auto prc:ev.price()) + if(prc.pricecategoryid()==pid){ + ep=prc; + break; + } + cartmodel->setData(idx0,pid,CART_PRICEIDROLE); + cartmodel->setData(idx0,ep.price().data(),CART_PRICEROLE); + QModelIndex idxp=cartmodel->index(i,COL_PRICE); + cartmodel->setData(idxp,QString("%1 (%2)").arg(cent2str(ep.price())).arg(ep.pricecategory().value().name().value())); + QModelIndex idxn=cartmodel->index(i,COL_NOTES); + cartmodel->setData(idxn,cartmodel->data(idxn,CART_NOTECOPY)); + } + + //go through rules and adjust tickets + //TODO: work on accumulated data + for(int i=0;irowCount();i++){ + //get price id + QModelIndex idx0=cartmodel->index(i,0); + if(cartmodel->data(idx0,CART_TYPEROLE).toInt()!=CART_TICKET)continue; + int pid=cartmodel->data(idx0,CART_ORIGPRICEIDROLE).toInt(); + //do we have a rule? + int npid=pid; + for(auto rule:couponinfo.rules()){ + if(pid==rule.fromcategory().data()){ + npid=rule.tocategory(); + break; + } + } + if(pid==npid)continue; + //corrections + MOEvent ev=cartmodel->data(idx0,CART_EVENTDATAROLE).value(); + MOEventPrice ep,epo; + for(auto prc:ev.price()){ + if(prc.pricecategoryid()==npid) + ep=prc; + if(prc.pricecategoryid()==pid) + epo=prc; + } + + cartmodel->setData(idx0,npid,CART_PRICEIDROLE); + cartmodel->setData(idx0,ep.price().data(),CART_PRICEROLE); + QModelIndex idxp=cartmodel->index(i,COL_PRICE); + cartmodel->setData(idxp,QString("%1 (%2)").arg(cent2str(ep.price())).arg(ep.pricecategory().value().name().value())); + QModelIndex idxn=cartmodel->index(i,COL_NOTES); + cartmodel->setData(idxn,tr("Original price: %1 (%2)").arg(cent2str(epo.price())).arg(epo.pricecategory().value().name().value())); + } } /********************************************************************************/ diff --git a/src/mwin/carttab.h b/src/mwin/carttab.h index 9a48a7d..6f3ddb9 100644 --- a/src/mwin/carttab.h +++ b/src/mwin/carttab.h @@ -20,6 +20,7 @@ #include #include "MOCustomer" +#include "MOCoupon" class QAction; class QCheckBox; @@ -105,6 +106,8 @@ class MCartTab:public QWidget void resetColor(bool addronly=false); /**update the price shown*/ void updatePrice(); + ///remove/add a voucher from the payment area + void changeVoucher(QString url); private: //the profile associated with this session @@ -112,12 +115,14 @@ class MCartTab:public QWidget //widgets QTableView*carttable; QStandardItemModel*cartmodel; - QLabel*cartcustomer,*deliveryaddr,*invoiceaddr,*price; + QLabel*cartcustomer,*deliveryaddr,*invoiceaddr,*price,*coupon,*vouchers,*priceremain; QTextEdit *cartcomment; QComboBox*cartship; //cart MOCustomer customer; qint64 deliveryaddrid,invoiceaddrid; + MOCoupon couponinfo; + QList> vouchersforpay; /**helper for order: returns true if an order can be sent*/ bool canorder(bool isreserve); @@ -136,6 +141,9 @@ class MCartTab:public QWidget /**helper for cartAddTicket and eventOrderTicket: choses a price category and then adds the ticket*/ void addTicketForEvent(qint64,qint64 prcid=-1); + + ///helper to apply coupon rules + void applyCoupon(); }; /**Helper class for shopping cart: allow editing amount, but nothing else*/ diff --git a/tzone b/tzone index d2852c3..d944922 160000 --- a/tzone +++ b/tzone @@ -1 +1 @@ -Subproject commit d2852c3d8f78d0ae39e2a0833e33c3551e89cea0 +Subproject commit d94492246f5b98e0936f5a184e9533496d240a63 diff --git a/wob/classes/cart.wolf b/wob/classes/cart.wolf index a15dee2..cfc7e99 100644 --- a/wob/classes/cart.wolf +++ b/wob/classes/cart.wolf @@ -94,6 +94,7 @@ + An optional coupon code to be used in this order. diff --git a/wob/classes/event.wolf b/wob/classes/event.wolf index b9371c3..b90414a 100644 --- a/wob/classes/event.wolf +++ b/wob/classes/event.wolf @@ -103,14 +103,6 @@ - - This class is the transport for seat plans when communicating over the wire - - - - - - @@ -172,4 +164,4 @@ - \ No newline at end of file + diff --git a/wob/classes/order.wolf b/wob/classes/order.wolf index 75a55b7..de18251 100644 --- a/wob/classes/order.wolf +++ b/wob/classes/order.wolf @@ -2,7 +2,7 @@ @@ -54,7 +54,7 @@ - + @@ -211,6 +211,9 @@ + + + @@ -231,6 +234,7 @@ + @@ -238,6 +242,7 @@ + @@ -263,6 +268,8 @@ + + @@ -290,6 +297,7 @@ + @@ -407,4 +415,60 @@ + + + + + + + + + + + + + + + + + + + + + + This class represents a coupon. + + + + + + + + + + + + + + + + + + + + + This class represents the bare minimum info of a coupon. + + + + + + + + + + + + + diff --git a/wob/classes/seatplan.wolf b/wob/classes/seatplan.wolf index 57fd30d..a582776 100644 --- a/wob/classes/seatplan.wolf +++ b/wob/classes/seatplan.wolf @@ -2,12 +2,15 @@ + + This is a helper for parsing seat plans: it defines a row of seats the ID of this group, if multiple row elements with the same ID exist, they refer to different sections of the same row amount of seats in this (part of the) row @@ -17,9 +20,11 @@ number of the first seat in this row + + This is a helper for parsing seat plans: it defines a group of rows of seats. the ID of this group, if multiple group elements with the same ID exist, they refer to different sections of the same group - amount of seats in this (part of the) group + amount of seats in this (part of the) group human readable name of the group defines whether the group contains numbered seats GUI: geometry information for the group @@ -27,9 +32,22 @@ GUI: foreground color, the one the group name is rendered in GUI: rotation of the group rectangle definition of rows in this group + The price categories in this group. - + + + + + This is a helper for parsing seat plans: it defines a virtual group of categories. + amount of seats in this (part of the) group + human readable name of the group + definition of categories in this group + + + + Backgroup objects for seatplan graphics. Type of background element, e.g. "circle", "rect", ... GUI: geometry information for the group GUI: background or fill color @@ -38,10 +56,102 @@ GUI: font size for text elements GUI: textual content - + + + + + Default price categories for a seat plan. + amount of seats in this (part of the) group + definition of categories in this group + default cost definition + + + + Image data that can be embedded in seat plan backgrounds + + + + - This class is used to parse seat plans on either side. + This class is used to parse seat plans on either side. It is not sent + over the wire, since it is just a helper class. + + + + + + + + + + This class is the transport for seat plans when communicating over the wire + + + + + + + + + + + + + + + This class represents only the info from a ticket that is required for calculating seating information. + + + + + + + + + + + + + + + + + + + + This class transports all data necessary to calculate the free seats of an event + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wob/db/cart.wolf b/wob/db/cart.wolf index 40aa618..accfa25 100644 --- a/wob/db/cart.wolf +++ b/wob/db/cart.wolf @@ -2,7 +2,7 @@ @@ -63,4 +63,4 @@ the time when the session will be deleted, updated whenever the session is used - \ No newline at end of file + diff --git a/wob/db/db.wolf b/wob/db/db.wolf index ce017b5..d4f5705 100644 --- a/wob/db/db.wolf +++ b/wob/db/db.wolf @@ -9,7 +9,7 @@ + version="01.09" versionRow="MagicSmokeVersion"> Time at which the change was made. diff --git a/wob/db/order.wolf b/wob/db/order.wolf index a79aebd..3360a41 100644 --- a/wob/db/order.wolf +++ b/wob/db/order.wolf @@ -26,6 +26,31 @@ + + This table contains coupon definitions + + short description of the coupon for internal use + Validity period of the coupon. + + How often it can be used, if NULL it is unlimited. + How many tickets per order can be changed, NULL for unlimited. + Filter: who can use this coupon. +
+ + This table contains rules of what category of ticket to exchange to what new category by use of a coupon. + Simple counter key. + + from what category of ticket... + ..to change to what category of ticket. + how often this rule can be used in one order or NULL for unlimited + + after the change tickets of this category must exist or NULL if there is no requirement + + + after the change this many tickets of "needcategory" must exist or NULL if there is no requirement; + e.g. a Two-for-One rule would change fromcategory="paid" to tocategory="free" if needcategory="paid" still has needtickets="1" ticket + +
This table contains all orders and sales. @@ -67,6 +92,7 @@ additional tags for this order, can be used by scripts + Optional coupon code used on this order. diff --git a/wob/transact/event.wolf b/wob/transact/event.wolf index 1526471..97043ea 100644 --- a/wob/transact/event.wolf +++ b/wob/transact/event.wolf @@ -86,7 +86,18 @@ - + + + returns the information necessary to handle sales with seat plans + + + + + + + + + create a new event @@ -152,4 +163,4 @@ - \ No newline at end of file + diff --git a/wob/transact/order.wolf b/wob/transact/order.wolf index 20f3bd1..4d2b5f4 100644 --- a/wob/transact/order.wolf +++ b/wob/transact/order.wolf @@ -2,7 +2,7 @@ @@ -23,9 +23,10 @@ - + + @@ -113,7 +114,19 @@ - + + + + unix timestamp for the oldest order to be returned (compared with ordertime) + + + + + + + + + CreateOrder creates orders that are queued to be executed immediately or sales that are marked executed and paid for immediately; they may contain tickets, vouchers, or shopping items users with this privilege can create vouchers with arbitrary value @@ -125,13 +138,17 @@ users with this privilege may put tickets into orders users with this privilege may put vouchers into orders users with this privilege may put shop items (merchandise) into orders + users with this privilege may pay for vouchers with other vouchers The cart contents true if this is a sale, false if this is an order + an optional list of vouchers to be used in this order for payment (coupon is in the cart instead) If successful: the created order + If vouchers were used: the current state of the vouchers after creating (and paying) the order + If this is a sale: the amount paid in cash (in cents) If unsuccessful: the cart with annotations @@ -206,6 +223,7 @@ The transaction will never transfer money from an order back to the voucher. If the voucher is no longer valid the transaction will fail. + users with this privilege may pay for vouchers with other vouchers ID of the order that is to be paid for ID of the voucher that is to be used for payment @@ -442,7 +460,7 @@ - + returns the valid payment types @@ -479,7 +497,7 @@ - + Gets the names of all files associated with the Order @@ -489,7 +507,7 @@ - + Gets a document associated with an order. @@ -500,7 +518,7 @@ - + Stores a document associated with an order. Overwrites the document if it already exists, creates it otherwise. @@ -511,7 +529,7 @@ - + Deletes a document associated with an order. Produces an error if the document does not exist. @@ -521,7 +539,7 @@ - + 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. @@ -531,7 +549,7 @@ - + Gets all settings pertaining to Print@Home @@ -539,7 +557,7 @@ - + Overrides settings for Print@Home, use isnull=true to delete entries. @@ -547,4 +565,43 @@ + + + Retrieves details about a single coupon. + + + + + + + + + + Retrieves all Coupon IDs + + + + + + + + + + Creates a new coupon. + + Input data for the coupon, if the ID is empty or null a new one is created + + + + actually stored coupon: the ID may be slightly changed by the server + + + + Changes a coupon. + + + + + + diff --git a/www/inc/classes/random.php b/www/inc/classes/random.php index 36d7027..621c807 100644 --- a/www/inc/classes/random.php +++ b/www/inc/classes/random.php @@ -76,14 +76,17 @@ define("RND_TICKET",0x00); //added to a voucher define("RND_VOUCHER",0x08); //not used yet: -define("RND_OTHER1",0x10); +define("RND_COUPON",0x10); define("RND_OTHER2",0x18); +//Code 39 chars that are used for barcodes +define("RND_CODE39_CHARS","123456789ABCDEFGHJKLMNPQRSTUVWXYZ"); + /**return a new Code-39 capable ID; length is the amount of characters*/ function getCode39ID($length,$range=RND_ANYRANGE) { //we use a subset of the Code-39 characters that is moderately un-ambiguous - $c39="123456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + $c39=RND_CODE39_CHARS; //get plenty of random bits (2 hex chars for each Code-39 char) $rnd=getRandom($length*8); //construct the code @@ -103,6 +106,24 @@ function getCode39ID($length,$range=RND_ANYRANGE) return $ret; } +///returns whether a barcode matches a specific range +function code39IsInRange($code,$range=RND_ANYRANGE,$length=-1) +{ + //check length + if($length>0){ + if(strlen($code)!=$length)return false; + } + //convert first char back to int + $c=strtoupper(substr($code,0,1)); + $i=strpos(RND_CODE39_CHARS,$c); + if($i===false)return false; + //shortcut + if($range==RND_ANYRANGE)return true; + //mask and compare + $i &= ~RND_MASK; + return $i == $range; +} + //eof return; ?> diff --git a/www/inc/db/barcodetable.php b/www/inc/db/barcodetable.php index 822e848..4d3ec30 100644 --- a/www/inc/db/barcodetable.php +++ b/www/inc/db/barcodetable.php @@ -8,6 +8,7 @@ abstract class BarcodeTable extends WobTable { private static $NumTicketChars=false; private static $NumVoucherChars=false; + private static $NumCouponChars=false; private static function init() { global $db; @@ -19,6 +20,10 @@ abstract class BarcodeTable extends WobTable self::$NumVoucherChars=$db->getConfig("VoucherIDChars")+0; if(self::$NumVoucherChars<=5)self::$NumVoucherChars=10; } + if(self::$NumCouponChars===false){ + self::$NumCouponChars=$db->getConfig("CouponIDChars")+0; + if(self::$NumCouponChars<=5)self::$NumCouponChars=10; + } } public static function getNewTicketId() { @@ -40,6 +45,36 @@ abstract class BarcodeTable extends WobTable if(count($res)==0)return $tid; }while(true); } + public static function getNewCouponId() + { + global $db; + self::init(); + do{ + $tid=getCode39ID(self::$NumCouponChars,RND_COUPON); + $res=$db->select("coupon","couponid","couponid=".$db->escapeString($tid)); + if(count($res)==0)return $tid; + }while(true); + } + ///checks that the ID is valid as a coupon and could not interfere with tickets or vouchers + public static function checkCouponIdValid($couponid) + { + self::init(); + //DB limits + if(strlen($couponid)<1 || strlen($couponid)>32)return false; + //not a ticket or voucher + if(code39IsInRange($couponid,RND_TICKET,self::$NumTicketChars) || code39IsInRange($couponid,RND_VOUCHER,self::$NumVoucherChars)) + return false; + //seems to be ok then... + return true; + } + ///checks whether a coupon ID exists + public static function checkCouponIdExists($couponid) + { + global $db; + self::init(); + $res=$db->select("coupon","couponid","couponid=".$db->escapeString($couponid)); + return count($res)!=0; + } }; //eof diff --git a/www/inc/wext/cart.php b/www/inc/wext/cart.php index a36117a..504c1d4 100644 --- a/www/inc/wext/cart.php +++ b/www/inc/wext/cart.php @@ -93,8 +93,13 @@ class WOCartOrder extends WOCartOrderAbstract return; } }else{ + //verify vouchers and items are valid $very&=$cart->verifyVouchers($trans,$vanyval,$vdiffprice); $very&=$cart->verifyItems($trans); + //verify vouchers are not paid with other vouchers (unless privilege exists) + if(!$trans->havePrivilege(WTrCreateOrder::Priv_CanPayVoucherWithVoucher)){ + $very&=count($trans->getvouchers())==0 || count($cart->getvouchers())==0; + } } //verification successful? if(!$very){ diff --git a/www/inc/wext/order.php b/www/inc/wext/order.php index 64fb6d7..6113309 100644 --- a/www/inc/wext/order.php +++ b/www/inc/wext/order.php @@ -135,7 +135,27 @@ class WOOrderInfo extends WOOrderInfoAbstract $cust=WTcustomer::selectFromDB("customerid IN ".$db->escapeIntList(array_unique($cst,SORT_NUMERIC))); $trans->setcustomers(WOCustomerInfo::fromTableArraycustomer($cust)); } - + + /**called from GetOrdersByUser and getMyOrders transaction*/ + static public function getOrdersByCoupon($trans) + { + global $session; + $cid=$trans->getcouponid(); + $old=$trans->getoldest(); + if($old===false)$old=0; + //get primary list: orders of this user + global $db; + $res=WTorder::selectFromDB("couponid=".$db->escapeString($cid)." AND ordertime>=".$db->escapeInt($old)); + //get list of customerids + $cst=array(); + foreach($res as $ord) + $cst[]=$ord->customerid; + //construct result + $trans->setorders(self::fromTableArrayorder(array_values($res))); + $cust=WTcustomer::selectFromDB("customerid IN ".$db->escapeIntList(array_unique($cst,SORT_NUMERIC))); + $trans->setcustomers(WOCustomerInfo::fromTableArraycustomer($cust)); + } + /**helper function for several transactions: selects customers by their IDs references by the given order info objects*/ static protected function getCustomerListByOrders(array $olist) { @@ -181,6 +201,12 @@ class WOOrder extends WOOrderAbstract //return return $prc; } + + ///returns true if the order is fully paid + public function isFullyPaid() + { + return $this->getTotalPrice()<=$this->prop_amountpaid; + } /**called by GetOrderByBarcode transaction*/ static public function getOrderByBarcode($trans) @@ -399,7 +425,15 @@ class WOOrder extends WOOrderAbstract $trans->abortWithError(tr("Amount to be paid must be positive.")); return; } + //check that voucher is not used to pay for other vouchers $order=WOOrder::fromTableorder($ord); + if(!$trans->havePrivilege(WTrUseVoucher::Priv_CanPayVoucherWithVoucher)){ + if(count($order->getvouchers())>0){ + $trans->abortWithError(tr("Cannot pay for voucher with another voucher.")); + return; + } + } + //make the payment $max=$order->prop_amountdue; if($max<=0)$pay=0;else if($max<$pay)$pay=$max;