<tr><td><b>Status</b></td><td><b>Description</b></td></tr>
<tr><td>placed</td><td>The order has been placed, nothing has been sent or paid yet.</td></tr>
<tr><td>sent</td><td>The order has been shipped.</td></tr>
+<tr><td>reserved</td><td>The order has been reserved. This means the tickets are blocked, but it cannot be paid or shipped.</td></tr>
<tr><td>cancelled</td><td>The user cancelled an unshipped and unpaid order.</td></tr>
<tr><td>closed</td><td>The order is final and cannot be altered. (State maybe not needed.)</td></tr>
</table><p/>
<h3>Checking or Creating Orders/Sales</h3>
The <tt>createorder</tt> transaction creates an open order. The initial state of the order is "placed".<br/>
+The <tt>createreservedorder</tt> transaction creates a reservation. The order looks like a normal order, but it is in the "reserved" state in which it cannot be paid or shipped - it has to be converted first<br/>
The <tt>createsale</tt> transaction creates an order that is marked as delivered and paid. The initial state of the created order is "sent" and the amount paid is equal to the total price.<br/>
The <tt>checkorder</tt> transaction performs all checks and lookups of an order, but does not commit it to the database.<p>
The <tt>cancelorder</tt> 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.
+<h3>Converting Reservations</h3>
+
+The <tt>reservationtoorder</tt> transaction marks a reservation as a normal order. From that time on it can be paid and shipped.<br/>
+The <tt>reservationtosale</tt> transaction marks a reservation as a sale. It will be marked as paid and shipped.<p>
+
+The request contains the order ID, the response is empty or contains an error message.<p>
+
+Use the <tt>cancelorder</tt> transaction (see above) to cancel a reservation.
+
+
<h3>Searching for an Order</h3>
The <tt>orderbyticket</tt> transaction can be used to find an order by a ticket ID from that order. The request contains the ticket ID, the response contains the order ID or returns as error if not found.<p>
return (m_status&Mask)==MaskIsChecked;
}
+bool MOrder::isReservation()const
+{
+ return m_status==Reserved;
+}
+
bool MOrder::canOrder()const
{
return m_status==CheckOk || m_status==CheckOrderOnly;
}
+bool MOrder::canReserve()const{return canOrder();}
+
bool MOrder::canSell()const
{
return m_status==CheckOk || m_status==CheckSaleOnly;
case Sent:return QCoreApplication::translate("MOrder","sent","state");
case Cancelled:return QCoreApplication::translate("MOrder","cancelled","state");
case Closed:return QCoreApplication::translate("MOrder","closed","state");
+ case Reserved:return QCoreApplication::translate("MOrder","reserved","state");
case CheckOk:return QCoreApplication::translate("MOrder","check: ok","state");
case CheckSaleOnly:return QCoreApplication::translate("MOrder","check: sale only","state");
case CheckOrderOnly:return QCoreApplication::translate("MOrder","check: order only","state");
if(st=="placed")m_status=Placed;else
if(st=="sent")m_status=Sent;else
if(st=="cancelled")m_status=Cancelled;else
+ if(st=="reserved")m_status=Reserved;else
if(st=="closed")m_status=Closed;else
if(st=="ok")m_status=CheckOk;else
if(st=="saleonly")m_status=CheckSaleOnly;else
return createOrder("createsale");
}
+MOrder MOrder::createReservation()
+{
+ return createOrder("createreservedorder");
+}
+
bool MOrder::cancelOrder()
{
if(!req->request("cancelorder",QByteArray::number(m_orderid)))return false;
return false;
}
+bool MOrder::reservationToOrder(QString cmd)
+{
+ //make sure we are in a sane state
+ makeComplete();
+ if(!req || m_orderid<0)return false;
+ //request
+ if(!req->request(cmd,QString::number(m_orderid).toUtf8()))return false;
+ if(req->responseStatus()==MWebRequest::Ok){
+ m_status=Placed;
+ return true;
+ }else
+ return false;
+}
+
+bool MOrder::reservationToSale()
+{
+ if(reservationToOrder("reservationtosale")){
+ m_status=Sent;
+ m_paid=m_price;
+ return true;
+ }else return false;
+}
/******************************************************************************
* Ticket
/**returns the order ID (-1 for invalid orders or after a simple check)*/
int orderID()const;
- /**returns whether the order is valid*/
+ /**returns whether the order is valid (it comes from the DB and it has been understood by the parser)*/
bool isValid()const;
/**returns the customer ID*/
Cancelled=0x12,
/**the order is closed (currently unused state)*/
Closed=0x13,
+ /**the order is reserved*/
+ Reserved=0x14,
/**status codes that have this bit (status&Mask==MaskIsChecked) set are checked only*/
MaskIsChecked=0x20,
/**check status: this order would pass*/
/**returns whether this order object is the result of a check operation*/
bool isCheck()const;
+ /**returns whether this order is a reservation*/
+ bool isReservation()const;
+
/**returns whether this order object can be used to generate an order (to be delivered later)*/
bool canOrder()const;
+ /**returns whether this order object can be used to generate a reservation (same rules as for canOrder)*/
+ bool canReserve()const;
+
/**returns whether this order object can be used to generate a sale (delivered and paid immediately)*/
bool canSell()const;
/**create a sale in the DB; returns it*/
MOrder createSale();
+ /**create a reservation in the DB; returns it*/
+ MOrder createReservation();
+
+ /**change a reservation into an order*/
+ bool reservationToOrder(QString cmd="reservationtoorder");
+
+ /**change a reservation into a sale*/
+ bool reservationToSale();
+
/**cancel the order; queries DB; returns true on success*/
bool cancelOrder();
QMenuBar*mb=menuBar();
QMenu *m=mb->addMenu(tr("&Order"));
m->addAction(tr("&Order..."),this,SLOT(createOrder()))
- ->setEnabled(req->hasRole("createorder")&&o.canOrder());
+ ->setEnabled((req->hasRole("createorder")&&o.canOrder()) ||
+ (req->hasRole("reservationtoorder")&&o.isReservation()));
m->addAction(tr("&Sell..."),this,SLOT(createSale()))
- ->setEnabled(req->hasRole("createsale")&&o.canSell());
+ ->setEnabled((req->hasRole("createsale")&&o.canSell()) ||
+ (req->hasRole("reservationtosale")&&o.isReservation()));
+ m->addAction(tr("Ma&ke Reservation..."),this,SLOT(createReservation()))
+ ->setEnabled(req->hasRole("createreservedorder")&&o.canReserve());
m->addAction(tr("&Prune and recheck..."),this,SLOT(recheckOrder()))
->setEnabled(o.orderID()<0);
m->addSeparator();
->setEnabled(req->hasRole("changeordershipping"));
m->addSeparator();
m->addAction(tr("&Close"),this,SLOT(close()));
+
m=mb->addMenu(tr("&Payment"));
m->setEnabled(o.isStored());
m->addAction(tr("Receive &Payment..."),this,SLOT(payment()))
->setEnabled(req->hasRole("orderpay"));
m->addAction(tr("&Refund..."),this,SLOT(refund()))
->setEnabled(req->hasRole("orderrefund"));
+
m=mb->addMenu(tr("P&rinting"));
m->setEnabled(o.isStored());
m->addAction(tr("Print &Bill..."),this,SLOT(printBill()));
void MOrderWindow::cancelOrder()
{
if(QMessageBox::question(this,tr("Cancel Order?"),tr("Cancel this order now?"),QMessageBox::Yes|QMessageBox::No,QMessageBox::Yes)!=QMessageBox::Yes)return;
- if(m_order.orderStatus()!=MOrder::Placed){
+ if(m_order.orderStatus()!=MOrder::Placed && m_order.orderStatus()!=MOrder::Reserved){
QMessageBox::warning(this,tr("Warning"),tr("Cannot cancel this order: it is in the wrong state."));
return;
}
m_state->setText(m_order.orderStatusString());
}
-void MOrderWindow::createOrder(bool issale)
+void MOrderWindow::createOrder(Create mode)
{
MOrder ord;
- if(issale)ord=m_order.createSale();
- else ord=m_order.createOrder();
- if(!ord.isValid())return;
+ //handle reservation changes specially
+ if(m_order.orderStatus()==MOrder::Reserved){
+ bool ok=false;
+ ord=m_order;
+ switch(mode){
+ case CreateOrder:ok=ord.reservationToOrder();break;
+ case CreateSale:ok=ord.reservationToSale();break;
+ default:ok=false;
+ }
+ if(!ok)return;
+ }else{
+ //handle case of new order
+ switch(mode){
+ case CreateSale:ord=m_order.createSale();break;
+ case CreateOrder:ord=m_order.createOrder();break;
+ case CreateReservation:ord=m_order.createReservation();break;
+ }
+ if(!ord.isValid())return;
+ }
//display final order
MOrderWindow *ow=new MOrderWindow(parentWidget(),req,ord);
ow->show();
void MOrderWindow::createSale()
{
- createOrder(true);
+ createOrder(CreateSale);
+}
+
+void MOrderWindow::createReservation()
+{
+ createOrder(CreateReservation);
}
void MOrderWindow::recheckOrder()
/**returns whether the order has been changed by this window*/
bool isChanged()const;
+
+ private:
+ /**helper enum for create* methods*/
+ enum Create{CreateOrder,CreateSale,CreateReservation};
private slots:
/**internal: mark order as changed*/
void shipOrder();
/**create a new order*/
- void createOrder(bool issale=false);
+ void createOrder(Create mode=CreateOrder);
/**create a sale*/
void createSale();
+ /**create a new reservation*/
+ void createReservation();
+
/**prune and recheck the order*/
void recheckOrder();
#define ORDERNONE 0
#define ORDERALL 0xff
-#define ORDEROPEN 7
+#define ORDEROPEN 0xf
#define ORDERPAY 1
#define ORDERREFUND 2
#define ORDERUNSENT 4
+#define ORDERRESERVE 8
MOverview::MOverview(MWebRequest*mw,QString pk)
{
ordermode->addItem(tr("-select mode-"),ORDERNONE);
ordermode->addItem(tr("All Orders"),ORDERALL);
ordermode->addItem(tr("Open Orders"),ORDEROPEN);
+ ordermode->addItem(tr("Open Reservations"),ORDERRESERVE);
ordermode->addItem(tr("Outstanding Payments"),ORDERPAY);
ordermode->addItem(tr("Outstanding Refunds"),ORDERREFUND);
ordermode->addItem(tr("Undelivered Orders"),ORDERUNSENT);
if((omode&ORDERPAY)!=0 && ord.needsPayment())return true;
if((omode&ORDERREFUND)!=0 && ord.needsRefund())return true;
if((omode&ORDERUNSENT)!=0 && !ord.isSent())return true;
+ if((omode&ORDERRESERVE)!=0 && ord.isReservation())return true;
return false;
}
define("ORDER_PLACED",0);
/**the order has been sent out (it must be placed first; direct sales are automatically sent)*/
define("ORDER_SENT",1);
+/**the order has been sold directly, alias for ORDER_SENT */
+define("ORDER_SOLD",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 is on reservation status: it is placed, but is waiting for confirmation, hence no shipment is possible and the tickets cannot be used*/
+define("ORDER_RESERVED",4);
/**the order has been finalized; no more changes possible; TODO: define exactly what this means*/
define("ORDER_CLOSED",0x80);
}
/**places/finalizes the order; returns false on failure, true on success or if the order already was finalized()*/
- public function placeOrder($isSale=false)
+ public function placeOrder($inistate=ORDER_PLACED)
{
//sanity check
// print(1);
if(!$this->canChange())return false;
if((count($this->newtickets)+count($this->newvouchers))==0)return false;
+ switch($inistate){
+ case ORDER_PLACED:
+ case ORDER_RESERVED:
+ case ORDER_SOLD:
+ //ok, understood
+ break;
+ default:
+ //don't understand anything else
+ return false;
+ break;
+ }
// print("a");
global $db,$session;
$db->beginTransaction();
- if(!$this->validateOrder($isSale?VALIDATEORDER_SALE:VALIDATEORDER_ORDER)){
+ if(!$this->validateOrder($inistate==ORDER_SOLD?VALIDATEORDER_SALE:VALIDATEORDER_ORDER)){
$db->rollbackTransaction();
return false;
}
// print(2);
//create order, incl shipping
- $this->status=ORDER_PLACED;
+ $this->status=$inistate;
if(isset($session))$usr=$session->getUser();
else $usr=false;
$this->seller=$usr;
//TODO: check return code of addToOrder
}
//update amountpaid for sales
- if($isSale){
+ if($inistate==ORDER_SOLD){
$db->update("order",array("amountpaid"=>$totalprice,"status"=>ORDER_SENT),"orderid=".$db->escapeInt($this->orderid));
$this->status=ORDER_SENT;
$this->amountpaid=$totalprice;
case ORDER_CANCELLED:
$doc->setAttribute("status","cancelled");
break;
+ case ORDER_RESERVED:
+ $doc->setAttribute("status","reserved");
+ break;
case ORDER_CLOSED:
$doc->setAttribute("status","closed");
break;
$sx->appendChild($xml->createTextNode($res[0]["description"]));
}
$doc->appendChild($sx);
- if($this->status==ORDER_PLACED || $this->status==ORDER_SENT)
+ if($this->status==ORDER_PLACED || $this->status==ORDER_SENT || $this->status==ORDER_RESERVED)
$totalprice+=$this->shippingcosts;
}
return $this->status;
}
- /**helper function: returns the amount due to be paid; returns a negative value for refunds*/
- public function amountDue()
+ /**helper function: returns the total price of the order*/
+ public function totalPrice()
{
global $db;
//calculate amount due
$totalprice+=$vc["price"];
}
//add shipping
- if($this->status==ORDER_PLACED || $this->status==ORDER_SENT)
+ if($this->status==ORDER_PLACED || $this->status==ORDER_RESERVED || $this->status==ORDER_SENT)
$totalprice+=$this->shippingcosts;
+ return $totalprice;
+ }
+
+ /**helper function: returns the amount due to be paid; returns a negative value for refunds*/
+ public function amountDue()
+ {
//compare with what has been paid, return diff
- return $totalprice-$this->amountpaid;
+ return $this->totalPrice()-$this->amountpaid;
}
/**returns the amount already paid*/
$db->rollbackTransaction();
return false;
}
- if($res[0]["status"]!=ORDER_PLACED){
+ if($res[0]["status"]!=ORDER_PLACED && $res[0]["status"]!=ORDER_RESERVED){
$db->rollbackTransaction();
return false;
}
+ //TODO: handle orders that have been sent, but are rolled back now
//check tickets
$res=$db->select("ticket","status","orderid=".$db->escapeInt($this->orderid));
for($i=0;$i<count($res);$i++){
{
$this->comment=trim($cm);
}
+
+ /**change a reservation into an order or sale*/
+ public function changeReservation($mode)
+ {
+ global $db;
+ $db->beginTransaction();
+ //check current 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_RESERVED){
+ $db->rollbackTransaction();
+ return false;
+ }
+ //set new status
+ $set=array("status"=>$mode);
+ if($mode==ORDER_SOLD)
+ $set["amountpaid"]=$this->totalPrice();
+ $db->update("order",$set,"orderid=".$db->escapeInt($this->orderid));
+ $db->commitTransaction();
+ return true;
+ }
};
function createOrderXml($xmldata,$action)
break;
case "order":
// create order
-// $order->validateOrder(true);
if($order->placeOrder()){
header("X-MagicSmoke-Status: Ok");
$order->dumpXml();
break;
case "sell":
//create order
- if($order->placeOrder(true)){
+ if($order->placeOrder(ORDER_SOLD)){
header("X-MagicSmoke-Status: Ok");
//finalize sale
$order->dumpXml();
die(tr("Cannot place sale, sorry."));
}
break;
+ case "reserve":
+ // create order
+ if($order->placeOrder(ORDER_RESERVED)){
+ header("X-MagicSmoke-Status: Ok");
+ $order->dumpXml();
+ }else{
+ header("X-MagicSmoke-Status: Error");
+ die(tr("Cannot place order, sorry."));
+ }
+ break;
default:
header("X-MagicSmoke-Status: Error");
die(tr("Internal Error: unknown action."));
case ORDER_CANCELLED:
$ox->setAttribute("status","cancelled");
break;
+ case ORDER_RESERVED:
+ $ox->setAttribute("status","reserved");
+ break;
case ORDER_CLOSED:
$ox->setAttribute("status","closed");
break;
header("X-MagicSmoke-Status: Error");
die(tr("Order cannot be changed, it is closed."));
}
+ if($res[0]["status"]==ORDER_RESERVED){
+ $db->rollbackTransaction();
+ header("X-MagicSmoke-Status: Error");
+ die(tr("Order cannot be paid for, it is only a reservation. Order or sell it first!"));
+ }
//correct DB
$amt2=$res[0]["amountpaid"]+($amt*$factor);
$db->update("order",array("amountpaid"=>$amt2),"orderid=".$db->escapeInt($oid));
}
header("X-MagicSmoke-Status: Ok");
}
+
+function changeReservationXml($oid,$mode)
+{
+ $ord=new Order($oid);
+ if(!$ord->isValid()){
+ header("X-MagicSmoke-Status: Error");
+ echo tr("Invalid Order.");
+ return;
+ }
+ if($ord->changeReservation($mode)){
+ header("X-MagicSmoke-Status: Ok");
+ }else{
+ header("X-MagicSmoke-Status: Error");
+ echo tr("Cannot change order from reservation.");
+ return;
+ }
+
+}
?>
\ No newline at end of file
/* TRANSLATOR TransactionNames:: */
/**all valid requests must be listed here (in lower case and wrapped in tr())*/
$ALLOWEDREQUESTS=array(
- tr("serverinfo"), //info request
- tr("startsession"),tr("sessionauth"),tr("closesession"), //session requests
+ //info request:
+ tr("serverinfo"),
+ //session requests:
+ tr("startsession"),tr("sessionauth"),tr("closesession"),
+ //////////
//all requests below here need authentication
- tr("getmyroles"), //role management: get my own ACLs
+ //role management: get my own ACLs
+ tr("getmyroles"),
+ /////////
//all requests below here need a role entry in the DB
- tr("getusers"),tr("setuserdescription"),tr("getuseracl"),tr("setuseracl"),tr("getuserhosts"),tr("setuserhosts"),tr("adduser"),tr("deleteuser"),tr("setmypasswd"),tr("setpasswd"),//user management
- tr("gethosts"),tr("sethost"),tr("addhost"),tr("deletehost"), //host management
- 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"),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"),tr("settemplatedescription"),tr("deletetemplate") //templates
+ //user management
+ tr("getusers"),tr("setuserdescription"),tr("getuseracl"),tr("setuseracl"),tr("getuserhosts"),
+ tr("setuserhosts"),tr("adduser"),tr("deleteuser"),tr("setmypasswd"),tr("setpasswd"),
+ //host management
+ tr("gethosts"),tr("sethost"),tr("addhost"),tr("deletehost"),
+ //event infos
+ tr("geteventlist"),tr("geteventdata"),tr("seteventdata"),tr("eventsummary"),tr("cancelevent"),
+ //room infos
+ tr("getroomdata"),tr("setroomdata"),
+ //customer info
+ tr("getcustomerlist"),tr("getcustomer"),tr("setcustomer"),tr("deletecustomer"),
+ //sell/order stuff
+ tr("checkorder"),tr("createorder"),tr("createsale"),tr("createreservedorder"),tr("getorderlist"),
+ tr("getorder"),tr("orderpay"),tr("orderrefund"),tr("ordershipped"),tr("cancelorder"),
+ tr("orderbyticket"),tr("getordersbyevents"),tr("setordercomment"),tr("orderchangeshipping"),
+ tr("reservationtoorder"),tr("reservationtosale"),
+ //shipping info
+ tr("getshipping"),tr("setshipping"),tr("deleteshipping"),
+ //ticket management
+ tr("getticket"),tr("useticket"),tr("changeticketprice"),tr("ticketreturn"),
+ //voucher management
+ tr("getvoucherprices"),tr("cancelvoucher"),tr("emptyvoucher"),tr("usevoucher"),tr("getvoucher"),
+ //templates
+ tr("gettemplatelist"),tr("gettemplate"),tr("settemplate"),tr("settemplatedescription"),
+ tr("deletetemplate")
);
/**special roles begin with _ and are listed here (in lower case and wrapped in tr())*/
$SPECIALROLES=array(
createOrderXml($REQUESTDATA,"order");
exit();
}
-//create order recors as a sale (already paid and delivered)
+//create order as a sale (already paid and delivered)
if($SMOKEREQUEST=="createsale"){
createOrderXml($REQUESTDATA,"sell");
exit();
}
+//create order as a reservation (like normal order, but cannot be used/paid/sent)
+if($SMOKEREQUEST=="createreservedorder"){
+ createOrderXml($REQUESTDATA,"reserve");
+ exit();
+}
+//change reservation into order
+if($SMOKEREQUEST=="reservationtoorder"){
+ changeReservationXml(trim($REQUESTDATA),ORDER_PLACED);
+ exit();
+}
+//change reservation into sale
+if($SMOKEREQUEST=="reservationtosale"){
+ changeReservationXml(trim($REQUESTDATA),ORDER_SOLD);
+ exit();
+}
//get list of all orders
if($SMOKEREQUEST=="getorderlist"){
getOrderListXml();