Seat-Plans
===========
-Specification Version (YYYYnn): 201501
+Specification Version (YYYYnn): 201601
Server Side Minimum:
---------------------
-<SeatPlan version="201501">
+<SeatPlan version="201601">
<Group capacity="123" id="pr" name="Premium" numbered="no" price="1 3"/>
<Group capacity="234" id="ch" name="Cheapo" numbered="no" price="2 4"/>
+ <VGroup capacity="maxcat" name="VIP" price="1 2"/>
+ <DefPrice capacity="200" price="1 2" cost="1300 ask"/>
+ <DefPrice capacity="200" price="3 4" cost="calc ask"/>
</SeatPlan>
In this simplest form there are two kinds of seats: "Premium" and "Cheapo".
<SeatPlan> defines the complete plan
version - version number of this spec, it is generally assumed that newer
versions are backwards compatible
-<Group> defines one group of seats
+<Group> 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
+<VGroup> 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
+<DefPrice> 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:
-<SeatPlan version="201501">
+<SeatPlan version="201601">
<Group capacity="123" id="pr" name="Premium" numbered="yes">
<Row id="2" capacity="23" first="2"/>
<Row id="3" capacity="25"/>
This part is never evaluated by the server, only by displays:
-<SeatPlan version="201501" size="100 200" bgcolor="darkred" tkcolor="black">
+<SeatPlan version="201601" size="100 200" bgcolor="darkred" tkcolor="black">
<Group capacity="123" id="pr" name="Premium" numbered="yes" geo="0 20 100 100" bgcolor="#fcc">
<Row id="2" capacity="23" first="2" geo="10 0 90 10/>
<Row id="3" capacity="25" geo="0 20 100 10"/>
<!-- insert a simple icon or picture -->
<Background type="image" geo="10 40 25 25" image="icon.png"/>
- <Image name="icon.png">base64-encoded data</Image>
+ <Image name="icon.png"><Data>base64-encoded data</Data></Image>
</SeatPlan>
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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- SeatPlan XML Schema Definition
- (c) Konrad Rosenbaum, 2015
+ (c) Konrad Rosenbaum, 2015-16
protected under the GNU GPL v.3 or at you option any newer
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="SeatPlanType">
<xs:choice maxOccurs="unbounded">
<xs:element name="Group"/>
+ <xs:element name="VGroup"/>
<xs:element name="Background"/>
<xs:element name="Image" type="ImageType"/>
</xs:choice>
</xs:complexType>
<xs:simpleType name="VersionType">
- <xs:annotation><xs:documentation>A version number for the file format. It always consists of 4-digit year plus a 2-digit counter.</xs:documentation></xs:annotation>
+ <xs:annotation>
+ <xs:documentation>
+ A version number for the file format. It always consists of 4-digit year plus a 2-digit counter.
+ </xs:documentation>
+ </xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="20[0-9]{4}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="SizeType">
- <xs:annotation><xs:documentation>Two unsigned integers noting Width and Height.</xs:documentation></xs:annotation>
+ <xs:annotation>
+ <xs:documentation>
+ Two unsigned integers noting Width and Height.
+ </xs:documentation>
+ </xs:annotation>
<xs:restriction base="UnsignedList">
<xs:length value="2"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="RectType">
- <xs:annotation><xs:documentation>Two unsigned integers noting top left X,Y and Width,Height.</xs:documentation></xs:annotation>
+ <xs:annotation>
+ <xs:documentation>
+ Two unsigned integers noting top left X,Y and Width,Height.
+ </xs:documentation>
+ </xs:annotation>
<xs:restriction base="UnsignedList">
<xs:length value="4"/>
</xs:restriction>
--- /dev/null
+#include"eventsaleinfo.h"
--- /dev/null
+#include"seatplanobj.h"
--- /dev/null
+#include"seatplanobj.h"
--- /dev/null
+#include"seatplanobj.h"
--- /dev/null
+//
+// C++ Implementation: MOOrder
+//
+// Description:
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2009-2011
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#include "eventsaleinfo.h"
+
+#include "MOSeatPlan"
+#include "MOSeatPlanGroup"
+#include "MOSeatPlanVGroup"
+
+#include <QVariant>
+
+static int mymeta=
+ qRegisterMetaType<MOEventSaleInfo>()+
+ qRegisterMetaType<QList<MOEventSaleInfo> >()+
+ qRegisterMetaType<Nullable<MOEventSaleInfo> >();
+static bool mocv=
+ QMetaType::registerConverter<Nullable<MOEventSaleInfo>,MOEventSaleInfo>([](const Nullable<MOEventSaleInfo>&n){return n.value();})|
+ QMetaType::registerConverter<QList<MOEventSaleInfo>,QVariantList>([](const QList<MOEventSaleInfo>&n){QVariantList r;for(auto v:n)r<<QVariant::fromValue(v);return r;});
+
+MOSeatPlan MOEventSaleInfo::seatplan()const
+{
+ return MOSeatPlan::fromString(seatplaninfo().value().plan());
+}
+
+MOSeatPlan MOEventSaleInfo::seatplanAdjusted()const
+{
+ MOSeatPlan plan=MOSeatPlan::fromString(seatplaninfo().value().plan());
+ QList<MOSeatPlanGroup>grp;
+ for(auto g:plan.Group()){
+ g.adjust(*this);
+ grp.append(g);
+ }
+ plan.setGroup(grp);
+ QList<MOSeatPlanVGroup>vgrp;
+ for(auto g:plan.VGroup()){
+ g.adjust(*this);
+ vgrp.append(g);
+ }
+ plan.setVGroup(vgrp);
+ return plan;
+}
--- /dev/null
+//
+// C++ Interface: unabstract
+//
+// Description: removes abstract flag from classes that only need to be abstract in PHP
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (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<MOEventSaleInfo>)
+Q_DECLARE_METATYPE(Nullable<MOEventSaleInfo>)
+
+#endif
--- /dev/null
+//
+// C++ Implementation: MOOrder
+//
+// Description:
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2009-2011
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#include "seatplanobj.h"
+
+#include "MOEventSaleInfo"
+#include "MOEventPrice"
+
+#include <QVariant>
+
+#include <limits>
+
+static int mymeta=
+ qRegisterMetaType<MOSeatPlanDefPrice>()+
+ qRegisterMetaType<QList<MOSeatPlanDefPrice> >()+
+ qRegisterMetaType<Nullable<MOSeatPlanDefPrice> >()+
+ qRegisterMetaType<MOSeatPlanGroup>()+
+ qRegisterMetaType<QList<MOSeatPlanGroup> >()+
+ qRegisterMetaType<Nullable<MOSeatPlanGroup> >()+
+ qRegisterMetaType<MOSeatPlanVGroup>()+
+ qRegisterMetaType<QList<MOSeatPlanVGroup> >()+
+ qRegisterMetaType<Nullable<MOSeatPlanVGroup> >();
+static bool mocv=
+ QMetaType::registerConverter<Nullable<MOSeatPlanDefPrice>,MOSeatPlanDefPrice>([](const Nullable<MOSeatPlanDefPrice>&n){return n.value();})|
+ QMetaType::registerConverter<QList<MOSeatPlanDefPrice>,QVariantList>([](const QList<MOSeatPlanDefPrice>&n){QVariantList r;for(auto v:n)r<<QVariant::fromValue(v);return r;})|
+ QMetaType::registerConverter<Nullable<MOSeatPlanGroup>,MOSeatPlanGroup>([](const Nullable<MOSeatPlanGroup>&n){return n.value();})|
+ QMetaType::registerConverter<QList<MOSeatPlanGroup>,QVariantList>([](const QList<MOSeatPlanGroup>&n){QVariantList r;for(auto v:n)r<<QVariant::fromValue(v);return r;})|
+ QMetaType::registerConverter<Nullable<MOSeatPlanVGroup>,MOSeatPlanVGroup>([](const Nullable<MOSeatPlanVGroup>&n){return n.value();})|
+ QMetaType::registerConverter<QList<MOSeatPlanVGroup>,QVariantList>([](const QList<MOSeatPlanVGroup>&n){QVariantList r;for(auto v:n)r<<QVariant::fromValue(v);return r;});
+
+
+static inline
+QList<MOEventPrice> getcategories(const MOEventSaleInfo&ei,QString spec)
+{
+ const QStringList specl=spec.split(' ',QString::SkipEmptyParts);
+ QList<MOEventPrice>prc;
+ QList<int>ids;
+ QStringList abbrs;
+ for(QString sp:specl){
+ bool isint=false;
+ const int id=sp.toInt(&isint);
+ if(isint)ids<<id;
+ else abbrs<<sp;
+ }
+ for(const MOEventPrice&ep:ei.price())
+ if(ids.contains(ep.pricecategoryid())||abbrs.contains(ep.pricecategory().value().abbreviation()))
+ prc.append(ep);
+ return prc;
+}
+
+void MOSeatPlanVGroup::adjust(const MOEventSaleInfo&ei)
+{
+ //retrieve categories
+ int max=-1,min=std::numeric_limits<int>::max(),sum=0;
+ QList<MOEventPrice>cats=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()<min)min=c.maxavailable();
+ sum+=c.maxavailable();
+ mcategoryids<<c.pricecategoryid();
+ mcategoryabbr<<c.pricecategory().value().abbreviation();
+ }
+ //adjust capacity
+ bool isint=false;
+ const QString cap=capacity().value();
+ mcapacity=cap.toInt(&isint);
+ if(!isint){
+ if(cap=="maxcat")mcapacity=max;
+ else if(cap=="mincat")mcapacity=min;
+ else if(cap=="sumcat")mcapacity=sum;
+ //ignore auto and other values
+ }
+ //count tickets
+}
+
+void MOSeatPlanGroup::adjust(const MOEventSaleInfo&ei)
+{
+}
+
+static inline
+QRect str2rect(QString s)
+{
+ QList<int>il;
+ for(QString c:s.split(' ',QString::SkipEmptyParts))
+ il<<c.toInt();
+ if(il.size()!=4)return QRect();
+ return QRect(il[0],il[1],il[2],il[3]);
+}
+
+QRect MOSeatPlanGroup::geometry() const
+{
+ return str2rect(geo());
+}
+
--- /dev/null
+//
+// C++ Interface: unabstract
+//
+// Description: removes abstract flag from classes that only need to be abstract in PHP
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (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 <QRect>
+
+#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<MOSeatPlanDefPrice>)
+Q_DECLARE_METATYPE(Nullable<MOSeatPlanDefPrice>)
+
+
+/**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<int> 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<int> 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<int> mcategoryids;
+ QStringList mcategoryabbr;
+ int mreserve=0,mordered=0,mused=0,mcancelled=0,mblocked=0,mcapacity=0;
+};
+
+Q_DECLARE_METATYPE(MOSeatPlanGroup)
+Q_DECLARE_METATYPE(QList<MOSeatPlanGroup>)
+Q_DECLARE_METATYPE(Nullable<MOSeatPlanGroup>)
+
+
+
+/**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<int> 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<int> 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<int> mcategoryids;
+ QStringList mcategoryabbr;
+ int mreserve=0,mordered=0,mused=0,mcancelled=0,mblocked=0,mcapacity=0;
+};
+
+Q_DECLARE_METATYPE(MOSeatPlanVGroup)
+Q_DECLARE_METATYPE(QList<MOSeatPlanVGroup>)
+Q_DECLARE_METATYPE(Nullable<MOSeatPlanVGroup>)
+
+#endif
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 \
wext/address.cpp \
wext/event.cpp \
wext/customer.cpp \
- wext/transaction.cpp
+ wext/transaction.cpp \
+ wext/seatplanobj.cpp \
+ wext/eventsaleinfo.cpp
-Subproject commit 2802df28960dcde2a86a78a154cdcb6590152856
+Subproject commit c214783152ecdbf1cf2b9715ab3328ec81716b2e
#include "carttab.h"
#include "MTGetAllShipping"
+#include "MTGetCoupon"
#include "MTGetEvent"
#include "MTGetValidVoucherPrices"
+#include "MTGetVoucher"
#include "MTCreateOrder"
#include "MTCreateReservation"
+#include "MOCoupon"
+#include "MOCouponRule"
+
#include <QApplication>
#include <QBoxLayout>
#include <QComboBox>
#include <QDebug>
+#include <QFormLayout>
#include <QFrame>
+#include <QGridLayout>
+#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QMenuBar>
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);
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()<<tr("Amount")<<tr("Title")<<tr("Start Time")<<tr("Price"));
+ cartmodel->setHorizontalHeaderLabels(QStringList()<<tr("Amount")<<tr("Title")<<tr("Start Time")<<tr("Price")<<tr("Notes"));
//clear customer
customer=MOCustomer();
cartcustomer->setText("");
cartship->setCurrentIndex(0);
//reset colors
resetColor();
+ //reset coupon and voucher labels
+ couponinfo=MOCoupon();
+ vouchersforpay.clear();
//update price display
updatePrice();
}
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()
{
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()+")";
void MCartTab::cartAddVoucher()
{
+ //check privileges
+ if(!req->hasRight(MInterface::PCreateOrder_CanPayVoucherWithVoucher)){
+ bool vfound=false;
+ for(int r=0;r<cartmodel->rowCount();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"));
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()){
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;
void MCartTab::verifyOrderTickets(const QList<MOCartTicket>&ticks)
{
+ //reset model
+ for(int i=0;i<cartmodel->rowCount();i++){
+ if(cartmodel->data(cartmodel->index(i,0),CART_TYPEROLE).toInt() != CART_TICKET)continue;
+ for(int j=0;j<cartmodel->columnCount();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<ticks.size();i++){
if(ticks[i].status()!=MOCartTicket::Ok){
//find it
int j=ticks[i].cartlineid();
if(j<0 || j>=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
}
void MCartTab::verifyOrderVouchers(const QList<MOCartVoucher>&voucs)
{
+ //reset model
+ for(int i=0;i<cartmodel->rowCount();i++){
+ if(cartmodel->data(cartmodel->index(i,0),CART_TYPEROLE).toInt() != CART_VOUCHER)continue;
+ for(int j=0;j<cartmodel->columnCount();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.size();i++){
if(voucs[i].status()!=MOCartVoucher::Ok){
//find it
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
}
}
//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(" <a href=\"vadd:coupon\">%1</a>").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;r<cartmodel->rowCount();r++)
+ if(cartmodel->data(cartmodel->index(r,0),CART_TYPEROLE).toInt()==CART_VOUCHER){
+ vfound=true;
+ break;
+ }
+ if(vfound)
+ vt+="<font color=\"red\">"+tr("<b>Warning:</b> Paying for vouchers with older vouchers!")+"</font><br/>";
+ //render vouchers for payment
+ vt+="<html><table>";
+ for(auto vou:vouchersforpay){
+ vt+=QString("<tr><td>%1 </td><td> - %2</td><td> <a href=\"vremove:%1\">%3</a></td></tr>")
+ .arg(vou.first).arg(cent2str(vou.second)).arg(tr("[remove]","remove voucher link/label"));
+ prc-=vou.second;
+ }
+ vt+="</table><br/>";
+ }
+ vt+="<a href=\"vadd:voucher\">"+tr("[Add Voucher]","link/label")+"</a>";
+ vouchers->setText(vt);
+ //format remaining price
+ if(prc<0)prc=0;
+ priceremain->setText(cent2str(prc));
+}
+
+void MCartTab::changeVoucher ( QString url )
+{
+ qDebug()<<url;
+ if(url=="vadd:coupon"){
+ //sanity check
+ if(!couponinfo.couponid().isNull()){
+ QMessageBox::warning(this,tr("Warning"),tr("Only one coupon can be used on an order."));
+ return;
+ }
+ //ask for ID
+ const QString cid=QInputDialog::getText(this,tr("Enter Coupon ID"),tr("Please enter the coupon ID:")).trimmed().toUpper();
+ if(cid.isEmpty())return;
+ //get coupon from server
+ MOCoupon cp=req->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;r<cartmodel->rowCount();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<QString,int>(vid,vou.getvoucher().data().value()));
+ }else if(url.startsWith("vremove:")){
+ const QString vid=url.mid(8);
+ for(int i=0;i<vouchersforpay.size();i++)
+ if(vouchersforpay[i].first==vid){
+ vouchersforpay.removeAt(i);
+ break;
+ }
+ }
+ //TODO: if coupon removal is implemented - change applyCoupon accordingly
+ //update the listing
+ updatePrice();
+}
+
+void MCartTab::applyCoupon()
+{
+ //TODO:
+ // - implement maxtickets (coupon) and maxuse (rule) limits
+ // - implement need* limits (rule)
+ // -> 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("<html><font color=\"red\">Sorry, coupon limits are not implemented yet.</font>"));
+ return;
+ }
+ for(auto rule:couponinfo.rules())
+ if(!rule.maxuse().isNull() || !rule.needcategory().isNull() || !rule.needtickets().isNull()){
+ coupon->setText(tr("<html><font color=\"red\">Sorry, coupon rule limits are not implemented yet.</font>"));
+ return;
+ }
+
+ //go through order and reset tickets
+ //TODO: count and accumulate
+ for(int i=0;i<cartmodel->rowCount();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<MOEvent>();
+ //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;i<cartmodel->rowCount();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<MOEvent>();
+ 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()));
+ }
}
/********************************************************************************/
#include <QTimer>
#include "MOCustomer"
+#include "MOCoupon"
class QAction;
class QCheckBox;
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
//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<QPair<QString,int>> vouchersforpay;
/**helper for order: returns true if an order can be sent*/
bool canorder(bool isreserve);
/**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*/
-Subproject commit d2852c3d8f78d0ae39e2a0833e33c3551e89cea0
+Subproject commit d94492246f5b98e0936f5a184e9533496d240a63
<Property name="tickets" type="List:CartTicket"/>
<Property name="vouchers" type="List:CartVoucher"/>
<Property name="items" type="List:CartItem"/>
+ <Property name="coupon" type="astring">An optional coupon code to be used in this order.</Property>
<Mapping table="cart">
<Map property="customerid"/>
</Mapping>
</Class>
- <Class name="SeatPlanInfo">
- <Doc>This class is the transport for seat plans when communicating over the wire</Doc>
- <Property name="roomid" type="string"/>
- <Property name="name" type="astring"/>
- <Property name="plan" type="string"/>
- <Property name="flags" type="astring"/>
- </Class>
-
<Class name="Event">
<Abstract lang="qt"/>
<Abstract lang="php"/>
</Map>
</Mapping>
</Class>
-</Wolf>
\ No newline at end of file
+</Wolf>
<!-- Order MagicSmoke WOLF
- Tables and Comm Objects for Orders, Tickets, Vouchers, etc.
-
- - (c) Konrad Rosenbaum, 2009-2011
+ - (c) Konrad Rosenbaum, 2009-2016
- this file is protected under the GNU AGPLv3 or at your option any newer
- see COPYING.AGPL for details
-->
</Map>
</Mapping>
</Class>
-
+
<Class name="TicketAudit" base="Ticket">
<Abstract lang="php"/>
<Property name="audittime" type="int64"/>
<Property name="comments" type="string"/>
<Property name="tags" type="string"/>
+
+ <Property name="couponid" type="astring"/>
+ <Property name="coupondescription" type="string"/>
<!-- etc.pp. -->
<Mapping table="order">
<Map column="senttime"/>
<Map column="comments"/>
<Map column="tags"/>
+ <Map column="couponid"/>
<Map property="tickets"><Call lang="php" method="WOTicket::fromTableArrayticket(WTticket::selectFromDB('orderid='.$GLOBALS['db']->escapeInt($table->orderid)))"/></Map>
<Map property="vouchers"><Call lang="php" method="WOVoucher::fromTableArrayvoucher(WTvoucher::selectFromDB('orderid='.$GLOBALS['db']->escapeInt($table->orderid)))"/></Map>
<Map property="totalprice"><Call lang="php" method="$data->getTotalPrice()"/></Map>
<Map property="amountdue"><Call lang="php" method="$data->prop_totalprice - $data->prop_amountpaid"/></Map>
+ <Map property="coupondescription"><Call lang="php" method="$GLOBALS['db']->isNull($table->couponid)?null:WTcoupon::getFromDB($table->couponid)->description"/></Map>
</Mapping>
</Class>
<Property name="ordertime" type="int64"/>
<Property name="senttime" type="int64"/>
+
+ <Property name="couponid" type="astring"/>
<!-- etc.pp. -->
<Mapping table="order">
<Map column="orderid"/>
<Map property="amountitems">
<Call lang="php" method="$data->getAmountItemsFromDB()"/>
</Map>
+ <Map column="couponid"/>
</Mapping>
</Class>
<Map column="visible"/>
</Mapping>
</Class>
+
+ <Class name="CouponRule">
+ <Property name="ruleid" type="int64"/>
+ <Property name="couponid" type="astring"/>
+ <Property name="fromcategory" type="int"/>
+ <Property name="tocategory" type="int"/>
+ <Property name="maxuse" type="int"/>
+ <Property name="needcategory" type="int"/>
+ <Property name="needtickets" type="int"/>
+ <Mapping table="couponrule">
+ <Map column="ruleid"/>
+ <Map column="couponid"/>
+ <Map column="fromcategory"/>
+ <Map column="tocategory"/>
+ <Map column="maxuse"/>
+ <Map column="needcategory"/>
+ <Map column="needtickets"/>
+ </Mapping>
+ </Class>
+
+ <Class name="Coupon">
+ <Doc>This class represents a coupon.</Doc>
+ <Property name="couponid" type="astring"/>
+ <Property name="description" type="string"/>
+ <Property name="validfrom" type="int64"/>
+ <Property name="validtill" type="int64"/>
+ <Property name="amountuse" type="int32"/>
+ <Property name="ticketsperorder" type="int32"/>
+ <Property name="flags" type="string"/>
+ <Property name="rules" type="List:CouponRule"/>
+ <Mapping table="coupon">
+ <Map column="couponid"/>
+ <Map column="description"/>
+ <Map column="validfrom"/>
+ <Map column="validtill"/>
+ <Map column="amountuse"/>
+ <Map column="ticketsperorder"/>
+ <Map column="flags"/>
+ <Map property="rules"><Call lang="php" method="WOCouponRule::fromTableArraycouponrule(WTcouponrule::selectFromDB('couponid='.$GLOBALS['db']->escapeString($table->couponid)))"/></Map>
+ </Mapping>
+ </Class>
+ <Class name="CouponInfo">
+ <Doc>This class represents the bare minimum info of a coupon.</Doc>
+ <Property name="couponid" type="astring"/>
+ <Property name="description" type="string"/>
+ <Property name="validfrom" type="int64"/>
+ <Property name="validtill" type="int64"/>
+ <Property name="flags" type="string"/>
+ <Mapping table="coupon">
+ <Map column="couponid"/>
+ <Map column="description"/>
+ <Map column="validfrom"/>
+ <Map column="validtill"/>
+ <Map column="flags"/>
+ </Mapping>
+ </Class>
</Wolf>
<!-- Seat Plan Definition MagicSmoke WOLF
- declares everything needed to define seat plans
-
- - (c) Konrad Rosenbaum, 2013
+ - (c) Konrad Rosenbaum, 2013-16
- this file is protected under the GNU AGPLv3 or at your option any newer
- see COPYING.AGPL for details
-->
<Wolf>
+ <!-- NON-Wire Helper Classes -->
<Class name="SeatPlanRow">
+ <!-- Abstract lang="qt"/>
+ <Abstract lang="php"/ -->
<Doc>This is a helper for parsing seat plans: it defines a row of seats</Doc>
<Property name="id" type="astring">the ID of this group, if multiple row elements with the same ID exist, they refer to different sections of the same row</Property>
<Property name="capacity" type="int">amount of seats in this (part of the) row</Property>
<Property name="first" type="int">number of the first seat in this row</Property>
</Class>
<Class name="SeatPlanGroup">
+ <Abstract lang="qt"/>
+ <Abstract lang="php"/>
<Doc>This is a helper for parsing seat plans: it defines a group of rows of seats.</Doc>
<Property name="id" type="astring">the ID of this group, if multiple group elements with the same ID exist, they refer to different sections of the same group</Property>
- <Property name="capacity" type="int">amount of seats in this (part of the) group</Property>
+ <Property name="capacity" type="astring">amount of seats in this (part of the) group</Property>
<Property name="name" type="astring">human readable name of the group</Property>
<Property name="numbered" type="bool">defines whether the group contains numbered seats</Property>
<Property name="geo" type="astring">GUI: geometry information for the group</Property>
<Property name="fgcolor" type="astring">GUI: foreground color, the one the group name is rendered in</Property>
<Property name="angle" type="int">GUI: rotation of the group rectangle</Property>
<Property name="Row" type="List:SeatPlanRow">definition of rows in this group</Property>
+ <Property name="price" type="astring">The price categories in this group.</Property>
</Class>
-
+
+ <Class name="SeatPlanVGroup">
+ <Abstract lang="qt"/>
+ <Abstract lang="php"/>
+ <Doc>This is a helper for parsing seat plans: it defines a virtual group of categories.</Doc>
+ <Property name="capacity" type="astring">amount of seats in this (part of the) group</Property>
+ <Property name="name" type="astring">human readable name of the group</Property>
+ <Property name="price" type="astring">definition of categories in this group</Property>
+ </Class>
+
<Class name="SeatPlanBackground">
+ <!-- Abstract lang="qt"/>
+ <Abstract lang="php"/ -->
+ <Doc>Backgroup objects for seatplan graphics.</Doc>
<Property name="type" type="astring">Type of background element, e.g. "circle", "rect", ...</Property>
<Property name="geo" type="astring">GUI: geometry information for the group</Property>
<Property name="bgcolor" type="astring">GUI: background or fill color</Property>
<Property name="fontsize" type="int">GUI: font size for text elements</Property>
<Property name="content" type="astring">GUI: textual content</Property>
</Class>
-
+
+ <Class name="SeatPlanDefPrice">
+ <Abstract lang="qt"/>
+ <Abstract lang="php"/>
+ <Doc>Default price categories for a seat plan.</Doc>
+ <Property name="capacity" type="astring">amount of seats in this (part of the) group</Property>
+ <Property name="price" type="astring">definition of categories in this group</Property>
+ <Property name="cost" type="astring">default cost definition</Property>
+ </Class>
+
+ <Class name="SeatPlanImage">
+ <Doc>Image data that can be embedded in seat plan backgrounds</Doc>
+ <Property name="name" type="astring"/>
+ <Property name="Data" type="blob"/>
+ </Class>
+
<Class name="SeatPlan">
- <Doc>This class is used to parse seat plans on either side.</Doc>
+ <Doc>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.</Doc>
+ <Property name="version" type="int"/>
<Property name="Group" type="List:SeatPlanGroup"/>
+ <Property name="VGroup" type="List:SeatPlanVGroup"/>
<Property name="Background" type="List:SeatPlanBackground"/>
+ <Property name="Image" type="List:SeatPlanImage"/>
+ <Property name="DefPrice" type="List:SeatPlanDefPrice"/>
+ </Class>
+
+
+ <!-- Wire Classes -->
+ <Class name="SeatPlanInfo">
+ <Doc>This class is the transport for seat plans when communicating over the wire</Doc>
+ <Property name="seatplanid" type="int"/>
+ <Property name="roomid" type="string"/>
+ <Property name="name" type="astring"/>
+ <Property name="plan" type="string"/>
+ <Property name="flags" type="astring"/>
+ <Mapping table="seatplan">
+ <Map column="seatplanid"/>
+ <Map column="roomid"/>
+ <Map column="name"/>
+ <Map column="plan"/>
+ <Map column="flags"/>
+ </Mapping>
</Class>
+
+ <Class name="TicketSaleInfo">
+ <Doc>This class represents only the info from a ticket that is required for calculating seating information.</Doc>
+ <Enum name="TicketState" refColumn="ticket:status"/>
+ <Property name="eventid" type="int"/>
+ <Property name="price" type="int"/>
+ <Property name="status" type="TicketState"/>
+ <Property name="pricecategoryid" type="int"/>
+ <Property name="tags" type="string"/>
+ <Property name="seat" type="astring"/>
+
+ <Mapping table="ticket">
+ <Map column="price"/>
+ <Map column="eventid"/>
+ <Map column="status"/>
+ <Map column="seat"/>
+ <Map column="pricecategoryid"/>
+ </Mapping>
+ </Class>
+
+ <Class name="EventSaleInfo">
+ <Abstract lang="qt"/>
+ <Doc>This class transports all data necessary to calculate the free seats of an event</Doc>
+ <Property name="eventid" type="int"/>
+ <Property name="iscancelled" type="bool"/>
+ <Property name="capacity" type="int"/>
+ <Property name="room" type="string"/>
+ <Property name="price" type="List:EventPrice"/>
+ <Property name="seatplanid" type="int"/>
+ <Property name="seatplaninfo" type="SeatPlanInfo"/>
+ <Property name="ticketsales" type="List:TicketSaleInfo"/>
+ <Property name="canuse" type="bool"/>
+ <Mapping table="event">
+ <Map column="eventid" property="id"/>
+ <Map column="roomid" property="room"/>
+ <Map column="seatplanid"/>
+ <Map column="capacity"/>
+ <Map column="iscancelled"/>
+ <Map property="price">
+ <Call lang="php" method="WOEventPrice::fromTableArrayeventprice(WTeventprice::selectFromDB('eventid='.$GLOBALS['db']->escapeInt($table->eventid),'ORDER BY prio'))"/>
+ </Map>
+ <Map property="seatplaninfo">
+ <Call lang="php" method="WOSeatPlanInfo::fromTableseatplan(WTseatplan::getFromDB($table->seatplanid))"/>
+ </Map>
+ <Map property="ticketsales">
+ <Call lang="php" method="WOTicketSalesInfo::fromTableArrayticket(WTticket::selectFromDB('eventid='.$GLOBALS['db']->escapeInt($table->eventid)));"/>
+ </Map>
+ <Map property="canuse">
+ <Call lang="php" method="$GLOBALS['session']->checkFlags($data->getflags())"/>
+ </Map>
+ </Mapping>
+ </Class>
+
+
</Wolf>
<!-- Cart MagicSmoke WOLF
- shopping cart management (web interface)
-
- - (c) Konrad Rosenbaum, 2009-2012
+ - (c) Konrad Rosenbaum, 2009-2016
- this file is protected under the GNU AGPLv3 or at your option any newer
- see COPYING.AGPL for details
-->
<!--unix timestamp at which to delete this session-->
<Column name="timeout" type="int64" notnull="yes">the time when the session will be deleted, updated whenever the session is used</Column>
</Table>
-</Wolf>
\ No newline at end of file
+</Wolf>
<DataBase
instance="db" schema="dbScheme" defaultUpdating="yes"
configTable="config" configKeyColumn="ckey" configValueColumn="cval"
- version="01.08" versionRow="MagicSmokeVersion">
+ version="01.09" versionRow="MagicSmokeVersion">
<AuditTables>
<Column name="audittime" type="int64">Time at which the change was made.
<Call lang="php" method="time()"/>
<Preset><V col="paytype" val="credit card"/><V col="description" val="credit card payment"/><V col="dataname" val="card data"/><V col="datapreset" val="date: %Y-%M-%D, card: ..."/></Preset>
<Preset><V col="paytype" val="voucher"/><V col="description" val="payment with voucher"/><V col="dataname" val="voucher ID"/><V col="flags" val="+_admin"/></Preset>
</Table>
+ <Table name="coupon" backup="yes">
+ <Doc>This table contains coupon definitions</Doc>
+ <Column name="couponid" type="string:32" primarykey="yes"/>
+ <Column name="description" type="string">short description of the coupon for internal use</Column>
+ <Column name="validfrom" type="int64" notnull="yes">Validity period of the coupon.</Column>
+ <Column name="validtill" type="int64" notnull="yes"/>
+ <Column name="amountuse" type="int32" null="yes">How often it can be used, if NULL it is unlimited.</Column>
+ <Column name="ticketsperorder" type="int32" null="yes">How many tickets per order can be changed, NULL for unlimited.</Column>
+ <Column name="flags" type="string">Filter: who can use this coupon.</Column>
+ </Table>
+ <Table name="couponrule" backup="yes">
+ <Doc>This table contains rules of what category of ticket to exchange to what new category by use of a coupon.</Doc>
+ <Column name="ruleid" type="seq64" primarykey="yes">Simple counter key.</Column>
+ <Column name="couponid" type="string:32" foreignkey="coupon:couponid" notnull="yes"/>
+ <Column name="fromcategory" type="int32" foreignkey="pricecategory:pricecategoryid" notnull="yes">from what category of ticket...</Column>
+ <Column name="tocategory" type="int32" foreignkey="pricecategory:pricecategoryid" notnull="yes">..to change to what category of ticket.</Column>
+ <Column name="maxuse" type="int32" null="yes">how often this rule can be used in one order or NULL for unlimited</Column>
+ <Column name="needcategory" type="int32" foreignkey="pricecategory:pricecategoryid" null="yes">
+ after the change tickets of this category must exist or NULL if there is no requirement
+ </Column>
+ <Column name="needtickets" type="int32" null="yes">
+ 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
+ </Column>
+ </Table>
<Table name="order" backup="yes" audit="yes" backupKey="orderid">
<Doc>This table contains all orders and sales.</Doc>
<Column name="orderid" type="seq32" primarykey="yes"/>
<Column name="tags" type="string" null="yes">
additional tags for this order, can be used by scripts
</Column>
+ <Column name="couponid" type="string:32" null="yes" foreignkey="coupon:couponid">Optional coupon code used on this order.</Column>
<!-- in audit: monitor the type of payment -->
<AuditColumn name="paytype" type="string:64" foreignkey="paymenttype:paytype"/>
<Var name="events" type="List:Event"/>
</Output>
</Transaction>
-
+
+ <Transaction name="GetEventSaleInfo" updating="no">
+ <Doc>returns the information necessary to handle sales with seat plans</Doc>
+ <Input>
+ <Var name="eventid" type="int"/>
+ </Input>
+ <Call lang="php" method="$this->setevent(WOEventSaleInfo::fromTableevent(WTevent::getFromDB($this->geteventid())));"/>
+ <Output>
+ <Var name="event" type="EventSaleInfo"/>
+ </Output>
+ </Transaction>
+
<Transaction name="CreateEvent">
<Doc>create a new event</Doc>
<Input>
<Var name="orders" type="List:Order"/>
</Output>
</Transaction>
-</Wolf>
\ No newline at end of file
+</Wolf>
<!-- Order MagicSmoke WOLF
- Tables and Comm Objects for Orders, Tickets, Vouchers, etc.
-
- - (c) Konrad Rosenbaum, 2009-2012
+ - (c) Konrad Rosenbaum, 2009-2016
- this file is protected under the GNU AGPLv3 or at your option any newer
- see COPYING.AGPL for details
-->
<Input>
<Var name="voucherid" type="astring"/>
</Input>
- <Call lang="php" method="$this->setvoucher(WOVoucher::fromTablevoucher(WTvoucher::getFromDB($this->getvoucherid())));"/>
+ <Call lang="php" method="$v=WOVoucher::fromTablevoucher(WTvoucher::getFromDB($this->getvoucherid())); $this->setvoucher($v); $this->setispaidfor(WOOrder::fromTableorder(WTorder::getFromDB($v->getorderid()))->isFullyPaid());"/>
<Output>
<Var name="voucher" type="Voucher"/>
+ <Var name="ispaidfor" type="bool"/>
</Output>
</Transaction>
<Var name="order" type="Order"/>
</Output>
</Transaction>
-
+
+ <Transaction name="GetOrdersByCoupon" updating="no">
+ <Input>
+ <Var name="oldest" type="int64">unix timestamp for the oldest order to be returned (compared with ordertime)</Var>
+ <Var name="couponid" type="astring"/>
+ </Input>
+ <Call lang="php" method="WOOrderInfo::GetOrdersByCoupon($this);"/>
+ <Output>
+ <Var name="orders" type="List:OrderInfo"/>
+ <Var name="customers" type="List:CustomerInfo"/>
+ </Output>
+ </Transaction>
+
<Transaction name="CreateOrder">
<Doc>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</Doc>
<Privilege name="AnyVoucherValue">users with this privilege can create vouchers with arbitrary value</Privilege>
<Privilege name="CanOrderTicket">users with this privilege may put tickets into orders</Privilege>
<Privilege name="CanOrderVoucher">users with this privilege may put vouchers into orders</Privilege>
<Privilege name="CanOrderItem">users with this privilege may put shop items (merchandise) into orders</Privilege>
+ <Privilege name="CanPayVoucherWithVoucher">users with this privilege may pay for vouchers with other vouchers</Privilege>
<Input>
<Var name="cart" type="CartOrder">The cart contents</Var>
<Var name="issale" type="bool">true if this is a sale, false if this is an order</Var>
+ <Var name="vouchers" type="List:string">an optional list of vouchers to be used in this order for payment (coupon is in the cart instead)</Var>
</Input>
<Call lang="php" method="WOCartOrder::createOrder($this);"/>
<Output>
<Var name="order" type="Order">If successful: the created order</Var>
+ <Var name="vouchers" type="List:Voucher">If vouchers were used: the current state of the vouchers after creating (and paying) the order</Var>
+ <Var name="paidcash" type="int">If this is a sale: the amount paid in cash (in cents)</Var>
<Var name="cart" type="CartOrder">If unsuccessful: the cart with annotations</Var>
</Output>
</Transaction>
The transaction will never transfer money from an order back to the voucher.
If the voucher is no longer valid the transaction will fail.
</Doc>
+ <Privilege name="CanPayVoucherWithVoucher">users with this privilege may pay for vouchers with other vouchers</Privilege>
<Input>
<Var name="orderid" type="int">ID of the order that is to be paid for</Var>
<Var name="voucherid" type="astring">ID of the voucher that is to be used for payment</Var>
</Output>
</Transaction>
- <Transaction name="GetPaymentTypes" update="no">
+ <Transaction name="GetPaymentTypes" updating="no">
<Doc>returns the valid payment types</Doc>
<Call lang="php" method="WOOrder::getPaymentTypesTransaction($this);"/>
<Output>
<Output/>
</Transaction>
- <Transaction name="GetOrderDocumentNames" update="no">
+ <Transaction name="GetOrderDocumentNames" updating="no">
<Doc>Gets the names of all files associated with the Order</Doc>
<Input>
<Var name="orderid" type="int"/>
<Var name="documentinfo" type="List:OrderDocumentInfo"/>
</Output>
</Transaction>
- <Transaction name="GetOrderDocument" update="no">
+ <Transaction name="GetOrderDocument" updating="no">
<Doc>Gets a document associated with an order.</Doc>
<Input>
<Var name="orderid" type="int"/>
<Var name="document" type="OrderDocument"/>
</Output>
</Transaction>
- <Transaction name="SetOrderDocument" update="yes">
+ <Transaction name="SetOrderDocument" updating="yes">
<Doc>Stores a document associated with an order. Overwrites the document if it already exists, creates it otherwise.</Doc>
<Input>
<Var name="document" type="OrderDocument">
<Call lang="php" method="WOOrder::setOrderDocument($this);"/>
<Output/>
</Transaction>
- <Transaction name="DeleteOrderDocument" update="yes">
+ <Transaction name="DeleteOrderDocument" updating="yes">
<Doc>Deletes a document associated with an order. Produces an error if the document does not exist.</Doc>
<Input>
<Var name="orderid" type="int"/>
<Output/>
</Transaction>
- <Transaction name="SendCustomerMail" update="no">
+ <Transaction name="SendCustomerMail" updating="no">
<Doc>Sends an eMail to a customer. Usually to update the customer about Order data.</Doc>
<Input>
<Var name="customerid" type="int">ID of the customer. Must be given! The mail address associated with the account is used.</Var>
<Output/>
</Transaction>
- <Transaction name="GetPrintAtHomeSettings" update="no">
+ <Transaction name="GetPrintAtHomeSettings" updating="no">
<Doc>Gets all settings pertaining to Print@Home</Doc>
<Input/>
<Call lang="php" method="WOOrder::getPrintAtHomeSettings($this);"/>
<Var name="settings" type="List:KeyValuePair"/>
</Output>
</Transaction>
- <Transaction name="SetPrintAtHomeSettings" update="yes">
+ <Transaction name="SetPrintAtHomeSettings" updating="yes">
<Doc>Overrides settings for Print@Home, use isnull=true to delete entries.</Doc>
<Input>
<Var name="settings" type="List:KeyValuePair"/>
<Call lang="php" method="WOOrder::setPrintAtHomeSettings($this);"/>
<Output/>
</Transaction>
+
+ <Transaction name="GetCoupon" updating="no">
+ <Doc>Retrieves details about a single coupon.</Doc>
+ <Input>
+ <Var name="couponid" type="astring"/>
+ </Input>
+ <Call lang="php" method="$this->setcoupon(WOCoupon::fromTablecoupon(WTcoupon::getFromDB($this->getcouponid())));" />
+ <Output>
+ <Var name="coupon" type="Coupon"/>
+ </Output>
+ </Transaction>
+ <Transaction name="GetCouponList" updating="no">
+ <Doc>Retrieves all Coupon IDs</Doc>
+ <Input>
+ <Var name="couponid" type="astring"/>
+ </Input>
+ <Call lang="php" method="$this->setcoupons(WOCoupon::fromTableArraycoupon(WTcoupon::selectFromDB('')));" />
+ <Output>
+ <Var name="coupons" type="List:CouponInfo"/>
+ </Output>
+ </Transaction>
+ <Transaction name="CreateCoupon" updating="yes">
+ <Doc>Creates a new coupon.</Doc>
+ <Input>
+ <Var name="coupon" type="Coupon">Input data for the coupon, if the ID is empty or null a new one is created</Var>
+ </Input>
+ <Call lang="php" method="WOCoupon::createCoupon($this);"/>
+ <Output>
+ <Var name="coupon" type="Coupon">actually stored coupon: the ID may be slightly changed by the server</Var>
+ </Output>
+ </Transaction>
+ <Transaction name="ChangeCoupon" updating="yes">
+ <Doc>Changes a coupon.</Doc>
+ <Input>
+ <Var name="coupon" type="Coupon"/>
+ </Input>
+ <Call lang="php" method="WOCoupon::changeCoupon($this);"/>
+ <Output/>
+ </Transaction>
</Wolf>
//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
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;
?>
{
private static $NumTicketChars=false;
private static $NumVoucherChars=false;
+ private static $NumCouponChars=false;
private static function init()
{
global $db;
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()
{
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
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){
$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)
{
//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)
$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;