From a29894e18e158eec76eb88365fd8bebdff774d69 Mon Sep 17 00:00:00 2001 From: konrad Date: Thu, 20 Nov 2008 18:43:07 +0000 Subject: [PATCH] implemented: *vouchers (ordering, using, cancelling, etc.pp.) *shipping costs (creating, deleting, using) git-svn-id: https://silmor.de/svn/softmagic/smoke/trunk@196 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33 --- doc/index.html | 2 +- doc/prog_protocol.html | 70 +++++++- doc/prog_tickettemplate.html | 46 ++++-- examples/voucher.xtt | Bin 0 -> 382 bytes src/eventsummary.cpp | 20 +-- src/misc.cpp | 49 +++++ src/misc.h | 31 ++++ src/order.cpp | 207 ++++++++++++++++++++-- src/order.h | 96 +++++++++- src/orderwin.cpp | 410 +++++++++++++++++++++++++++++++---------- src/orderwin.h | 68 ++++++-- src/overview.cpp | 218 +++++++++++++++++++---- src/overview.h | 12 +- src/shipping.cpp | 291 ++++++++++++++++++++++++++++++ src/shipping.h | 104 +++++++++++ src/smoke.pro | 10 +- src/ticketrender.cpp | 150 ++++++++++------ src/ticketrender.h | 49 ++++- src/version.cpp | 17 ++ src/version.h | 4 + src/webrequest.cpp | 47 +++++- src/webrequest.h | 23 ++- www/inc/classes/order.php | 394 +++++++++++++++++++++++++++++++++++++--- www/inc/classes/random.php | 18 ++- www/inc/classes/ticket.php | 2 +- www/inc/classes/voucher.php | 306 +++++++++++++++++++++++++++++++ www/inc/db/db.php | 10 + www/inc/db/db_scheme.php | 38 +++- www/inc/loader.php | 2 - www/inc/loader_nonadmin.php | 1 + www/inc/machine/version.inc | 27 +++ www/inc/machine/version.php | 32 ++++ www/machine.php | 61 ++++++- 33 files changed, 2478 insertions(+), 337 deletions(-) create mode 100644 examples/voucher.xtt create mode 100644 src/misc.cpp create mode 100644 src/misc.h create mode 100644 src/shipping.cpp create mode 100644 src/shipping.h create mode 100644 src/version.cpp create mode 100644 src/version.h create mode 100644 www/inc/classes/voucher.php create mode 100644 www/inc/machine/version.inc create mode 100644 www/inc/machine/version.php diff --git a/doc/index.html b/doc/index.html index 41ae20a..22d0126 100644 --- a/doc/index.html +++ b/doc/index.html @@ -22,7 +22,7 @@ to be written:
  • Templates:
  • Building the Program
  • Installing the Program
  • diff --git a/doc/prog_protocol.html b/doc/prog_protocol.html index 3af00be..f0cfe90 100644 --- a/doc/prog_protocol.html +++ b/doc/prog_protocol.html @@ -455,7 +455,7 @@ The order XML representation looks as follows: <Order id="orderid" customer="customerid" seller="sellerid" ordertime="timestamp" senttime="timestamp" totalprice="amountInCent" paid="amountInCent" status="orderstate"> <Ticket event="eventid" id="ticketid" price="priceInCent" status="ticketstate" /> - <Voucher id="voucherid" price="priceInCent" value="remainingValueInCent" /> + <Voucher id="voucherid" price="priceInCent" value="remainingValueInCent" used="0|1" status=""/> <Shipping price="priceInCent" type="shippingtypeID">Shipping Comment</Shipping> <DeliveryAddress>deliver address</DeliveryAddress> <Comment>comment...</Comment> @@ -485,6 +485,8 @@ The order XML representation looks as follows:   idID of the voucher1   pricePrice of the voucher (adds to price of the order)1   valueValue (in cent) of the voucher (does not add to the order, but refers to how much the voucher is worth)1 +  statusonly in validation responses: result of the validation0-1 +  usedFlag whether the voucher has been used to pay for anything (assumed as 0 if not present)0-1 Shippinginformation about the kind of shipping used and how much it costs; if text is used it is a copy of the text from the shipping table information - it cannot be changed here0-1   pricePrice of the shipping option1 @@ -532,7 +534,7 @@ The request is an order object without most fields filled in, the response is a ElementAttributes in RequestAttributes in Response Ordercustomerid(1), customer, seller(3), ordertime(1,4), totalprice(5), paid(1), status(6), senttime(2,4) Ticketevent, price(7)event, id(1), price, status(6) -Vouchervalue, price(7)value, price, id(1) +Vouchervalue, price(7)value, price, id(1), status(11) Shippingtype(8), price(9)type(10), price, [text](10) DeliveryAddress[text][text] Comment[text][text] @@ -546,7 +548,8 @@ The request is an order object without most fields filled in, the response is a (7)the price field will only be honoured in the request, if the user also has the privilege to use the changeticketprice transaction
    (8)the shipping types that are allowed depend on whether the user has the _anyshipping privilege
    (9)price is optional, if set it is ignored if the user does not have the _repriceshipping privilege
    -(10)the type in the response may be -1 if an error occurred in this case the text contains the error message, otherwise it contains a copy of the shipping option text

    +(10)the type in the response may be -1 if an error occurred in this case the text contains the error message, otherwise it contains a copy of the shipping option text
    +(11)in validation responses if the voucher contains a non-empty state it is not deemed valid by the server, the state must be one of the status constants below

    Order status for checks: @@ -573,6 +576,14 @@ Ticket status for checks: (1)Attention: the return code "ok" means the order would have succeeded at the time of the check, it may still fail when it is actually placed
    (2)if two or more tickets contradict on this state the order is returned as failed

    +Voucher status for checks: +

    + + + + +
    StateDescription
    [empty]the order can be executed as is
    invalidvaluethe user is not allowed to use this value for the voucher
    invalidpricefor this user the price must equal the value (or be ommitted)
    +

    Getting an Overview List of Orders

    The getorderlist transaction can be used to get a list of all currently stored orders. The request does not contain any data, the response looks like:

    @@ -596,7 +607,7 @@ The ordershipped transaction is used to mark an order as shipped. This

    Cancelling an Order

    -The cancelorder transaction is used to mark an order as cancelled. It also marks all tickets inside the order as cancelled and voids any vouchers in it. This transaction is only legal for orders in the "placed" state. +The cancelorder transaction is used to mark an order as cancelled. It also marks all tickets inside the order as cancelled and voids any vouchers in it. This transaction is only legal for orders in the "placed" state. It will fail if any tickets or vouchers have already been used.

    Searching for an Order

    @@ -626,19 +637,21 @@ If type is not present, the price is taken from the new shipping type. The response contains the updated order object or just an error message. -

    Creating/Changing Shipping Options

    +

    Creating/Changing/Deleting Shipping Options

    The setshipping creates or changes a shipping option. The request looks like:
    -<ShippingOption id="shippingOptionID" price="priceInCent" web="0|1" anyUser="0|1">
    +<ShippingOption type="shippingOptionID" price="priceInCent" web="0|1" anyUser="0|1">
       Text that describes the option
     </ShippingOption>
     
    -If the id attribute is missing the server creates a new option. Otherwise it changes an existing one. The web attribute tells whether the option is available to customers using the web interface. The anyUser attribute tells whether the option can be used by anyone who can create an order/sale (=1) or only by users with the "_anyshipping" privilege (=0).

    +If the type attribute is missing the server creates a new option. Otherwise it changes an existing one. The web attribute tells whether the option is available to customers using the web interface. The anyUser attribute tells whether the option can be used by anyone who can create an order/sale (=1) or only by users with the "_anyshipping" privilege (=0).

    -The response is empty or contains an error message. +The response contains the new shipping option id or contains an error message.

    + +The deleteshipping transaction can be used to delete a shipping option. The request contains just the ID of the option, the response is empty.

    Listing Shipping Options

    @@ -646,7 +659,7 @@ The getshipping transaction can be used to return all shipping options.
     <ShippingList>
    -  <ShippingOption id="shippingOptionID" price="priceInCent" web="0|1" anyUser="0|1">
    +  <ShippingOption type="shippingOptionID" price="priceInCent" web="0|1" anyUser="0|1">
         Text that describes the option
       </ShippingOption>
       ...
    @@ -697,6 +710,45 @@ The ticketreturn transaction can be used to return a ticket that has no
     
    +

    Vouchers

    + +Rules for vouchers: + + +

    Getting Valid Voucher Prices

    + +The getvoucherprices transaction can be used to retrieve allowed voucher prices. The request is empty. The response contains the allowed prices in cents, separated by spaces - it is empty if no voucher prices are configured. + +

    Using a Voucher

    + +The usevoucher transaction can be used to use a voucher for payment. The request contains the voucher-ID on the first line and the order to be paid on the second line. The response contains the amount that remains on the voucher on success or contains an error message if an error occurred. + +

    Returning/Cancelling a Voucher

    + +The cancelvoucher transaction can be used to cancel a voucher. The request contains just the voucher-ID. The response is empty or contains an error message.

    + +For this transaction to succeed the voucher must not have been used. The price and the value will be reset to zero. If the voucher is already cancelled the transaction will report success. + +

    Destroying a Voucher

    + +The emptyvoucher transaction can be used to void any remaining value of the voucher. The request contains just the voucher-ID. The response is empty or contains an error message.

    + +This transaction simulates buying nothing for a lot of money: it sets the voucher to used and resets the remaining value to zero. The voucher will still have a price that needs to be paid and it cannot be returned/reimbursed afterwards.

    + +Only privileged users should be able to do this (if anybody at all). + +

    Getting a Voucher

    + +The getvoucher transaction can be used to retrieve information about a specific voucher. The request just contains the voucher ID, the response contains the XML representation of it (see the Voucher tag in the Order XML object for details). + +

    Templates

    Templates are used for printouts and documents. There are several types of templates:

    diff --git a/doc/prog_tickettemplate.html b/doc/prog_tickettemplate.html index e691887..b5005d2 100644 --- a/doc/prog_tickettemplate.html +++ b/doc/prog_tickettemplate.html @@ -1,44 +1,48 @@ -Magic Smoke Ticket Templates +Magic Smoke Label Templates -

    Magic Smoke Ticket Templates

    +

    Magic Smoke Label (Ticket/Voucher) Templates

    + +Both tickets and vouchers are handled as labels by MagicSmoke.

    The ticket template file must be called ticket.xtt.

    -Templates for Tickets are ZIP archive files that contain a description of the ticket layout as XML and all necessary pictures and optionally font files. The XML description must link all other resources in the archive file to make them visible to the system.

    +The voucher template file must be called voucher.xtt.

    + +Templates for labels are ZIP archive files that contain a description of the label layout as XML and all necessary pictures and optionally font files. The XML description must link all other resources in the archive file to make them visible to the system.

    The description file must be called template.xml and must be in the root directory of the archive.

    XML format

    -The template.xml file describes the painting operations that form the ticket. The operations are executed in the order that appears in the template file - that means an element that appears later in the file can erase an earlier element if it occupies the same position. The XML format looks like:

    +The template.xml file describes the painting operations that form the label. The operations are executed in the order that appears in the template file - that means an element that appears later in the file can erase an earlier element if it occupies the same position. The XML format looks like:

    -<TicketTemplate unit="mm|in|px" size="11 22">
    +<LabelTemplate unit="mm|in|px" size="11 22">
       <LoadFont file="nameInsideArchive.ttf"/>
       <Picture file="nameInsideArchive.png" size="11 22" offset="33 44" smooth="1"/>
       <Text font="fontFamilyName" fontsize="sizeInPt" offset="11 22" size="33 44" align="left|right|center"
          valign="top|bottom|center" >some string with @VARIABLES@ to print</Text>
       <Barcode offset="11 22" size="33 44"/>
       ...
    -</TicketTemplate>
    +</LabelTemplate>
     
    The elements are: - - - - - + + + + +
    ElementDescription
    TicketTemplatethis is the document element, it describes the complete ticket
    LoadFontloads a font file (only TTF is supported) that is stored in the template ZIP file into the internal font database - it is removed from it after the ticket is rendered
    Picturepaints a picture file that is stored in the template ZIP onto the ticket; a number of formats are supported, but it is recommended to use PNG; if size is present it is scaled first - it is recommended to scale, since different printers can have different resolutions and hence the picture will have different sizes; the default scaling algorithm is smooth
    Textdescribes text that is rendered on the ticket
    Barcodegenerates a code-39 barcode from the ticket-ID and scales it onto the ticket
    LabelTemplatethis is the document element, it describes the complete label (actually the current implementation ignores the name of this element)
    LoadFontloads a font file (only TTF is supported) that is stored in the template ZIP file into the internal font database - it is removed from it after the label is rendered
    Picturepaints a picture file that is stored in the template ZIP onto the label; a number of formats are supported, but it is recommended to use PNG; if size is present it is scaled first - it is recommended to scale, since different printers can have different resolutions and hence the picture will have different sizes; the default scaling algorithm is smooth
    Textdescribes text that is rendered on the label
    Barcodegenerates a code-39 barcode from the BARCODE variable and scales it onto the label

    The attributes are - + @@ -48,9 +52,14 @@ The attributes are
    AttributeDescription
    unitdescribes in what unit sizes and offsets are described in the template, possible values are "mm" (Millimeter), "in" (Inches); the default is "mm". All of them are allowed to use fractions.
    sizedescribes the size the element is scaled to (or in the case of the complete ticket: its total size), it is two positive numbers separated by a space: "width height"
    sizedescribes the size the element is scaled to (or in the case of the complete label: its total size), it is two positive numbers separated by a space: "width height"
    offsetdescribes the position of the element as "X Y" coordinates. They describe the distance from the upper left corner.
    filea relative file name within the template ZIP file
    smoothfor Pictures: describes whether the scaling should be done using edged (0) or smooth (1) scaling
    valignvertical alignment of the text: "top" puts the text below its offset, "bottom" places the text above of its offset, "center" puts the vertical center of the text on the offset

    -Variables for Text elements are enclosed in "@" signs (eg. @VARNAME@). The following variables exist: +Variables for Text elements are enclosed in "@" signs (eg. @VARNAME@). + +

    Ticket Variables

    + +The following variables exist for tickets: + @@ -59,4 +68,15 @@ Variables for Text elements are enclosed in "@" signs (eg. @VARNAME@). The follo
    VariableDescription
    BARCODEan alias for TICKETID
    TICKETIDthe ID of the ticket
    PRICEthe price of the ticket
    ROOMthe room of the tickets event
    ARTISTthe artist for this event
    +

    Voucher Variables

    + +The following variables exist for vouchers: + + + + + + +
    VariableDescription
    BARCODEan alias for VOUCHERID
    VOUCHERIDthe ID of the voucher
    PRICEthe price of the voucher
    VALUEthe remaining value of the voucher (the system does not store the initial value)
    + \ No newline at end of file diff --git a/examples/voucher.xtt b/examples/voucher.xtt new file mode 100644 index 0000000000000000000000000000000000000000..3accf138d62523decce52851bde053fb8effe52c GIT binary patch literal 382 zcmWIWW@Zs#U|`^2XzVMtl%0L++9M$EJtG4H4}&N}NosCEPGU)_UPW$BXb2|*vyqF6 zCkTgDurRz}e!<*3@ox4Z1AznczpmeL_pw68{xyBuO5}}L6AiNNJD1%(G%Ms*NZXI& z`c|)>oz98Vn|}Ilk-boAP|c*19=Tq598tQyJzk0?Mec@diWPclhaIO}oZh(T(zZPd zioD&p`T|5ToBGC_HjQWL9y-xr$yeVH>| z_palt`|GcL{_?$3`dGsM`%TddZ2Wt~u1Yf|w=dIst@iQ2$8(D#GP7-8J?MM=^PtL{ zbH@rAFRcvh?O!7={`vaT%QtTx>%Q;Xc0a(Ikx85xR}k?6 o1B-zH7+MTV8bK`70Aqy&SZD #include @@ -26,23 +27,6 @@ #include #include -inline QString htmlize(QString str) -{ - QString out; - for(int i=0;i')out+=">";else - if(c=='&')out+="&";else - if(c=='\n')out+="
    \n";else - if(c.isSpace()||(c.unicode()>=32&&c.unicode()<=0x7f))out+=c; - else out+="&#"+QString::number(ci)+";"; - } - return out; -} - - MEventSummary::MEventSummary(QWidget*par,MWebRequest*rq,int eid) :QDialog(par) { diff --git a/src/misc.cpp b/src/misc.cpp new file mode 100644 index 0000000..7f23964 --- /dev/null +++ b/src/misc.cpp @@ -0,0 +1,49 @@ +// +// C++ Implementation: misc +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2008 +// +// Copyright: See README/COPYING files that come with this distribution +// +// + +#include "misc.h" + +#include + +QString htmlize(QString str) +{ + QString out; + for(int i=0;i')out+=">";else + if(c=='&')out+="&";else + if(c=='\n')out+="
    \n";else + if(c.isSpace()||(c.unicode()>=32&&c.unicode()<=0x7f))out+=c; + else out+="&#"+QString::number(ci)+";"; + } + return out; +} + +QString cent2str(int c) +{ + QString ret=QCoreApplication::translate("misc","%1.%2","price with decimal dot"); + return ret.arg(c/100).arg(c%100,2,10,QChar('0')); +} + +int str2cent(QString s) +{ + QString p=s.replace(QCoreApplication::translate("misc",".","decimal dot in price"),"."); + double c=s.toDouble()*100; + return int(c); +} + +QRegExp priceRegExp() +{ + return QRegExp(QCoreApplication::translate("misc","[0-9]+\\.[0-9]{2}","regexp for price")); +} diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 0000000..8062066 --- /dev/null +++ b/src/misc.h @@ -0,0 +1,31 @@ +// +// C++ Interface: misc +// +// Description: miscellaneous helper functions +// +// +// Author: Konrad Rosenbaum , (C) 2008 +// +// Copyright: See README/COPYING files that come with this distribution +// +// + +#ifndef MAGICSMOKE_MISC_H +#define MAGICSMOKE_MISC_H + +#include +#include + +/**converts special HTML characters into harmless &-codes, so the text can be included*/ +QString htmlize(QString str); + +/**converts a cent value into a localized string*/ +QString cent2str(int c); + +/**converts a localized string back into a cent value (must not contain spaces or extra dots)*/ +int str2cent(QString s); + +/**return a localized regular expression that validates prices*/ +QRegExp priceRegExp(); + +#endif diff --git a/src/order.cpp b/src/order.cpp index 3f94850..269ee7c 100644 --- a/src/order.cpp +++ b/src/order.cpp @@ -10,6 +10,7 @@ // // +#include "misc.h" #include "order.h" #include "webrequest.h" @@ -58,6 +59,8 @@ MOrder& MOrder::operator=(const MOrder&o) m_seller=o.m_seller; m_deladdress=o.m_deladdress; m_comment=o.m_comment; + m_vouchers=o.m_vouchers; + m_shipping=o.m_shipping; return *this; } @@ -128,7 +131,7 @@ int MOrder::totalPrice()const QString MOrder::totalPriceString(int off)const { - return cent2string(m_price+off); + return cent2str(m_price+off); } int MOrder::amountPaid()const @@ -138,13 +141,7 @@ int MOrder::amountPaid()const QString MOrder::amountPaidString(int off)const { - return cent2string(m_paid+off); -} - -//static -QString MOrder::cent2string(int c) -{ - return QString::number(c/100)+QCoreApplication::translate("MOrder",".","decimal dot")+QString().sprintf("%02d",c%100); + return cent2str(m_paid+off); } bool MOrder::needsPayment()const @@ -182,12 +179,12 @@ int MOrder::amountToRefund()const QString MOrder::amountToPayStr(int off)const { - return cent2string(amountToPay()+off); + return cent2str(amountToPay()+off); } QString MOrder::amountToRefundStr(int off)const { - return cent2string(amountToRefund()+off); + return cent2str(amountToRefund()+off); } bool MOrder::isSent()const @@ -221,6 +218,8 @@ void MOrder::parseXml(const QDomElement&e) m_deladdress=""; m_comment=""; m_tickets.clear(); + m_vouchers.clear(); + m_shipping=MShipping(); //Basics bool b; m_orderid=e.attribute("id","-1").toInt(&b); @@ -271,7 +270,19 @@ void MOrder::parseXml(const QDomElement&e) m_tickets.append(tick); } } - //TODO: scan vouchers + nl=e.elementsByTagName("Voucher"); + for(int i=0;invc; + for(int i=0;i MOrder::vouchers(){return m_vouchers;} + +MShipping MOrder::shipping(){return m_shipping;} + +QString MOrder::sendShipping(MShipping s) +{ + if(!req)return QCoreApplication::translate("MOrder","Cannot query DB, don't know it."); + //construct XML + QDomDocument doc; + QDomElement el=doc.createElement("OrderChangeShipping"); + el.setAttribute("orderid",m_orderid); + if(s.isValid()){ + el.setAttribute("type",s.id()); + el.setAttribute("price",s.price()); + } + doc.appendChild(el); + //send + if(!req->request("orderchangeshipping",doc.toByteArray())) + return QCoreApplication::translate("MOrder","Cannot update shipping: error while sending."); + if(req->responseStatus()!=MWebRequest::Ok) + return QCoreApplication::translate("php::",req->responseBody()); + QDomDocument rsp; + rsp.setContent(req->responseBody().trimmed()); + parseXml(rsp.documentElement()); + return ""; +} + +QString MOrder::voucherReturn(QString tid) +{ + for(int i=0;irequest(type,doc.toByteArray())==false){ @@ -720,3 +789,113 @@ int MTicket::amountToPay()const if(m_status==Bought || m_status==Used)return m_price; else return 0; } + + +/****************************************************************************** + * Voucher + ******************************************************************************/ + +MVoucher::MVoucher() +{ + req=0; + m_price=m_value=0; + m_orderid=-1; + isused=false; +} + +MVoucher::MVoucher(MWebRequest*r,const QDomElement&e) +{ + req=r; + parseXml(e); +} + +MVoucher::MVoucher(MWebRequest*r,QString v) +{ + req=0; + m_price=m_value=0; + m_orderid=-1; + isused=false; + if(!r->request("getvoucher",v.toUtf8()))return; + if(r->responseStatus()!=MWebRequest::Ok)return; + QDomDocument doc; + if(!doc.setContent(r->responseBody().trimmed()))return; + parseXml(doc.documentElement()); + req=r; +} + +void MVoucher::parseXml(const QDomElement&e) +{ + m_id=e.attribute("id"); + m_price=e.attribute("price","0").toInt(); + m_value=e.attribute("value","0").toInt(); + isused=e.attribute("used","0").toInt()!=0; + xmlstate=e.text().trimmed(); + m_orderid=-1; +} + +MVoucher::MVoucher(const MVoucher&v) +{ + operator=(v); +} + +MVoucher& MVoucher::operator=(const MVoucher&v) +{ + req=v.req; + m_id=v.m_id; + m_price=v.m_price; + m_value=v.m_value; + isused=v.isused; + m_orderid=v.m_orderid; + xmlstate=v.xmlstate; + return *this; +} + +QString MVoucher::voucherID()const{return m_id;} + +int MVoucher::price()const{return m_price;} +QString MVoucher::priceString()const{return cent2str(m_price);} +int MVoucher::value()const{return m_value;} +QString MVoucher::valueString()const{return cent2str(m_value);} + +bool MVoucher::isValid()const{return req!=0 && m_id!="";} + +bool MVoucher::isUsed()const{return isused;} + +bool MVoucher::isCancelled()const{return m_price==0 && m_value==0;} + +bool MVoucher::isEmpty()const{return m_value==0;} + +QString MVoucher::xmlState()const{return xmlstate;} + +QString MVoucher::statusString()const +{ + if(!req)return QCoreApplication::translate("MVoucher","invalid"); + if(xmlstate!="")return QCoreApplication::translate("php::",xmlstate.toAscii()); + if(isCancelled())return QCoreApplication::translate("MVoucher","cancelled"); + if(isEmpty())return QCoreApplication::translate("MVoucher","empty"); + if(isUsed())return QCoreApplication::translate("MVoucher","used"); + else return QCoreApplication::translate("MVoucher","unused"); +} + +void MVoucher::setOrderID(int o){m_orderid=o;} + +int MVoucher::orderID()const{return m_orderid;} + +void MVoucher::xmlForOrder(QDomDocument&doc,QDomElement&root) +{ + QDomElement vx=doc.createElement("Voucher"); + vx.setAttribute("value",m_value); + vx.setAttribute("price",m_price); + root.appendChild(vx); +} + +QString MVoucher::voucherReturn() +{ + if(!req)return QT_TRANSLATE_NOOP("MVoucher","Voucher is not stored, can't return it."); + if(!req->request("cancelvoucher",m_id.toUtf8())) + return QT_TRANSLATE_NOOP("MVoucher","Failed to execute request"); + if(req->responseStatus()!=MWebRequest::Ok) + return QString::fromUtf8(req->responseBody()); + m_value=m_price=0; + return ""; +} diff --git a/src/order.h b/src/order.h index 7ac4301..5961748 100644 --- a/src/order.h +++ b/src/order.h @@ -13,13 +13,13 @@ #ifndef MAGICSMOKE_ORDER_H #define MAGICSMOKE_ORDER_H -#include -#include #include +#include +#include #include "customer.h" - #include "event.h" +#include "shipping.h" class MWebRequest; class QDomDocument; @@ -158,10 +158,81 @@ class MTicket void scanXml(const QDomElement&); }; +/**this class represents a voucher*/ +class MVoucher +{ + public: + /**create empty/invalid voucher*/ + MVoucher(); + /**create voucher from XML*/ + MVoucher(MWebRequest*,const QDomElement&); + /**retrieve voucher from DB*/ + MVoucher(MWebRequest*,QString); + /**copy voucher*/ + MVoucher(const MVoucher&); + + /**copy voucher*/ + MVoucher& operator=(const MVoucher&); + + /**returns the ID of the voucher*/ + QString voucherID()const; + + /**returns the price in cent of the voucher (what it costs)*/ + int price()const; + /**returns the price of the voucher as string*/ + QString priceString()const; + /**returns the remaining value in cent of the voucher (what it is worth)*/ + int value()const; + /**returns the remaining value of the voucher as string*/ + QString valueString()const; + + /**returns whether this is a valid voucher object*/ + bool isValid()const; + + /**returns whether the voucher has already been used*/ + bool isUsed()const; + + /**returns whether the voucher is cancelled*/ + bool isCancelled()const; + + /**returns whether the voucher is empty, ie. its remaining value is zero*/ + bool isEmpty()const; + + /**for order validation: returns the validation result of the voucher (empty string = ok)*/ + QString xmlState()const; + + /**returns a status string for the voucher for displaying*/ + QString statusString()const; + + /**Returns the ID of the order this voucher belongs to (only when retrieved with an order)*/ + int orderID()const; + + /**attempts to return the voucher; returns empty string on success, error message on failure*/ + QString voucherReturn(); + + protected: + friend class MOrder; + /**sets the order-ID of the ticket, used by MOrder*/ + void setOrderID(qint32); + /**used by createOrder to generate XML elements for each ticket*/ + void xmlForOrder(QDomDocument&,QDomElement&); + + private: + MWebRequest*req; + QString m_id; + qint32 m_price,m_value,m_orderid; + bool isused; + QString xmlstate; + + //helper: parses XML + void parseXml(const QDomElement&); +}; + +/**this class represents a complete order*/ class MOrder { public: - /**create invalid host*/ + /**create invalid order*/ MOrder(); /**create order by id*/ MOrder(MWebRequest*,qint32); @@ -309,6 +380,18 @@ class MOrder /**used to cancel/return a ticket from this order; returns empty string on success, error message on failure*/ QString ticketReturn(QString); + /**used to cancel/return a voucher from this order; returns empty string on success, error message on failure*/ + QString voucherReturn(QString); + + /**returns the list of vouchers of this order*/ + QList vouchers(); + + /**returns the shipping method of this order*/ + MShipping shipping(); + + /**sends a new shipping method to the DB and updates the order object; returns empty string on success, error string otherwise*/ + QString sendShipping(MShipping); + /**create a new order in the DB; returns it*/ MOrder createOrder(QString type="createorder"); @@ -334,6 +417,8 @@ class MOrder QString m_seller,m_deladdress,m_comment; bool m_complete; QList m_tickets; + QList m_vouchers; + MShipping m_shipping; /**internal: requests the order from the database*/ void getFromDB(qint32); @@ -341,9 +426,6 @@ class MOrder /**internal: parse XML*/ void parseXml(const QDomElement&); - /**helper: converts a cent value into a localized string*/ - static QString cent2string(int); - /**internal: makes sure the order is completely retrieved*/ void makeComplete(); }; diff --git a/src/orderwin.cpp b/src/orderwin.cpp index 565b990..89d185e 100644 --- a/src/orderwin.cpp +++ b/src/orderwin.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -59,12 +60,14 @@ MOrderWindow::MOrderWindow(QWidget*par,MWebRequest*r,const MOrder&o) m->addAction(tr("&Mark Order as Shipped..."),this,SLOT(shipOrder())) ->setEnabled(req->hasRole("ordershipped")); m->addSeparator(); - m->addAction(tr("Ch&ange Ticket-Price..."),this,SLOT(changeTicket())) + m->addAction(tr("Ch&ange Item-Price..."),this,SLOT(changeItem())) ->setEnabled(req->hasRole("changeticketprice")); - m->addAction(tr("&Return Ticket..."),this,SLOT(ticketReturn())) + m->addAction(tr("&Return Item..."),this,SLOT(itemReturn())) ->setEnabled(req->hasRole("ticketreturn")); m->addAction(tr("Change Commen&t..."),this,SLOT(changeComment())) ->setEnabled(req->hasRole("setordercomment")); + m->addAction(tr("Change Sh&ipping Method..."),this,SLOT(changeShipping())) + ->setEnabled(req->hasRole("changeordershipping")); m->addSeparator(); m->addAction(tr("&Close"),this,SLOT(close())); m=mb->addMenu(tr("&Payment")); @@ -79,8 +82,9 @@ MOrderWindow::MOrderWindow(QWidget*par,MWebRequest*r,const MOrder&o) m->addAction(tr("Save Bill &as file..."),this,SLOT(saveBill())); m->addSeparator(); m->addAction(tr("Print &Tickets..."),this,SLOT(printTickets())); - m->addAction(tr("Print &Current Ticket..."),this,SLOT(printCurrentTicket())); - m->addAction(tr("&View Tickets..."),this,SLOT(ticketView())); + m->addAction(tr("Print V&ouchers..."),this,SLOT(printVouchers())); + m->addAction(tr("Print &Current Item..."),this,SLOT(printCurrentItem())); + m->addAction(tr("&View Items..."),this,SLOT(itemView())); QWidget*w; setCentralWidget(w=new QWidget); @@ -110,6 +114,10 @@ MOrderWindow::MOrderWindow(QWidget*par,MWebRequest*r,const MOrder&o) gl->addWidget(m_paid=new QLabel(m_order.amountPaidString()),rw,1); gl->addWidget(new QLabel(tr("Order State:")),++rw,0); gl->addWidget(m_state=new QLabel(m_order.orderStatusString()),rw,1); + gl->addWidget(new QLabel(tr("Shipping Method:")),++rw,0); + gl->addWidget(m_shipmeth=new QLabel(m_order.shipping().description()),rw,1); + gl->addWidget(new QLabel(tr("Shipping Costs:")),++rw,0); + gl->addWidget(m_shipprice=new QLabel(m_order.shipping().priceString()),rw,1); gl->addWidget(new QLabel(tr("Order Comment:")),++rw,0); gl->addWidget(m_comment=lab=new QLabel(m_order.comment()),rw,1); lab->setWordWrap(true); @@ -119,12 +127,12 @@ MOrderWindow::MOrderWindow(QWidget*par,MWebRequest*r,const MOrder&o) QSize sz=size(); vl->addSpacing(10); - vl->addWidget(m_tickettable=new QTableView,10); - m_tickettable->setModel(m_ticketmodel=new QStandardItemModel(this)); - m_tickettable->setEditTriggers(QAbstractItemView::NoEditTriggers); + vl->addWidget(m_table=new QTableView,10); + m_table->setModel(m_model=new QStandardItemModel(this)); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); updateTable(); //make sure everything is visible - QSize vsz=m_tickettable->maximumViewportSize(); + QSize vsz=m_table->maximumViewportSize(); vsz.setWidth(vsz.width()+40); if(sz.width()dataDir(); } +static const int ITEM_TICKET=1; +static const int ITEM_VOUCHER=2; + void MOrderWindow::updateTable() { QList tickets=m_order.tickets(); QList events; if(tickets.size()>0) events=req->getAllEvents(); - m_ticketmodel->clear(); - m_ticketmodel->setHorizontalHeaderLabels(QStringList()<insertRows(0,tickets.size()); + QList vouchers=m_order.vouchers(); + m_model->clear(); + m_model->setHorizontalHeaderLabels(QStringList()<insertRows(0,tickets.size()+vouchers.size()); for(int i=0;isetData(m_ticketmodel->index(i,0),tickets[i].ticketID()); - m_ticketmodel->setData(m_ticketmodel->index(i,3),tickets[i].statusString()); - m_ticketmodel->setData(m_ticketmodel->index(i,4),tickets[i].priceString()); + m_model->setData(m_model->index(i,0),tickets[i].ticketID()); + m_model->setData(m_model->index(i,0),ITEM_TICKET,Qt::UserRole); + m_model->setData(m_model->index(i,3),tickets[i].statusString()); + m_model->setData(m_model->index(i,4),tickets[i].priceString()); //find event MEvent ev;int eid=tickets[i].eventID(); for(int j=0;jsetData(m_ticketmodel->index(i,1),ev.title()); - m_ticketmodel->setData(m_ticketmodel->index(i,2),ev.startTimeString()); + m_model->setData(m_model->index(i,1),ev.title()); + m_model->setData(m_model->index(i,2),ev.startTimeString()); + } + int off=tickets.size(); + for(int i=0;isetData(m_model->index(i+off,0),ITEM_VOUCHER,Qt::UserRole); + m_model->setData(m_model->index(i+off,0),vouchers[i].voucherID()); + m_model->setData(m_model->index(i+off,1),tr("Voucher (current value: %1)").arg(vouchers[i].valueString())); + m_model->setData(m_model->index(i+off,3),vouchers[i].statusString()); + m_model->setData(m_model->index(i+off,4),vouchers[i].priceString()); } - m_tickettable->resizeColumnsToContents(); + m_table->resizeColumnsToContents(); } void MOrderWindow::setChanged() @@ -169,31 +190,42 @@ bool MOrderWindow::isChanged()const return m_changed; } -void MOrderWindow::ticketView() +void MOrderWindow::itemView() { QListtickets=m_order.tickets(); - if(tickets.size()<1)return; - MTicketView tv(this,tickets); + QListvouchers=m_order.vouchers(); + if(tickets.size()<1 && vouchers.size()<1)return; + MOrderItemView tv(this,req,tickets,vouchers); tv.exec(); } -void MOrderWindow::printCurrentTicket() +void MOrderWindow::printCurrentItem() { - //get current ticketid - QModelIndexList lst=m_tickettable->selectionModel()->selectedIndexes(); + //get current ticketid/voucherid + QModelIndexList lst=m_table->selectionModel()->selectedIndexes(); if(lst.size()<1)return; - QModelIndex idx=m_ticketmodel->index(lst[0].row(),0); - QString id=m_ticketmodel->data(idx).toString(); + QModelIndex idx=m_model->index(lst[0].row(),0); + QString id=m_model->data(idx).toString(); if(id=="")return; - //find ticket - QListtickets=m_order.tickets(); - MTicket tick; - for(int i=0;i()<data(idx,Qt::UserRole).toInt(); + //find ticket/voucher + if(type==ITEM_TICKET){ + QListticks; + QListtickets=m_order.tickets(); + for(int i=0;ivouchs; + QListvouchers=m_order.vouchers(); + for(int i=0;i tickets) +void MOrderWindow::printVouchers() +{ + printVouchers(m_order.vouchers()); +} + +void MOrderWindow::printTickets(QList ticketsin) { + //reduce ticket list to usable ones + QList tickets; + for(int i=0;igetTemplate("ticket.xtt"); if(tf==""){ @@ -217,7 +263,7 @@ void MOrderWindow::printTickets(QList tickets) if(pd.exec()!=QDialog::Accepted)return; //label arrangement MTicketRenderer render(tf); - MLabelDialog ld(this,&printer,tickets.size(),render.ticketSize(printer)); + MLabelDialog ld(this,&printer,tickets.size(),render.labelSize(printer)); if(ld.exec()!=QDialog::Accepted) return; //print @@ -231,6 +277,45 @@ void MOrderWindow::printTickets(QList tickets) } } +void MOrderWindow::printVouchers(QList vouchersin) +{ + //reduce voucher list to usable ones + QListvouchers; + for(int i=0;i0 && vouchersin[i].xmlState()=="") + vouchers.append(vouchersin[i]); + } + //sanity check + if(vouchers.size()<1){ + QMessageBox::warning(this,tr("Warning"),tr("There are no vouchers left to print.")); + return; + } + //get template + QString tf=req->getTemplate("voucher.xtt"); + if(tf==""){ + QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (voucher.xtt). Giving up.")); + return; + } + //get printer settings + QPrinter printer; + QPrintDialog pd(&printer,this); + if(pd.exec()!=QDialog::Accepted)return; + //label arrangement + MVoucherRenderer render(tf); + MLabelDialog ld(this,&printer,vouchers.size(),render.labelSize(printer)); + if(ld.exec()!=QDialog::Accepted) + return; + //print + QPainter painter(&printer); + for(int i=0;irowCount()+av); + if(vn=="TICKETS")value=QString::number(m_model->rowCount()+av); if(vn=="ADDRESSLINES")value=QString::number(m_order.customer().address().split("\n").size()+av); } void MOrderWindow::getLoopIterations(QString loopname,int&iterations) { - if(loopname=="TICKETS")iterations=m_ticketmodel->rowCount(); + if(loopname=="TICKETS")iterations=m_model->rowCount(); if(loopname=="ADDRESSLINES")iterations=m_order.customer().address().split("\n").size(); } void MOrderWindow::getLoopVariable(QString loopname,int it,QString vn,int av,QString&value) { if(loopname=="TICKETS"){ - if(it<0 || it>=m_ticketmodel->rowCount())return; + if(it<0 || it>=m_model->rowCount())return; QListtickets=m_order.tickets(); @@ -347,7 +432,7 @@ void MOrderWindow::payment() bool ok; double rt=QInputDialog::getDouble(this,tr("Enter Payment"),tr("Please enter the amount that has been paid:"),m_order.amountToPay()/100.0,0,m_order.amountToPay()/100.0,2,&ok); if(!ok)return; - int pay=rt*100.0; + int pay=int(rt*100.0); if(pay<=0)return; //submit QByteArray rq=QByteArray::number(m_order.orderID())+" "+QByteArray::number(pay); @@ -370,7 +455,7 @@ void MOrderWindow::refund() bool ok; double rt=QInputDialog::getDouble(this,tr("Enter Refund"),tr("Please enter the amount that will be refunded:"),m_order.amountToRefund()/100.0,0,m_order.amountToRefund()/100.0,2,&ok); if(!ok)return; - int pay=rt*100.0; + int pay=int(rt*100.0); if(pay<=0)return; //submit QByteArray rq=QByteArray::number(m_order.orderID())+" "+QByteArray::number(pay); @@ -386,62 +471,93 @@ void MOrderWindow::refund() m_paid->setText(m_order.amountPaidString()); } -void MOrderWindow::changeTicket() +void MOrderWindow::changeItem() { if(!m_order.isValid())return; //get ticket selection - QModelIndexList lst=m_tickettable->selectionModel()->selectedIndexes(); + QModelIndexList lst=m_table->selectionModel()->selectedIndexes(); if(lst.size()<1)return; - QModelIndex idx=m_ticketmodel->index(lst[0].row(),0); - QString id=m_ticketmodel->data(idx).toString(); + QModelIndex idx=m_model->index(lst[0].row(),0); + QString id=m_model->data(idx).toString(); if(id=="")return; - //find ticket - QListtickets=m_order.tickets(); - MTicket tick; - for(int i=0;idata(idx,Qt::UserRole).toInt(); + if(type==ITEM_TICKET){ + //find ticket + QListtickets=m_order.tickets(); + MTicket tick; + for(int i=0;isetText(m_order.totalPriceString()); updateTable(); } -void MOrderWindow::ticketReturn() +void MOrderWindow::itemReturn() { if(!m_order.isValid())return; //get ticket selection - QModelIndexList lst=m_tickettable->selectionModel()->selectedIndexes(); + QModelIndexList lst=m_table->selectionModel()->selectedIndexes(); if(lst.size()<1)return; - QModelIndex idx=m_ticketmodel->index(lst[0].row(),0); - QString id=m_ticketmodel->data(idx).toString(); + QModelIndex idx=m_model->index(lst[0].row(),0); + QString id=m_model->data(idx).toString(); if(id=="")return; - //find ticket - QListtickets=m_order.tickets(); - MTicket tick; - for(int i=0;isetText(m_order.totalPriceString()); - updateTable(); - if(r!="")QMessageBox::warning(this,tr("Warning"),trUtf8(r.toUtf8())); + int type=m_model->data(idx,Qt::UserRole).toInt(); + if(type==ITEM_TICKET){ + //find ticket + QListtickets=m_order.tickets(); + MTicket tick; + for(int i=0;isetText(m_order.totalPriceString()); + updateTable(); + if(r!="")QMessageBox::warning(this,tr("Warning"),trUtf8(r.toUtf8())); + }else + if(type==ITEM_VOUCHER){ + //find ticket + QListvouchers=m_order.vouchers(); + MVoucher vou; + for(int i=0;isetText(m_order.totalPriceString()); + updateTable(); + if(r!="")QMessageBox::warning(this,tr("Warning"),trUtf8(r.toUtf8())); + }else + QMessageBox::warning(this,tr("Warning"),tr("Cannot return this item type.")); } void MOrderWindow::cancelOrder() @@ -527,12 +643,27 @@ void MOrderWindow::changeComment() m_comment->setText(cmt); } +void MOrderWindow::changeShipping() +{ + //create editor dialog + MShippingChange d(this,req,m_order.shipping()); + //get status + if(d.exec()!=QDialog::Accepted)return; + //send to server + m_order.sendShipping(d.selection()); + //reset display + m_shipmeth->setText(m_order.shipping().description()); + m_shipprice->setText(m_order.shipping().priceString()); + m_total->setText(m_order.totalPriceString()); +} + /*************************************************************************************/ -MTicketView::MTicketView(QWidget*w,QListt) - :QDialog(w),tickets(t) +MOrderItemView::MOrderItemView(QWidget*w,MWebRequest*r,QListt,QListv) + :QDialog(w),tickets(t),vouchers(v) { setWindowTitle(tr("Preview Tickets")); + req=r; QVBoxLayout*vl; setLayout(vl=new QVBoxLayout); @@ -540,26 +671,105 @@ MTicketView::MTicketView(QWidget*w,QListt) vl->addWidget(cb=new QComboBox,0); cb->setEditable(false); for(int i=0;iaddItem(tickets[i].ticketID()); + cb->addItem(tr("Ticket: ")+tickets[i].ticketID()); + for(int i=0;iaddItem(tr("Voucher: ")+vouchers[i].voucherID()); vl->addWidget(disp=new QLabel,10); - //TODO: get the real template! - render=new MTicketRenderer("../examples/ticket.xtt"); - changeTicket(0); - connect(cb,SIGNAL(currentIndexChanged(int)),this,SLOT(changeTicket(int))); + //get the templates + trender=new MTicketRenderer(req->getTemplate("ticket.xtt")); + vrender=new MVoucherRenderer(req->getTemplate("voucher.xtt")); + changeItem(0); + connect(cb,SIGNAL(currentIndexChanged(int)),this,SLOT(changeItem(int))); } -MTicketView::~MTicketView() +MOrderItemView::~MOrderItemView() { - delete render; + delete trender; + delete vrender; } -void MTicketView::changeTicket(int idx) +void MOrderItemView::changeItem(int idx) { + //ticket or voucher? + if(idxlabelSize(*disp); + QPixmap tick(sz.toSize()); + tick.fill(); + if(!trender->render(tickets[idx],tick)) + qDebug("unable to render"); + disp->setPixmap(tick); + }else{ + QSizeF sz=vrender->labelSize(*disp); + QPixmap tick(sz.toSize()); + tick.fill(); + if(!vrender->render(vouchers[idx-tickets.size()],tick)) + qDebug("unable to render"); + disp->setPixmap(tick); + } +} + + +/*************************************************************************************/ + +MShippingChange::MShippingChange(QWidget*pa,MWebRequest*r,MShipping s) + :QDialog(pa) +{ + req=r; + all=req->getAllShipping(); + setWindowTitle(tr("Change Shipping Method")); + + int cid=-1; + if(s.isValid())cid=s.id(); + + QGridLayout*gl; + setLayout(gl=new QGridLayout); + gl->addWidget(new QLabel(tr("Method:")),0,0); + gl->addWidget(opt=new QComboBox,0,1); + gl->addWidget(new QLabel(tr("Price:")),1,0); + gl->addWidget(prc=new QDoubleSpinBox,1,1); + gl->setRowMinimumHeight(2,15); + gl->setRowStretch(2,1); + QHBoxLayout*hl; + gl->addLayout(hl=new QHBoxLayout,3,0,1,2); + hl->addStretch(10); + QPushButton*p; + hl->addWidget(p=new QPushButton(tr("Ok"))); + connect(p,SIGNAL(clicked()),this,SLOT(accept())); + hl->addWidget(p=new QPushButton(tr("Cancel"))); + connect(p,SIGNAL(clicked()),this,SLOT(reject())); - QSizeF sz=render->ticketSize(*disp); - QPixmap tick(sz.toSize()); - tick.fill(); - if(!render->render(tickets[idx],tick)) - qDebug("unable to render"); - disp->setPixmap(tick); + prc->setRange(0.,1.0e9);//hmm, even in Yen this should be big enough + prc->setDecimals(2); + prc->setValue(s.price()/100.); + prc->setEnabled(req->hasRole("orderchangeshipping")); + + opt->addItem(tr("(None)","shipping method")); + int scid=0; + for(int i=0;iaddItem(all[i].description()); + if(all[i].id()==cid) + scid=i+1; + } + opt->setCurrentIndex(scid); + connect(opt,SIGNAL(currentIndexChanged(int)),this,SLOT(changeItem(int))); +} + +MShipping MShippingChange::selection()const +{ + int cid=opt->currentIndex(); + if(cid==0)return MShipping(); + MShipping cp=all[cid-1]; + cp.setPrice(price()); + return cp; +} + +int MShippingChange::price()const +{ + return int(prc->value()*100.); +} + +void MShippingChange::changeItem(int cid) +{ + if(cid==0)prc->setValue(0); + else prc->setValue(double(all[cid-1].price())/100.0); } diff --git a/src/orderwin.h b/src/orderwin.h index 49492a8..d3f1039 100644 --- a/src/orderwin.h +++ b/src/orderwin.h @@ -40,13 +40,17 @@ class MOrderWindow:public QMainWindow void updateTable(); /**internal: show the tickets as graphics*/ - void ticketView(); + void itemView(); /**internal: print the currently selected ticket*/ - void printCurrentTicket(); + void printCurrentItem(); /**internal: print all tickets*/ void printTickets(); /**internal helper: print list of tickets*/ void printTickets(QList); + /**internal: print all vouchers*/ + void printVouchers(); + /**internal helper: print list of vouchers*/ + void printVouchers(QList); /**print a bill*/ void printBill(); @@ -64,14 +68,17 @@ class MOrderWindow:public QMainWindow /**generate a refund*/ void refund(); - /**change a ticket price*/ - void changeTicket(); - /**return a ticket*/ - void ticketReturn(); + /**change a ticket/voucher price*/ + void changeItem(); + /**return a ticket/voucher*/ + void itemReturn(); /**change the comment on the order*/ void changeComment(); + /**change the shipping option*/ + void changeShipping(); + /**cancel the order*/ void cancelOrder(); /**mark as shipped*/ @@ -90,27 +97,58 @@ class MOrderWindow:public QMainWindow MWebRequest*req; MOrder m_order; bool m_changed; - QLabel *m_orderid,*m_orderdate,*m_sentdate,*m_state,*m_paid,*m_total,*m_comment; - QTableView *m_tickettable; - QStandardItemModel *m_ticketmodel; + QLabel *m_orderid,*m_orderdate,*m_sentdate,*m_state,*m_paid,*m_total,*m_comment, + *m_shipmeth,*m_shipprice; + QTableView *m_table; + QStandardItemModel *m_model; }; class MTicketRenderer; +class MVoucherRenderer; -/**helper class: displays tickets*/ -class MTicketView:public QDialog +/**helper class: displays tickets and vouchers*/ +class MOrderItemView:public QDialog { Q_OBJECT public: - MTicketView(QWidget*,QList); - ~MTicketView(); + MOrderItemView(QWidget*,MWebRequest*,QList,QList); + ~MOrderItemView(); private slots: - void changeTicket(int); + void changeItem(int); private: + MWebRequest*req; QList tickets; + QList vouchers; QLabel*disp; - MTicketRenderer*render; + MTicketRenderer*trender; + MVoucherRenderer*vrender; +}; + +class QDoubleSpinBox; +class QComboBox; + +/**helper class: allows to change the shipping option*/ +class MShippingChange:public QDialog +{ + Q_OBJECT + public: + /**creates the dialog*/ + MShippingChange(QWidget*,MWebRequest*,MShipping); + + /**returns the selected shipping option, including corrected price*/ + MShipping selection()const; + /**returns the entered price in cent*/ + int price()const; + + private slots: + /**internal: updates price when new option is selected*/ + void changeItem(int); + private: + MWebRequest*req; + QList all; + QComboBox*opt; + QDoubleSpinBox*prc; }; #endif diff --git a/src/overview.cpp b/src/overview.cpp index 973b0cd..219e63f 100644 --- a/src/overview.cpp +++ b/src/overview.cpp @@ -10,12 +10,13 @@ // // -#include "overview.h" -#include "webrequest.h" -#include "eventedit.h" #include "checkdlg.h" +#include "eventedit.h" #include "eventsummary.h" +#include "misc.h" #include "orderwin.h" +#include "overview.h" +#include "webrequest.h" #include #include @@ -84,12 +85,17 @@ MOverview::MOverview(MWebRequest*mw,QString pk) m=mb->addMenu(tr("C&art")); m->addAction(tr("Add &Ticket"),this,SLOT(cartAddTicket())); - m->addAction(tr("Add &Voucher"),this,SLOT(cartAddVoucher()))->setEnabled(false); + m->addAction(tr("Add &Voucher"),this,SLOT(cartAddVoucher())); m->addAction(tr("&Remove Item"),this,SLOT(cartRemoveItem())); m->addAction(tr("&Abort Shopping"),this,SLOT(initCart())); + m->addSeparator(); + m->addAction(tr("&Update Shipping Options"),this,SLOT(updateShipping())); m=mb->addMenu(tr("&Misc")); - m->addAction(tr("&Return ticket..."),this,SLOT(ticketReturn())); + m->addAction(tr("Return &ticket..."),this,SLOT(ticketReturn())); + m->addAction(tr("Return &voucher..."),this,SLOT(voucherReturn())); + m->addSeparator(); + m->addAction(tr("Edit &Shipping Options..."),this,SLOT(editShipping())); m=mb->addMenu(tr("C&onfigure")); m->addAction(tr("&Auto-Refresh settings..."),this,SLOT(setRefresh())); @@ -152,7 +158,6 @@ MOverview::MOverview(MWebRequest*mw,QString pk) connect(p,SIGNAL(clicked()),this,SLOT(cartAddTicket())); hl2->addWidget(p=new QPushButton(tr("Add Voucher")),0); connect(p,SIGNAL(clicked()),this,SLOT(cartAddVoucher())); - p->setEnabled(false); hl2->addWidget(p=new QPushButton(tr("Remove Item")),0); connect(p,SIGNAL(clicked()),this,SLOT(cartRemoveItem())); QFrame*frm; @@ -165,6 +170,9 @@ MOverview::MOverview(MWebRequest*mw,QString pk) vl2->addWidget(frm=new QFrame,0); frm->setFrameShape(QFrame::HLine); vl2->addSpacing(10); + vl2->addWidget(new QLabel(tr("Shipping Method:")),0); + vl2->addWidget(cartship=new QComboBox,0); + cartship->setEditable(false); vl2->addWidget(new QLabel(tr("Delivery Address:")),0); vl2->addWidget(cartaddr=new QTextEdit); vl2->addSpacing(10); @@ -303,6 +311,7 @@ MOverview::MOverview(MWebRequest*mw,QString pk) }else{ initCart(); } + updateShipping(); if(req->hasRole("getorderlist")){ updateOrders(); }else{ @@ -422,6 +431,15 @@ void MOverview::eventCancel() } } +void MOverview::updateShipping() +{ + cartship->clear(); + cartship->addItem(tr("(No Shipping)"),-1); + QListship=req->getAllShipping(); + for(int i=0;iaddItem(ship[i].description(),ship[i].id()); +} + void MOverview::updateUsers() { QListusl=req->getAllUsers(); @@ -722,27 +740,6 @@ void MOverview::exportHost() } -void MOverview::eventOrderTicket() -{ - //get selected event - int id; - QModelIndexList ilst=eventtable->selectionModel()->selectedIndexes(); - if(ilst.size()<1)return; - QModelIndex idx=eventmodel->index(ilst[0].row(),0); - id=eventmodel->data(idx,Qt::UserRole).toInt(); - if(id<0)return; - //activate order tab - tab->setCurrentWidget(carttab); - //insert event into table - int cr=cartmodel->rowCount(); - cartmodel->insertRows(cr,1); - cartmodel->setData(cartmodel->index(cr,0),1); - cartmodel->setData(cartmodel->index(cr,0),id,Qt::UserRole); - cartmodel->setData(cartmodel->index(cr,1),eventmodel->data(eventmodel->index(ilst[0].row(),1))); - cartmodel->setData(cartmodel->index(cr,2),eventmodel->data(eventmodel->index(ilst[0].row(),0))); - carttable->resizeColumnsToContents(); -} - void MOverview::initCart() { //clear cart @@ -754,6 +751,8 @@ void MOverview::initCart() //clear address/comment cartaddr->setPlainText(""); cartcomment->setPlainText(""); + //clear shipping + cartship->setCurrentIndex(0); } void MOverview::setCustomer() @@ -764,6 +763,14 @@ void MOverview::setCustomer() cartcustomer->setText(customer.getNameAddress()); } +static const int CART_TICKET=1; +static const int CART_VOUCHER=2; + +static const int CART_IDROLE=Qt::UserRole;//ticket id +static const int CART_PRICEROLE=Qt::UserRole;//voucher 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? + void MOverview::cartAddTicket() { //create ticket selection dialog @@ -797,13 +804,95 @@ void MOverview::cartAddTicket() int cr=cartmodel->rowCount(); cartmodel->insertRows(cr,1); cartmodel->setData(cartmodel->index(cr,0),1); - cartmodel->setData(cartmodel->index(cr,0),id,Qt::UserRole); + cartmodel->setData(cartmodel->index(cr,0),id,CART_IDROLE); + cartmodel->setData(cartmodel->index(cr,0),CART_TICKET,CART_TYPEROLE); cartmodel->setData(cartmodel->index(cr,1),eventmodel->data(eventmodel->index(idx.row(),1))); cartmodel->setData(cartmodel->index(cr,2),eventmodel->data(eventmodel->index(idx.row(),0))); carttable->resizeColumnsToContents(); } } +void MOverview::eventOrderTicket() +{ + //get selected event + int id; + QModelIndexList ilst=eventtable->selectionModel()->selectedIndexes(); + if(ilst.size()<1)return; + QModelIndex idx=eventmodel->index(ilst[0].row(),0); + id=eventmodel->data(idx,Qt::UserRole).toInt(); + if(id<0)return; + //activate order tab + tab->setCurrentWidget(carttab); + //insert event into table + int cr=cartmodel->rowCount(); + cartmodel->insertRows(cr,1); + cartmodel->setData(cartmodel->index(cr,0),1); + cartmodel->setData(cartmodel->index(cr,0),id,CART_IDROLE); + cartmodel->setData(cartmodel->index(cr,0),CART_TICKET,CART_TYPEROLE); + cartmodel->setData(cartmodel->index(cr,1),eventmodel->data(eventmodel->index(ilst[0].row(),1))); + cartmodel->setData(cartmodel->index(cr,2),eventmodel->data(eventmodel->index(ilst[0].row(),0))); + carttable->resizeColumnsToContents(); +} + +void MOverview::cartAddVoucher() +{ + //create voucher selection dialog + QDialog dlg; + dlg.setWindowTitle(tr("Select Voucher")); + QComboBox*cp,*cv; + QHBoxLayout*hl; + QGridLayout*gl; + QStandardItemModel mdl; + QRegExpValidator regv(priceRegExp(),this); + QListprc=req->getVoucherPrices(); + mdl.insertRows(0,prc.size()); + mdl.insertColumns(0,1); + for(int i=0;iaddWidget(new QLabel(tr("Select voucher price and value:")),0,0,1,2); + gl->addWidget(new QLabel(tr("Price:")),1,0); + gl->addWidget(cp=new QComboBox,1,1); + cp->setModel(&mdl); + cp->setEnabled(req->hasRole("_anypricevoucher")); + cp->setEditable(true); + cp->setValidator(®v); + gl->addWidget(new QLabel(tr("Value:")),2,0); + gl->addWidget(cv=new QComboBox,2,1); + cv->setModel(&mdl); + cv->setEditable(req->hasRole("_anyvoucher")); + cv->setValidator(®v); + gl->setRowMinimumHeight(3,15); + gl->setColumnStretch(0,0); + gl->setColumnStretch(1,1); + gl->setRowStretch(3,1); + gl->addLayout(hl=new QHBoxLayout,4,0,1,2); + hl->addStretch(10); + QPushButton*p; + hl->addWidget(p=new QPushButton(tr("Ok")),0); + connect(p,SIGNAL(clicked()),&dlg,SLOT(accept())); + hl->addWidget(p=new QPushButton(tr("Cancel")),0); + connect(p,SIGNAL(clicked()),&dlg,SLOT(reject())); + //wait for it + if(dlg.exec()==QDialog::Accepted){ + //get selection + int price,value; + value=str2cent(cv->currentText()); + if(req->hasRole("_anypricevoucher")) + price=str2cent(cp->currentText()); + else + price=value; + //copy to cart + int cr=cartmodel->rowCount(); + cartmodel->insertRows(cr,1); + cartmodel->setData(cartmodel->index(cr,0),1); + cartmodel->setData(cartmodel->index(cr,0),price,CART_PRICEROLE); + cartmodel->setData(cartmodel->index(cr,0),value,CART_VALUEROLE); + cartmodel->setData(cartmodel->index(cr,0),CART_VOUCHER,CART_TYPEROLE); + cartmodel->setData(cartmodel->index(cr,1),tr("Voucher (price: %1, value %2)").arg(cent2str(price)).arg(cent2str(value))); + carttable->resizeColumnsToContents(); + } +} + void MOverview::cartRemoveItem() { //get selection @@ -843,17 +932,39 @@ void MOverview::cartOrder() cc.appendChild(doc.createTextNode(s)); root.appendChild(cc); } - //scan tickets - // TODO: scan vouchers + //scan tickets & scan vouchers for(int i=0;irowCount();i++){ - int amt=cartmodel->data(cartmodel->index(i,0)).toInt(); - int evid=cartmodel->data(cartmodel->index(i,0),Qt::UserRole).toInt(); - for(int j=0;jindex(i,0); + int tp=cartmodel->data(idx,CART_TYPEROLE).toInt(); + if(tp==CART_TICKET){ + int amt=cartmodel->data(idx).toInt(); + int evid=cartmodel->data(idx,CART_IDROLE).toInt(); + for(int j=0;jdata(idx).toInt(); + int price=cartmodel->data(idx,CART_PRICEROLE).toInt(); + int value=cartmodel->data(idx,CART_VALUEROLE).toInt(); + for(int j=0;jcurrentIndex()>0){ + QDomElement si=doc.createElement("Shipping"); + si.setAttribute("type",cartship->itemData(cartship->currentIndex()).toInt()); + root.appendChild(si); + } + //finalize doc.appendChild(root); //send if(req->request("checkorder",doc.toByteArray())==false){ @@ -880,6 +991,13 @@ void MOverview::customerMgmt() mcl.exec(); } +void MOverview::editShipping() +{ + MShippingEditor se(req,this); + se.exec(); + updateShipping(); +} + void MOverview::tabChanged() { QWidget*w=tab->currentWidget(); @@ -1162,6 +1280,27 @@ void MOverview::ticketReturn() if(r!="")QMessageBox::warning(this,tr("Warning"),trUtf8(r.toUtf8())); } +void MOverview::voucherReturn() +{ + //get ticket + bool ok; + QString tid=QInputDialog::getText(this,tr("Return Voucher"),tr("Please enter the voucher ID to return:"),QLineEdit::Normal,"",&ok); + if(!ok || tid=="")return; + MVoucher vouc(req,tid); + if(!vouc.isValid()){ + QMessageBox::warning(this,tr("Warning"),tr("This is not a valid voucher.")); + return; + } + //check state + if(vouc.isUsed()){ + QMessageBox::warning(this,tr("Warning"),tr("This voucher cannot be returned, it has already been used.")); + return; + } + //submit + QString r=vouc.voucherReturn(); + if(r!="")QMessageBox::warning(this,tr("Warning"),trUtf8(r.toUtf8())); +} + void MOverview::refreshData() { QSettings set; @@ -1172,6 +1311,8 @@ void MOverview::refreshData() updateUsers(); if(set.value("refreshHosts",false).toBool() && req->hasRole("gethosts")) updateHosts(); + if(set.value("refreshShipping",false).toBool() && req->hasRole("getshipping")) + updateShipping(); } void MOverview::setRefresh() @@ -1190,13 +1331,15 @@ void MOverview::setRefresh() hl->addWidget(rate=new QSpinBox,0); rate->setRange(1,999); rate->setValue(set.value("refresh",300).toInt()/60); - QCheckBox *rev,*rus,*rho; + QCheckBox *rev,*rus,*rho,*rsh; vl->addWidget(rev=new QCheckBox(tr("refresh &event list"))); rev->setChecked(set.value("refreshEvents",false).toBool()); vl->addWidget(rus=new QCheckBox(tr("refresh &user list"))); rus->setChecked(set.value("refreshUsers",false).toBool()); vl->addWidget(rho=new QCheckBox(tr("refresh &host list"))); rho->setChecked(set.value("refreshHosts",false).toBool()); + vl->addWidget(rsh=new QCheckBox(tr("refresh &shipping list"))); + rho->setChecked(set.value("refreshShipping",false).toBool()); vl->addSpacing(15); vl->addStretch(10); vl->addLayout(hl=new QHBoxLayout); @@ -1211,6 +1354,7 @@ void MOverview::setRefresh() set.setValue("refreshEvents",rev->isChecked()); set.setValue("refreshUsers",rus->isChecked()); set.setValue("refreshHosts",rho->isChecked()); + set.setValue("refreshShipping",rsh->isChecked()); set.setValue("refresh",rate->value()*60); //reset timer rtimer.stop(); diff --git a/src/overview.h b/src/overview.h index 1128611..97aa944 100644 --- a/src/overview.h +++ b/src/overview.h @@ -60,6 +60,9 @@ class MOverview:public QMainWindow /**cancel the event*/ void eventCancel(); + /**update shipping info*/ + void updateShipping(); + /**get all orders, update list*/ void updateOrders(); /**open order detail window*/ @@ -104,7 +107,7 @@ class MOverview:public QMainWindow /**add a ticket to the cart*/ void cartAddTicket(); /**add a voucher to the cart*/ - //void cartAddVoucher(); + void cartAddVoucher(); /**remove item from the cart*/ void cartRemoveItem(); /**check the order on the server*/ @@ -113,6 +116,9 @@ class MOverview:public QMainWindow /**manage customers*/ void customerMgmt(); + /**edit shipping options*/ + void editShipping(); + /**generic check which tab is active*/ void tabChanged(); @@ -124,6 +130,8 @@ class MOverview:public QMainWindow /**return a ticket*/ void ticketReturn(); + /**return a voucher*/ + void voucherReturn(); /**find an order by ticket*/ void orderByTicket(); /**find/select orders by event*/ @@ -149,7 +157,7 @@ class MOverview:public QMainWindow QPushButton*thishostbutton; QLabel*cartcustomer,*entrancelabel; QTextEdit *cartaddr,*cartcomment; - QComboBox*ordermode; + QComboBox*ordermode,*cartship; QLineEdit*entrancescan; //event list QAction*showoldevents; diff --git a/src/shipping.cpp b/src/shipping.cpp new file mode 100644 index 0000000..22e7521 --- /dev/null +++ b/src/shipping.cpp @@ -0,0 +1,291 @@ +// +// C++ Implementation: shipping +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2008 +// +// Copyright: See README/COPYING files that come with this distribution +// +// + +#include "shipping.h" +#include "webrequest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MShipping::MShipping(MWebRequest*r) +{ + req=r; + m_id=-1; + m_price=0; + m_web=m_any=false; +} + +MShipping::MShipping(MWebRequest*r,const QDomElement&e) +{ + req=r; + bool b; + m_id=e.attribute("type","-1").toInt(&b); + if(!b)m_id=-1; + m_price=e.attribute("price","0").toInt(); + m_web=e.attribute("web","0")=="1"; + m_any=e.attribute("anyUser","0")=="1"; + m_descr=e.text(); +} +MShipping::MShipping(const MShipping&s) +{ + req=s.req; + m_id=s.m_id; + m_price=s.m_price; + m_web=s.m_web; + m_any=s.m_any; + m_descr=s.m_descr; +} +MShipping& MShipping::operator=(const MShipping&s) +{ + req=s.req; + m_id=s.m_id; + m_price=s.m_price; + m_web=s.m_web; + m_any=s.m_any; + m_descr=s.m_descr; + return *this; +} + +bool MShipping::isValid()const +{ + return req && m_id>=0; +} + +int MShipping::id()const{return m_id;} +int MShipping::price()const{return m_price;} + +QString MShipping::priceString()const +{ + return QString::number(m_price/100)+QCoreApplication::translate("MShipping",".","decimal dot")+QString().sprintf("%02d",m_price%100); +} + +bool MShipping::webUsable()const{return m_web;} +bool MShipping::anyUserCanUse()const{return m_any;} + +bool MShipping::canUse()const +{ + if(m_any)return true; + if(!req)return false; + return req->hasRole("_anyshipping"); +} + +QString MShipping::description()const{return m_descr;} + +void MShipping::setPrice(int p) +{ + if(p>=0)m_price=p; +} + +void MShipping::setWebUsable(bool b){m_web=b;} + +void MShipping::setAnyUserCanUse(bool b){m_any=b;} + +void MShipping::setDescription(QString d){m_descr=d;} + +bool MShipping::save(bool asnew) +{ + if(!req)return false; + //create XML + QDomDocument doc; + QDomElement el=doc.createElement("ShippingOption"); + if(isValid()&&!asnew)el.setAttribute("type",m_id); + el.setAttribute("price",m_price); + el.setAttribute("web",m_web?"1":"0"); + el.setAttribute("anyUser",m_any?"1":"0"); + el.appendChild(doc.createTextNode(m_descr)); + doc.appendChild(el); + //execute request + if(!req->request("setshipping",doc.toByteArray()))return false; + if(req->responseStatus()!=MWebRequest::Ok)return false; + //get result + bool b; + int id=QString::fromAscii(req->responseBody()).trimmed().toInt(&b); + if(b && id>=0)m_id=id; + return true; +} + +/******************************************************************************************/ + +MShippingEditor::MShippingEditor(MWebRequest*r,QWidget*par) + :QDialog(par) +{ + req=r; + all=req->getAllShipping(); + setWindowTitle(tr("Edit Shipping Options")); + + QHBoxLayout*hl; + QVBoxLayout*vl,*vl2; + setLayout(vl=new QVBoxLayout); + vl->addLayout(hl=new QHBoxLayout,1); + hl->addWidget(table=new QTableView,1); + table->setModel(model=new QStandardItemModel(this)); + table->setEditTriggers(QAbstractItemView::NoEditTriggers); + updateTable(); + hl->addLayout(vl2=new QVBoxLayout,0); + QPushButton*p; + vl2->addWidget(p=new QPushButton(tr("Change Description"))); + connect(p,SIGNAL(clicked()),this,SLOT(changeDescription())); + vl2->addWidget(p=new QPushButton(tr("Change Price"))); + connect(p,SIGNAL(clicked()),this,SLOT(changePrice())); + vl2->addWidget(p=new QPushButton(tr("Change Availability"))); + connect(p,SIGNAL(clicked()),this,SLOT(changeAvail())); + vl2->addSpacing(20); + vl2->addWidget(p=new QPushButton(tr("Add Option"))); + connect(p,SIGNAL(clicked()),this,SLOT(addNew())); + vl2->addWidget(p=new QPushButton(tr("Delete Option"))); + connect(p,SIGNAL(clicked()),this,SLOT(deleteShip())); + vl2->addStretch(1); + + vl->addSpacing(15); + vl->addLayout(hl=new QHBoxLayout,0); + hl->addStretch(10); + hl->addWidget(p=new QPushButton(tr("Ok"))); + connect(p,SIGNAL(clicked()),this,SLOT(accept())); + hl->addWidget(p=new QPushButton(tr("Cancel"))); + connect(p,SIGNAL(clicked()),this,SLOT(reject())); +} +void MShippingEditor::updateTable() +{ + model->clear(); + model->insertColumns(0,5); + model->insertRows(0,all.size()); + model->setHorizontalHeaderLabels(QStringList()<setData(model->index(i,0),all[i].id()); + model->setData(model->index(i,1),all[i].description()); + model->setData(model->index(i,2),all[i].priceString()); + model->setData(model->index(i,3),all[i].webUsable()?tr("Yes"):tr("No")); + model->setData(model->index(i,4),all[i].anyUserCanUse()?tr("Yes"):tr("No")); + } + table->resizeColumnsToContents(); +} + +void MShippingEditor::changeDescription() +{ + //find item + QModelIndexList lst=table->selectionModel()->selectedIndexes(); + if(lst.size()<1)return; + QModelIndex idx=lst[0]; + //get shipping + MShipping s=all[idx.row()]; + //get new value + QString r=QInputDialog::getText(this,tr("Shipping Option Description"),tr("Please select a new description for this shipping option:"),QLineEdit::Normal,s.description()); + if(r=="")return; + s.setDescription(r); + if(!s.save()){ + QMessageBox::warning(this,tr("Warning"),tr("Could not store the changes.")); + return; + } + all[idx.row()]=s; + updateTable(); +} + +void MShippingEditor::changePrice() +{ + //find item + QModelIndexList lst=table->selectionModel()->selectedIndexes(); + if(lst.size()<1)return; + QModelIndex idx=lst[0]; + //get shipping + MShipping s=all[idx.row()]; + //get new value + bool b; + double r=QInputDialog::getDouble(this,tr("Shipping Option Price"),tr("Please select a new price for this shipping option:"),s.price()/100.,0,1e9,2,&b); + if(!b)return; + s.setPrice(int(r*100.)); + if(!s.save()){ + QMessageBox::warning(this,tr("Warning"),tr("Could not store the changes.")); + return; + } + all[idx.row()]=s; + updateTable(); +} +void MShippingEditor::changeAvail() +{ + //find item + QModelIndexList lst=table->selectionModel()->selectedIndexes(); + if(lst.size()<1)return; + QModelIndex idx=lst[0]; + //get shipping + MShipping s=all[idx.row()]; + //get new value + bool b; + QStringList opt; + opt<selectionModel()->selectedIndexes(); + if(lst.size()<1)return; + QModelIndex idx=lst[0]; + //get shipping + int id=all[idx.row()].id(); + if(!req->request("deleteshipping",QString::number(id).toAscii())){ + QMessageBox::warning(this,tr("Warning"),tr("Unable to delete this option.")); + return; + } + if(req->responseStatus()!=MWebRequest::Ok){ + QMessageBox::warning(this,tr("Warning"),tr("Unable to delete this option.")); + return; + } + all.removeAt(idx.row()); + updateTable(); +} diff --git a/src/shipping.h b/src/shipping.h new file mode 100644 index 0000000..2587dcc --- /dev/null +++ b/src/shipping.h @@ -0,0 +1,104 @@ +// +// C++ Interface: shipping +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2008 +// +// Copyright: See README/COPYING files that come with this distribution +// +// + +#ifndef MAGICSMOKE_SHIPPING_H +#define MAGICSMOKE_SHIPPING_H + +#include +#include + +class QDomElement; +class MWebRequest; + +/**this class represents a shipping type*/ +class MShipping +{ + public: + /**creates an invalid (empty) shipping type; if the MWebRequest pointer is given the object can later be saved to the DB*/ + MShipping(MWebRequest*r=0); + /**creates a shipping type from XML*/ + MShipping(MWebRequest*,const QDomElement&); + /**copies a shipping type*/ + MShipping(const MShipping&); + /**copies a shipping type*/ + MShipping& operator=(const MShipping&); + + /**returns whether the shipping type is valid*/ + bool isValid()const; + + /**returns the ID of the shipping type*/ + int id()const; + + /**returns the price (cent) of the type*/ + int price()const; + + /**returns the price as string*/ + QString priceString()const; + + /**returns whether the type is usable via web interface*/ + bool webUsable()const; + + /**returns whether the type is usable by any user*/ + bool anyUserCanUse()const; + + /**returns whether the currently open session/user can use it*/ + bool canUse()const; + + /**returns the description of the type*/ + QString description()const; + + /**changes the price (does not write to the DB, use save())*/ + void setPrice(int); + + /**changes whether web users can use it (does not write to the DB, use save())*/ + void setWebUsable(bool); + + /**change whether any user can use it (does not write to the DB, use save())*/ + void setAnyUserCanUse(bool); + + /**change the description (does not write to the DB, use save())*/ + void setDescription(QString); + + /**save data to the database (if the object did not come from the DB or if asnew is true it will be created); returns true on success*/ + bool save(bool asnew=false); + private: + MWebRequest*req; + int m_id,m_price; + bool m_web,m_any; + QString m_descr; +}; + +class QStandardItemModel; +class QTableView; + +class MShippingEditor:public QDialog +{ + Q_OBJECT + public: + MShippingEditor(MWebRequest*,QWidget*); + + private slots: + void changeDescription(); + void changePrice(); + void changeAvail(); + void addNew(); + void deleteShip(); + void updateTable(); + + private: + MWebRequest*req; + QListall; + QStandardItemModel*model; + QTableView*table; +}; + +#endif diff --git a/src/smoke.pro b/src/smoke.pro index e37aeaa..a62f6df 100644 --- a/src/smoke.pro +++ b/src/smoke.pro @@ -7,7 +7,6 @@ CONFIG += debug CONFIG += qt thread QT += xml network -MSVERSION = "0.1 alpha" win32-* { #RC-File containing the icon: @@ -24,6 +23,7 @@ RCC_DIR = .ctmp SOURCES = \ main.cpp \ debug.cpp \ + misc.cpp \ keygen.cpp \ mainwindow.cpp \ hmac.cpp \ @@ -36,13 +36,15 @@ SOURCES = \ user.cpp \ host.cpp \ order.cpp \ + shipping.cpp \ customer.cpp \ checkdlg.cpp \ eventsummary.cpp \ odtrender.cpp \ ticketrender.cpp \ orderwin.cpp \ - labeldlg.cpp + labeldlg.cpp \ + version.cpp HEADERS = \ keygen.h \ @@ -57,13 +59,15 @@ HEADERS = \ user.h \ host.h \ order.h \ + shipping.h \ customer.h \ checkdlg.h \ eventsummary.h \ odtrender.h \ ticketrender.h \ orderwin.h \ - labeldlg.h + labeldlg.h \ + misc.h #some PHP files are listed in this file to scan them for translatable items #use genphpscan.sh to regenerate it. diff --git a/src/ticketrender.cpp b/src/ticketrender.cpp index 41b8adf..9ed75de 100644 --- a/src/ticketrender.cpp +++ b/src/ticketrender.cpp @@ -30,25 +30,25 @@ #include "order.h" -class MTicketRendererPrivate +class MLabelRendererPrivate { protected: - friend class MTicketRenderer; + friend class MLabelRenderer; - MTicketRendererPrivate(QString file,MTicketRenderer*p); - ~MTicketRendererPrivate(); + MLabelRendererPrivate(QString file,MLabelRenderer*p); + ~MLabelRendererPrivate(); //part of the constructor void prepare(QUnZip&); - //called by MTicketRenderer - bool render(const MTicket&,QPaintDevice&,QPainter*,QPointF); + //called by MLabelRenderer + bool render(const MLabel&,QPaintDevice&,QPainter*,QPointF); - //called by MTicketRenderer - QSizeF ticketSize(const QPaintDevice&); + //called by MLabelRenderer + QSizeF labelSize(const QPaintDevice&); private: - MTicketRenderer*parent; + MLabelRenderer*parent; //internal font-IDs QList fdb; //image cache: @@ -57,19 +57,16 @@ class MTicketRendererPrivate QDomDocument txml; //contains the unit of measurement QString unit; - //contains the size of the ticket in "unit" + //contains the size of the Label in "unit" QSizeF tsize; //after constructor: contains whether this object is correctly initialized bool canrender; //does the actual rendering work - void render(QDomElement&,const MTicket&,QPaintDevice&,QPainter*,QPointF); + void render(QDomElement&,const MLabel&,QPaintDevice&,QPainter*,QPointF); //renders a single line - QString renderLine(const MTicket&,QString); - - //gets variable content from the ticket - QString getVariable(const MTicket&,QString); + QString renderLine(const MLabel&,QString); //parses element to extract offset QPointF getoffset(QDomElement&,bool); @@ -84,12 +81,12 @@ class MTicketRendererPrivate QSizeF tonatural(const QPaintDevice&,QSizeF); }; -MTicketRenderer::MTicketRenderer(QString file) +MLabelRenderer::MLabelRenderer(QString file) { - d=new MTicketRendererPrivate(file,this); + d=new MLabelRendererPrivate(file,this); } -MTicketRendererPrivate::MTicketRendererPrivate(QString file,MTicketRenderer*p) +MLabelRendererPrivate::MLabelRendererPrivate(QString file,MLabelRenderer*p) { parent=p; canrender=false; @@ -101,14 +98,14 @@ MTicketRendererPrivate::MTicketRendererPrivate(QString file,MTicketRenderer*p) prepare(temp); temp.close(); tfile.close(); - qDebug("Ticket Renderer initialized: %s",canrender?"ready!":"error, can't render."); + qDebug("Label Renderer initialized: %s",canrender?"ready!":"error, can't render."); } -void MTicketRendererPrivate::prepare(QUnZip&temp) +void MLabelRendererPrivate::prepare(QUnZip&temp) { //make sure this is a valid ZIP file if(!temp.locateFile("template.xml")){ - qDebug("Ticket renderer: can't find template.xml"); + qDebug("Label renderer: can't find template.xml"); canrender=false; return; } @@ -117,7 +114,7 @@ void MTicketRendererPrivate::prepare(QUnZip&temp) buffer.open(QBuffer::ReadWrite); temp.getCurrentFile(buffer); if(!txml.setContent(buffer.data())){ - qDebug("Ticket renderer: can't parse template.xml - XML fault."); + qDebug("Label renderer: can't parse template.xml - XML fault."); canrender=false; return; } @@ -127,7 +124,7 @@ void MTicketRendererPrivate::prepare(QUnZip&temp) QDomElement doc=txml.documentElement(); unit=doc.attribute("unit","mm"); if(unit!="mm"&&unit!="in"){ - qDebug("Ticket renderer: illegal unit in template.xml."); + qDebug("Label renderer: illegal unit in template.xml."); canrender=false; return; } @@ -141,23 +138,23 @@ void MTicketRendererPrivate::prepare(QUnZip&temp) if(el.isNull())continue; QString fn=el.attribute("file"); if(fn==""){ - qDebug("Ticket renderer warning: Font element without file attribute. Line %i Column %i.",el.lineNumber(),el.columnNumber()); + qDebug("Label renderer warning: Font element without file attribute. Line %i Column %i.",el.lineNumber(),el.columnNumber()); continue; } if(fndb.contains(fn)){ - qDebug("Ticket renderer warning: Font file %s was loaded more than once. Line %i Column %i.",fn.toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer warning: Font file %s was loaded more than once. Line %i Column %i.",fn.toAscii().data(),el.lineNumber(),el.columnNumber()); continue; } fndb.append(fn); if(!temp.locateFile(fn)){ - qDebug("Ticket renderer warning: Font element references unknown file. Line %i Column %i.",el.lineNumber(),el.columnNumber()); + qDebug("Label renderer warning: Font element references unknown file. Line %i Column %i.",el.lineNumber(),el.columnNumber()); continue; } QBuffer buffer; temp.getCurrentFile(buffer); int fid=QFontDatabase::addApplicationFontFromData(buffer.data()); if(fid<0){ - qDebug("Ticket renderer warning: Font could not be loaded: %s",fn.toAscii().data()); + qDebug("Label renderer warning: Font could not be loaded: %s",fn.toAscii().data()); continue; } fdb.append(fid); @@ -169,14 +166,14 @@ void MTicketRendererPrivate::prepare(QUnZip&temp) if(el.isNull())continue; QString fn=el.attribute("file"); if(fn==""){ - qDebug("Ticket renderer error: Picture element without file attribute. Line %i Column %i.",el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Picture element without file attribute. Line %i Column %i.",el.lineNumber(),el.columnNumber()); //failed pictures are fatal canrender=false; return; } if(idb.contains(fn))continue; if(!temp.locateFile(fn)){ - qDebug("Ticket renderer error: Picture element references unknown file. Line %i Column %i.",el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Picture element references unknown file. Line %i Column %i.",el.lineNumber(),el.columnNumber()); //failed pictures are fatal canrender=false; return; @@ -185,7 +182,7 @@ void MTicketRendererPrivate::prepare(QUnZip&temp) temp.getCurrentFile(buffer); QImage img; if(!img.loadFromData(buffer.data())){ - qDebug("Ticket renderer error: Picture file %s could not be interpreted.",fn.toAscii().data()); + qDebug("Label renderer error: Picture file %s could not be interpreted.",fn.toAscii().data()); //failed pictures are fatal canrender=false; return; @@ -194,28 +191,28 @@ void MTicketRendererPrivate::prepare(QUnZip&temp) } } -MTicketRenderer::~MTicketRenderer() +MLabelRenderer::~MLabelRenderer() { delete d; d=0; } -MTicketRendererPrivate::~MTicketRendererPrivate() +MLabelRendererPrivate::~MLabelRendererPrivate() { //delete fonts for(int i=0;irender(ticket,pdev,painter,offset); } -bool MTicketRendererPrivate::render(const MTicket&ticket,QPaintDevice&pdev,QPainter*painter,QPointF offset) +bool MLabelRendererPrivate::render(const MLabel&ticket,QPaintDevice&pdev,QPainter*painter,QPointF offset) { //sanity check if(!canrender){ - qDebug("Ticket Renderer: render called, but can't render."); + qDebug("Label Renderer: render called, but can't render."); return false; } //actually render @@ -225,12 +222,12 @@ bool MTicketRendererPrivate::render(const MTicket&ticket,QPaintDevice&pdev,QPain return canrender; } -QPointF MTicketRendererPrivate::getoffset(QDomElement&el,bool isfatal) +QPointF MLabelRendererPrivate::getoffset(QDomElement&el,bool isfatal) { QStringList off=el.attribute("offset").split(" "); if(off.size()!=2){ if(isfatal){ - qDebug("Ticket renderer error: Illegal offset in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Illegal offset in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); canrender=false; } return QPointF(); @@ -240,7 +237,7 @@ QPointF MTicketRendererPrivate::getoffset(QDomElement&el,bool isfatal) ret.setX(off[0].toDouble(&b)); if(!b){ if(isfatal){ - qDebug("Ticket renderer error: Illegal offset in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Illegal offset in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); canrender=false; } return QPointF(); @@ -248,7 +245,7 @@ QPointF MTicketRendererPrivate::getoffset(QDomElement&el,bool isfatal) ret.setY(off[1].toDouble(&b)); if(!b){ if(isfatal){ - qDebug("Ticket renderer error: Illegal offset in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Illegal offset in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); canrender=false; } return QPointF(); @@ -256,12 +253,12 @@ QPointF MTicketRendererPrivate::getoffset(QDomElement&el,bool isfatal) return ret; } -QSizeF MTicketRendererPrivate::getsize(QDomElement&el,bool isfatal) +QSizeF MLabelRendererPrivate::getsize(QDomElement&el,bool isfatal) { QStringList lst=el.attribute("size").split(" "); if(lst.size()!=2){ if(isfatal){ - qDebug("Ticket renderer error: Illegal size (%i items) in %s at line %i column %i.",lst.size(),el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Illegal size (%i items) in %s at line %i column %i.",lst.size(),el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); canrender=false; } return QSizeF(); @@ -271,7 +268,7 @@ QSizeF MTicketRendererPrivate::getsize(QDomElement&el,bool isfatal) ret.setWidth(lst[0].toDouble(&b)); if(!b){ if(isfatal){ - qDebug("Ticket renderer error: Illegal size (invalid width) in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Illegal size (invalid width) in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); canrender=false; } return QSizeF(); @@ -279,7 +276,7 @@ QSizeF MTicketRendererPrivate::getsize(QDomElement&el,bool isfatal) ret.setHeight(lst[1].toDouble(&b)); if(!b){ if(isfatal){ - qDebug("Ticket renderer error: Illegal size (invalid height) in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: Illegal size (invalid height) in %s at line %i column %i.",el.tagName().toAscii().data(),el.lineNumber(),el.columnNumber()); canrender=false; } return QSizeF(); @@ -287,7 +284,7 @@ QSizeF MTicketRendererPrivate::getsize(QDomElement&el,bool isfatal) return ret; } -QPointF MTicketRendererPrivate::tonatural(const QPaintDevice&dev,QPointF p) +QPointF MLabelRendererPrivate::tonatural(const QPaintDevice&dev,QPointF p) { double fac; if(unit=="mm")fac=25.4; @@ -297,7 +294,7 @@ QPointF MTicketRendererPrivate::tonatural(const QPaintDevice&dev,QPointF p) return p; } -QSizeF MTicketRendererPrivate::tonatural(const QPaintDevice&dev,QSizeF s) +QSizeF MLabelRendererPrivate::tonatural(const QPaintDevice&dev,QSizeF s) { double fac; if(unit=="mm")fac=25.4; @@ -307,7 +304,7 @@ QSizeF MTicketRendererPrivate::tonatural(const QPaintDevice&dev,QSizeF s) return s; } -void MTicketRendererPrivate::render(QDomElement&sup, const MTicket&tick, QPaintDevice&pdev, QPainter*painter, QPointF noff) +void MLabelRendererPrivate::render(QDomElement&sup, const MLabel&tick, QPaintDevice&pdev, QPainter*painter, QPointF noff) { //initialize painter QPainter *paint; @@ -382,13 +379,13 @@ void MTicketRendererPrivate::render(QDomElement&sup, const MTicket&tick, QPaintD if(!canrender){ return; } - QString cd=getVariable(tick,"TICKETID"); + QString cd=tick.getVariable("BARCODE"); //paint //TODO: find a way to switch off antialiasing QRectF rect(tonatural(pdev,off)+noff,tonatural(pdev,sz)); paint->drawImage(rect,code39(cd)); }else{ - qDebug("Ticket renderer error: unknown element %s in ticket template at line %i column %i.",enm.toAscii().data(),el.lineNumber(),el.columnNumber()); + qDebug("Label renderer error: unknown element %s in label template at line %i column %i.",enm.toAscii().data(),el.lineNumber(),el.columnNumber()); canrender=false; return; } @@ -397,7 +394,7 @@ void MTicketRendererPrivate::render(QDomElement&sup, const MTicket&tick, QPaintD delete paint; } -QString MTicketRendererPrivate::renderLine(const MTicket&tick,QString line) +QString MLabelRendererPrivate::renderLine(const MLabel&tick,QString line) { QString ret,vname; bool isvar=false; @@ -411,7 +408,7 @@ QString MTicketRendererPrivate::renderLine(const MTicket&tick,QString line) if(vname=="")ret+="@"; else{ //this is a variable, get value - ret+=getVariable(tick,vname); + ret+=tick.getVariable(vname); } //reset mode isvar=false; @@ -447,9 +444,31 @@ QString MTicketRendererPrivate::renderLine(const MTicket&tick,QString line) return ret; } -QString MTicketRendererPrivate::getVariable(const MTicket&tick,QString var) +QSizeF MLabelRenderer::labelSize(const QPaintDevice&dev) +{ + return d->labelSize(dev); +} + +QSizeF MLabelRendererPrivate::labelSize(const QPaintDevice&dev) +{ + return tonatural(dev,tsize); +} + +MLabel::MLabel(){} +MLabel::~MLabel(){} + +class MTicketLabel:public MLabel +{ + public: + MTicketLabel(const MTicket&t):tick(t){} + QString getVariable(QString var)const; + private: + MTicket tick; +}; + +QString MTicketLabel::getVariable(QString var)const { - if(var=="TICKETID")return tick.ticketID(); + if(var=="TICKETID"||var=="BARCODE")return tick.ticketID(); if(var=="PRICE")return tick.priceString(); if(var=="DATETIME")return tick.event().startTimeString(); if(var=="ROOM")return tick.event().room(); @@ -458,12 +477,31 @@ QString MTicketRendererPrivate::getVariable(const MTicket&tick,QString var) return ""; } -QSizeF MTicketRenderer::ticketSize(const QPaintDevice&dev) +MTicketRenderer::MTicketRenderer(QString f):MLabelRenderer(f){} +bool MTicketRenderer::render(const MTicket&label,QPaintDevice&pdev,QPainter*painter,QPointF offset) { - return d->ticketSize(dev); + return MLabelRenderer::render(MTicketLabel(label),pdev,painter,offset); } -QSizeF MTicketRendererPrivate::ticketSize(const QPaintDevice&dev) +class MVoucherLabel:public MLabel { - return tonatural(dev,tsize); + public: + MVoucherLabel(const MVoucher&t):vouc(t){} + QString getVariable(QString var)const; + private: + MVoucher vouc; +}; + +QString MVoucherLabel::getVariable(QString var)const +{ + if(var=="VOUCHERID"||var=="BARCODE")return vouc.voucherID(); + if(var=="PRICE")return vouc.priceString(); + if(var=="VALUE")return vouc.valueString(); + return ""; +} + +MVoucherRenderer::MVoucherRenderer(QString f):MLabelRenderer(f){} +bool MVoucherRenderer::render(const MVoucher&label,QPaintDevice&pdev,QPainter*painter,QPointF offset) +{ + return MLabelRenderer::render(MVoucherLabel(label),pdev,painter,offset); } diff --git a/src/ticketrender.h b/src/ticketrender.h index b07ce48..ce424c4 100644 --- a/src/ticketrender.h +++ b/src/ticketrender.h @@ -17,31 +17,62 @@ #include #include -class MTicketRendererPrivate; +class MLabelRendererPrivate; class MTicket; +class MVoucher; class QPaintDevice; class QPainter; -/**abstract base class for all ODT rendering classes*/ -class MTicketRenderer +/**base class that describes labels*/ +class MLabel +{ + public: + /**constructs the label*/ + MLabel(); + /**deconstructs the label*/ + virtual ~MLabel(); + + /**abstract: overwrite this to return data for a label*/ + virtual QString getVariable(QString)const=0; +}; + +/**base class for all label rendering classes*/ +class MLabelRenderer { public: /**instantiates a renderer loaded from template file*/ - MTicketRenderer(QString file); + MLabelRenderer(QString file); /**deletes the renderer*/ - virtual ~MTicketRenderer(); + virtual ~MLabelRenderer(); /**renders the ticket; returns whether the rendering was successful*/ - virtual bool render(const MTicket&ticket,QPaintDevice&pdev,QPainter*painter=0,QPointF offset=QPointF()); + virtual bool render(const MLabel&label,QPaintDevice&pdev,QPainter*painter=0,QPointF offset=QPointF()); /**returns the size of the ticket in the coordinates of the specified paint device (used for preview)*/ - virtual QSizeF ticketSize(const QPaintDevice&); + virtual QSizeF labelSize(const QPaintDevice&); protected: - friend class MTicketRendererPrivate; + friend class MLabelRendererPrivate; private: - MTicketRendererPrivate*d; + MLabelRendererPrivate*d; +}; + +/**convenience class: renders vouchers directly*/ +class MVoucherRenderer:public MLabelRenderer +{ + public: + MVoucherRenderer(QString f); + bool render(const MVoucher&label,QPaintDevice&pdev,QPainter*painter=0,QPointF offset=QPointF()); }; +/**convenience class: renders vouchers directly*/ +class MTicketRenderer:public MLabelRenderer +{ + public: + MTicketRenderer(QString f); + bool render(const MTicket&label,QPaintDevice&pdev,QPainter*painter=0,QPointF offset=QPointF()); +}; + + #endif diff --git a/src/version.cpp b/src/version.cpp new file mode 100644 index 0000000..7c2ddc8 --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,17 @@ +// +// C++ Implementation: version information +// +// Description: processes version.inc to form some meaningful C++ code +// +// +// Author: Konrad Rosenbaum , (C) 2008 +// +// Copyright: See README/COPYING files that come with this distribution +// +// + +#include "version.h" + +#define defversion(x,y) const char VERSION_##x[] = #y; +#include "../www/inc/machine/version.inc" +#undef defversion diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..b8a12eb --- /dev/null +++ b/src/version.h @@ -0,0 +1,4 @@ + +#define defversion(x,y) extern const char VERSION_##x[]; +#include "../www/inc/machine/version.inc" +#undef defversion diff --git a/src/webrequest.cpp b/src/webrequest.cpp index 2a80131..10d622a 100644 --- a/src/webrequest.cpp +++ b/src/webrequest.cpp @@ -10,10 +10,11 @@ // // -#include "webrequest.h" -#include "keygen.h" #include "hmac.h" +#include "keygen.h" #include "main.h" +#include "version.h" +#include "webrequest.h" #include #include @@ -478,6 +479,48 @@ QList MWebRequest::getAllOrders() return ret; } +QList MWebRequest::getAllShipping() +{ + if(!hasRole("getshipping"))return QList(); + errstr=""; + if(!request("getshipping",""))return QList(); + if(responseStatus()!=Ok)return QList(); + //parse return document + QDomDocument doc; + QString msg;int ln,cl; + if(!doc.setContent(rspdata,&msg,&ln,&cl)){ + errstr=tr("Error parsing ShippingList XML data (line %1 column %2): %3").arg(ln).arg(cl).arg(msg); + return QList(); + } + QDomElement root=doc.documentElement(); + QDomNodeList nl=root.elementsByTagName("ShippingOption"); + QListret; + for(int i=0;iMWebRequest::getVoucherPrices() +{ + if(!hasRole("getvoucherprices"))return QList(); + errstr=""; + if(!request("getvoucherprices",""))return QList(); + if(responseStatus()!=Ok)return QList(); + //parse return document + QStringList lst=QString::fromAscii(responseBody()).split(" "); + QListret; + for(int i=0;i MWebRequest::getOrdersByEvents(QListevids) { errstr=""; diff --git a/src/webrequest.h b/src/webrequest.h index c34e45b..97fdf9a 100644 --- a/src/webrequest.h +++ b/src/webrequest.h @@ -13,21 +13,22 @@ #ifndef MAGICSMOKE_WEBREQUEST_H #define MAGICSMOKE_WEBREQUEST_H -#include -#include #include -#include +#include #include -#include #include -#include +#include +#include +#include +#include -#include "room.h" +#include "customer.h" #include "event.h" -#include "user.h" #include "host.h" -#include "customer.h" #include "order.h" +#include "room.h" +#include "shipping.h" +#include "user.h" /**abstraction of requests to the web server, handles sessions and all data transfer*/ class MWebRequest:public QObject @@ -94,6 +95,12 @@ class MWebRequest:public QObject /**returns a list of all orders*/ QListgetAllOrders(); + /**returns a list of all available shipping methods*/ + QListgetAllShipping(); + + /**returns valid voucher prices in cents*/ + QListgetVoucherPrices(); + /**returns a list of all orders that order from one of the specified events*/ QListgetOrdersByEvents(QList); diff --git a/www/inc/classes/order.php b/www/inc/classes/order.php index c60ed14..6d7b0eb 100644 --- a/www/inc/classes/order.php +++ b/www/inc/classes/order.php @@ -19,8 +19,8 @@ define("ORDER_PLACED",0); define("ORDER_SENT",1); /**the order has been cancelled by the user (this is only possible as long as no money has been paid and nothing has been sent yet)*/ define("ORDER_CANCELLED",2); -/**the order has been finalized; no more changes possible*/ -define("ORDER_CLOSED",10); +/**the order has been finalized; no more changes possible; TODO: define exactly what this means*/ +define("ORDER_CLOSED",0x80); /**order validation: output XML*/ define("VALIDATEORDER_XML",1); @@ -44,6 +44,8 @@ class Order private $amountpaid=0; private $ordertime=false; private $senttime=false; + private $shippingcosts=0; + private $shippingtype=false; //to be submitted private $newtickets; private $newticketamounts; @@ -68,6 +70,10 @@ class Order $this->amountpaid=$res[0]["amountpaid"]; $this->ordertime=$res[0]["ordertime"]; $this->senttime=$res[0]["senttime"]; + if(!$db->isNull($res[0]["shippingtype"])){ + $this->shippingcosts=$res[0]["shippingcosts"]+0; + $this->shippingtype=$res[0]["shippingtype"]; + } } $this->newtickets=array(); $this->newticketamounts=array(); @@ -129,6 +135,17 @@ class Order $this->newtickets[$eid][]=$price; } + /**used by XML functions: add a voucher (if value is omitted it equals price); returns true on success*/ + public function addVoucher($value,$price=false) + { + if(!is_numeric($value))return false; + if($price===false)$price=$value; + if(!is_numeric($price))return false; + if($price<0 || $value<=0)return false; + $this->newvouchers[]=array("price"=>$price,"value"=>$value); + return true; + } + /**sets the customer of this order; returns true on success, false on failure*/ public function setCustomer($cust) { @@ -144,6 +161,27 @@ class Order return $this->setCustomer(new Customer($cust)); } + /**sets the shipping type/price of the order; if price is omitted, it is taken from the DB; if type is false it means no shipping involved; returns true on success*/ + public function setShipping($stype,$sprice=false) + { + if(!$this->canChange())return false; + //check for no shipping + if($stype===false){ + $this->shippingtype=false; + $this->shippingcosts=0; + } + //get DB data, check that type exists + global $db; + $res=$db->select("shipping","cost","shipid=".$db->escapeInt($stype)); + if($res===false || count($res)<1)return false; + //check price + if($sprice===false)$sprice=$res[0]["cost"]; + //remember + $this->shippingtype=$stype+0; + $this->shippingcosts=$sprice+0; + return true; + } + /**places/finalizes the order; returns false on failure, true on success or if the order already was finalized()*/ public function placeOrder($isSale=false) { @@ -159,14 +197,14 @@ class Order return false; } // print(2); - //create order + //create order, incl shipping $this->status=ORDER_PLACED; if(isset($session))$usr=$session->getUser(); else $usr=false; $this->seller=$usr; $this->ordertime=time(); $this->amountpaid=0; - $this->orderid=$db->insert("order",array("customerid"=>$this->customerid,"soldby"=>$usr,"deliveryaddress"=>$this->deliveryaddress,"status"=>$this->status,"ordertime"=>$this->ordertime,"comments"=>$this->comment,"amountpaid"=>0)); + $this->orderid=$db->insert("order",array("customerid"=>$this->customerid,"soldby"=>$usr,"deliveryaddress"=>$this->deliveryaddress,"status"=>$this->status,"ordertime"=>$this->ordertime,"comments"=>$this->comment,"amountpaid"=>0,"shippingtype"=>$this->shippingtype,"shippingcosts"=>$this->shippingcosts)); // print(3); //orderid ok? if($this->orderid===false){ @@ -174,7 +212,7 @@ class Order return false; } //insert tickets - $totalprice=0; + $totalprice=$this->shippingcosts; foreach($this->newtickets as $evid=>$tcs){ $amount=count($tcs); for($i=0;$i<$amount;$i++){ @@ -186,7 +224,13 @@ class Order //TODO: check return code of addToOrder } } - //TODO: insert vouchers + //insert vouchers + foreach($this->newvouchers as $vc){ + $vouc=new Voucher; + $vouc->addToOrder($this->orderid,$vc["price"],$vc["value"]); + $totalprice+=$vc["price"]; + //TODO: check return code of addToOrder + } //update amountpaid for sales if($isSale){ $db->update("order",array("amountpaid"=>$totalprice,"status"=>ORDER_SENT),"orderid=".$db->escapeInt($this->orderid)); @@ -331,9 +375,69 @@ class Order } } - //TODO: check vouchers + //check vouchers + global $session; + $cananyvval=$session->canExecute("_anyvoucher"); + $cananyvprc=$session->canExecute("_anypricevoucher"); + $vvals=array(); + foreach(explode(" ",$db->getConfig("ValidVouchers")) as $v)$vvals[]=$v+0; + foreach($this->newvouchers as $vc){ + $vx=$xml->createElement("Voucher"); + $vx->setAttribute("price",$vc["price"]); + $vx->setAttribute("value",$vc["value"]); + $vx->setAttribute("id",$ftid++); + //check for valid value + if(!$cananyvval && !in_array($vvals)){ + $vx->setAttribute("status",tr("invalidvalue","voucher state")); + $ostat="fail"; + $ret=false; + }else + //check for value==price + if(!$cananyvprc && $vc["price"]!=$vc["value"]){ + $vx->setAttribute("status",tr("invalidprice","voucher state")); + $ostat="fail"; + $ret=false; + }else + $totalprice+=$vc["price"]; + //dump it + $ord->appendChild($vx); + } - //TODO: check shipping + //check shipping + if($this->shippingtype!==false){ + $cananyship=$session->canExecute("_anyshipping"); + $cananysprc=$session->canExecute("_repriceshipping"); + //check shipping type exists + $res=$db->select("shipping","*","shipid=".$db->escapeInt($this->shippingtype)); + $sp=$xml->createElement("Shipping"); + if($res!==false && count($res)>0){ + //check user has right to use this + if(!$res[0]["canallusers"] && !$cananyship){ + $sp->setAttribute("type","-1"); + $sp->setAttribute("price",0); + $sp->appendChild($xml->createTextNode(tr("Shipping type not available to user."))); + $ostat="fail"; + $ret=false; + }else{ + //correct price + if(!$cananysprc) + $this->shippingcosts=$res[0]["cost"]; + //create target + $sp->setAttribute("type",$this->shippingtype); + $sp->setAttribute("price",$this->shippingcosts); + $sp->appendChild($xml->createTextNode($res[0]["description"])); + //add to sum + $totalprice+=$this->shippingcosts; + } + }else{ + $sp->setAttribute("type","-1"); + $sp->setAttribute("price",0); + $sp->appendChild($xml->createTextNode(tr("Illegal shipping type."))); + $ostat="fail"; + $ret=false; + } + $ord->appendChild($sp); + } //add other data and dump XML if($dumpxml){ @@ -386,7 +490,7 @@ class Order $totalprice=0; global $db; $res=$db->select("ticket","ticketid","orderid=".$db->escapeInt($this->orderid)); - if(count($res)>0) + if($res!==false && count($res)>0) foreach($res as $tc){ $tick=new Ticket($tc["ticketid"]); $tx=$xml->createElement("Ticket"); @@ -397,7 +501,32 @@ class Order if($tick->mustBePaid())$totalprice+=$tick->getPrice(); $doc->appendChild($tx); } - //TODO: add vouchers + //add vouchers + $res=$db->select("voucher","voucherid,price,value,isused","orderid=".$db->escapeInt($this->orderid)); + if($res!==false && count($res)>0) + foreach($res as $vc){ + $vx=$xml->createElement("Voucher"); + $vx->setAttribute("id",$vc["voucherid"]); + $vx->setAttribute("price",$vc["price"]); + $vx->setAttribute("value",$vc["value"]); + $vx->setAttribute("used",$vc["isused"]?"1":"0"); + $totalprice+=$vc["price"]; + $doc->appendChild($vx); + } + + //add shipping + if($this->shippingtype !== false){ + $sx=$xml->createElement("Shipping"); + $sx->setAttribute("price",$this->shippingcosts); + $sx->setAttribute("type",$this->shippingtype); + $res=$db->select("shipping","*","shipid=".$db->escapeInt($this->shippingtype)); + if($res!==false && count($res)>0){ + $sx->appendChild($xml->createTextNode($res[0]["description"])); + } + $doc->appendChild($sx); + if($this->status==ORDER_PLACED || $this->status==ORDER_SENT) + $totalprice+=$this->shippingcosts; + } //add sum $doc->setAttribute("totalprice",$totalprice); @@ -417,22 +546,43 @@ class Order return $this->status; } - /**helper function: returns whether the order has outstanding payments/refunds*/ - public function getPaymentStatus() + /**helper function: returns the amount due to be paid; returns a negative value for refunds*/ + public function amountDue() { global $db; //calculate amount due $totalprice=0; $res=$db->select("ticket","ticketid","orderid=".$db->escapeInt($this->orderid)); - if(count($res)>0) + if($res!==false && count($res)>0) foreach($res as $tc){ $tick=new Ticket($tc["ticketid"]); if($tick->mustBePaid())$totalprice+=$tick->getPrice(); } - //TODO: add vouchers - //compare with what has been paid - if($totalprice==$this->amountpaid)return "ok"; - if($totalprice<$this->amountpaid)return "needrefund"; + //add vouchers + $res=$db->select("voucher","price","orderid=".$db->escapeInt($this->orderid)); + if($res!==false && count($res)>0) + foreach($res as $vc){ + $totalprice+=$vc["price"]; + } + //add shipping + if($this->status==ORDER_PLACED || $this->status==ORDER_SENT) + $totalprice+=$this->shippingcosts; + //compare with what has been paid, return diff + return $totalprice-$this->amountpaid; + } + + /**returns the amount already paid*/ + public function amountPaid() + { + return $this->amountpaid; + } + + /**helper function: returns whether the order has outstanding payments/refunds*/ + public function getPaymentStatus() + { + $adue=$this->amountDue(); + if($adue==0)return "ok"; + if($adue<0)return "needrefund"; else return "needpayment"; } @@ -450,14 +600,46 @@ class Order /**sets the order to being cancelled, returns true on success*/ public function setCancelled() { - if(!$this->isValid())return false; - if($this->status!=ORDER_PLACED)return false; global $db; - $db->update("order",array("status"=>ORDER_CANCELLED,"senttime"=>time()),"orderid=".$db->escapeInt($this->orderid)); + $db->beginTransaction(); + //check validity and status + $res=$db->select("order","status","orderid=".$db->escapeInt($this->orderid)); + if($res===false || count($res)<1){ + $db->rollbackTransaction(); + return false; + } + if($res[0]["status"]!=ORDER_PLACED){ + $db->rollbackTransaction(); + return false; + } + //check tickets + $res=$db->select("ticket","status","orderid=".$db->escapeInt($this->orderid)); + for($i=0;$irollbackTransaction(); + return false; + } + } + //check vouchers + $res=$db->select("voucher","price,isused,value","orderid=".$db->escapeInt($this->orderid)); + for($i=0;$irollbackTransaction(); + return false; + } //propagate to tickets $db->update("ticket",array("status"=>TICKET_CANCELLED),"orderid=".$db->escapeInt($this->orderid)); - //TODO: propagate to vouchers - + //propagate to vouchers + $db->update("voucher",array("price"=>0,"value"=>0,"isused"=>0),"orderid=".$db->escapeInt($this->orderid)); + //set order to cancelled + $db->update("order",array("status"=>ORDER_CANCELLED,"senttime"=>time()),"orderid=".$db->escapeInt($this->orderid)); + $db->commitTransaction(); return true; } @@ -494,7 +676,25 @@ function createOrderXml($xmldata,$action) }else $price=-1; $order->addTicket($tc->getAttribute("event")+0,$price); } - //TODO: get vouchers + //get vouchers + foreach($doc->getElementsByTagName("Voucher") as $vc){ + $v=trim($vc->getAttribute("value")); + if($vc->hasAttribute("price")) + $p=trim($vc->getAttribute("price")); + else + $p=false; + $order->addVoucher($v,$p); + } + + //get shipping + foreach($doc->getElementsByTagName("Shipping") as $sp){ + if($sp->hasAttribute("price")) + $p=trim($sp->getAttribute("price")); + else + $p=false; + $t=trim($sp->getAttribute("type")); + $order->setShipping($t,$p); + } //get opt. address foreach($doc->getElementsByTagName("DeliveryAddress") as $da){ @@ -549,14 +749,21 @@ function getOrderListXml($where="") global $db; $xml=new DomDocument; $doc=$xml->createElement("OrderList"); - $res=$db->select("order","orderid,customerid,status,amountpaid",$where); + $res=$db->select("order","orderid,customerid,status,amountpaid,shippingtype,shippingcosts",$where); foreach($res as $ord){ + $price=0; + //check shipping + if(!$db->isNull($ord["shippingtype"])) + $price+=$ord["shippingcosts"]; //collect tickets $tres=$db->select("ticket","price,status","orderid=".$db->escapeInt($ord["orderid"])); - $price=0; foreach($tres as $tc) if(($tc["status"]&TICKET_MPAY)!=0) $price+=$tc["price"]; + //collect vouchers + $tres=$db->select("voucher","price","orderid=".$db->escapeInt($ord["orderid"])); + foreach($tres as $tc) + $price+=$tc["price"]; //generate XML $ox=$xml->createElement("Order"); $ox->setAttribute("id",$ord["orderid"]); @@ -775,7 +982,142 @@ function setOrderCommentXml($txt) header("X-MagicSmoke-Status: Ok"); else{ header("X-MagicSmoke-Status: Error"); - echo "Unable to update order comment."; + echo tr("Unable to update order comment."); } } + +//change the shipping method on an order +function setOrderShippingXml($txt) +{ + //parse XML data + $xml=new DomDocument; + $xml->loadXml($txt); + $doc=$xml->documentElement; + $oid=$doc->getAttribute("orderid")+0; + if($doc->hasAttribute("type")) + $type=$doc->getAttribute("type"); + else + $type=false; + if($doc->hasAttribute("price")) + $price=$doc->getAttribute("price"); + else + $price=false; + //set shipping + global $db; + global $session; + $db->beginTransaction(); + $res=$db->select("order","status","orderid=".$oid); + if($res===false || count($res)<1){ + header("X-MagicSmoke-Status: Error"); + echo tr("Invalid Order."); + $db->rollbackTransaction(); + return; + } + //TODO: check order status (define rules first) + if($type===false){ + //remove shipping + $db->update("order",array("shippingtype"=>false,"shippingcosts"=>0),"orderid=".$db->escapeInt($oid)); + }else{ + //set a shipping option + $ship=$db->select("shipping","cost","shipid=".$db->escapeInt($type)); + if($ship===false || count($ship)<1){ + header("X-MagicSmoke-Status: Error"); + echo tr("Invalid Shipping Method."); + $db->rollbackTransaction(); + return; + } + //check price + if($price===false || !$session->canExecute("_repriceshipping")) + $price=$ship[0]["cost"]; + $db->update("order",array("shippingtype"=>$type,"shippingcosts"=>$price),"orderid=".$db->escapeInt($oid)); + } + $db->commitTransaction(); + //dump order object + $ord=new Order($oid); + header("X-MagicSmoke-Status: Ok"); + $ord->dumpXml(); +} + +//get shipping list +function getShippingXml() +{ + $xml=new DomDocument; + $root=$xml->createElement("ShippingList"); + global $db,$session; + $res=$db->select("shipping","*",""); + $all=$session->canExecute("setshipping")||$session->canExecute("_anyshipping"); + if($res!==false && count($res)>0) + foreach($res as $sh){ + if(!$sh["canallusers"] && !$all)continue; + $sx=$xml->createElement("ShippingOption"); + $sx->setAttribute("type",$sh["shipid"]); + $sx->setAttribute("price",$sh["cost"]); + $sx->setAttribute("web",$sh["canuseweb"]?"1":"0"); + $sx->setAttribute("anyUser",$sh["canallusers"]?"1":"0"); + $sx->appendChild($xml->createTextNode($sh["description"])); + $root->appendChild($sx); + } + $xml->appendChild($root); + header("X-MagicSmoke-Status: Ok"); + print($xml->saveXml()); +} + +//implement set shipping info +function setShippingXml($txt) +{ + //parse XML data + $xml=new DomDocument; + $xml->loadXml($txt); + $doc=$xml->documentElement; + if($doc->hasAttribute("type")) + $type=$doc->getAttribute("type")+0; + else + $type=false; + $price=$doc->getAttribute("price")+0; + if($price<0)$price=0; + $web=$doc->getAttribute("web")+0; + $any=$doc->getAttribute("anyUser")+0; + $dsc=""; + foreach($doc->childNodes as $cn) + if($cn->nodeType==XML_TEXT_NODE) + $dsc=$cn->wholeText; + //change/create + global $db; + if($type===false){ + $type=$db->insert("shipping",array("cost" => $price, "canuseweb" => $web?1:0, + "canallusers" => $any?1:0, "description" => $dsc)); + if($type===false){ + header("X-MagicSmoke-Status: Error"); + echo tr("Unable to create new shipping method."); + return; + } + }else{ + $succ=$db->update("shipping",array("cost" => $price, "canuseweb" => $web?1:0, + "canallusers" => $any?1:0, "description" => $dsc), + "shipid=".$db->escapeInt($type)); + if($succ===false || $succ<1){ + header("X-MagicSmoke-Status: Error"); + echo tr("Unable to change shipping method."); + return; + } + } + header("X-MagicSmoke-Status: Ok"); + echo $type; +} +//delete shipping info +function deleteShippingXml($sid) +{ + global $db; + if(!is_numeric($sid)){ + header("X-MagicSmoke-Status: Error"); + echo tr("Expected a numeric shipping ID."); + } + $r=$db->deleteRows("shipping","shipid=".$db->escapeInt($sid)); + if($r==false || $r<1){ + header("X-MagicSmoke-Status: Error"); + echo tr("Unable to delete shipping method."); + return; + } + header("X-MagicSmoke-Status: Ok"); +} ?> \ No newline at end of file diff --git a/www/inc/classes/random.php b/www/inc/classes/random.php index ad638f0..aa63dea 100644 --- a/www/inc/classes/random.php +++ b/www/inc/classes/random.php @@ -50,8 +50,20 @@ function getSalt() return getRandom(16*4); } +//don't apply de-randomization +define("RND_ANYRANGE",-1); +//bits to keep +define("RND_MASK",7); +//added to a ticket +define("RND_TICKET",0x00); +//added to a voucher +define("RND_VOUCHER",0x08); +//not used yet: +define("RND_OTHER1",0x10); +define("RND_OTHER1",0x18); + /**return a new Code-39 capable ID; length is the amount of characters*/ -function getCode39ID($length) +function getCode39ID($length,$range=RND_ANYRANGE) { $c39="23456789ABCDEFGHJKLMNPRSTUVWXYZ+"; $rnd=getRandom($length*8); @@ -60,6 +72,10 @@ function getCode39ID($length) //cut random $r="0x".substr($rnd,$i*2,2); $r=($r+0)&31; + //if this is the first char, further manipulate it + if($i==0 && $range!=RND_ANYRANGE){ + $r=($r&RND_MASK)|$range; + } //append char $ret.=substr($c39,$r,1); } diff --git a/www/inc/classes/ticket.php b/www/inc/classes/ticket.php index f622ded..024c16f 100644 --- a/www/inc/classes/ticket.php +++ b/www/inc/classes/ticket.php @@ -152,7 +152,7 @@ class Ticket //generate ticket ID $db->beginTransaction(); do{ - $tid=getCode39ID(self::$NumTicketChars); + $tid=getCode39ID(self::$NumTicketChars,RND_TICKET); $res=$db->select("ticket","ticketid","ticketid=".$db->escapeString($tid)); if(count($res)==0)break; }while(true); diff --git a/www/inc/classes/voucher.php b/www/inc/classes/voucher.php new file mode 100644 index 0000000..41e9aa6 --- /dev/null +++ b/www/inc/classes/voucher.php @@ -0,0 +1,306 @@ +, (C) 2008 +// +// Copyright: See README/COPYING files that come with this distribution +// +// + +/* TRANSLATOR php:: */ + +class Voucher +{ + private $voucherid=false; + private $orderid=false; + private $price=false; + private $value=false; + private $isused=false; + + private static $NumVoucherChars=false; + + /**create a new voucher: with id from DB or for later creation*/ + public function __construct($voucherid=false) + { + global $db; + if(self::$NumVoucherChars===false){ + self::$NumVoucherChars=$db->getConfig("VoucherIDChars")+0; + if(self::$NumVoucherChars<=5)self::$NumVoucherChars=10; + } + if($voucherid!==false){ + $res=$db->select("voucher","*","voucherid=".$db->escapeString($voucherid)); + if($res===false || count($res)<1)return; + $this->voucherid=$res[0]["voucherid"]; + $this->orderid=$res[0]["orderid"]; + $this->price=$res[0]["price"]; + $this->value=$res[0]["value"]; + $this->isused=$res[0]["isused"]; + } + } + + /**return whether this voucher has an equivalent in the DB*/ + public function isValid() + { + return $this->voucherid!==false; + } + + /**returns the remaining value in cent*/ + public function remainingValue() + { + return $this->value; + } + + /**returns the price of the voucher*/ + public function price() + { + return $this->price; + } + + /**returns the ID of the order this voucher belongs to*/ + public function orderID() + { + return $this->orderid; + } + + /**returns whether the voucher is cancelled*/ + public function isCancelled() + { + return $this->price==0 && $this->value==0; + } + + /**returns whether the voucher has already been used*/ + public function isUsed() + { + return $this->isused; + } + + /**returns whether the voucher can be cancelled*/ + public function canCancel() + { + if($this->isCancelled())return true; + if(!$this->isUsed())return true; + return false; + } + + /**returns whether the voucher can be forcefully emptied*/ + public function canEmpty() + { + return !$this->isCancelled(); + } + + /**returns whether the voucher can pay for anything*/ + public function canPay() + { + return $this->value!=0; + } + + /**actually cancel the voucher (does all checks again); returns true on success*/ + public function cancelVoucher() + { + if(!isValid())return false; + global $db; + $db->beginTransaction(); + //recheck + $res=$db->select("voucher","*","voucherid=".$db->escapeString($this->voucherid)); + if($res===false || count($res)<1){ + $db->rollbackTransaction(); + return false; + } + //is it non-cancelled and used? + if(($res[0]["price"]!=0 || $res[0]["value"]!=0) && $res[0]["isused"]){ + $db->rollbackTransaction(); + return false; + } + //overwrite + $db->update("voucher",array("price"=>0,"value"=>0,"isused"=>0),"voucherid=".$db->escapeString($this->voucherid)); + $db->commitTransaction(); + return true; + } + + /**actually empty a voucher*/ + public function emptyVoucher() + { + //sanity check + if(!$this->isValid())return; + if(!$this->canEmpty())return; + //now do the deed + global $db; + $db->update("voucher",array("value"=>0,"isused"=>1),"voucherid=".$db->escapeString($this->voucherid)); + } + + /**create the voucher in the database; returns false on failue*/ + public function addToOrder($orderid,$price,$value) + { + //since this is called from Order only, we assume orderid to be correct + //sanity check (should not fail, since Order also checks) + if($price<0 || $value<=0)return false; + //create a new ID + global $db; + $db->beginTransaction(); + do{ + $vid=getCode39ID(self::$NumVoucherChars,RND_VOUCHER); + $res=$db->select("voucher","voucherid","voucherid=".$db->escapeString($vid)); + if(count($res)==0)break; + }while(true); + //create entry + $res=$db->insert("voucher",array("voucherid"=>$vid,"price"=>$price,"value"=>$value,"isused"=>0,"orderid"=>$orderid)); + if($res===false){ + $db->rollbackTransaction(); + return false; + } + $db->commitTransaction(); + $this->voucherid=$vid; + $this->orderid=$oroderid; + $this->price=$price+0; + $this->value=$value+0; + $this->isused=false; + return true; +} + + /**use the voucher to pay for an order; return true on success*/ + public function payForOrder($orderid) + { + //pre-check + if(!$this->isValid() || !$this->canPay())return false; + //now go to the DB + global $db; + $db->beginTransaction(); + //get voucher data and recheck + $vres=$db->select("voucher","*","voucherid=".$db->escapeString($this->voucherid)); + if($vres===false || count($vres)<1){ + $db->rollbackTransaction(); + return false; + } + if($vres[0]["value"]<=0){ + $db->rollbackTransaction(); + return false; + } + //get my own order status + $myord=new Order($this->orderid); + if(!$myord->isValid()){ + $db->rollbackTransaction(); + return false; + } + $ps=$myord->getPaymentStatus(); + if($ps!="needrefund" && $ps!="ok"){ + $db->rollbackTransaction(); + return false; + } + //get the target order data + $ord=new Order($orderid); + if(!$ord->isValid()){ + $db->rollbackTransaction(); + return false; + } + $adue=$ord->amountDue(); + if($adue<=0){ + $db->rollbackTransaction(); + return false; + } + //get amount to swap + $pay=$vres[0]["value"]; + if($adue<$pay)$pay=$adue; + //store corrected voucher + $this->value=$vres[0]["value"]-$pay; + $b=$db->update("voucher",array("value"=>$this->value,"isused"=>1),"voucherid=".$db->escapeString($this->voucherid))!==false; + //store corrected order + $a=$ord->amountPaid()+$pay; + $b&=$db->update("order",array("amountpaid"=>$a),"orderid=".$db->escapeInt($orderid))!==false; + //if anything went wrong: roll back + if(!$b){ + $db->rollbackTransaction(); + return false; + } + //whoo. got it! + $db->commitTransaction(); + return true; + } + + /**dumps the XML representation of the voucher*/ + function dumpXml() + { + $xml=new DomDocument; + $doc=$xml->createElement("Voucher"); + $doc->setAttribute("id",$this->voucherid); + $doc->setAttribute("price",$this->price); + $doc->setAttribute("value",$this->value); + $doc->setAttribute("used",$this->isused?"1":"0"); + $xml->appendChild($doc); + print($xml->saveXml()); + } +}; + +function getVoucherPricesXml() +{ + global $db; + header("X-MagicSmoke-Status: Ok"); + $r=$db->getConfig("ValidVouchers"); + if($r!==false)print($r); +} + +function cancelVoucherXml($vid) +{ + $vc=new Voucher($vid); + if($vc->isValid() && $vc->canCancel()){ + if($vc->cancelVoucher()){ + header("X-MagicSmoke-Status: Ok"); + return; + } + } + header("X-MagicSmoke-Status: Error"); + echo tr("Unable to cancel voucher."); +} + +function emptyVoucherXml($vid) +{ + $vc=new Voucher($vid); + if(!$vc->isValid()){ + header("X-MagicSmoke-Status: Error"); + echo tr("Invalid voucher, cannot empty it."); + return; + } + header("X-MagicSmoke-Status: Ok"); + $vc->emptyVoucher(); +} + +function useVoucherXml($txt) +{ + //split data + $splt=explode("\n",$txt); + if(count($splt)<2){ + header("X-MagicSmoke-Status: SyntaxError"); + echo tr("Expected two arguments: voucher id and order id."); + return; + } + $vc=new Voucher(trim($splt[0])); + if(!$vc->isValid()){ + header("X-MagicSmoke-Status: Error"); + echo tr("Invalid voucher id."); + return; + } + if($vc->payForOrder(trim($splt[1]))){ + header("X-MagicSmoke-Status: Ok"); + print($vc->remainingValue()); + }else{ + header("X-MagicSmoke-Status: Error"); + echo tr("Unable to process payment via voucher."); + } +} + +function getVoucherXml($vid) +{ + $vc=new Voucher($vid); + if(!$vc->isValid()){ + header("X-MagicSmoke-Status: Error"); + echo tr("Invalid voucher ID."); + return; + } + header("X-MagicSmoke-Status: Ok"); + $vc->dumpXml(); +} + +?> \ No newline at end of file diff --git a/www/inc/db/db.php b/www/inc/db/db.php index 8e7cfc4..f6f9e8a 100644 --- a/www/inc/db/db.php +++ b/www/inc/db/db.php @@ -232,6 +232,9 @@ abstract class DbEngine if($dbScheme->isBlobColumn($table,$k)) $ret.=$this->escapeBlob($v); else + if($dbScheme->isBoolColumn($table,$k)) + $ret.=$this->escapeBool($v); + else //don't know how to escape it... $ret.="NULL"; } @@ -370,6 +373,13 @@ abstract class DbEngine /**returns the error string of the last operation*/ public abstract function lastError(); + + /**returns whether the result value is NULL; the default interprets both null and false as NULL; overwrite if the DB can return false for a boolean*/ + public function isNull($val) + { + if($val===false || $val===null)return true; + else return false; + } }; ?> \ No newline at end of file diff --git a/www/inc/db/db_scheme.php b/www/inc/db/db_scheme.php index 98bb58d..931baba 100644 --- a/www/inc/db/db_scheme.php +++ b/www/inc/db/db_scheme.php @@ -19,7 +19,7 @@ class DbScheme { ); $this->preset["config"]=array( array("ckey"=>"MagicSmokeVersion","cval"=>$this->sversion), - array("ckey"=>"ValidVouchers","cval"=>"10 20 25 50"), + array("ckey"=>"ValidVouchers","cval"=>"1000 2000 2500 5000"), array("ckey"=>"OrderStop","cval"=>"24"), array("ckey"=>"SaleStop","cval"=>"0"), array("ckey"=>"TicketIDChars","cval"=>"10"), @@ -123,8 +123,9 @@ class DbScheme { $this->scheme["shipping"]=array( "shipid" => array("seq32","primarykey"), "cost" => array("int32","notnull"), //default cost of this shipping type - "canuseweb" => array("bool","default:false"), //is offered on web interface - "canallusers" => array("bool","default:true") //all GUI users may select it + "canuseweb" => array("bool","defaultbool:false"), //is offered on web interface + "canallusers" => array("bool","defaultbool:true"), //all GUI users may select it + "description" => array("string") //description for the shipping type ); //orders by customers @@ -148,12 +149,13 @@ class DbScheme { //this is for comparison with the price fields in ticket and voucher tables "amountpaid" => array("int32"), //shipping price - "shippingcosts" => array("int32","default:0"), + "shippingcosts" => array("int32","defaultint:0"), //pointer to shipping type (none per default, programmatic default is in config) "shippingtype" => array("int32","null","foreignkey:shipping:shipid") ); //tickets $this->scheme["ticket"]=array( + //a 8-32 char code (code39: case-insensitive letters+digits) for the ticket "ticketid" => array("string:32","primarykey"), "eventid" => array("int32","foreignkey:event:eventid"), //initially a copy from event, can be adjusted by seller @@ -168,14 +170,15 @@ class DbScheme { ); //vouchers and re-imbursments $this->scheme["voucher"]=array( - //a 8-32 char code (code39: case-insensitive letters+digits) for the voucher) + //a 8-32 char code (code39: case-insensitive letters+digits) for the voucher "voucherid" => array("string:32","primarykey"), - //if ordered: order-info + //price of the voucher (0 if cancelled) "price" => array("int32","notnull"), + //order this voucher belongs to "orderid" => array("int32","foreignkey:order:orderid"), - //unix-timestamp of original sales date/time - "salestime" => array("int32","notnull"), - //remaining value in cents + //marker: voucher has been used to pay something + "isused" => array("bool","defaultbool:false"), + //remaining value in cents (0 for cancelled) "value" => array("int32","notnull") ); @@ -312,7 +315,7 @@ class DbScheme { } } - /**returns true if the given column is of a string type*/ + /**returns true if the given column is of a blob type*/ public function isBlobColumn($tab,$col) { if(!isset($this->scheme[$tab][$col])) @@ -325,6 +328,21 @@ class DbScheme { return false; } } + + /**returns true if the given column is of a bool type*/ + public function isBoolColumn($tab,$col) + { + if(!isset($this->scheme[$tab][$col])) + return false; + $tpa=explode(":",$this->scheme[$tab][$col][0]); + switch($tpa[0]){ + case "bool": + case "boolean": + return true; + default: + return false; + } + } }; $dbScheme=new DbScheme; ?> \ No newline at end of file diff --git a/www/inc/loader.php b/www/inc/loader.php index 979866f..f3cfe2a 100644 --- a/www/inc/loader.php +++ b/www/inc/loader.php @@ -1,8 +1,6 @@ "","("=>"",")"=>"",";"=>""))); + $sp=explode(",",$line); + //syntax check + if(count($sp)!=2)continue; + //store + $k=trim($sp[0]);$v=trim($sp[1]); + $MSVERSION[$k]=$v; + } +} + +/**this variable contains version information about the server protocol: + MINSERVER=minimum protocol version the server understands; + CURSERVER=protocol version the server implements; + HRSERVER=human readable server version; + note that the database layer has its own version that is not stored here*/ +$MSVERSION=array(); + +//load version include +defversion(); + +?> \ No newline at end of file diff --git a/www/machine.php b/www/machine.php index ec41e5f..af4f794 100644 --- a/www/machine.php +++ b/www/machine.php @@ -9,6 +9,7 @@ if($_SERVER["REQUEST_METHOD"] != "POST" || !isset($_SERVER["HTTP_X_MAGICSMOKE_RE header("Content-Type: application/x-MagicSmoke"); include_once("inc/tr.php"); +include_once("inc/machine/version.php"); //check whether the request is known /* TRANSLATOR TransactionNames:: */ @@ -24,15 +25,19 @@ $ALLOWEDREQUESTS=array( tr("geteventlist"),tr("geteventdata"),tr("seteventdata"),tr("eventsummary"),tr("cancelevent"),//event infos tr("getroomdata"),tr("setroomdata"),//room infos tr("getcustomerlist"),tr("getcustomer"),tr("setcustomer"),tr("deletecustomer"), //customer info - tr("checkorder"),tr("createorder"),tr("createsale"),tr("getorderlist"),tr("getorder"),tr("orderpay"),tr("orderrefund"),tr("ordershipped"),tr("cancelorder"),tr("orderbyticket"),tr("getordersbyevents"),tr("setordercomment"), //sell/order stuff + tr("checkorder"),tr("createorder"),tr("createsale"),tr("getorderlist"),tr("getorder"),tr("orderpay"),tr("orderrefund"),tr("ordershipped"),tr("cancelorder"),tr("orderbyticket"),tr("getordersbyevents"),tr("setordercomment"),tr("orderchangeshipping"), //sell/order stuff + tr("getshipping"),tr("setshipping"),tr("deleteshipping"), //shipping info tr("getticket"),tr("useticket"),tr("changeticketprice"),tr("ticketreturn"),//ticket management + tr("getvoucherprices"),tr("cancelvoucher"),tr("emptyvoucher"),tr("usevoucher"),tr("getvoucher"), //voucher management tr("gettemplatelist"),tr("gettemplate"),tr("settemplate") //templates ); /**special roles begin with _ and are listed here (in lower case and wrapped in tr())*/ $SPECIALROLES=array( tr("_admin"),//system administrator tr("_anyshipping"),//user can assign any kind of shipping - tr("_repriceshipping")//user may alter shipping price + tr("_repriceshipping"),//user may alter shipping price + tr("_anyvoucher"),//user may generate vouchers of any value/price, not just configured ones + tr("_anypricevoucher")//user may generate vouchers with price different from value ); /* TRANSLATOR php:: */ @@ -54,7 +59,10 @@ include("inc/loader.php"); // server info can be answered without performing any more initialization if($SMOKEREQUEST=="serverinfo"){ header("X-MagicSmoke-Status: Ok"); - print("\n $MAGICSMOKEVERSION\n $ClientAuthAlgo\n"); + print("\n ".$MSVERSION["HRSERVER"]); + print("\n $ClientAuthAlgo\n"); exit(); } @@ -340,6 +348,27 @@ if($SMOKEREQUEST=="setordercomment"){ setOrderCommentXml(trim($REQUESTDATA)); exit(); } +if($SMOKEREQUEST=="orderchangeshipping"){ + setOrderShippingXml(trim($REQUESTDATA)); + exit(); +} + +//get shipping info +if($SMOKEREQUEST=="getshipping"){ + getShippingXml(); + exit(); +} +//set/create shipping info +if($SMOKEREQUEST=="setshipping"){ + setShippingXml(trim($REQUESTDATA)); + exit(); +} +//delete shipping info +if($SMOKEREQUEST=="deleteshipping"){ + deleteShippingXml(trim($REQUESTDATA)); + exit(); +} + //get a ticket if($SMOKEREQUEST=="getticket"){ @@ -362,6 +391,32 @@ if($SMOKEREQUEST=="ticketreturn"){ exit(); } +//get all valid prices for vouchers +if($SMOKEREQUEST=="getvoucherprices"){ + getVoucherPricesXml(); + exit(); +} +//return a voucher: cancels it +if($SMOKEREQUEST=="cancelvoucher"){ + cancelVoucherXml(trim($REQUESTDATA)); + exit(); +} +//return a voucher: emties it +if($SMOKEREQUEST=="emptyvoucher"){ + emptyVoucherXml(trim($REQUESTDATA)); + exit(); +} +//use a voucher to pay +if($SMOKEREQUEST=="usevoucher"){ + useVoucherXml(trim($REQUESTDATA)); + exit(); +} +//get info about a voucher +if($SMOKEREQUEST=="getvoucher"){ + getVoucherXml(trim($REQUESTDATA)); + exit(); +} + //EOF header("X-MagicSmoke-Status: Error"); die(tr("Internal Error: unknown command, hiccup in code structure.")); -- 1.7.2.5