/**
* Order.php
*
* Orders are the bookings which purchasers make in the system
*
* @class Order
* @package Order
* @copyright 2010 - 2015 BookingLive Software Limited
*/
class Order extends DataObject implements NotificationInterface,PrerequisiteInterface {
static $currentOrder = null;
static $type = PermissionsExtension::CONDITIONAL;
static $db = array(
'Code' => 'Varchar',
'Reference' => 'Varchar',
'SessionID' => 'Varchar',
'IPAddress' => 'Varchar',
'DateRegistered' => 'SS_Datetime',
'OriginalDateRegistered' => 'SS_Datetime',
'Status' => 'Varchar',
'WaitingList' => 'Boolean',
'MainStatus' => "Enum(array(
'Completed'
'Pending',
'Aborted',
'Cancelled',
'Provisional',
'WaitingList'
),'Pending')",
'Source' => "Enum(array(
'Online',
'Staff',
'EPOS'
),'Online')",
'Occasion' => 'Varchar',
'HowDidYouHearAboutUs' => 'Varchar(255)',
'Actioned' => 'Boolean',
//Override
'TotalCostOverride' => 'BLDecimal',
'DepositOverride' => 'BLDecimal',
//Refund (on after write hack)
'PerformRefund' => 'Boolean',
'CreditsToRefund' => 'Int',
'AmountToRefund' => 'BLDecimal',
'RefundLog' => 'Text',
//Cancellation (on after write hack)
'Cancel' => 'Boolean',
'CancelCode' => 'Varchar(5)',
'CancelText' => 'Text',
'CancelDate' => 'SS_Datetime',
'CancelMemberID' => 'Int',
//(calced)
'CheckHash' => 'Text',
'EventCheckHash' => 'Text',
'Summary' => 'Text',
'BillingForm' => 'Text',
'Deposit' => 'BLDecimal', // total of each order item deposit + tax
'TotalCost' => 'BLDecimal', // total of all items excluding discounts and charges and promotions (net)
'TotalTax' => 'BLDecimal',
'FinalTotalCost' => 'BLDecimal', // total of all items including discounts and charges and promotions (gross)
'ChargesTotal' => 'BLDecimal', // amount of charges on that total
'TotalTransactionValue' => 'BLDecimal',
'TotalTransactionRefund' => 'BLDecimal',
'UnverifiedTransactionValue'=> 'BLDecimal',
'AmountDue' => 'BLDecimal', // the amount that is currently required to be paid (gross)
'TotalAmountDue' => 'BLDecimal', // total amount due for whole order (include not yet submitted invoices)
'TotalAmountOverdue' => 'BLDecimal', // total amount due that missed the payment date
'InvoiceAmountDue' => 'BLDecimal', // amount due to be paid for next not yet submitted invoice
'AllParticipantInformation' => 'Boolean',
'EPOS' => 'Boolean',
'API' => 'Boolean',
//this indicates it is a booking to "link" people together
//these should be hidden from all normal views (they will be "Cancelled" anyway)
//but are used so members can be selected on billing page
'FakeOrder' => 'Boolean',
'CedArRefInvoiced' => 'Boolean',
'Invoiced' => 'Boolean',
//The value of this field show will SMS message be send if there are changes in the order
'SMSNotification' => 'Boolean',
//The value of this field show will Email message be send if there are changes in the order
//This field is different than $order->param('NotificationByEmail'). $order->param('NotificationByEmail')
//should be used to determine do I need to send email for this change.
'EmailNotification' => 'Boolean',
// Resource Management TODO consider those fields
//'DamagedCount' => 'Int',
//'MissingCount' => 'Int',
'SimulatedDate' => 'SS_Datetime',
'EditAttempts' => 'Int',
// Indicate the initial date time when order was registered
'OriginalCheckoutDate' => 'SS_Datetime',
'CancellationCode' => 'Varchar(255)',
'CancellationReason' => 'Text',
'EmailSent' => 'Int',
);
static $has_one = array(
'PendingPurchaser' => 'PendingMember',
'Staff' => 'Member',
'Purchaser' => 'Member',
'Organisation' => 'Organisation'
);
static $bl_icon = 'icon-cart3';
static $has_many = array(
'Transactions' => 'Transaction',
'OrderItems' => 'OrderItem',
'OrderNotes' => 'OrderNote',
'OrderParams' => 'OrderParam',
'OrderSummaries' => 'OrderSummary',//TODO depreciate
'BillingPageSubmissions'=> 'BillingPageSubmission',
'Charges' => 'ChargeItem'
);
static $many_many = array(
'Tags' => 'OrderTag',
'Prerequisites' => 'Prerequisite',
'MemberDocuments' => 'MemberDocument'
);
static $default_sort = 'DateRegistered DESC';
public static $indexes = array(
'SessionID' => true,
'MainStatus' => true,
'DateRegistered' => true
);
public static $summary_fields = array(
'Reference',
'OriginalDateRegistered',
'StartDate',
'EndDate',
'PurchaserFullName',
'FinalTotalCostAdminFormat',
'MainStatus'
);
public static $searchable_fields = array(
'Status' => array('filter'=> 'OrderCustomStatusFilter'),
'Reference',
'OrderItems.ProductID' => array('title' => 'Products in order'),
);
static $field_labels = array(
'MainStatus' => 'Status'
);
static $extensions = [
ZonalOrderExtension::class,
];
static $default_search_statuses = array('Completed', 'Provisional');
public $OriginalID = 0; // Original order id of the duplicated order
public function getTagsSummary() {
return implode(', ', $this->Tags()->column('Title'));
}
public function getTitle() {
return $this->Reference;
}
public function canDelete($member = null) {
return false;
}
public function getDependentRelationsForDelete() {
if($this->MainStatus == 'Aborted')
return array();
return array('Transactions','OrderItems','Invoices');
}
public static function getDefaultSearchValues() {
$value = array();
$StatusCustomLabel = StatusCustomLabel::get();
if (!isset($_POST) || (isset($_POST) && !in_array(['action_save','action_doSaveAndQuit'],$_POST))) {
$StatusCustomLabel = $StatusCustomLabel->filter('DefaultSearch',true);
}
foreach ($StatusCustomLabel->sort('Label ASC, IsSystemStatus DESC') as $customStatus) {
$value[] = $customStatus->Label;
}
return $value;
}
/**
* scaffold the form fields for model admins
* @param null $_params
* @return FieldList
*/
function scaffoldSearchFields($_params = null){
$fields = parent::scaffoldSearchFields($_params);
// TODO make a static varible with this
//$value = self::$default_search_statuses;
$value = self::getDefaultSearchValues();
$orderItemValue = array();
if (isset($_REQUEST['q'])) {
$value = isset($_REQUEST['q']['MainStatus']) ? $_REQUEST['q']['MainStatus'] : array();
$orderItemValue = isset($_REQUEST['q']['OrderItems__ProductID']) ? $_REQUEST['q']['OrderItems__ProductID'] : array();
}
$arrSelections = CustomStatusesField::CustomLabels()->map('Label','Label')->toArray();
if (isset($arrSelections['Waiting List'])) {
$arrSelections['WaitingList'] = $arrSelections['Waiting List'];
unset($arrSelections['Waiting List']);
}
$fields->replaceField(
'Status',
ListboxField::create('MainStatus', 'Status', $arrSelections)
->setMultiple(true)
->setValue($value)
);
$fields->replaceField(
'OrderItems__ProductID',
ListboxField::create('OrderItems__ProductID', 'Products in orders',Product::get()->map('ID','Name')->toArray())
->setMultiple(true)
->setValue($orderItemValue)
);
$this->extend('updateScaffoldSearchFields', $fields);
return $fields;
}
public function OrderCMSFields() {
$fActions = ActionsDropdownField::create('ActionsDropdown');
if (in_array($this->MainStatus, array('Completed','Provisional','WaitingList'))) {
if ($this->HasResourceManagementProduct()) {
if ($this->CanCheckIn())
$fActions->addAction('Check In', array(
'class' => 'checkinout-popup',
'data-link' => 'OrderAdminController/CheckInResources/'.$this->ID
));
if ($this->CanCheckOut())
$fActions->addAction('Check Out',array(
'class' => 'checkinout-popup',
'data-link' => 'OrderAdminController/CheckOutResources/'.$this->ID
));
$fActions->addAction('Edit Collection & Return',array(
'class' => 'collection-return-edit',
'data-link' => 'OrderAdminController/EditCollectionReturnDates/'.$this->ID
));
}
$fActions->addActions(array(
'Add to Order' => array(
'id' => 'action_AddToORder',
'data-link' => '#'
),
'Edit Details' => array(
'class' => 'EditPurchaserDetails',
'data-link' => 'admin/eventadmin/EditBooking/'.$this->ID.'?EditPurchaserOnly=1'
),
'Resend Confirmation' => array(
'id' => 'action_ResendConfirmationEmail',
'data-link' => 'EventAdmin/ResendConfirmationEmail/'.$this->ID
),
'Cancel Order' => array(
'id' => 'action_CancelOrder',
'data-id' => $this->ID
),
'Order Timeline' => array(
'id' => 'action_Log',
'data-link' => 'OrderAdminController/showTimeLine/'.$this->ID
),
'Send Balance Reminder Email' => array(
'id' => 'action_SendReminderEmail',
'data-link' => 'OrderAdminController/SendReminderEmail/'.$this->ID
)
));
if ($this->IfOutstandingPayment())
$fActions->addAction('Add Payment',array(
'id' => 'action_AddPayment',
'data-orderid' => $this->ID
));
}
if (in_array($this->MainStatus,array('Cancelled','Provisional')))
$fActions->addAction('Complete Order',array(
'id' => 'action_CompleteOrder',
'data-id' => $this->ID
));
return $fActions->forTemplate();
}
public static function OrderCMSTableRow($strLabel,$stValue,$bStrong=false) {
$stValue = $bStrong ? ''.$stValue.'' : $stValue;
return '
| '.$strLabel.': | '.$stValue.' |
';
}
public function OrderCMSTable() {
$strTableRows = self::OrderCMSTableRow('Reference',$this->Reference,true);
$strTableRows .= self::OrderCMSTableRow('Date',DateUtils::PublicDateTimeFormat($this->DateRegistered));
if ($this->MainStatus == 'Cancelled')
$strTableRows .= self::OrderCMSTableRow('Cancellation',DateUtils::PublicDateTimeFormat($this->CancelDate));
if (SiteConfigOverride::CurrentSiteConfig()->SystemTakesPayments) {
$currency = $this->CurrencySymbol();
$strTableRows .= self::OrderCMSTableRow('Total',CurrencyUtils::getCurrencyWithFormat($this->FinalTotalCost));
$strTableRows .= self::OrderCMSTableRow('Total Payable Now',CurrencyUtils::getCurrencyWithFormat($this->AmountDue + $this->UnverifiedTransactionValue));
}
return '';
}
private function GetTaglistForTagField() {
$q = DatabaseUtils::ArrayFromDBQuery('SELECT ID FROM `OrderTag` GROUP BY "Title"');
$currentOrderTags = $this->Tags()->map('ID','ID')->toArray();
foreach ($q as $value) {
$currentOrderTags[$value['ID']] = $value['ID'];
}
return $currentOrderTags;
}
public function getCMSFields() {
$fields = parent::getCMSFields();
PresentationUtils::AddRequirements();
Requirements::javascript('mysite/javascript/CustomersAdmin.js');
Requirements::javascript('mysite/javascript/OrderAdminEvents.js');
Requirements::javascript('mysite/javascript/CustomStatuses.js');
$iPhysicalItemCount = Product::get()->filter(array(
'Status' => 'On',
'ClassName' => 'ProductPhysicalItem'
))->count();
$iUpsellCount = 0;
//This will be enabled whit next release and when investigation on how Add Upsell to order works.
// foreach ($this->OrderItems() as $orderItem)
// if ($orderItem->Product()->ProductUpsells()->count() > 0)
// $iUpsellCount = 1;
$fields->removeByName(array(
'AllParticipantInformation',
'AmountToRefund',
'BillingForm',
'BillingPageSubmissions',
'CheckHash',
'CreditsToRefund',
'DepositOverride',
'FakeOrder',
'OrderSummaries',
'PerformRefund',
'RefundLog',
'SessionID',
'Summary',
'TotalCostOverride',
'Invoices',
'Tags',
));
if (!$this->Charges()->first())
$fields->removeByName('Charges');
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
if(!SiteConfigCategoryZonal::getCurrent()->Enabled) {
$fields->removeByName('ZonalMessages');
}
$fields->findOrMakeTab('Root.Main')
->setTitle('Main')
->setChildren(
FieldList::create(
LiteralField::create('BookingSummaryBegin','
'.$this->OrderCMSFields().'
'.$this->OrderEmailIcon().'
'.$this->OrderSMSIcon().'
'.$this->OrderCMSTable()
),
TagField::create('Tags', 'Tags', OrderTag::get()->filter('ID',$this->GetTaglistForTagField()), $this->Tags())
->setShouldLazyLoad(false)
->setCanCreate(true),
LiteralField::create('BookingSummaryEnd','
'.$this->CustomStatusesActionDropDown().'
')
)
);
if ($fOrderItems = $fields->dataFieldByName('OrderItems')) {
$orderItems = $this->OrderItems()
->LeftJoin('OrderItem_Events','OrderItem_Events.OrderItemID = OrderItem.ID')
->LeftJoin('Event','Event.ID = OrderItem_Events.EventID')
->Sort('Event.StartDateTime ASC');
$fields->addFieldToTab(
'Root.Main',
$grid = FormUtils::GridFieldEdit('Products', 'Products', $orderItems, $siteConfig->SystemTakesPayments
? array(
'AdminSummaryDescription' => 'Description',
'AddedByName' => 'Added By',
'LastEditedByName' => 'Last Edited By',
'Cost' => _t('BookingLive.Net', 'Net'),
'Tax' => _t('BookingLive.Tax', 'Tax'),
'GrossAmount' => _t('BookingLive.Gross', 'Gross')
)
: array(
'AdminSummaryDescription' => 'Description',
'AddedByName' => 'Added By',
'LastEditedByName' => 'Last Edited By',
),
array(
'StartDateTime' => function($value, &$item) {
return date(ADMINGRIDFIELDDATETIME, strtotime($value));
},
'EndDateTime' => function($value, &$item) {
return date(ADMINGRIDFIELDDATETIME, strtotime($value));
}
))->addExtraClass('remove-click-state')->addExtraClass('remove-hover-state')
);
$config = $grid->getConfig();
$config->removeComponentsByType('GridFieldAddNewButton')
->removeComponentsByType('GridFieldPaginator')
->removeComponentsByType('GridFieldAddExistingAutocompleter')
->removeComponentsByType('GridFieldDeleteAction')
->removeComponentsByType('GridFieldFilterHeader')
->removeComponentsByType('GridFieldEditButton')
->addComponent(new GridFieldOrderItemActions())
->addComponent(new GridFieldOrderSummaryRow(), 'GridFieldPaginator')
->addComponent($pagination = new GridFieldPaginator(10))
->getComponentByType('GridFieldDataColumns');
$grid->getConfig()
->getComponentByType('GridFieldDataColumns')
->setFieldFormatting(array(
'Tax' => function($value, &$item) {
return $item->CurrencySymbol().' '.$value;
},
'Cost' => function($value, &$item) {
return $item->CurrencySymbol().' '.$value;
},
'GrossAmount' => function($value, &$item) {
return $item->CurrencySymbol().' '.number_format($value, 2,'.','');
}
));
$fields->removeByName('OrderItems');
}
$fields->addFieldToTab('Root.Main',LiteralField::create('',
PresentationUtils::ParseTemplateWithArray(array(
'ID' => $this->ID,
'PhysicalItemCount' => $iPhysicalItemCount,
'UpsellCount' => $iUpsellCount
),'AddToOrder')
));
if (!$siteConfig->SystemTakesPayments) {
$fields->removeByName('Transactions');
} else {
if ($fTransactions = $fields->dataFieldByName('Transactions')) {
$fTransactions->addExtraClass('remove-click-state');
$config = $fTransactions->getConfig()
->removeComponentsByType('GridFieldAddNewButton')
->removeComponentsByType('GridFieldPaginator')
->removeComponentsByType('GridFieldFilterHeader')
->removeComponentsByType('GridFieldAddExistingAutocompleter')
->removeComponentsByType('GridFieldViewButton')
->removeComponentsByType('GridFieldEditButton')
->removeComponentsByType('GridFieldDeleteAction')
->addComponent($pagination = new GridFieldPaginator(5))
->addComponent(GridFieldViewTransaction::create())
->addComponent(new GridFieldOrderSummaryRow(), 'GridFieldPaginator');
$config->getComponentByType('GridFieldDataColumns')
->setDisplayFields(array(
'Date' => 'Date',
'Status' => 'Status',
'TypeDescriptive' => 'Type',
'Source' => 'Source',
'Amount' => 'Amount',
));
$config->getComponentByType('GridFieldDataColumns')
->setFieldFormatting(array(
'TypeDescriptive' => function ($value, &$item) {
if ($value == 'Voucher') {
return $value . ' ' . ($item->Voucher()->Status != 'Verification' ? $item->Voucher()->Status : 'Awaiting Verification');
}
return $value;
},
'Date' => function ($value, &$item) {
return date(ADMINGRIDFIELDDATETIME, strtotime($value));
},
'Amount' => function ($value, &$item) {
return $item->CurrencySymbol() . ' ' . $value;
}
));
$fields->removeByName('Transactions');
$fields->addFieldToTab('Root.Main', $fTransactions);
}
}
if ($notes = $fields->dataFieldByName('OrderNotes')) {
$config = $notes->setTitle('Notes')
->getConfig()
->removeComponentsByType('GridFieldPaginator')
->removeComponentsByType('GridFieldDeleteAction')
->addComponent($pagination = new GridFieldPaginator(5));
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Created' => 'Date',
'Type' => 'Type',
'Staff.FirstName' => 'Name',
'Note' => 'Note'
));
$config->getComponentByType('GridFieldDataColumns')->setFieldFormatting(array(
'Created' => function($value, &$item) {
return date(ADMINGRIDFIELDDATETIME, strtotime($value));
}
));
$fields->removeByName('OrderNotes');
$fields->addFieldToTab('Root.Main',$notes);
}
if (!$siteConfig->SystemTakesPayments)
$fields->removeByName('Transactions');
if ($grid = $fields->dataFieldByName('OrderNotes'))
$grid->getConfig()
->removeComponentsByType('GridFieldAddExistingAutocompleter')
->removeComponentsByType('GridFieldFilterHeader');
$arrSiteConfigOrderParams = array();
foreach (OrderParamPattern::get()->filter('OrderProminent',1) as $strOrderParam)
$arrSiteConfigOrderParams[] = $strOrderParam->AutoAttachNames;
if (empty($_POST['IsInXLSExport2016'])) {
$orderParams = $this->OrderParams()
->filterAny(array(
'IsTemplate' => true,
'Name' => $arrSiteConfigOrderParams
))
->exclude('Name','template');
if ($orderParams->exists()) {
if ($grid = $fields->dataFieldByName('OrderParams')) {
$grid->getConfig()
->removeComponentsByType('GridFieldAddNewButton')
->removeComponentsByType('GridFieldFilterHeader')
->removeComponentsByType('GridFieldAddExistingAutocompleter')
->removeComponentsByType('GridFieldDeleteAction')
->removeComponentsByType('GridFieldViewButton');
if (empty($arrSiteConfigOrderParams))
$grid->getConfig()->removeComponentsByType('GridFieldEditButton');
$grid->setList($orderParams);
$grid->setTitle('Additional Order Information');
$grid->getConfig()
->getComponentByType('GridFieldDataColumns')
->setDisplayFields(array(
'Name' => 'Description',
'getDescribedValue' => 'Value',
));
$fields->removeByName('OrderParams');
$fields->insertBefore($grid,'OrderNotes');
}
}
else
$fields->removeByName('OrderParams');
}
if ($memberDocuments = $fields->dataFieldByName('MemberDocuments')) {
$config = $memberDocuments->setTitle('Documents')
->getConfig()
->removeComponentsByType('GridFieldPaginator')
->removeComponentsByType('GridFieldAddNewButton')
->removeComponentsByType('GridFieldDeleteAction')
->addComponent(new GridfieldDownloadMemberDocument())
->addComponent(new GridfieldChangeDocumentOwner())
->addComponent(new GridFieldDeleteAction())
->addComponent($pagination = new GridFieldPaginator(5));
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'File.Name' => 'Document Name',
'OrderItem.Product.Name' => 'Product',
'Member.FullName' => 'User'
));
$fields->removeByName('MemberDocuments');
$fields->addFieldToTab('Root.Main',$memberDocuments);
}
$this->extend('overrideCMSFields', $fields);
return $fields;
}
function onBeforeWrite(){
parent::onBeforeWrite();
if(!$this->IsInDB())
$this->SkipOnAfterWriteRecalc = true;
else {
if ($this->isChanged('MainStatus', 2)) {
$arrChanges = $this->getChangedFields(true,2);
if (in_array($arrChanges['MainStatus']['after'],array('Completed','Provisional'))) {
foreach ($this->OrderItems('DateRegistered IS NULL') as $orderItem) {
$orderItem->DateCompleted = SS_Datetime::now()->getValue();
$orderItem->write();
}
}
}
}
}
function onAfterWrite(){
parent::onAfterWrite();
$bWriteAtEnd = false;
if($this->Cancel){
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
$this->MainStatus = 'Cancelled';
$this->UpdateStatus();
$this->CancelDate = SS_Datetime::now()->getValue();
$this->CancelMemberID = Member::CurrentUserID();
$this->Cancel = false;
$bWriteAtEnd = true;
foreach($this->OrderItems() as $orderItem)
$orderItem->Cancel();
WebHook::process($this, 'OrderCancelled');
if ($siteConfig->SendOrderCancelEmail) {
Message::PrepareAndSendByType('Cancellation', $this);
SMSMessage::PrepareAndSendByType('Cancellation', $this);
}
}
if(!$this->Reference) {
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
$this->Reference = ($siteConfig->DefaultReferencePrefix ? $siteConfig->DefaultReferencePrefix.'-' : '')
.(200000 + $this->ID)
.'-'.RandomData::StringAlpha(3);
$bWriteAtEnd = true;
}
if($bWriteAtEnd) {
$this->write();
}
if(!$this->SkipOnAfterWriteRecalc && in_array($this->MainStatus, array('Aborted', 'Pending', 'Cancelled')))
$this->ReCalculate(true);
$this->SkipOnAfterWriteRecalc = false;
}
/**
* Gets the current order in payment mode
*
* @return mixed
*/
public static function GetPaymentProcessingOrder() {
$orderParam = OrderParam::get()->where("
Name = 'PaymentProcessing'
AND Unset = 0
AND OrderID IN (
SELECT ID
FROM `Order`
WHERE SessionID='" . session_id() . "'
)")->first();
if ($orderParam)
return $orderParam->Order();
}
public function CancelWithReason($sCode, $sReason) {
$array = array(
'Type' => 'CancellationNote',
'Note' => $sCode."
".$sReason,
'OrderID' => $this->ID,
MemberExtension::IsStaff() ? 'StaffID' : 'MemberID' => Member::currentUserID()
);
$orderNote = OrderNote::create($array);
$orderNote->write();
$this->CancellationCode = $sCode;
$this->CancellationReason = $sReason;
$this->Cancel = 1;
$this->write();
}
/**
* Get current order.
*
* If there is order to edit in the session (CurrentlyEditingOrder) it will be the returned order
* only if current user is Staff or it's the purchaser or OrganisationId is set and it's equal for
* the user and the order.
* This can give more access to some users if Organisation is used, but user must not has privileges
* to edit.
*
* If the current order is completed or has any problem with purchaser the order is aborted.
* Then take any pending order or let to create a new order
* @param string $strSessionID, the session id
* @param boolean $bCreateOrder ,if it is true a new order is created, and no otherwise
* @param boolean $bRecoverAbortedIfNoItems
* @return Order
*/
public static function GetCurrentOrder($strSessionID='',$bCreateOrder=true, $bRecoverAbortedIfNoItems=true) {
//JSONFeed does not need order and this is big speed improvement
if (get_class(Controller::curr()) == 'JSONFeed_Controller')
return null;
if(empty($strSessionID))
$strSessionID = ServerUtils::GetSessionID();
$iCurrentlyEditingOrder = (int)Session::get('CurrentlyEditingOrder');
//Reuse cached order if it's still Pending and the sessionID is the same. Also if Session::get('CurrentlyEditingOrder') is set then we do not need the current order
if (!is_null(self::$currentOrder) && self::$currentOrder !== false && !$iCurrentlyEditingOrder)
if (self::$currentOrder->SessionID == $strSessionID && self::$currentOrder->MainStatus == 'Pending')
return self::$currentOrder;
if(!$bCreateOrder && self::$currentOrder === false)
return null;
self::$currentOrder = $order = null;
if($iSelectedOrderID = (int)Session::get('CurrentlyEditingOrder')){
if($order = Order::get()->byId($iSelectedOrderID)){
if ($member = MemberExtension::currentUser())
if (!MemberExtension::IsStaff($member)
&& $member->ID != $order->PurchaserID
&& (!$member->OrganisationID || $member->OrganisationID != $order->OrganisationID)
)
$order = null;
if($order && !$order->allowEdit())
$order = null;
}
}
if (!$order) {
if ($order = Order::GetPaymentProcessingOrder()) {
$order->OrderInPayment = true;
return $order;
}
}
if(empty($strSessionID))
$strSessionID = ServerUtils::GetSessionID();
if(!$order){
StartOrder_Controller::EndEditSession();
$order = $newPendingOrder = Order::get()->filter(array(
'SessionID' => $strSessionID,
'MainStatus' => 'Pending'
))->first();
}
if($order && !$order->getOrderParam('NoRecover')) {
if ($bRecoverAbortedIfNoItems
&& (!$order || !$order->OrderItems()->exists())
&& ($order && !$order->getOrderParam('RebookPurchaser')) && !defined('BUSKERS_PATH') //not the best hack
) {
if($abortedOrder = Order::get()->filter(array(
'SessionID' => $strSessionID,
'MainStatus' => 'Aborted',
'PurchaserID' => Member::currentUserID()
))->first())
{
$order = $abortedOrder;
}
}
if($order && $order->MainStatus == 'Aborted' && !defined('BUSKERS_PATH')) { //not the best hack
if($order->getOrderParam('Recovering'))
return $order;
$order->setOrderParam('Recovering', true);
if($order->canRecover()) {
LogEntry::log('Recover '.$order->Reference);
$order->SessionID = ServerUtils::RegenerateSessionID();
$order->MainStatus = 'Pending';
$order->write();
$order->unsetOrderParam('Recovering');
}
else {
$order->unsetOrderParam('Recovering');
$order = isset($newPendingOrder) ? $newPendingOrder : null;
}
}
}
if (!$order && $bCreateOrder)
$order = Order::NewOrder($strSessionID);
//If Session::get('CurrentlyEditingOrder') is set then this is order editing, so do not cache it as current order
if(!$iCurrentlyEditingOrder)
self::$currentOrder = $order;
if(!$order)
self::$currentOrder = false;
return $order;
}
/**
* canRecover
* Checks if all the Orders Events are still available.
* @return bool
*/
public function canRecover() {
foreach($this->OrderItems() as $orderItem)
if (!$orderItem->AreEventsAvailable())
return false;
return true;
}
/**
* Get order items of currently editing order one at a time
* @return int, the id of that order item
*/
public function CurrentlyEditingOrderItem(){
return $this->OrderItems()
->filter('ID',$this->getOrderParam('CurrentlyEditingOrderItem'))
->first();
}
public static function IsEventRelatedToCurrentlyEditingOrderItem($event){
return self::EventRelatedToCurrentlyEditingOrderItem($event) ? true : false;
}
public static function EventRelatedToCurrentlyEditingOrderItem($event){
$order = self::GetCurrentOrder();
if(!$order->IsInEdit())
return false;
$alEvents = DatabaseUtils::ArrayListFromDBQuery('
SELECT EventID
FROM OrderItem_Events
WHERE OrderItemID ='.intval($order->getOrderParam('CurrentlyEditingOrderItem'))
);
$arrEventIDs = explode(',',$event->EventIDs);
foreach ($arrEventIDs as $iEventID)
if($alEvents->find('EventID',$iEventID))
return Event::get()->byId($iEventID);
return false;
}
/**
* Check whether the order is in the edit mode
* @return boolean, true if the order in edit mode, false otherwise
*/
public static function IsOrderEditMode($order = null){
if ($order == null)
$order = self::GetCurrentOrder('',false);
if ($order == null)
return false;
return $order->IsInEdit();
}
public function IsInEdit(){
return $this->allowEdit() && intval(Session::get('CurrentlyEditingOrder')) == $this->ID;
}
public function allowEdit() {
if($this->isInDB()) {
$arrAllowedOrderStatus = array('Completed', 'Provisional');
if($this->HasResourceManagementProduct())
$arrAllowedOrderStatus[] = 'Pending';
return in_array($this->MainStatus, $arrAllowedOrderStatus);
}
}
public function IsBasketEdit() {
return $this->getOrderParam('BasketOrderItemEdit');
}
public function CurrentlyEditingBasketOrderItem() {
if($iOrderItemId = $this->IsBasketEdit()) {
$orderItem = $this->OrderItems()->filter('ID', $iOrderItemId)->first();
return $orderItem;
}
}
public function editOrderItem($orderItem) {
if($orderItem->Order()->ID == $this->ID) {
LogEntry::Log('Currently editing order '.$this->ID);
if (Session::get('CurrentlyEditingOrder')) {
return false;
}
Session::set('CurrentlyEditingOrder', $this->ID);
$this->SessionID = ServerUtils::RegenerateSessionID();
$this->write();
$this->setOrderParam('CurrentlyEditingOrderItem', $orderItem->ID);
return true;
}
}
public static function OrderAlredyInEditMesage() : string {
return '' . _t('OrderAlreadyInEdit', 'Please finish editing your order before proceeding') . '
';
}
public function editOrderItemDetails($orderItem) {
if($this->editOrderItem($orderItem) !== false) {
$this->setOrderParam('EditOrderItemDetails', 1);
$orderItem->AddParticipantsAsPendingParticipants($orderItem);
return true;
}
return false;
}
/**
* Check whether the order is empty
* @return boolean, true if the order has no order items, false otherwise
*/
public function IsEmpty(){
return !$this->OrderItems()->first();
}
public function HasHiddenTemplate(){
$bIsStaff = MemberExtension::IsStaff();
foreach ($this->OrderItems() as $orderItem) {
if ($templateSet = $orderItem->GetTemplateSet()) {
foreach ($templateSet->Templates() as $template) {
if ((!$bIsStaff && $template->HiddenFromPurchaser) || ($bIsStaff && $template->HiddenFromAdmin)) {
return true;
}
}
}
}
}
public function PendingParticipantCount(){
$iCount = 0;
foreach ($this->OrderItems() as $orderItem)
$iCount += $orderItem->PendingParticipants()->Count();
return $iCount;
}
/**
* Create a new order, set the values for its attributes and enter the values to the database
* @param string $strSessionID is the current session id
* @param boolean $bWrite, if true, the data is written to the database,no otherwise
* @return Order, the details of the order
*/
public static function NewOrder($strSessionID,$bWrite=true,$bNewPlayerOnly=false,$pendingMember=null) {
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
// Abort any orders with same session id to avoid having > 1 orders with pending statuses
Order::AbortPending($strSessionID);
$order = Order::create(array(
'MainStatus' => 'Pending',
'IPAddress' => ServerUtils::IPAddress(),
'SessionID' => $strSessionID,
));
if ($member = MemberExtension::currentUser()) {
if ($member->IsStaff()) {
$purchaser = Member::get()->byID($member->BookingOnBehalfOfMemberID);
$order->update(array(
'StaffID' => $member->ID,
'Source' => 'Staff',
'PurchaserID' => $member->BookingOnBehalfOfMemberID,
'OrganisationID' => $purchaser && $purchaser->Organisation()->Status == 'On' ? $purchaser->OrganisationID : 0,
));
}
else {
$order->PurchaserID = $member->ID;
$order->OrganisationID = $member->Organisation()->Status == 'On' ? $member->OrganisationID : 0;
}
}
if (!$order->PurchaserID) {
$pendingMember = PendingMember::create();
if(!$bNewPlayerOnly && $siteConfig->AutoFillData)
$pendingMember = RandomData::PopulateMember($pendingMember, Template::GetPurchaserTemplate());
}
if ($bWrite) {
if (!$order->PurchaserID) {
$pendingMember->OrganisationID = $order->Organisation()->Status == 'On' ? $order->OrganisationID : 0;
$order->PendingPurchaserID = $pendingMember->write();
}
$order->write();
}
if(Queue::isQueueITEnabled()) {
$order->setOrderParam('NoRecover', true);
}
return $order;
}
/**
* Abort the pending ordersParticipants needed for the order items
* @param string $strSessionID, the session id
*/
public static function AbortPending($strSessionID='',$iAgeInMinutes = 0) {
$orders = Order::get()->filter('MainStatus','Pending');
if(empty($strSessionID)) {
$strSessionID = session_id();
if(empty($strSessionID))
return;//Just incase still empty return instantly to avoid aborting all pending orders
}
if($strSessionID != 'All')
$orders = $orders->filter('SessionID', $strSessionID);
if($iAgeInMinutes)
$orders = $orders->filter('LastEdited:LessThan',date(MYSQLDATETIME, DateUtils::MinusMinutesFromNow($iAgeInMinutes)));
LogEntry::log('Orders to abort: ' . $orders->count());
foreach ($orders->limit(40) as $order) { // avoid memory issues and timeouts - limited per request
if ($order->getOrderParam('BillingPage') == 1) {
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
if ($siteConfig->SendEmailToAdminOnOrderAbort) {
Message::PrepareAndSendByType('OrderAbortedAdminNotification',$order);
}
if ($siteConfig->SendEmailToPurchaserOnOrderAbort) {
if ($order->PendingPurchaser()) {
Message::PrepareAndSendByType('OrderAbortedPurchaserNotification', $order);
}
}
}
$order->Abort();
}
}
public static function DeleteAbortedOrders() {
//delete empty orders that are old more than 24 hours
$lastEditedTime = date(MYSQLDATETIME, DateUtils::MinusDaysFromDate(strtotime(SS_Datetime::now()->getValue()),1));
$orders = DB::query("SELECT o.ID
FROM `Order` o
LEFT JOIN `OrderParam` op ON op.OrderID = o.ID AND op.Name = 'BillingPage'
WHERE o.MainStatus = 'Aborted'
AND o.LastEdited <= '$lastEditedTime'
AND IFNULL(op.Value, 0) != 1
ORDER BY o.ID DESC
LIMIT 10")->column('ID');
foreach($orders as $orderID) {
$order = Order::get()->byID($orderID);
$order->delete();
}
//delete empty orders that are old more than 90 days
$lastEditedTime = date(MYSQLDATETIME, DateUtils::MinusDaysFromDate(strtotime(SS_Datetime::now()->getValue()),90));
$orders = DB::query("
SELECT o.ID
FROM `Order` o
JOIN OrderParam op on op.OrderID = o.ID and op.Name = 'BillingPage'
WHERE `MainStatus` = 'Aborted'
and o.LastEdited <= '$lastEditedTime'
AND op.Value = 1
ORDER BY o.ID DESC
LIMIT 10")->column('ID');
foreach($orders as $orderID) {
$order = Order::get()->byID($orderID);
$order->delete();
}
}
/**
* Abort the orders and update the status in the database
*/
public function Abort() {
LogEntry::log('Abort ' . $this->Reference);
$this->MainStatus = 'Aborted';
$this->SessionID = ''; //clear session id so not resotre orders after they get aborted.
$this->write();
$this->RemoveMemberDocuments();
DatabaseUtils::UpdateField($this->Transactions(),'Status','Aborted');
}
public function RemoveMemberDocuments() {
if (!$this->ID)
return;
$memberDocuments = $this->MemberDocuments();
foreach ($memberDocuments as $memberDocument) {
if ($file = $memberDocument->File()) {
if ($file->exists()) {
$file->delete();
}
}
$memberDocument->delete();
}
}
//TODO use polymorphism on below function
public function IsOrderItemsAvailableWhileAborted(){
$bAvailable = true;
$arrPost = array();
$dlOrderItems =$this->OrderItems();
foreach($dlOrderItems as $orderItem){
$product = $orderItem->Product();
$arrPost['ProductID'] = $product->ID;
$arrPost['Quantity'] = $orderItem->Quantity;
$event = $orderItem->Events()->First();
switch ($product->Type){
case 'FixedEvent' :
$arrPost['EventID'] = ($event) ? $event->ID:'0';
break;
case 'TemplateEvents' :
$dtStartDateTime = strtotime($event->StartDateTime);
$arrPost['Availability']['StartDate'] = date(UKJSDATE,$dtStartDateTime);
$arrPost['Availability']['StartTime'] = date(MYSQLTIME,$dtStartDateTime);
break;
case 'EventSeries':
$arrPost['EventGroupID'] = $event->EventGroupID;
$dlProductGroups = $product->ProductGroups();
$arrPost['ProductGroupID'] = ($dlProductGroups->count())?$dlProductGroups->sort('Priority DESC')->ID:0;
break;
case 'PhysicalItem':
if($product->PhysicalItem()->StockControlled){
$arrPost['RelatedProduct_'.$product->ID] = $orderItem->Quantity;
$arrPost['RelatedProduct_' . $product->ID . '_ItemType'] = $orderItem->PhysicalItemTypeID;
}
}
$bAvailable = $bAvailable
&& $product->IsAvailable($orderItem->Quantity,strtotime($event->StartDateTime),'',$event->ID,'',false,$arrPost) == 100;
}
return $bAvailable;
}
/**
* PerformOneOrderOnly
* if the site config is set to one order only remove any other OrderItems, called from the
* AddItems function
*/
private function PerformOneOrderOnly(){
if(SiteConfigOverride::CurrentSiteConfig()->OneItemOnly)
foreach($this->OrderItems() as $orderItem)
self::RemoveFromBasket($orderItem);
}
private function PerformProductGroupOneOrderOnly($iSequence) {
$preItems = $this->OrderItems()->exclude('Sequence', $iSequence);
$postItems = $this->OrderItems()->filter('Sequence', $iSequence);
$bItemsRemoved = false;
foreach($preItems->filter([
'Product.ProductGroups.ID' => ProductGroup::get()->filter('Products.OrderItems.ID', $postItems->column('ID'))->column('ID'),
'Product.ProductGroups.OneOrderOnly' => true,
]) as $orderItem) {
self::RemoveFromBasket($orderItem);
$bItemsRemoved = true;
}
if ($bItemsRemoved) {
$this->ReCalculate(true);
}
}
public static function RemoveFromBasket($orderItem) {
$orderItem->Order()->removeOrderItem($orderItem);
}
/**
* Add Items to the order and recalculate
* Calls the function of the relavant products to add them to the basket.
* Makes the OrderItems, Events and the PendingParticipant objects
* @param array $arrPost
* @param boolean $bWrite, if true, the data is written to the database,no otherwise
* @param int $iSeq, sequence number
* @param boolean $bUseAnonymousMembers, true if anonymous members are considered, false otherwise
*/
public function AddItems($arrPost,$bWrite = true, $iSeq = 0, $bCheckAvailability = true) {
FileUtils::BuskerLog('AddItems - '.StringUtils::Obj2Str($arrPost).' - '.StringUtils::Obj2Str($bWrite));
LogEntry::log('AddItems - '.StringUtils::Obj2Str($arrPost).' - '.StringUtils::Obj2Str($bWrite),999);
$this->ErrorCode = '';
$this->Error = '';
$this->setOrderParam("BillingPage", '0');
if ($bWrite) {
if(!$this->IsInEdit())
$this->PerformOneOrderOnly();
}
$this->PerformWaitingListOrderCheck();
if ($iSeq == 0)
$iSeq = $this->GetNextSequence();
$iIsAvailable = '';
if (ArrayUtils::IsSetAndNotEqual($arrPost, 'ProductGroupID', 0)) {
$events = Event::get()->byIDs(explode(',', $arrPost['EventSeriesID']));
if($events) {
$glEvents = new GroupedList($events);
foreach ($glEvents->groupedBy("ProductID") as $eventGroup) {
$arrPostforGroup = $arrPost;
$arrPostforGroup['ProductID'] = $eventGroup->ProductID;
$arrPostforGroup['EventSeriesID'] = implode(',', $eventGroup->Children->Column('ID'));
$this->AddProductItems($arrPostforGroup, $bWrite, $iSeq, $bCheckAvailability, $strErrorMessage, $iIsAvailable);
if($productGroup = ProductGroup::get()->byID($arrPost['ProductGroupID']))
foreach($this->OrderItems() as $orderItem)
if($orderItem->Sequence == $iSeq) {
$orderItem->ProductGroupID = $productGroup->ID;
if($bWrite)
$orderItem->write();
}
}
}
}
else {
$this->AddProductItems($arrPost, $bWrite, $iSeq, $bCheckAvailability, $strErrorMessage, $iIsAvailable);
}
if($bWrite) {
$this->PerformProductGroupOneOrderOnly($iSeq);
}
return $this;
}
function AddProductItems($arrPost, $bWrite, $iSeq, $bCheckAvailability, &$strErrorMessage, &$iIsAvailable) {
FileUtils::BuskerLog(' AddProductItems');
$bEditMode = $this->isInDB() ? $this->IsInEdit() : self::IsOrderEditMode();
$product = DataObject::get_by_id($arrPost['ProductClassName'], $arrPost['ProductID']);
if (isset($arrPost['EventID'])) {
self::CheckForOverBooking($arrPost['EventID']);
}
OrderItemLock::LockByProduct($product, $arrPost);
$iQuantity = isset($arrPost['Quantity']) ? abs(intval($arrPost['Quantity'])) : 1;
if(isset($arrPost['ProductGroupProduct']) && !empty($arrPost['ProductGroupProduct']))
$arrPost['ProductID'] = $arrPost['ProductGroupProduct'];
$alRelatedProducts = new ArrayList();
$strErrorMessage = '';
if ($product && ArrayUtils::IsAllSetAndNotEmpty($arrPost, array('ProductID','ProductClassName'))) {
if($iQuantity == 0)
$iIsAvailable = 115;
else{
$iIsAvailable = 100;
if ($bCheckAvailability) {
FileUtils::BuskerLog(' Run is Available check');
$iIsAvailable = $product->IsAvailable($arrPost, $strErrorMessage, $bEditMode);
FileUtils::BuskerLog(' End Available check - '.$iIsAvailable);
if (isset($arrPost['EventID'])) {
self::CheckForOverBooking($arrPost['EventID']);
}
}
if($iIsAvailable == 100){
$arrRequiredProducts = [];
if ($product->HasEnabledRequiredUpsells()) {
foreach($product->ProductUpsells() as $upsell) {
if(!$upsell instanceof ProductUpsellBookingPageRequiredOne) {
continue;
}
if (!$upsell->HasEnabledUpsells()) {
continue;
}
if($arrRequiredProductsData = ArrayUtils::IsSetAndNotEmpty($arrPost, "RequiredProduct_{$upsell->ID}")) {
if($iRequiredProduct = ArrayUtils::IsSetAndNotEmpty($arrRequiredProductsData, 'Product')) {
if($requiredProduct = $upsell->Upsells()->byID($iRequiredProduct)) {
$arrRequiredData = ArrayUtils::IsSetAndNotEmpty($arrRequiredProductsData, $iRequiredProduct, []);
if($requiredProduct->IsAvailable($arrRequiredData,$strErrorMessage,$bEditMode) != 100) {
$iIsAvailable = 206;
break;
}
else {
$arrRequiredProducts[] = [
'Product' => $requiredProduct,
'Data' => $arrRequiredData,
];
}
}
else {
$iIsAvailable = 111;
}
}
else {
$iIsAvailable = 111;
}
}
else {
$iIsAvailable = 111;
}
}
}
$alRelatedProducts = $this->SelectedRelatedProducts($arrPost);
if(!$this->NewPlayerOnly && $alRelatedProducts->first()) {
foreach ($alRelatedProducts as $relatedProduct) {
if ($relatedProduct->IsAvailable($arrPost, $strErrorMessage, $bEditMode) != 100) {
$iIsAvailable = 206;
}
}
}
//Check for protected events - user can book the same quantity as in the previous event
//Those protected events are only for products that have fixed events.
if (in_array($product->ClassName,array('ProductFixedEvent','ProductEventSeries'))
&& $events = $product->GetEventsFromPost($arrPost)) {
foreach(ProductEventOrder::GetCurrentProductEventOrdersForMember(Member::get()->byID(Order::getRealPurchaserID(null, false)))->filter(array(
'NextEventID' => DatabaseUtils::QuickMap($events, 'Event.ID','Event.ID'),
'NextProductID' => $product->ID,
)) as $productEventOrder)
if (($iProtectedQuantity = $productEventOrder->ProtectedQuantity()) < $product->AddItemQuantity($arrPost)) {
$iIsAvailable = $product->AddItemQuantity($arrPost) == 1
|| $iProtectedQuantity == 0
? 212
: 213;
break;
}
}
if($iIsAvailable == 100){
if($member = MemberExtension::currentUser()) {
if($member->ID == SiteConfigOverride::CurrentSiteConfig()->SlowDownAddingOrderItemForUser) {
if($iSlowDown = SiteConfigOverride::CurrentSiteConfig()->SlowDownAddingOrderItem) {
sleep($iSlowDown);
}
}
}
FileUtils::BuskerLog(' Run new orderitems');
$newOrderItem = $product->AddItemsToOrder($this,$iSeq, $arrPost, $bWrite)->first();
$requiredOrderItems = new ArrayList;
foreach($arrRequiredProducts as $arrProduct) {
$requiredOrderItems->merge($arrProduct['Product']->AddItemsToOrder($this,$iSeq, $arrProduct['Data'], $bWrite,$newOrderItem));
}
if (isset($arrPost['EventID'])) {
self::CheckForOverBooking($arrPost['EventID']);
}
FileUtils::BuskerLog(' End new orderitems');
// get all the participant information and remove the order items from the order
//need TO Haddle Edit Booking
if(!$this->NewPlayerOnly && $bEditMode && $bWrite && $newOrderItem){
$doEditingOrderItem = $this->CurrentlyEditingOrderItem();
$newOrderItem->PurchserID = $doEditingOrderItem->PurchaserID;
if($doEditingOrderItem && $doEditingOrderItem->ProductID == $product->ID){
$product->EditBooking($doEditingOrderItem, $newOrderItem);
if (!empty($doEditingOrderItem->AddedByID)) //keep the old record for added by
$newOrderItem->AddedByID = $doEditingOrderItem->AddedByID;
foreach($requiredOrderItems as $orderItem) {
$orderItem->Sequence = $doEditingOrderItem->Sequence;
$orderItem->write();
}
$newOrderItem->Sequence = $doEditingOrderItem->Sequence;
$newOrderItem->write();
}
}
}
}
if($iIsAvailable == 110)
$strErrorMessage .= 'This Resource is close in this time period ';
}
}
else
$iIsAvailable = 116;
FileUtils::BuskerLog('Availablecode='.$iIsAvailable);
LogEntry::log('Availablecode='.$iIsAvailable,999);
$this->PerformWaitingListOrderCheck(false);
if (!$this->NewPlayerOnly) {
$doEditingOrderItem = $this->CurrentlyEditingOrderItem();
foreach($alRelatedProducts as $relatedProduct) {
$relatedProduct->AddItemsToOrder($this, $doEditingOrderItem ? $doEditingOrderItem->Sequence : $iSeq, $arrPost, $bWrite, $newOrderItem ? $newOrderItem : 0);
}
$this->ReCalculate($bWrite);
}
FileUtils::BuskerLog(' Run Package Matching');
if($bWrite)
NewPackage::PackageMatching($this);
FileUtils::BuskerLog(' End Package Matching');
LogEntry::log('FinalAvailablecode='.$iIsAvailable,999);
FileUtils::BuskerLog('FinalAvailablecode='.$iIsAvailable);
if($iIsAvailable != 100) {
$this->Error = ErrorUtils::getErrorMessage($iIsAvailable).'
'.$strErrorMessage;
$this->ErrorCode = $iIsAvailable;
LogEntry::log('Error='.$this->Error,300);
}
OrderItemLock::UnLockByProduct($product);
if (isset($arrPost['EventID'])) {
self::CheckForOverBooking($arrPost['EventID'],true);
}
return $this;
}
public static function CheckForOverBooking($eventID = 0, $sendEmail = false) {
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
if ($siteConfig->LogOverBooking) {
$result = DatabaseUtils::ArrayFromDBQuery("
SELECT COUNT(e.ID) as 'Count' FROM `Event` e
LEFT JOIN OrderItem_Events oie ON oie.EventID = e.ID
LEFT JOIN OrderItem oi ON oi.ID = oie.OrderItemID
LEFT JOIN `Order` o ON o.ID = oi.OrderID
LEFT JOIN Resource r ON r.ID = e.ResourceID
WHERE o.MainStatus IN ('Provisional','Completed','Pending')
AND e.ID = $eventID
");
FileUtils::BuskerLog(' LastOverbookingCheck [ ' . $_POST['EventID'] . ' ] - ' . $result[0]['Count']);
if ($result[0]['Count'] >= 2) {
if ($sendEmail) {
$email = new Email(
$siteConfig->NoReplyEmailAddress,
'plamen.nankov@bookingive.com; jordan.crew@bookinglive.com; joe.beck@bookinglive.com',
'Overbooking Detected - ' . $siteConfig->Name,
'Hello guys,
Overbooking has been detected with EventID ' . $_POST['EventID'] . '
' . Director::baseURL()
);
$email->send();
}
}
}
return;
}
/**
* Remove Waiting list Items from Order
* @param $CleanWaitingListOrdersAnyway boolean
*/
function PerformWaitingListOrderCheck($bCleanWaitingListOrdersAnyway = true){
$bClear = false;
if($this->ID){
if($bCleanWaitingListOrdersAnyway || (!$bCleanWaitingListOrdersAnyway && $this->HasNonWaitingListItem()))
$bClear = true;
if($bClear && $this->IsWaitingListOrder()){
$this->RemoveWaitingListOrderItems();
$this->unsetOrderParam('IsWaitingListOrder');
}
}
}
function MarkAsWaitingList(){
return $this->setOrderParam('IsWaitingListOrder', 1);
}
function IsWaitingListOrder() {
return !$this->ID || $this->getOrderParam('IsWaitingListOrder') == 1;
}
/**
* Check Waiting list Item
* @return boolean
*/
function HasWaitingListItem(){
foreach ($this->OrderItems() as $orderItem)
if($orderItem->IsWaitingListItem())
return true;
return false;
}
/**
* Check non Waiting list Item
* @return boolean
*/
function HasNonWaitingListItem(){
foreach ($this->OrderItems() as $orderItem)
if(!$orderItem->IsWaitingListItem())
return true;
return false;
}
/**
* Get Waiting list Item
* @return boolean
*/
function GetWaitingListItem(){
foreach ($this->OrderItems() as $orderItem)
if($orderItem->IsWaitingListItem())
return $orderItem;
}
// Remove Waiting list order Item
public function RemoveWaitingListOrderItems(){
foreach ($this->OrderItems() as $orderItem) {
if($orderItem->IsWaitingListItem())
$this->removeOrderItems($orderItem);
}
}
/**
* Get valid payment setting
*
* It will get the first OrderItem with event
* and will check for PaymentSetting connected
* to it's location.
* If there is there is not PaymentSetting connected
* to the event location, or there are no items with
* events it will return the default PaymentSetting
*
* @return PaymentSetting
*/
public function GetValidPaymentSetting() {
foreach($this->OrderItems() as $orderItem)
if ($event = $orderItem->Events()->first())
break;
if (!empty($event) && $iLocationID = $event->getRealLocationID())
if ($paymentSetting = PaymentSetting::get()->filter(array(
'LocationID' => $iLocationID,
'MOTO' => MemberExtension::IsStaff()
))->first())
return $paymentSetting;
return PaymentSetting::GetDefaultPaymentSetting();
}
/**
* Get the Event ending time
* @param date $dtStartTime, the start time of the event
* @param $eventTemplate
* @param date $dtDefaultEndTime, null by deault
* @return date the ending time of the event
*/
public static function GetEventEndTime($dtStartTime,$eventTemplate,$dtDefaultEndTime='') {
$dtEndTime = $dtDefaultEndTime ? $dtDefaultEndTime : '';
if($eventTemplate->WithinAvailableHours && $eventTemplate->ProductID){
$strStartDate = date(MYSQLDATEQUOTED,$dtStartTime);
$dlEvents = DatabaseUtils::ArrayListFromDBQuery("
SELECT EndDateTime
FROM Event
WHERE Type='Available'
AND ProductID = ".$eventTemplate->ProductID."
AND DATE(StartDateTime) = ".$strStartDate."
ORDER BY StartDateTime
");
if($dlEvents->count())
$dtEndTime = strtotime($dlEvents->first()->EndDateTime);
}
return $dtEndTime;
}
/**
* Remove order items from the order
* Check the packages and apply these.
* Remove all the events which were created for the order items and the relations
* Remove the stcok controls for the Physical items
* Delete all the pending participant for the events
* And after all the above is done it recalculated the order with respect to the boolean
*
* @param int $iOrderItemID, the id of the data item to be removed
* @param boolean $bRecalculate, true by default
*/
function removeOrderItems(OrderItem $orderItem, $bRecalculate = true, $bRemoveAllInSameSequence = true){
$orderItems = $bRemoveAllInSameSequence
? $this->OrderItems()->filter('Sequence', $orderItem->Sequence)
: [$orderItem];
foreach($orderItems as $orderItemToRemove) {
$this->removeOrderItem($orderItemToRemove);
}
if(!$this->HasWaitingListItem()) {
$this->unsetOrderParam('IsWaitingListOrder');
}
NewPackage::PackageMatching($this);
if($bRecalculate) {
$this->ReCalculate(true);
}
}
public function removeOrderItem(OrderItem $orderItemToRemove = null) {
$this->setOrderParam("BillingPage", '0');
if($orderItemToRemove) {
LogEntry::log('Order::removeOrderItems '. $orderItemToRemove->ID);
$orderItemToRemove->RemoveMemberDocuments();
// TODO refactor and move most code into Product
// TODO decide if this function will be called with onBeforeDelete or manually
$orderItemToRemove->Product()->RemoveOrderItem($orderItemToRemove);
$orderItemToRemove->Events()->removeAll();
$orderItemToRemove->removePendingParticipants();
if($physicalItemType = $orderItemToRemove->PhysicalItemType())
StockControl::get()->filter(array(
'PhysicalItemTypesID' => $physicalItemType->ID,
'OrderItemID' => $orderItemToRemove->ID
))->removeAll();
$this->OrderItems()->remove($orderItemToRemove);
$orderItemToRemove->delete();
return true;
}
}
/**
* Update PhysicalItem order items is the physical item has capacity / stocks
* if updates the order item and recalculate the order item
*
* @param $OrderItemID integer
* @param $ProductID integer
* @param $PhysicalItemType string
* @param $Quantity integer
* @return error code integer
*/
public function UpdatePhysicalItemOrderItem($iOrderItemID, $iProductID, $iPhysicalItemType, $iQuantity){
$iCode = 0;
$orderItem = $this->OrderItems('ID = ' . $iOrderItemID)->first();
$product = Product::get()->byId($iProductID);
if($product && $product->Type == 'PhysicalItem'){
$physicalItem = $product->PhysicalItem();
$iCode = $physicalItem->CheckPhysicalItemStocks($iPhysicalItemType, $iQuantity, $iOrderItemID);
if($iCode == 100){
$stockControl = StockControl::get()->filter(array(
'OrderItemID' => $orderItem->ID,
'Type' => 'Out',
'PhysicalItemTypesID' => $orderItem->PhysicalItemTypeID
))->first();
if($stockControl){
$stockControl->Amount = $iQuantity;
$stockControl->PhysicalItemTypesID = $iPhysicalItemType;
$stockControl->write();
$orderItem->Quantity = $iQuantity;
$orderItem->PhysicalItemTypeID = $iPhysicalItemType;
$orderItem->write();
$this->ReCalculate(true);
}
}
}
return $iCode;
}
/**
* Returns an array list containing all the products which
* are related and which are selected from the booking page
*
* @param array $arrPost,the details of the product for which the related products are selected
* @return ArrayList, the related products to the given product
*
*/
public function SelectedRelatedProducts($arrPost){
$alRet = new ArrayList();
if (ArrayUtils::IsAllSetAndNotEmpty($arrPost,'ProductID') &&
!ArrayUtils::IsAllSetAndNotEmpty($arrPost,'ProductGroupID')) {
if ($product = Product::get()->byID($arrPost['ProductID']))
foreach ($product->GetRelatedProducts('BookingPage') as $product)
if ($this->IsRelatedProductSetAndNotZero($arrPost, $product))
$alRet->push($product);
} elseif (ArrayUtils::IsAllSetAndNotEmpty($arrPost,'ProductGroupID'))
if ($productGroup = ProductGroup::get()->byID($arrPost['ProductGroupID']))
$alRet = $productGroup->addRelatedProducts($this,$arrPost);
$alRet->removeDuplicates();
return $alRet;
}
public function IsRelatedProductSetAndNotZero($arrPost,$product) {
$id = $product->ID;
if ($product->IsExpandedPricingSchemeEnumeratorEnabled()) {
foreach($product->PricingScheme()->SelectablePricingSchemeOptions($product, false) as $alOptions) {
if(ArrayUtils::IsSetAndNotEmpty($arrPost, "RelatedProduct_{$id}__{$alOptions->ID}")) {
return true;
}
}
}
return (isset($arrPost['RelatedProduct_'.$id]) && $arrPost['RelatedProduct_'.$id] != 0)
|| (isset($arrPost['RelatedProduct_'.$id.'_Quantity']) && $arrPost['RelatedProduct_'.$id.'_Quantity'] != 0);
}
/**
* Returns an array list of the required related products which are
* submitted via the booking page
*
* @param array $arrPost,the details of the product for which the related required products are selected
* @return ArrayList the related required products to the given product
*/
public function SelectedRelatedRequiredProducts($arrPost){
$alRet = new ArrayList();
if(isset($arrPost['RequiredProduct'])){
if ($product = Product::get()->byId($arrPost['ProductID'])) {
$requiredOneProdcut = $this->ProductUpsells("ClassName = 'ProductUpsellBookingPageRequiredOne'")->first();//there can be only one!
if($requiredOneProdcut)
$alRet->push($requiredOneProdcut);
}
}
return $alRet;
}
/**
* Get the next sequence for an Order
*
* @return int, the next sequence number that is current sequence number + 1
*/
public function GetNextSequence(){
$iMaxSeq = 0;
foreach($this->OrderItems() as $orderItem)
if ($iMaxSeq < $orderItem->Sequence)
$iMaxSeq = $orderItem->Sequence;
return $iMaxSeq + 1;
}
/**
* Generate check hash of an order
* Users Purchaser, OrderItem hashes, and transactions
* This helps to determine whether the order has changed with data which
* affects the calculations
* @return string ,the check hash value of an order
*/
public function GenerateCheckHash(){
//return implode('/',DatabaseUtils::ArrayFromSQLFile('OrderHash', array('OrderID'=>$this->ID)));
$purchaser = $this->Purchaser();
$strCheckHash = '/Order/'.$this->MainStatus.'/Purchaser/' . $purchaser->FirstName .'/'. $purchaser->Surname . '/' . $purchaser->Email;
foreach ($this->OrderItems() as $doOrderItem)
$strCheckHash .= $doOrderItem->GetCheckHash();
foreach ($this->Transactions("Status IN ('Completed','Authenticated','Refunded')") as $doTransaction)
$strCheckHash .= $doTransaction->GetCheckHash();
foreach ($this->Charges() as $chargeItem)
$strCheckHash .= $chargeItem->GetCheckHash();
return $strCheckHash;
}
/**
* Recalculates the orders costs
*
* Calls ReCalculate but forces it to calc by updating the hash and calling write
*/
public function ForceReCalculate() {
$this->CheckHash = '';
$this->ReCalculate(true);
}
/**
* Recalculates the orders costs
* This checks the hash of the order and if the previous hash and the current hash doesnt match,
* it runs the rest of the function
* It makes a order previous version if the order is not a pending order
* Also it generates the summary's again and make pending participants of needed.
*
*
* @param bool $bWrite
*/
public function ReCalculate($bWrite = false, $bRecalculateOrderItems = true) {
$strCheckHash = $this->GenerateCheckHash();
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
$orderItems = $this->OrderItems();
$this->extend('updateOrderItemsForRecalculate', $orderItems);
if(!$this->NewPlayerOnly)
$this->AllParticipantInformation = !$this->IsParticipantInformationIncomplete();
if ($this->CheckHash != $strCheckHash) {
$this->CheckHash = $strCheckHash;
$bEditMode = $this->IsInEdit();
$bPendingOrEditAndNotEPOS = !$this->EPOS && ($this->MainStatus == 'Pending' || $bEditMode);
foreach ($orderItems as $orderItem) {
$bOptimizedProduct = in_array($orderItem->Product()->ClassName, array('ProductFixedEvent', 'ProductEventSeries', 'ProductTemplateEvents'));
if ($siteConfig->IsProductTemplateOptimized) {
$bOptimizedProduct = $orderItem->Product()->ClassName == 'ProductAvailability';
}
// TODO currently only those products support the optimization
// This is disabled because of the PricingSchemeEnumerationMembershipPeakTimes
if($bRecalculateOrderItems /*&& !$bOptimizedProduct*/)
$orderItem->ReCalculatePrice();
if($bWrite) {
if($orderItem->isChanged())
$orderItem->write();
if($bPendingOrEditAndNotEPOS && $orderItem->ID && !$bOptimizedProduct)
$orderItem->MakePendingParticipants($bEditMode);
}
}
if (empty($this->DateRegistered))
$this->DateRegistered = SS_Datetime::now()->getValue();
if (empty($this->OriginalDateRegistered))
$this->OriginalDateRegistered = SS_Datetime::now()->getValue();
$this->TotalCost = 0.0;
foreach($orderItems as $orderItem)
$this->TotalCost += $orderItem->Cost;
$this->TotalTax = 0.0;
foreach($orderItems as $orderItem)
$this->TotalTax += $orderItem->Tax;
$this->ChargesTotal = 0.0;
foreach($this->Charges() as $chargeItem)
$this->ChargesTotal += $chargeItem->Amount;
$this->TotalTransactionValue = 0.0;
if ($this->ID || $this->NewPlayerOnly) {
$arrRefundTypes = Transaction::GetRefundTypes();
$this->TotalTransactionValue = $this->Transactions()
->filter('Status', array('Completed', 'Refunded'))
->exclude('Type','PackageDiscount')
->exclude('Type',$arrRefundTypes)
->sum('Amount');
$this->TotalTransactionRefund = $this->Transactions()
->filter('Type',$arrRefundTypes)
->sum('Amount');
$this->TotalTransactionValue += Voucher::CalculateAwaitingTransactions($this, $bWrite);
// Calculate childcare vouchers UnverifiedTransactionValue
$this->UnverifiedTransactionValue = $this->Transactions()->filter(array(
'Status' => 'Completed',
'Type' => 'Voucher',
'Voucher.Status' => 'Verification',
'Voucher.Type' => 'ChildCare',
))->sum('Amount');
}
$this->Deposit = $this->ID ? $this->CalculateDeposit() : 0;
}
if($bWrite) {
$this->CalculateProrataPayments($orderItems);
}
//If hash is not changed there are no changes in the OrderItems but we still need to recalculate the costs
if ($this->isInDB()) {
$fDiscount = $this->Transactions()->filter(array(
'Status' => array('Completed', 'Refunded'),
'Type' => 'PackageDiscount'
))->sum('Amount');
$arrRefundTypes = Transaction::GetRefundTypes();
$this->TotalTransactionRefund = $this->Transactions()
->filter('Type',$arrRefundTypes)
->sum('Amount');
if ($fDiscount != 0.0) {
$this->Deposit = $this->CalculateDeposit();
}
$this->FinalTotalCost = $this->TotalCost + $this->TotalTax + $this->ChargesTotal - $fDiscount;
$this->AmountDue = $this->TotalAmountDue = $this->AmountDueToPay();
if ($this->AmountDue == 0) {
$this->Deposit = 0;
}
}
$this->TotalAmountOverdue = 0;
$this->InvoiceAmountDue = 0;
$this->WriteTriggeredOnReCalc = false;
if ($bWrite && $this->isChanged()) {
$this->UpdateStatus();
$this->SkipOnAfterWriteRecalc = true;
$this->write();
$this->extend('onRecalculate');
$this->SkipOnAfterWriteRecalc = false;
$this->WriteTriggeredOnReCalc = true;
}
if ($bWrite && $this->MainStatus == 'Completed')
$this->updateTimeLineGrouping();
}
public function SupportInvoicing() {
if ($member = $this->Purchaser()) {
return $member->CanPayByInvoice();
}
return false;
}
/**
* Group OrderItems in groups for time line
*
* This grouping is only for ProductFixedEvent
*/
public function updateTimeLineGrouping() {
$arrHashParts = explode('/OrderItem/', $this->CheckHash);
unset($arrHashParts[0]);
// If there are no OrderItems exit
if (count($arrHashParts) == 0) {
return;
}
$arrHashParts[count($arrHashParts)] = StringUtils::GetBeforeFirst($arrHashParts[count($arrHashParts)], '/Trans');
$strOrderEventHash = implode('/OrderItem/', $arrHashParts);
if ($this->EventCheckHash == $strOrderEventHash)
return;
$this->EventCheckHash = $strOrderEventHash;
$this->SkipOnAfterWriteRecalc = false;
$this->write();
$glOrderItem = new GroupedList($this->OrderItems()
->filter('Product.ClassName', 'ProductFixedEvent')
->leftJoin('OrderItem_Events', 'oie.OrderItemID = OrderItem.ID', 'oie')
->leftJoin('Event', 'e.ID = oie.EventID', 'e')
->sort('e.StartDateTime ASC'));
$groupedOrderItems = $glOrderItem->groupBy('ProductID');
$iBookingTimeLineGapInterval = SiteConfigOverride::CurrentSiteConfig()->BookingTimeLineGapInterval;
$iDensity = 3;
foreach($groupedOrderItems as $iProductID => $dlOrderItems) {
$dtCurrentTimePoint = null;
$arrOrderItems = array();
$iTimeLineGroupIDCounter = 1;
foreach($dlOrderItems as $orderItem) {
$event = $orderItem->Events()->first();
if (is_null($dtCurrentTimePoint))
$dtCurrentTimePoint = new DateTime($event->StartDateTime);
$dtNextTimePoint = new DateTime($event->StartDateTime);
if (empty($iBookingTimeLineGapInterval) || $dtNextTimePoint->diff($dtCurrentTimePoint)->format('%a') > $iBookingTimeLineGapInterval) {
//Group found/stop
$iTimeLineGroupIDCounter = $this->setOrderItemsTimeLineGroupID($arrOrderItems, $iTimeLineGroupIDCounter, $iDensity <= count($arrOrderItems));
$arrOrderItems = array($orderItem);
} else
$arrOrderItems[] = $orderItem;
$dtCurrentTimePoint = $dtNextTimePoint;
}
$iTimeLineGroupIDCounter = $this->setOrderItemsTimeLineGroupID($arrOrderItems, $iTimeLineGroupIDCounter, $iDensity <= count($arrOrderItems));
}
}
private function setOrderItemsTimeLineGroupID($arrOrderItems, $iTimeLineGroupIDCounter, $bGrouped) {
foreach($arrOrderItems as $orderItem) {
$orderItem->update(array('TimeLineGroupID' => $iTimeLineGroupIDCounter))->write();
if (!$bGrouped)
$iTimeLineGroupIDCounter++;
}
if ($bGrouped)
$iTimeLineGroupIDCounter++;
return $iTimeLineGroupIDCounter;
}
public function GetPayableTaxAmount() {
$fPayableTax = 0.0;
foreach ($this->OrderItems() as $orderItem) {
if (floatval($orderItem->CostProrata)) {
$fPayableTax += ($orderItem->CostProrata / ($orderItem->Cost + $orderItem->Tax)) * $orderItem->Tax;
} else {
$fPayableTax += $orderItem->Tax;
}
}
return $fPayableTax;
}
/**
* Loops through the orders items and transactions assigning a proportion of each transaction to each
* booking item for use with the AccountingReport.
*
* Require $orderItems as parameter. This is because OrderItems are processed
* by updateOrderItemsForRecalculate hook.
*
* @param $orderItems
*/
public function CalculateProrataPayments($orderItems) {
if(!$this->isInDB())
return;
// Helpers
$arrOrderItems = array();
$arrOrderItemProrataCost = array();
foreach($orderItems as $orderItem) {
$arrOrderItems[$orderItem->ID] = $orderItem;
$arrOrderItemProrataCost[$orderItem->ID] = $orderItem->GetGrossAmount();
}
$transactions = $this->Transactions();
// Packages
$allDiscounts = $transactions
->filter(array(
'Type' => array('PromotionalCode', 'PackageDiscount'),
'Status' => array('Completed', 'Refunded')
))->toArray();
foreach($allDiscounts as $transaction) {
$transactionOrderItemsIDs = $transaction->OrderItems()->column('ID');
foreach(Order::DistributeMoney(
$transaction->Amount - $transaction->RefundedAmount,
array_intersect_key($arrOrderItemProrataCost, array_combine($transactionOrderItemsIDs, $transactionOrderItemsIDs))
) as $iOrderItemID => $fAmount) {
$arrOrderItemProrataCost[$iOrderItemID] -= $fAmount;
}
}
// Goodwill
foreach(self::DistributeMoney(
$transactions->filter('Type', 'Goodwill')->sum('Amount'),
$arrOrderItemProrataCost
) as $iOrderItemID => $fAmount)
$arrOrderItemProrataCost[$iOrderItemID] -= $fAmount;
// Vouchers
$arrPayableByVoucherOrderItemProrataCost = array();
foreach($orderItems->filter('Product.PayableByChildCareVoucher', true) as $orderItem)
$arrPayableByVoucherOrderItemProrataCost[$orderItem->ID] =
$arrOrderItemProrataCost[$orderItem->ID];
foreach($transactions->filter(array('Type'=>'Voucher', 'Status'=>['Completed','Refunded'])) as $voucherTransaction) {
foreach(Order::DistributeMoney(
$voucherTransaction->Amount - $voucherTransaction->RefundedAmount,
$arrPayableByVoucherOrderItemProrataCost
) as $iOrderItemID => $fAmount) {
$arrOrderItemProrataCost[$iOrderItemID] -= $fAmount;
}
}
// Paid
foreach($arrOrderItems as $orderItem)
$orderItem->ProrataTransactions()->removeAll();
foreach($transactions
->filter('Type', array(
'Card','Cash','Cheque','PDQ','BACS')
)
->exclude('Status', array('Failed','Aborted', 'Pending',)
) as $transaction) {
$arrProportionPaid = self::DistributeMoney(
$transaction->Amount - $transaction->RefundedAmount, $arrOrderItemProrataCost);
$arrProportionPaidGross = self::DistributeMoney(
$transaction->Amount, $arrOrderItemProrataCost);
foreach($arrProportionPaid as $iOrderItemID => $dummy) {
$orderItem = $arrOrderItems[$iOrderItemID];
$orderItem->ProrataTransactions()->add($transaction, array(
'ProportionPaid' => $arrProportionPaid[$iOrderItemID],
'ProportionPaidGross' => $arrProportionPaidGross[$iOrderItemID]
));
$arrOrderItemProrataCost[$iOrderItemID] -= $arrProportionPaid[$orderItem->ID];
}
}
// Apply prorata costs
foreach($arrOrderItemProrataCost as $iOrderItemID => $fCostProrata) {
if($fCostProrata < 0) {
$fCostProrata = 0.0;
}
$arrOrderItems[$iOrderItemID]->update(array('CostProrata' => $fCostProrata))->write();
}
}
public static function DistributeMoney($fAmount, $arrSource) {
if(!$fAmount)
return array_fill_keys(array_keys($arrSource), 0.0);
$arrDistributedAmounts = array();
$fTotalDistributed = 0.0;
$fTotalAmount = array_sum(array_values($arrSource));
foreach($arrSource as $key => $fValue) {
$fAmountGross = 0;
if($fTotalAmount > 0.0)
$fAmountGross = floor(($fAmount * ($fValue / $fTotalAmount)) * 100) / 100;
$arrDistributedAmounts[$key] = $fAmountGross;
$fTotalDistributed = $fTotalDistributed + $fAmountGross;
}
if($iDifference = round($fTotalDistributed - $fAmount, 2)) {
if($iDifference < 0) {
$fAdd = 0.01;
arsort($arrSource);
}
else {
$fAdd = -0.01;
asort($arrSource);
}
foreach($arrDistributedAmounts as $key => &$value) {
if(round($fTotalDistributed, 2) == $fAmount)
break;
$value += $fAdd;
$fTotalDistributed += $fAdd;
}
}
return $arrDistributedAmounts;
}
/**
* Get Booking Time Line Event for the order with respect to an event.
* Checks the relative times which are given for the BookingTimeLineEvent objects
* for the product of the passed event.
*
* If $which == 'Next' returns the first item when they
* are arranged to the date ascending order
* If $which == 'Past' returns the latest passed item if there are any passed events
* If $which == 'Any' or something else returns the latest passed item if there
* are any passed events or returns the first item when they are arranged to the date ascending order
*
* This function is called to show summary (espetially the deposit) on basket page. For custom events
* the $event parameter is event that is not connected to any OrderItems and is not saved in the database.
* This is one of the reasons to pass the $mixedProduct.
*
* Calulcating the deposit also requires $mixedProduct because multiple products can be attaced to the same
* event (this event is Custom event) but having different Deposit TimeLines configured. For example
* ProductAvailability and EventUpsell will be connected to the same Custom event.
*
* @param Event $event
* @param string $which enum('Next', 'Past', 'Any') $which - default 'Any'
* @param array $arrType
* @param int $mixedProduct
* @return Bookingtimelineevent object
*/
function GetBookingTimeLineEvent($event, $which = 'Any',
$strType = BookingTimelineEventChangePaymentRequired::class,
$mixedProduct = null) {
if ($mixedProduct)
$product = is_object($mixedProduct)
? $mixedProduct
: Product::get()->byID(intval($mixedProduct));
if (empty($product)) {
$orderItem = $event->OrderItems()->filter('OrderID', $this->ID)->first();
$product = $orderItem->Product();
}
if ($bookingTimeLine = $product->getActiveBookingTimeline()) {
return $bookingTimeLine->findTimeLineEvent(intval($event->getRealLocationID()),
$strType,strtotime($event->StartDateTime),$which);
}
return null;
}
/**
* Calculate the deposit amount which needs to be paid,
* with respect to the OrderItems and events in the order
*
* In some cases $eventItemRow should be passed. This will be the
* case when there is Custom event and the deposite is calculated
* for the summary. In this case the $eventItemRow will not be in
* the database and will have no attached OrderItems.
*
* @param Event $eventItemRow
* @return float
*/
function CalculateDeposit($eventItemRow = null){
$fDeposit = 0.00;
if (empty($eventItemRow)) {
$eventItemRow = Event::get()->filter('OrderItems.OrderID', $this->ID)->sort('StartDateTime ASC')->first();
}
if($eventItemRow) {
$arrProcessedProductsIDs = [];
foreach(GroupedList::create($this->OrderItems())->groupBy('ProductID') as $iProductID => $alGroup) {
if ($timelineEvent = $this->GetBookingTimeLineEvent($eventItemRow, 'Next', BookingTimelineEventChangePaymentRequired::class, $iProductID)) {
$fDeposit += $timelineEvent->getDeposit($alGroup);
}
if($timelineEvent) {
$arrProcessedProductsIDs[] = $iProductID;
}
}
if(SiteConfig::current_site_config()->BookingTimeLineDespoitIncludeRemaining && !empty($arrProcessedProductsIDs)) {
foreach($this->OrderItems()->exclude('ProductID', $arrProcessedProductsIDs) as $orderItem) {
$fDeposit += $orderItem->GetGrossAmount();
}
}
}
return $fDeposit;
}
/**
* Calculate the deposit for the summary
*
* This is used to calculate the deposit for the summary on the booking page.
* It requires this method, because Custom events are not storred in the database
* yet and it will create a dummy object (not saved in the database).
*
* @param array $arrData - this is the data from the form ($_POST)
* @return float
*/
function CalculateDepositForSummary($arrData = array()){
//get deposit for TemplateEvents
if ($product = Product::get()->ByID($arrData['ProductID'])) {
$event = $product->GetEventsFromPost($arrData);
}
return empty($event)
? 0.00
: $this->CalculateDeposit($event->first());
}
/**
* Calculate amount due to pay for an order.
* If it is an Invoiced order then it takes the value of all the submitted invoices of the first invoice's cost
* depending on the number of invoices.
*
* @return string amount due
*/
public function AmountDueToPay(){
$fAmountToPay = 0;
if($this->isInDB()) {
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
if (!($siteConfig->CancelClearsAmountDue && in_array($this->MainStatus, ['Cancelled','Aborted']))) {
$fAmountToPay = $this->FinalTotalCost;
$fAmountToPay -= $this->PaidAmount();
if($fAmountToPay < 0) {
$fAmountToPay = 0;
}
}
}
return number_format($fAmountToPay, 2, '.', '');
}
public function TotalAmountDueIncludingUnverified() {
$due = $this->TotalAmountDue + $this->UnverifiedTransactionValue;
return $due ? number_format($due, 2,'.','') : '0.00';
}
/**
* Get the purchaser
* If a completed order this returns a Member object
* else returns the PendingPurchaser object
* @return Purchaser, the current purchaser or current pending purchaser
*/
public function GetPurchaser() {
if ($this->MainStatus == 'Pending' && empty($this->PurchaserID) ||
$this->MainStatus == 'Aborted' && empty($this->PurchaserID) && !empty($this->PendingPurchaserID))
return $this->PendingPurchaser();
return $this->Purchaser();
}
/**
* Show alternate events for cancel OrderSummaryMyAccountBooking
*
* TODO this has been reduced to a stub as the previous object
* has been removed and this will require rebuilding
*
* @return ArrayList
*/
public function ActiveWaitingList(){
return new ArrayList();
}
protected function PreparePurchaser($pendingPurchaser) {
$purchaser = null;
if($pendingPurchaser->Email)
$purchaser = Member::get()->filter('Email',$pendingPurchaser->Email)->first();
if(!$purchaser && !MemberExtension::IsStaff() && Member::currentUserID())
$purchaser = MemberExtension::currentUser();
if(!$purchaser)
$purchaser = new Member(array('HasConfiguredDashboard' => '1'));
$purchaser->write();
if (!$purchaser->InGroup('Purchaser'))
$purchaser->Groups()->add(Group::get()->filter('Code','purchaser')->first());
//This is moved to after assigning group so that extensions
//can hook on this member being in Purchaser to trigger in onAfterWrite (PDSMSDynamicsMemberExtension)
DatabaseUtils::PopulateDataFromObject($pendingPurchaser, $purchaser);
$purchaser->write();
$this->PendingPurchaser()->MoveCustomFieldsToMember($purchaser);
if ($pendingPurchaser instanceof PendingMember)
$pendingPurchaser->MoveUploadedFilesToMemberDocuments($purchaser);
$this->PendingPurchaserID = 0;
$this->PurchaserID = $purchaser->ID;
$this->OrganisationID = $purchaser->Organisation()->Status == 'On' ? $purchaser->OrganisationID : 0;
$this->SkipOnAfterWriteRecalc = false;
$this->write();
// Promote pending purchaser relations
foreach(MemberRelationship::GetRelations($pendingPurchaser, Template::create(['Type' => 'Participant']), $this) as $relation) {
$relation->MemberIDa = $purchaser->ID;
$relation->write();
}
return $purchaser;
}
/**
* Checkout function is used to update the order's status mainly when the payments are completed.
* But also this is used for Provisional orders etc.
*
* This updates the Order's status to the passed Status which is Completed by default.
*
* Take all the data from the pending members for purchasers and participants and add them in to Member objects.
*
* Saves the card tokens for the order.
*
* Sends the messages to purchaser with respect to the Order params set and also will send emails to the
* other recipients, and the participants.
*
* Syncs the order with Xerox, calls the bookinglive basic hooks
*
* @param string $strTargetStatus, target status of the booking completed by default
* @param int $iPurchaserID
*/
public function Checkout($strTargetStatus='Completed') {
$iCount = 15;
while (($result = $this->getSetOrderParamValue('CheckoutSessionFlag', 'ON')) == 'ON' && $iCount-- > 0) {
sleep(1);
}
if ($result == 'ON') {
LogEntry::log('Order Checkout: CheckoutSessionFlag not released for Order ID ' . $this->ID, 999);
return;
}
$bEditMode = $this->IsInEdit();
if ($this->getOrderParam('AutoTest'))
if ($strCheckoutTargetStatus = $this->getOrderParam('CheckoutTargetStatus'))
$strTargetStatus = $strCheckoutTargetStatus;
if(($this->MainStatus == $strTargetStatus) && !$bEditMode) {
$this->setOrderParam('CheckoutSessionFlag', 'OFF');
return;
}
if($this->IsEmpty()) {
$this->setOrderParam('CheckoutSessionFlag', 'OFF');
return;
}
$pendingPurchaser = $this->GetPurchaser();
LogEntry::log("Checkout($strTargetStatus)"
.' - '.StringUtils::Obj2Str($this)
.' - '.StringUtils::Obj2Str($pendingPurchaser),
999
);
$purchaser = $this->PreparePurchaser($pendingPurchaser);
foreach ($pendingPurchaser->CardTokens() as $cardToken){
$existingCardToken = CardToken::get()->filter(array(
'CardType' => $cardToken->CardType,
'Last4Digits' => $cardToken->Last4Digits,
'ExipiryDate' => $cardToken->ExipiryDate,
'MemberID' => $purchaser->ID
))->first();
if (!$existingCardToken)
$purchaser->CardTokens()->add($cardToken);
else
$cardToken->delete();
}
$orderItems = $this->OrderItems();
if($this->MainStatus == 'WaitingList' && $strTargetStatus == 'Completed')
foreach($orderItems as $orderItem)
$orderItem->Promote();
//if edit order get only the orderitem that is edited
if($bEditMode && $this->CurrentlyEditingOrderItem()) {
$orderItems = $orderItems->filter('ID', $this->CurrentlyEditingOrderItem()->ID);
}
if(!$this->NewPlayerOnly) {
foreach ($orderItems as $orderItem) {
$orderItem->Checkout($strTargetStatus, $bEditMode);
}
}
Order::ApplySimulatedDate();
Voucher::MakeEVouchers($this);
Voucher::ProcessVouchers($this);
Order::ClearSimulatedDate();
if($this->HasPDFContent())
self::MakePDFContent($this);
if ($strTargetStatus == 'WaitingList')
$this->WaitingList = true;
self::AddAttachmentsAsMemberDocuments($this);
$this->MainStatus = $strTargetStatus;
// no more edit now
if (!$this->NewPlayerOnly){
StartOrder_Controller::EndEditSession();
$this->ReCalculate(!$bEditMode, false); // no need of recalculate on checkout for new players, there is no change
}
if(empty($this->OriginalCheckoutDate)) {
$this->OriginalCheckoutDate = SS_Datetime::now()->getValue();
$this->write();
}
if(!$this->WriteTriggeredOnReCalc && $this->isChanged())
$this->write();
$this->UnsetSimulatedDate();
$this->unsetOrderParam('BillingAdminAddedTransactions');
$this->extend('updateCheckout', $this);
//send notifications if order is in edit
if ($bEditMode) {
$this->SendNotification('Amendment', $bEditMode);
}
if ($purchaser->MarketingPermission) {
MailChimp::MailchimpSubscribe($pendingPurchaser->FirstName,$pendingPurchaser->Surname,$pendingPurchaser->Email);
}
MemberExtension::PushDetailsRequester($purchaser);
try {
if ($bEditMode)
WebHook::process($this,'OrderUpdate');
else {
WebHook::process($this->Purchaser(),'NewUser');
WebHook::process($this, 'NewOrder');
}
}
finally {
MemberExtension::PopDetailsRequester();
}
if(SiteConfigCategoryZonal::getCurrent()->Enabled) {
try {
if(ZonalMessage::GetPartyOrderItems($this)->exists()) {
MemberExtension::PushDetailsRequester($purchaser);
ZonalMessage::CreateNewBookingWithPayment($this)->PrepareNewRequest()->Send();
MemberExtension::PopDetailsRequester();
}
}
catch(ZonalException $e) {
LogEntry::log(get_class($e) .': '. $e->getMessage());
}
}
Session::clear('ClosedNotifications');
if (!$bEditMode
&& in_array($strTargetStatus,['Completed','Provisional','WaitingList'])
&& ($this->EmailSent == 0)
&& $this->CanSendEmail()
) {
$this->EmailSent = 1;
$this->write();
}
$this->setOrderParam('CheckoutSessionFlag','OFF');
}
function HasParticipants() {
foreach ($this->OrderItems() as $orderItem)
if ($list = $orderItem->ParticipantsForItem())
if ($list->first())
return true;
}
//TODO combine all three (HasPDFContent,MakePDFContent,AddAttachmentsAsMemberDocuments)
//of these functions
function HasPDFContent() {
foreach ($this->OrderItems() as $orderItem)
if ($product = $orderItem->Product())
if (!empty($product->PDFContent))
return true;
}
public static function MakePDFContent($order) {
$member = null;
// This might add duplicate document for a member as document might contain merge
// tags with specific data that will be later merged with the custom contents
$orderPurchaser = $order->Purchaser();
$arrPurchaserProducts = [];
foreach ($order->OrderItems() as $orderItem) {
if ($product = $orderItem->Product()) {
if (!empty($product->PDFContent)) {
if ($iEventGroup = $orderItem->EventGroup) {
if ($order->OrderItems()->filter(['EventGroup' => $iEventGroup, 'Sequence' => $orderItem->Sequence])->sort('GroupSequence asc')->first()->ID != $orderItem->ID) {
continue;
}
}
if ($orderItem->ParentOrderItemID > 0) {
if ($event = Event::get()->filter('OrderItems.ID', $orderItem->ParentOrderItemID)->first()) {
$member = $event->Participants()->filter('OrderItemID', $orderItem->ParentOrderItemID)->first();
}
} else {
$member = ($event = $orderItem->Events()->first())
? $event->Participants()->filter('OrderItemID', $orderItem->ID)->first()
: $orderPurchaser;
}
MemberDocument::createNew(
$product->Name . '.pdf',
$orderItem,
$member,
false,
null,
$product,
null,
null,
true
);
if (!isset($arrPurchaserProducts[$product->ID])) {
if ($orderPurchaser->ID != $member->ID) {
if ($product->ClassName == 'ProductMembership') {
continue;
}
if ($ParentOrderItem = $orderItem->ParentOrderItem()) {
if ($ParentOrderItem->Product()->ClassName == 'ProductMembership') {
continue;
}
}
MemberDocument::createNew(
$product->Name . '.pdf',
$orderItem,
$orderPurchaser,
false,
null,
$product,
null,
null,
true
);
$arrPurchaserProducts[$product->ID] = $product->ID;
}
}
}
}
}
}
public static function AddAttachmentsAsMemberDocuments($order) {
//product groups will not be added here because when we book a product which is in some product group
//the user will recieve this attachement.
foreach ($order->OrderItems() as $orderItem) {
$arrMember['PurchaserAttachments'] = $purchaser = $order->Purchaser();
if ($event = $orderItem->Events()->first()) {
if ($eventMember = $event->Participants()->filter('OrderItemID', $orderItem->ID)->first())
$arrMember['ParticipantAttachments'] = $eventMember;
if ($resource = $event->Resource()) {
$arrTypes['Resource'] = $resource;
if ($location = $resource->Location())
$arrTypes['Location'] = $location;
}
}
$arrTypes['Product'] = $product = $orderItem->Product();
foreach ($arrTypes as $objectType => $type) {
foreach ($arrMember as $strAttachementsType => $member) {
foreach ($type->$strAttachementsType() as $attachment) {
self::AddAttachmentAsMemberDocument($member, $attachment, $orderItem);
}
}
}
foreach ($arrMember as $strAttachementsType => $member) {
self::AddOrganisationDocumentsAsMemberDocument($member, $orderItem);
}
//attach the .ics files as member documents
foreach ($arrMember as $strAttachementsType => $member) {
self::AddIcsFilesAsMemberDocument($member, $orderItem);
}
}
}
protected static function AddIcsFilesAsMemberDocument($member,OrderItem $orderItem) {
if (SiteConfig::current_site_config()->EnableSendingICSFiles) {
if ($orderItem->Events()->first()) {
$product = $orderItem->Product();
if (!in_array($product->ClassName,['ProductPhysicalItem','ProductEventUpsell','ProductMembership'])) {
$strName = 'Calendar_' . FileUtils::SanitizeFileName($product->Name) . "_" . $member->ID . "" . $orderItem->ID . ".ics";
if (!$member->Documents()->filter(array(
'Name' => $strName,
'OrderItemID' => $orderItem->ID
))->exists()) {
CalendarMemberDocument::createNew(
$strName,
$orderItem,
$member,
false
);
}
}
}
}
}
protected static function AddOrganisationDocumentsAsMemberDocument($member,OrderItem $orderItem) {
if ($organisation = $member->Organisation()) {
if($organisation->Status != 'On') {
return;
}
if ($organisation->OrganisationRoles()->first()) {
foreach (OrganisationDocument::get()->filter(array(
'OrganisationID' => $organisation->ID
)) as $organisationDocument) {
$file = $organisationDocument->File();
$file->Title = $organisationDocument->Name;
self::AddAttachmentAsMemberDocument($member, $file, $orderItem,$organisation->ID);
}
}
}
}
protected static function AddAttachmentAsMemberDocument($member, $attachment, OrderItem $orderItem,$organisationID=0) {
if (!$member->Documents()->filter(array(
'Name' => $attachment->Title,
'OrderItemID' => $orderItem->ID
))->exists()) {
MemberDocument::createNew(
$attachment->Title,
$orderItem,
$member,
false,
null,
null,
$attachment,
$organisationID
);
}
}
/**
* Send an email containing a notification of confirmation to the purchaser
*/
function SetOrderChangedNotification(){
Message::PrepareAndSendByType('Confirmation',$this);
SMSMessage::PrepareAndSendByType('Confirmation', $this);
return $this;
}
/**
* Send an email type of Amendment to the purchaser
*/
public function SendNotification($strType, $bEditMode = false){
Message::PrepareAndSendByType($strType, $this, $bEditMode);
if($this->SMSNotification) {
SMSMessage::PrepareAndSendByType($strType, $this, $bEditMode);
}
return $this;
}
public static function GetOrderItemsGrouped($orderItems) {
$arrOrderItems = $orderItems->toArray();
uasort($arrOrderItems, function ($a,$b) {
$strA = $strB = '0000-00-00 00:00:00';
if ($aEvent = $a->Events()->first()) {
$strA = $aEvent->StartDateTime;
}
if ($bEvent = $b->Events()->first()) {
$strB = $bEvent->StartDateTime;
}
return strcmp($strA, $strB);
});
$arrOrderItemsGroups = array();
foreach($arrOrderItems as $orderItem) {
$keyComponents = array(
$orderItem->Sequence,
$orderItem->ProductID,
$orderItem->ChoosenDayParticipant,
$orderItem->ChoosenDay,
);
if($event = $orderItem->Events()->first()) {
if($event->isInDB())
$keyComponents[] = $event->ID;
else {
$keyComponents = array_merge($keyComponents, array(
$event->StartDateTime,
$event->EndDateTime,
$event->ResourceID,
$event->ProductID,
));
}
}
$key = implode('_', $keyComponents);
if(!isset($arrOrderItemsGroups[$key]))
$arrOrderItemsGroups[$key] = new ArrayList();
$arrOrderItemsGroups[$key]->add($orderItem);
}
return $arrOrderItemsGroups;
}
public function GroupOrderItemsForSummary() {
$order = $this->duplicate(false);
$order->OriginalID = $this->ID;
$newOrderItems = array();
foreach(self::GetOrderItemsGrouped($this->OrderItems()) as $alOrderItems) {
$newOrderItem = $alOrderItems->shift();
foreach($alOrderItems as $orderItem) {
$newOrderItem->Cost += $orderItem->Cost;
$newOrderItem->Quantity += $orderItem->Quantity;
}
$newOrderItems[] = $newOrderItem;
}
foreach($newOrderItems as $orderItem) {
// Unset order item id before adding to order to prevent loading orignal related order items...
$id = $orderItem->ID;
$orderItem->ID = 0;
$order->OrderItems()->add($orderItem);
// ... and assign it again to keep all relations to order item like location resource etc
$orderItem->ID = $id;
}
foreach($this->Transactions() as $transaction)
$order->Transactions()->add($transaction);
foreach($this->OrderNotes() as $orderNote)
$order->OrderNotes()->add($orderNote);
return $order;
}
/**
* Get the array summary for a controller.
* This summary array is stored as YAML in the database and if saved previously it uses that and returns, else it generates the summary and
* returns. The summaries are different for each status of the booking, the Pending bookings has a different set, completed has another etc.
*
* @param integer $Context, boolean $Regenerate, boolean $ReplaceExisting
* @return html content
*/
public function GetSummaryForController($strContext = '',$bRegenerate = false,$bReplaceExisting = false, $strSummaryType = ''){
if ($this->NewPlayerOnly)
return '';
$OrderSummary = new OrderSummary();
return $OrderSummary->GetBookingSummary('BookingSummary'.$strContext,$this);
}
/**
* Return a link to edit order item
* @return a URL for order item
*/
public function ReturnToEditLink() {
if ($this->IsInDB()) {
if($orderItem = $this->CurrentlyEditingOrderItem())
return Director::baseURL() . 'book/edit/o/' . $orderItem->ID;
}
}
/**
* If the order can be editeed, then return to that link
* @return $Link string
*/
public function HasReturnToEditLink() {
$strLink = $this->ReturnToEditLink();
return !empty($strLink);
}
public function PurchaserCustomerAdminLink() {
if($purchaser = $this->Purchaser())
return Director::baseURL() . 'admin/customers/Purchaser/EditForm/field/Purchaser/item/' . $purchaser->ID . '/edit';
}
/**
* Take the summary of an order for Email context
* @return
*/
function OrderSummaryEmail(){
return $this->GetSummaryForController('Email');
}
/**
* Put the order summaries to an array
* @return ArrayData contains the summary of an order
*/
function OrderSummaryCMS(){
return $this->GetSummaryForController('CMSAdmin', true, true);
}
function OrderSummaryAdminEdit(){
return $this->GetSummaryForController('AdminEdit', true, true);
}
public function HasErrors() {
return !empty($this->Error);
}
/**
*Change the Status of the transaction from Pending to Aborted
*/
public function AbortCardPendingTransactions(){
foreach($this->Transactions()->filter(array(
'Status' => 'Pending',
'Type' => 'Card')
) as $transaction){
$transaction->Status = 'Aborted';
$transaction->write();
}
$this->ReCalculate(true);
}
///////////////////////
//
// Merge Elements
/**
* Take the reference of the current order
* @return varchar, the reference of the current order
*/
public function OrderReference() {
return $this->Reference;
}
/**
* get the summary of the order
* @return varchar, the summary of the order
*/
public function OrderSummary() {
return $this->Summary;
}
/**
* get the deposit amount of the order
* @return decimal, deposit of the order
*/
public function DepositAmount() {
return $this->Deposit;
// return number_format($this->Deposit,2);
}
/**
* Get the total amount of transaction of the order
* @return decimal, total amount of transaction
*/
public function PaidAmount() {
return $this->TotalTransactionValue - $this->TotalTransactionRefund;
//return number_format($this->TotalTransactionValue,2);
}
/**
* Get the amount to be paid for the order
* @return decimal, amount to be paid for the order
*/
public function DueAmount() {
return $this->AmountDue;
// return number_format($this->AmountDue,2);
}
/**
* Get the full name of the purchaser
* @return string, the full name of the purchaser
*/
public function PurchaserFullName() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->FullName();
}
/**
* Take the SocialTitle of the purchaser
* @return String, the SocialTitle of the purchaser
*/
public function PurchaserTitle() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->SocialTitle;
}
/**
* Get the first name of the purchaser
* @return string, the first name of the purchaser
*/
public function PurchaserFirstName() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->FirstName;
}
public function HasDeposit() {
return $this->Deposit > 0;
}
/**
* Get the sur name of the purchaser
* @return string ,the sur name of the purchaser
*/
public function PurchaserSurname() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->Surname;
}
/**
* Check whether there is some amount to be paid
* @return boolean,true if there is some amount to be paid, false otherwise
*/
public function IsBalanceToPay(){
return $this->AmountDue > 0;
}
/**
* the tCheck whether total amount for the order has been paid
* @return boolean, true if the amount is paid, false otherwise
*/
public function IsPayFullAmount(){
return $this->IsBalanceToPay();
}
/**
* Get the location of the order
* @return Varchar, the location
*/
public function OrderLocation(){
return WackyUtils::GetPartyLocation($this);
}
/**
* Get the currency symbol used
* @return Vachar, the currency symbol
*/
public function CurrencySymbol(){
if($strCode = $this->CurrencyCode())
return CurrencyUtils::Symbol($strCode);
}
/**
* Get the currency code
* @return ISO 4217 currency code
*/
public function CurrencyCode(){
return SiteConfigOverride::GetDefaultCurrencyCode();
}
/**
* Get the address of the purchaser of the order
* @return Varchar, the address
*/
public function PurchaserAddress() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->getFullAddress('TEXT');
}
/**
* Get the address of the purchaser of the order
* @return Varchar, the address
*/
public function PurchaserAddressHTML() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->getFullAddress('HTML');
}
/**
* Get the post code of the purchaser of the order
* @return Varchar, the Postcode
*/
public function PurchaserPostCode() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->PostCode;
}
/**
* Get the number of mobile telephone of the purchaser of the order
* @return Varchar,the number of mobile telephone
*/
public function PurchaserMobileTelephone() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->MobileTelephone;
}
public function PurchaserTelephone() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->Telephone;
}
public function PurchaserOrganisationName() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->Organisation()->Name;
}
public function PurchaserID(){
if ($purchaser = $this->GetPurchaser())
return $purchaser->ID;
}
public function CompanyName(){
return SiteConfigOverride::CurrentSiteConfig()->Name;
}
public function InvoiceFooter(){
return SiteConfigOverride::CurrentSiteConfig()->InvoiceFooter;
}
public function CompanyAddress(){
return SiteConfigOverride::CurrentSiteConfig()->getFullAddress('TEXT');
}
public function FirstOrderItemID(){
if($orderItem = $this->OrderItems()->first())
return $orderItem->ID;
}
public function CompanyPhoneNumber() {
return SiteConfigOverride::CurrentSiteConfig()->ContactNumber;
}
public function CompanyVatRegisterNumber() {
return SiteConfigOverride::CurrentSiteConfig()->VATRegisterNumber;
}
/**
* Get the number of another telephone of the purchaser of the order
* @return Varchar,the number of another telephone
*/
public function PurchaserTelephoneOther() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->TelephoneOther;
}
/**
* Get custom texts for the order,
* goes through al lthe order items and the location and merge their CustomEmailText fields in to
* an array and returns a ArrayList
* @return texts
*/
public function GetCustomTexts($bHTML = false, $object = null) {
$arrText = array();
$arrEventDescriptions = array();
$orderItems = $this->OrderItems();
if(get_class($object) == 'OrderItem') {
$orderItems = $orderItems->filter('ID', $object->ID);
}
foreach ($orderItems as $orderItem) {
$arrText = array_merge($arrText, $orderItem->GetCustomTexts());
foreach ($orderItem->Events() as $event) {
if ($location = $event->getRealLocation()) {
$arrText[] = $location->CustomEmailText;
}
//proceed with event description
if ($event->Description && $event->ShowInEmail) {
$dStartDate = date('Y-m-d', strtotime($event->StartDateTime));
$dEndDate = date('Y-m-d', strtotime($event->EndDateTime));
$dEventDateDescription =
date('d/m/Y H:i', strtotime($event->StartDateTime)) . ' - ' . ($dStartDate == $dEndDate ?
date('H:i', strtotime($event->EndDateTime)) :
date('d/m/Y H:i', strtotime($event->EndDateTime)));
if (empty($arrEventDescriptions[$event->ProductID . '_' . $event->ID])) {
$arrEventDescriptions[$event->ProductID . '_' . $event->ID] = array(
'Dates' => array($dEventDateDescription),
'Description' => $event->Description,
'StartDateTime' => $event->StartDateTime,
);
} else {
$result = array_search($event->Description, array_column($arrEventDescriptions, 'Description'));
if ($result !== false) {
$i = 0;
foreach ($arrEventDescriptions as $key => $value) {
if ($result == $i) {
array_push($arrEventDescriptions[$key]['Dates'], $dEventDateDescription);
$arrEventDescriptions[$key]['StartDateTime'] = min($arrEventDescriptions[$key]['StartDateTime'], $event->StartDateTime);
}
$i++;
}
}
}
}
//end processing event descriptions
$arrText[] = $event->Resource()->CustomEmailText;
}
}
if (!empty($arrEventDescriptions)) {
uasort($arrEventDescriptions, function ($a, $b) {
return strcmp($a['StartDateTime'], $b['StartDateTime']);
});
foreach ($arrEventDescriptions as $arrEventDescription) {
$arrEventDescription['Dates'] = array_unique($arrEventDescription['Dates']);
$arrText[] = PresentationUtils::ParseTemplateWithArray(
array(
'Dates' => implode(', ', $arrEventDescription['Dates']),
'Description' => $arrEventDescription['Description']
),
'EventDescriptionForEmail'
);
}
}
$arrText = array_unique($arrText);
$strText = '';
if(!empty($arrText))
foreach($arrText as $str)
if(!empty($str))
$strText .= $bHTML ? $str : strip_tags(nl2br($str),'
');
return $strText;
}
/**
* Get list of all BCC emails
*
* Goes through all OrderItems and get BCC emails
*
* @return array
*/
public function GetBCCEmails() {
$arrBCCEmails = array();
foreach($this->Orderitems() as $orderItem)
$arrBCCEmails = array_merge($arrBCCEmails, $orderItem->GetBCCEmails());
$this->extend('updateGetBCCEmails', $arrBCCEmails);
return array_unique($arrBCCEmails);
}
/**
* Returns all the locations where the events are booked for.
* @return ArrayList, an array list of locations
*/
public function LocationsBooked() {
$alLocations = new ArrayList();
foreach(Event::get()->filter('OrderItems.OrderID', $this->ID) as $event) {
$alLocations->add($event->getRealLocation(true));
}
return $alLocations;
}
/**
* CustomText get all the CustomTexts from GetCustomTexts function and parse them in to a table.
* @return string, a custom text
*/
public function CustomText() {
$dosCustomTexts = $this->GetCustomTexts();
if ($dosCustomTexts->Count()) {
$vd = new ViewableData();
return $vd->customise(
new ArrayData(array('CustomTexts' => $dosCustomTexts))
)->renderWith(SSViewer::fromString('<% loop CustomTexts %>| $Text |
<% end_loop %>| |
'));
}
}
/**
* Get the products contained in an order item
* @param Product $products, the products
* @return boolean, true if the given product is in the given order item, false otherwise
*/
function ContainsProducts($products) {
foreach ($this->OrderItems() as $orderItem)
if ($products->find('ID',$orderItem->ProductID))
return true;
}
/**
* Get the first location of resource of an event of an order item
* @return boolean
*/
function GetFirstLocation() {
foreach ($this->OrderItems() as $orderItem)
foreach ($orderItem->Events() as $event)
return $event->Resource()->Location();
foreach ($this->OrderItems() as $orderItem)
if ($orderItem->LocationID)
return Location::get()->byId($orderItem->LocationID);
}
/**
* Get first and last dates of events in an order item
* @param int $iSeq
* @return array, the start and end date and time
*/
function GetFirstLastDates($iSeq = 0) {
$arrStartEndDates = array();
foreach ($this->OrderItems() as $orderItem) {
if ($iSeq && $iSeq != $orderItem->Seq)
continue;
foreach ($orderItem->Events() as $event)
$arrStartEndDates[] = array(
'StartDateTime' => $event->StartDateTime,
'EndDateTime' => $event->EndDateTime
);
}
//this assumes no overlaps, i.e. 9-10, 10-11
//not 8-11,9-10
if (!empty($arrStartEndDates))
usort($arrStartEndDates, function ($a, $b) {
return $a['StartDateTime'] > $b['StartDateTime'];
});
return $arrStartEndDates;
}
/**
* Get the purchaser email.
* @return String, the purchaser email
*/
public function PurchaserEmail() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->Email;
}
/**
* Get the purchaser mobile.
* @return String, the purchaser mobile
*/
public function PurchaserMobile() {
if ($purchaser = $this->GetPurchaser())
return $purchaser->MobileTelephone;
}
/**
* Get the name of the first location of the order
* @return Varchar, name of the location
*/
public function VenueName(){
if($location = $this->GetFirstLocation())
return $location->Name;
}
/**
* Get the telephone number of the first location of the order
* @return Varchar, the telephone number
*/
public function VenueTelephone(){
if($location = $this->GetFirstLocation())
return $location->Telephone;
}
public function FixedEvent(){
foreach($this->OrderItems() as $orderItem)
if($orderItem->Product()->Type == 'FixedEvent')
return $orderItem;
}
public function GetFixedEvent(){
foreach($this->OrderItems() as $orderItem)
if($orderItem->Product()->Type == 'FixedEvent')
return $orderItem->Events()->first();
}
function FixedEventDateTime(){
if($event = $this->GetFixedEvent()){
$dtDatetime = strtotime($event->StartDateTime);
return DateUtils::PublicDateFormat($dtDatetime).' '.DateUtils::PublicTimeFormat($dtDatetime);
}
}
function FixedEventDate(){
if($event = $this->GetFixedEvent())
return DateUtils::PublicDateFormat(strtotime($event->StartDateTime));
}
function FixedEventTime(){
if($event = $this->GetFixedEvent())
return DateUtils::PublicTimeFormat(strtotime($event->StartDateTime));
}
public function FixedEventName(){
foreach($this->OrderItems() as $orderItem)
if($orderItem->ProductID && $orderItem->Product()->Type == 'FixedEvent')
return $orderItem->Product()->Name;
}
public function TicketsPurchased(){
foreach($this->OrderItems() as $orderItem){
if($orderItem->Product()->Type == 'FixedEvent')
return $orderItem->Quantity;
}
}
function FixedEventVenueName(){
$orderItem = $this->FixedEvent();
if($orderItem && $orderItem->Events()->count())
return $orderItem->Events()->first()->Location()->Name;
}
function FixedEventVenuePhoneNumber(){
$orderItem = $this->FixedEvent();
if($orderItem && $orderItem->Events()->count())
return $orderItem->Events()->first()->Location()->Telephone;
}
/**
* Foreach used instead of filter as filter can cause error on payment/callback
* "filter can't be called on UnsavedRelationList"
* @return mixed
*/
public function CustomerNotes(){
foreach($this->OrderNotes() as $orderNote)
if($orderNote->Type == 'CustomerNote')
return nl2br($orderNote->Note);
}
public function SetCustomerNotes($strComments){
$orderNote = $this->OrderNotes()->filter('Type', 'CustomerNote')->first();
if($this->MainStatus == 'Pending' || !$orderNote)
$orderNote = new OrderNote(array(
'Type' => 'CustomerNote',
'OrderID' => $this->ID
));
$orderNote->Note = $strComments;
$orderNote->write();
}
/**
* Check whether the payment is completed or not
* @return boolean, true if the payment is incomplete, false otherwise
*/
public function IfOutstandingPayment(){
return $this->AmountDue > 0;
}
/**
* returns all the order items which has physical items purchased
* @return DataList
*/
function GetPhysicalItemOrderItems(){
return OrderItem::get()->where("
OrderID = $this->ID
AND EXISTS (
SELECT 1
FROM Product
WHERE ClassName = 'PhysicalItemProduct'
AND OrderItem.ProductID = Product.ID
)
");
}
/**
* Get the active time that is left for a web booking
* @param String $strType, a default value 'WebBookingKeepAliveTime'
* @return int, the time left in minutes for the active booking
*/
function GetMinutesLeftBeforeExpire($strType='WebBookingKeepAliveTime'){
if ($this->MainStatus != 'Pending') {
return;
}
$iTimeLeft = intval(
strtotime($this->LastEdited)
+ SiteConfigOverride::CurrentSiteConfig()->WebBookingKeepAliveTime * 60
- strtotime(SS_Datetime::now()->getValue())
);
if($iTimeLeft <= 0){
$this->Abort();
Requirements::customScript("
jQuery(document).ready(function(){
bl.Availability.ClearBasketItemCount();
bl.Availability.GetBasketItemCount()
});
");
}
return ceil($iTimeLeft / 60);
}
function IfUsingCredits(){
return false;
}
/**
* Check whether the Participant information is incomplete
* @return boolean, true if the Participant information is incomplete,false otherwise
*/
public function IsParticipantInformationIncomplete() {
foreach ($this->OrderItems() as $orderItem)
if ($orderItem->IsOrderItemParticipantInformationIncomplete())
return true;
}
public function CheckIfPartyBooking(){
return WackyUtils::IsPartyBooking($this);
}
public function addTag($strTag) {
if (!($orderTag = OrderTag::get()->filter('Title', $strTag)->first())) {
$orderTag = OrderTag::create(array(
'Title' => $strTag
));
$orderTag->write();
};
if (!$this->Tags()->filter('Title', $strTag)->exists()) {
$this->Tags()->add($orderTag->ID);
}
}
/**
* Get details of the physical item options of an order item returns a groups list
* @return ArrayList, the physical item options
*/
public function GetPhysicalItemOptionDetails() {
$alTemp = ArrayList::create();
$alOutput = ArrayList::create();
foreach ($this->OrderItems() as $orderItem) {
$physicalItemOptions = PhysicalItemOption::get()->filter('ID',explode(',', $orderItem->getField('PhysicalItemOptionCSV')));
if ($physicalItemOptions->first()) {
$glPhysicalItemOptions = new GroupedList($physicalItemOptions);
$glPhysicalItemOptionList = $glPhysicalItemOptions->groupBy('PhysicalItemTypeID');
foreach ($glPhysicalItemOptionList as $iTypeID => $alPhysicalItemOptions) {
$doPhysicalItemType = PhysicalItemType::get()->byId($iTypeID);
$alTemp->push(DataObject::create(array(
'TypeName' => $doPhysicalItemType ? $doPhysicalItemType->Name : '',
'ItemID' => $doPhysicalItemType ? $doPhysicalItemType->PhysicalItemID : 0,
'ItemName' => $doPhysicalItemType ? $doPhysicalItemType->PhysicalItem()->Name : '',
'SelectedOptions' => $alPhysicalItemOptions
)));
}
}
}
$glOutput = new GroupedList($alTemp);
foreach ($glOutput->groupBy('ItemID') as $iItemID => $list)
if ($list->first())
if ($doPhysicalItem = PhysicalItem::get()->byId($iItemID))
$alOutput->push(ArrayData::create(array(
'ItemID' => $iItemID,
'ItemName' => $doPhysicalItem->Name,
'Children' => $list
)));
return $alOutput;
}
/**
* Get the first template
* @return Template, the first template
*/
function FormTemplate(){
return Template::get()->first();
}
/**
* Get Guest participants
* @return guest participant html content
*/
function Guests(){
$alParticipants = new ArrayList();
foreach($this->OrderItems() as $orderItem)
foreach($orderItem->Events() as $event)
$alParticipants->merge($event->Participants());
return StringUtils::GetIDListCSV($alParticipants);
}
public function LastPaymentDate() {
//TODO if still needed to use the "booking timeline" instead
}
public function CompletedTransactions(){
$completedTransactions = new ArrayList;
foreach($this->Transactions() as $transaction)
if(in_array($transaction->Status, array('Completed','Refunded')))
$completedTransactions->add($transaction);
return $completedTransactions;
}
public function IsHasCompletedTransactions(){
return $this->CompletedTransactions()->count();
}
public function IsCompleted(){
return $this->MainStatus == 'Completed';
}
public function IsCancelled(){
return $this->MainStatus == 'Cancelled';
}
/**
* Get Billing page purchaser details from order
* @return Purchaser details array list
*/
function BillingPagePurchaserDetails(){
$alRet = new ArrayList();
$purchaser = $this->GetPurchaser();
if($purchaser && $purchaserTemplate = Template::get()->filter('Type','Purchaser')->first()){
foreach($purchaserTemplate->TemplateItems() as $templateItem)
$alRet->push(new DataObject(array(
'Type' => $templateItem->Type,
'Label' => $templateItem->Label,
'Value' => $purchaser->getField($templateItem->Type)
)));
}
$alRet->removeDuplicates('Type');
return $alRet;
}
public function BaseURL() {
return Director::absoluteBaseURL();
}
public function MyAccountLink(){
return Director::absoluteBaseURL() . '/myaccount';
}
public function Thumbnail(){
if($this->Product()->ImageID)
return '
';
}
public function AmountPaid() {
return $this->PaidAmount();
}
public function MinimumAmountOutstanding() {
$fAmountRequired = $this->CalculateDeposit();
return number_format(
($fAmountRequired > $this->TotalTransactionValue) ? $fAmountRequired - $this->TotalTransactionValue : 0.00,
2
,'.',''
);
}
public function FullAmountOutstanding() {
return number_format($this->AmountDue,2,'.','');
}
/**
* Get Order Unique Participants
* @return Member ArrayList
*/
public function GetUniqueParticipants(){
$alRet = new ArrayList();
$arrParticipantOrderItemsMap = [];
foreach($this->OrderItems() as $orderItem) {
foreach($orderItem->AllEventParticipants() as $participant) {
$arrParticipantOrderItemsMap[$participant->ID][] = $orderItem;
if (!$alRet->find('ID', $participant->ID)) {
$alRet->push($participant);
}
}
}
foreach($alRet as $participant) {
$participant->OrderItems = $arrParticipantOrderItemsMap[$participant->ID];
}
return $alRet;
}
public function GetTotalPayableNow() {
$fTotalPayable = ($this->Deposit > 0 ? $this->Deposit : $this->AmountDue) - $this->AmountPaid();
return number_format($fTotalPayable,2,'.','');
}
public function StaffMember(){
if($event = $this->GetFixedEvent())
return $event->StaffMembers()->first();
}
public function StaffMemberName(){
if($staffMember = $this->StaffMember())
return $staffMember->FullName();
}
public function StaffMemberEmail(){
if($staffMember = $this->StaffMember())
return $staffMember->Email;
}
public function StaffMemberAddress(){
if($staffMember = $this->StaffMember())
return $staffMember->getFullAddress('TEXT');
}
public function StaffMemberTelephone(){
if($staffMember = $this->StaffMember())
return $staffMember->MobileTelephone;
}
public function setOrderParams($arrParams,$bIsTemplate = false){
foreach ($arrParams as $name => $value)
$this->setOrderParam($name,$value,$bIsTemplate);
return $this;
}
/**
* Set Order Param to Order
* @param $name string
* @param $value string
*/
public function setOrderParam($name,$value,$bIsTemplate = false,$customFieldID = 0){
//DB::query('LOCK TABLES OrderParam WRITE, OrderParamPattern WRITE, AuditLog WRITE');
if (!$orderParam = $this->OrderParams()->filter('Name',$name)->first()) {
$orderParam = new OrderParam(array('Name' => $name));
}
$orderParam->update(array(
'Value' => $value,
'Unset' => 0,
'OrderID' => $this->ID,
'IsTemplate' => $bIsTemplate,
'CustomFieldID' => $customFieldID
));
$orderParam->write();
//DB::query('UNLOCK TABLES');
return $this;
}
public function getOrderParam($name){
if (!$this->isInDB())
return ;
$orderParam = $this->OrderParams()->filter(array(
'Name' => $name,
'Unset' => 0
))->first();
if ($orderParam)
return $orderParam->Value;
}
/**
* Return the current value of an OrderParam, or null if not set/unset.
* Then set new value as provided.
* As an atomic process.
*
* @param string $name
* @param mixed $value
* @return mixed
*/
public function getSetOrderParamValue(string $name, $value) {
//DB::query('LOCK TABLES OrderParam WRITE, OrderParamPattern WRITE, AuditLog WRITE');
if ($orderParam = $this->OrderParams()->filter('Name',$name)->first()) {
$ret = $orderParam->Unset ? null : $orderParam->Value;
} else {
$ret = null;
$orderParam = OrderParam::create(['Name' => $name]);
}
$orderParam->update([
'Value' => $value,
'Unset' => 0,
'OrderID' => $this->ID,
])->write();
//DB::query('UNLOCK TABLES');
return $ret;
}
public function unsetOrderParam($name){
if ($orderParam = $this->OrderParams()->filter('Name',$name)->first()){
$orderParam->Unset = 1;
$orderParam->write();
}
}
public function unsetOrderParams($arrParams){
foreach($arrParams as $strParam)
$this->unsetOrderParam($strParam);
}
public function registerBasketPageError($msg, $type='error0') {
$errors = array();
if ($orderParamValue = $this->getOrderParam('BasketPageErrors')) {
//LogEntry::log('***'.StringUtils::Obj2str($orderParam).'***',999);
$errors = json_decode($orderParamValue, true);
}
$errors[] = array(
'type' => 'error',
'message' => $msg
);
$this->setOrderParam('BasketPageErrors', json_encode($errors));
}
public function ChangeDue() {
$fChangeDue = 0.0;
if ($val = $this->getOrderParam('ChangeDue'))
$fChangeDue = floatval($val);
return number_format($fChangeDue,2,'.','');
}
public function TillReceipt() {
if ($content = Content::get()->filter('Type','TillReceipt')->first()) {
$strReceipt = $content->Content;
if (strpos($strReceipt,'{$OrderReference}') !== false)
$strReceipt = str_replace('{$OrderReference}',$this->Reference,$strReceipt);
if (strpos($strReceipt,'{$ChangeDue}') !== false)
$strReceipt = str_replace('{$ChangeDue}',$this->ChangeDue(),$strReceipt);
if (strpos($strReceipt,'{$TotalCostCurrencyFormatted}') !== false)
$strReceipt = str_replace('{$TotalCostCurrencyFormatted}',number_format($this->TotalCost,2,'.',''),$strReceipt);
if (strpos($strReceipt,'{$DueAmountCurrencyFormatted}') !== false)
$strReceipt = str_replace('{$DueAmountCurrencyFormatted}',number_format($this->AmountDue,2,'.',''),$strReceipt);
$siteConfig = SiteConfigOverride::CurrentSiteConfig();
if (strpos($strReceipt,'{$CompanyName}') !== false)
$strReceipt = str_replace('{$CompanyName}',$siteConfig->Name,$strReceipt);
if (strpos($strReceipt,'{$RegisteredNumber}') !== false)
$strReceipt = str_replace('{$RegisteredNumber}',$siteConfig->CompanyNumber,$strReceipt);
if (strpos($strReceipt,'{$VATNumber}') !== false)
$strReceipt = str_replace('{$VATNumber}',$siteConfig->VATRegisterNumber,$strReceipt);
if ($arrParts = StringUtils::GetBeforeBetweenAndAfter($strReceipt,'{$OrderItemLOOP{','}ENDOrderItemLOOP}')) {
$strLines = '';
foreach ($this->OrderItems() as $orderItem) {
$strLine = $arrParts['Between'];
if (strpos($strLine,'{$Quantity}') !== false)
$strLine = str_replace('{$Quantity}',1,$strLine);
if (strpos($strLine,'{$ProductName}') !== false)
$strLine = str_replace('{$ProductName}',$orderItem->Product()->Name,$strLine);
if (strpos($strLine,'{$EventCost}') !== false)
$strLine = str_replace('{$EventCost}',$orderItem->Cost,$strLine);
if ($firstEvent = $orderItem->Events()->first()) {
if (strpos($strLine,'{$LocationName}') !== false)
$strLine = str_replace('{$LocationName}',$firstEvent->Resource()->Location()->Name,$strLine);
if (strpos($strLine,'{$EventStartDateDateFormatted}') !== false)
$strLine = str_replace('{$EventStartDateDateFormatted}',$firstEvent->Resource()->Location()->Name,$strLine);
if (strpos($strLine,'{$EventStartDateTimeFormatted}') !== false)
$strLine = str_replace('{$EventStartDateTimeFormatted}',$firstEvent->Resource()->Location()->Name,$strLine);
if (strpos($strLine,'{$EventEndDateTimeFormatted}') !== false)
$strLine = str_replace('{$EventEndDateTimeFormatted}',$firstEvent->Resource()->Location()->Name,$strLine);
}
$strLines .= $strLine;
}
$strReceipt = $arrParts['Before'].$strLines.$arrParts['After'];
}
if ($arrParts = $arrParts = StringUtils::GetBeforeBetweenAndAfter($strReceipt,'{$CompletedTransactionsLOOP{','}ENDCompletedTransactionsLOOP}')) {
$strLines = '';
foreach ($this->Transactions() as $transaction) {
$strLine = $arrParts['Between'];
if (strpos($strLine,'{$TransactionName}') !== false)
$strLine = str_replace('{$TransactionName}',$transaction->Type,$strLine);
if (strpos($strLine,'{$AmountCurrencyFormatted}') !== false)
$strLine = str_replace('{$AmountCurrencyFormatted}',$transaction->Amount,$strLine);
$strLines .= $strLine;
}
$strReceipt = $arrParts['Before'].$strLines.$arrParts['After'];
}
return $strReceipt;
}
return 'no till template';
}
/**
* Get EventID and Available Slots string
* @return string
*/
public function EPOSEventUpdate() {
$strEventUpdate = '';
$arrEvents = array();
foreach ($this->OrderItems() as $orderItem) {
foreach ($orderItem->Events() as $event) {
if (!in_array($event->ID,$arrEvents)) {
$strEventUpdate .= $event->ID.':'.$event->NumberOfSlotsAvailable().',';
$arrEvents[] = $event->ID;
}
}
}
return $strEventUpdate;
}
public function GetEditURL() {
return 'admin/orders/Order/EditForm/field/Order/item/' . $this->ID . '/edit';
}
public function GetWaitinglistQuantitySum() {
//A waitling list can only be for one product, but might have quantity
//so just isolate how many are on each event (should be equal) and then return one of those numbers (first one)
$arrEvents = array();
foreach ($this->OrderItems() as $orderItem) {
if ($event = $orderItem->Events()->first()) {
$id = $event->ID;
if(isset($arrEvents[$id]))
$arrEvents[$id]++;
else
$arrEvents[$id] = 1;
}
}
return array_shift($arrEvents);
}
public function SortedOrderItems(){
LogEntry::log('Order::SortedOrderItems');
return $this->OrderItems()->sort('Sequence', 'ASC');
}
public function ChildCareVouchers() {
return Voucher::get()->filter(array(
'Status' => 'Verification',
'ID' => $this->Transactions()->filter(array(
'Type' => 'Voucher',
'Status' => 'Completed'
))->column('VoucherID')
));
}
public function GetMaximumVouchersAmount() {
$fMaximumVouchersAmount = 0.0;
foreach ($this->OrderItems() as $orderItem)
if ($orderItem->Product()->PayableByChildCareVoucher)
$fMaximumVouchersAmount += $orderItem->CostProrata;
return $fMaximumVouchersAmount;
}
public function HasDiscount() {
return $this->CompletedTransactions()
->filter('Type', 'PackageDiscount')
->limit(1)
->count();
}
public function TotalDiscount() {
$fTotalAmount = 0;
foreach($this->CompletedTransactions() as $transaction)
if($transaction->IsPackageTransaction())
$fTotalAmount += $transaction->Amount;
return $fTotalAmount;
}
public function LastCompletedTransaction() {
return new ArrayList(array(
$this->CompletedTransactions()->sort('Created','DESC')->first()
));
}
public function LastOrderItem() {
return new ArrayList(array(
$this->OrderItems()->sort(array(
'Sequence' => 'ASC',
'Created' => 'DESC'
))->first()
));
}
public function ExistingID() {
if($this->exists())
return $this->ID;
return $this->OriginalID;
}
/**
* Check if order's MainStatus is 'Completed'
*
* If no order is passed will use current
* order.
*
* @param Order $order - default null
* @return Order|false
*/
public static function IsCurrentOrderCompleted($order = null){
if (!$order)
$order = Order::GetCurrentOrder();
return $order->MainStatus == 'Completed' ? $order : false;
}
public function FinalTotalCostAdminFormat() {
return $this->CurrencySymbol() . $this->FinalTotalCost;
}
public function getStartDate() {
$dtEarliestDate = false;
foreach($this->OrderItems() as $orderItem) {
foreach($orderItem->Events() as $event) {
$dtEventStart = strtotime($event->StartDateTime);
if ($dtEarliestDate == false)
$dtEarliestDate = $dtEventStart;
if ($dtEventStart < $dtEarliestDate)
$dtEarliestDate = $dtEventStart;
}
}
return $dtEarliestDate ? date(DateUtils::PublicDateFormatter().' ' . DateUtils::PublicTimeFormatter(), $dtEarliestDate) : '';
}
public function getEndDate() {
$dtLatestDate = false;
foreach($this->OrderItems() as $orderItem) {
foreach($orderItem->Events() as $event) {
$dtEventEnd = strtotime($event->EndDateTime);
if ($dtLatestDate == false)
$dtLatestDate = $dtEventEnd;
if ($dtEventEnd > $dtLatestDate)
$dtLatestDate = $dtEventEnd;
}
}
return $dtLatestDate ? date(DateUtils::PublicDateFormatter().' ' . DateUtils::PublicTimeFormatter(), $dtLatestDate) : '';
}
/**
* Return Order as JSON
*
* Set additional relations you want to be returned.
* Returned structure:
* All info about the object (order)
* _Relations - contains all the relations
* every relation has 'data' key - relations data
* every relation has 'type' key - List (if has many relation of this type) or Object (if has only one relation)
*
* example: Array (
* [ClassName] => Order
* [Created] => 2015-09-01 11:33:00
* [LastEdited] => 2015-09-02 15:35:29
* [ID] => 8
* ........
* [RecordClassName] => Order
* [_Relations] => Array (
* [OrderItems] => Array (
* [data] => Array (
* [0] => Array(...)
* [1] => Array(...)
* )
* [type] => List
* )
* [Purchaser] => Array (
* [data] => Array(...)
* [type] => Object
* )))
*
* @param array $arrRelations - list of all additional relation
* @return string
*/
public function GetAsJSON($arrRelations = array('OrderItems','Purchaser','Transactions')) {
$arrOrderData = $this->toMap();
$arrOrderData['_Relations'] = array();
foreach($arrRelations as $strRelation) {
if (is_subclass_of($this->$strRelation(), 'DataList')) {
$arrOrderData['_Relations'][$strRelation]['data'] = array();
$arrOrderData['_Relations'][$strRelation]['type'] = 'List';
foreach($this->$strRelation() as $relation)
$arrOrderData['_Relations'][$strRelation]['data'][] = $relation->toMap();
}
else {
$arrOrderData['_Relations'][$strRelation]['data'] = $this->$strRelation()->toMap();
$arrOrderData['_Relations'][$strRelation]['type'] = 'Object';
}
}
return json_encode($arrOrderData);
}
public function AllowedStatuses() {
$arrStatuses = $this->dbObject('MainStatus')->enumValues();
$this->extend('updateAllowedStatuses', $arrStatuses);
return $arrStatuses;
}
//TODO can remove?
public function IfStatusCompleteOrProvisional () {
return in_array($this->MainStatus, array('Completed','Provisional'));
}
//TODO can remove?
public function ActionCanAddPayment() {
return $this->IfOutstandingPayment() && $this->IfStatusCompleteOrProvisional();
}
//TODO can remove?
public function ActionCanComplete() {
return $this->MainStatus == 'Provisional';
}
public function OrderItemsForSummary() {
$al = new ArrayList();
foreach($this->OrderItems() as $item) {
if ($item->Product()->ClassName == 'ProductEventSeries' && !$item->IsMainOrderItem())
continue;
$al->push($item);
}
return $al;
}
public function GetUniqueParticipantsFromPendingMember() {
$alPendingParticipants = ArrayList::create();
foreach($this->OrderItems() as $orderItem) {
$alPendingParticipants->merge($orderItem->PendingParticipants());
}
$alPendingParticipants->removeDuplicates();
$arrExclude = array();
foreach($alPendingParticipants as $pendingParticipant) {
if($alPendingParticipants->find('ID', $pendingParticipant->OtherPendingMemberID))
$arrExclude[] = $pendingParticipant->ID;
if($template = Template::get()->find('ID', $pendingParticipant->TemplateID)) {
if(!$template->CollectsData()) {
$arrExclude[] = $pendingParticipant->ID;
}
}
}
return $alPendingParticipants->exclude('ID', $arrExclude);
}
// Summary Merge functions
public function Participants() {
return $this->isInDB()
? ($this->MainStatus == 'Pending'
? $this->GetUniqueParticipantsFromPendingMember()
: $this->GetUniqueParticipants())
: $this->GetParticipantsBasedOnOrderItems();
}
protected function GetParticipantsBasedOnOrderItems() {
$alParticipants = ArrayList::create();
$orderItems = $this->OrderItems();
foreach($orderItems as $orderItem) {
if($orderItem->ChoosenDayParticipant)
$alParticipants->add(ArrayData::create(array(
'ID' => $orderItem->ChoosenDayParticipant,
'DetailsSet' => false,
'ParticipantIndex' => $orderItem->ChoosenDayParticipant,
'ParticipantOrderItems' => ArrayList::create(),
'CurrencySymbol' => $this->CurrencySymbol(),
'OrderItemsTotalCost' => 0,
)));
}
$alParticipants->removeDuplicates('ID');
foreach($alParticipants as $participant) {
foreach(self::ParticipantOrderItems($orderItems, $participant->ParticipantIndex) as $orderItem) {
$orderItemData = self::OrderItemToDataList($orderItems, $orderItem);
$orderItemData->EventsCount = 0;
foreach(self::GetChildEventOrderItems($orderItems, $orderItem) as $item) {
$orderItemData->ChildEventOrderItems->add(self::OrderItemToDataList($orderItems, $item));
$participant->OrderItemsTotalCost += $item->Cost;
$orderItemData->EventsCount++;
}
if($orderItem->IfEventSeries())
$orderItemData->EventsCount = 1;
$orderItemData->HasEventChildItems = $orderItemData->ChildEventOrderItems->count() > 0;
foreach(self::GetChildOrderItems($orderItems, $orderItem) as $item) {
$orderItemData->ChildOrderItems->add(self::OrderItemToDataList($orderItems, $item));
$participant->OrderItemsTotalCost += $item->Cost;
}
$participant->ParticipantOrderItems->add($orderItemData);
}
}
return $alParticipants;
}
protected static function OrderItemToDataList($orderItems, $orderItem) {
return ArrayData::create(array(
'ClassForSummaryRow' => 'PrimaryItem',
'ProductName' => $orderItem->ProductName(),
'FirstStartDate' => self::OrderItemFirstStartDate($orderItems, $orderItem),
'LastStartDate' => self::OrderItemLastStartDate($orderItems, $orderItem),
'HasEventChildItems' => false,
'ChildEventOrderItems' => ArrayList::create(),
'IsUpsellProduct' => $orderItem->IsUpsellProduct(),
'ChoosenDate' => $orderItem->ChoosenDate(),
'ChildOrderItems' => ArrayList::create(),
'EventStartDate' => $orderItem->EventStartDate(),
'EventEndDate' => $orderItem->EventEndDate(),
'IsMultidaysEvent' => $orderItem->IsMultidaysEvent(),
'EventCost' => $orderItem->EventCost(),
'hasLocation' => $orderItem->hasLocation(),
'LocationName' => $orderItem->LocationName(),
'IsWaitingListItem' => $orderItem->IsWaitingListItem(),
'IfEventSeries' => $orderItem->IfEventSeries(),
'EventsCount' => 0,
));
}
protected static function GetChildOrderItems($orderItems, $orderItem) {
$alChildOrderItems = ArrayList::create();
foreach($orderItems as $item)
if($item->Product()->ClassName == 'ProductEventUpsell' && $item->ChoosenDayParticipant == $orderItem->ChoosenDayParticipant)
$alChildOrderItems->add($item);
return $alChildOrderItems;
}
protected static function GetChildEventOrderItems($orderItems, $orderItem) {
$arrChildOrderItems = array();
foreach($orderItems as $item)
if($item->ProductID == $orderItem->ProductID
&& $item->ChoosenDayParticipant == $orderItem->ChoosenDayParticipant)
{
$arrChildOrderItems[] = $item;
}
usort($arrChildOrderItems, function($orderItem1, $orderItem2) {
if(($event1 = $orderItem1->Events()->first()) && ($event2 = $orderItem2->Events()->first()))
return strtotime($event1->StartDateTime) > strtotime($event2->StartDateTime)
? 1
: -1;
return 0;
});
return ArrayList::create($arrChildOrderItems);
}
protected static function IsOrderItemPrimary($orderItems, $orderItem) {
if($primaryOrderItem = self::GetChildEventOrderItems($orderItems, $orderItem)->first())
return $primaryOrderItem === $orderItem;
}
protected static function ParticipantOrderItems($orderItems, $participantIndex) {
$alOrderItems = ArrayList::create();
foreach($orderItems as $orderItem)
if($orderItem->ChoosenDayParticipant == $participantIndex
&& $orderItem->Product()->ClassName != 'ProductEventUpsell'
&& self::IsOrderItemPrimary($orderItems, $orderItem))
{
$alOrderItems->add($orderItem);
}
return $alOrderItems;
}
protected static function OrderItemFirstStartDate($orderItems, $orderItem) {
if($firstOrderitem = self::GetChildEventOrderItems($orderItems, $orderItem)->first())
if($event = $firstOrderitem->Events()->first())
return is_object($event) ? $event->StartDateTime : Event::get()->byID($event)->StartDateTime;
}
protected static function OrderItemLastStartDate($orderItems, $orderItem) {
if($lastOrderitem = self::GetChildEventOrderItems($orderItems, $orderItem)->last())
if($event = $lastOrderitem->Events()->last())
return is_object($event) ? $event->StartDateTime : Event::get()->byID($event)->StartDateTime;
}
public function PhysicalItems() {
$alPhysicalOrderItems = ArrayList::create();
foreach($this->OrderItems() as $orderItem)
if($orderItem->Product()->ClassName == 'ProductPhysicalItem')
$alPhysicalOrderItems->add($orderItem);
$newOrderItems = ArrayList::create();
foreach(self::GetOrderItemsGrouped($alPhysicalOrderItems) as $alOrderItems) {
$newOrderItem = $alOrderItems->shift();
$newOrderItem->Quantity = 1;
foreach($alOrderItems as $orderItem) {
$newOrderItem->Cost += $orderItem->Cost;
$newOrderItem->Quantity++;
}
$newOrderItems->add($newOrderItem);
}
return $newOrderItems;
}
public static function PurchaserOnlyProducts($product) {
return !in_array($product->ClassName, array('ProductFixedEvent', 'ProductEventSeries', 'ProductEventUpsell', 'ProductAppointmentEvent','ProductTemplateEvents'));
}
public function PurchaserOrderItems() {
$alPhysicalOrderItems = ArrayList::create();
foreach($this->OrderItems() as $orderItem)
if(self::PurchaserOnlyProducts($orderItem->Product()))
$alPhysicalOrderItems->add($orderItem);
$newOrderItems = ArrayList::create();
foreach(self::GetOrderItemsGrouped($alPhysicalOrderItems) as $alOrderItems) {
$newOrderItem = $alOrderItems->shift();
$newOrderItem->Quantity = 1;
foreach($alOrderItems as $orderItem) {
$newOrderItem->Cost += $orderItem->Cost;
$newOrderItem->Quantity++;
}
$newOrderItems->add($newOrderItem);
}
return $newOrderItems;
}
public function SystemTakesPayments() {
return SiteConfigOverride::CurrentSiteConfig()->SystemTakesPayments;
}
/**
* Get all feature order items of a given order
*
* @param Order $order
* @return DataList
*/
public static function getOrderItemsInTheFeature(Order $order) {
return OrderItem::filterFeatureOrderItems($order->OrderItems());
}
/**
* Check if order has order items in the feature
*
* @return DataObject
*/
public function hasOrderItemsInTheFeature() {
return self::getOrderItemsInTheFeature($this)->first();
}
/**
* Is simultaneous booking
*
* Booking is simultaneous if it's by the same user or admin is booking behalf of the same user
* and IP or SessionID is different.
*
* When user login new record in MemberLoginLogs is created. If staff user starts a booking
* then BookingOnBehalfOfMemberID is set to the real purchaser and new record in MemberLoginLogs is created.
*
* @return bool
*/
public static function IsSimultaneousBookings() {
//If SiteConfig::OneSimultaneousBookingProcessPerUser == 0 it's safe to return false
if (!SiteConfigOverride::CurrentSiteConfig()->OneSimultaneousBookingProcessPerUser)
return false;
if (!$member = MemberExtension::currentUser())
return false;
$memberLoginLog = MemberLoginLog::get()
->filter('PurchaserID', MemberExtension::IsStaff($member) ? $member->BookingOnBehalfOfMemberID : $member->ID)
->sort('RegisteredDateTime', 'DESC')
->sort('ID', 'DESC')
->first();
if (!$memberLoginLog)
$memberLoginLog = MemberLoginLog::RegisterMemberLogin();
// if ($memberLoginLog->IP != ServerUtils::IPAddress() || $memberLoginLog->SessionID != session_id()) {
if ($memberLoginLog->UserAgent != ServerUtils::getUserBrowserInfo() || $memberLoginLog->SessionID != session_id()) {
LogEntry::log("[Order::IsSimultaneousBookings({$member->ID})] SimultaneousBooking: expected ({$memberLoginLog->IP}, {$memberLoginLog->SessionID} \n {$memberLoginLog->UserAgent}) but get (" . ServerUtils::IPAddress() .", " . session_id() . "\n" . ServerUtils::getUserBrowserInfo() . ")"
. "\n\n[Order::IsSimultaneousBookings({$member->ID})] Member: " . print_r($member->toMap(), true)
. "\n\n[Order::IsSimultaneousBookings({$member->ID})] MemberLoginLog: " . print_r($memberLoginLog->toMap(), true)
. "\n\n[Order::IsSimultaneousBookings({$member->ID})] Is Staff: " . intval(MemberExtension::IsStaff($member))
. "\n\n[Order::IsSimultaneousBookings({$member->ID})] BookingOnBehalfOfMemberID: {$member->BookingOnBehalfOfMemberID}", 999
);
return true;
}
return false;
}
/**
* Get the purchaser ID
*
* If current user is Staff will get the purchaser ID
* from the order parameters.
*
* If purchaser ID can not be found will throw error.
*
* @param Member $member
* @param boolean $bThrowError - if true will throw user error otherwise will return null
* @return int
*/
public static function getRealPurchaserID(Member $member = null, $bThrowError = true, Order $order = null) {
if (!$member)
$member = MemberExtension::currentUser();
if (!MemberExtension::IsStaff($member)) {
if ($member)
return $member->ID;
else if ($bThrowError)
user_error('BR-0003: Purchaser not found !', E_USER_ERROR);
else
return '';
}
if ($order = $order ?? Order::GetCurrentOrder('', false)) {
if ($order->IsInEdit()) {
return $order->PurchaserID;
}
if ($iMemberID = $order->getOrderParam('RebookPurchaser')) {
return $iMemberID;
}
}
if ($bThrowError)
user_error('BR-0001: Current member is staff but OrderParam RebookPurchaser not set !', E_USER_ERROR);
return null;
}
/**
* Get all orders of a given purchaser with events that are in the feature
*
* MainStatus is in array('Completed','Pending','Provisional'),
*
* @param int $iPurchaserID - if not passed will use Order::getRealPurchaserID()
* @return DataList
*/
public static function GetOrdersWithFeatureEvents($iPurchaserID = null) {
if (empty($iPurchaserID))
$iPurchaserID = Order::getRealPurchaserID();
return Order::get()
->filter(array(
'PurchaserID' => $iPurchaserID,
'MainStatus' => array('Completed','Pending','Provisional'),
))->innerJoin('OrderItem', 'oi.OrderID = "Order".ID', 'oi')
->innerJoin('OrderItem_Events', 'oie.OrderItemID = oi.ID', 'oie')
->innerJoin('Event', 'e.ID = oie.EventID', 'e')
->where("e.StartDateTime > '" . SS_Datetime::now()->Format(MYSQLDATETIME) . "'" );
}
public function AddPromoCode($strCode): ArrayList {
$packageFilters = PackageFilter_PromoCodeTriggered::get()->filter('PromoCodeTrigger',$strCode);
if(!$packageFilters->exists()) {
throw new PackagePromoCodeNotFoundException;
}
$arrInitialParams = $this->GetPromoCodes();
$this->SetPromoCodes(array_merge($arrInitialParams, [$strCode]));
$matchedFilters = new ArrayList;
foreach(NewPackage::PackageMatching($this) as $package) {
foreach($packageFilters as $packageFilter) {
if($package->ID == $packageFilter->NewPackageID) {
$matchedFilters->add($packageFilter);
}
}
}
if($matchedFilters->exists()) {
$matchedFilters->removeDuplicates('ID');
return $matchedFilters;
}
// At this point promo code wasnt applied so remove it from the Order
$this->SetPromoCodes($arrInitialParams);
return $matchedFilters;
}
public function RemovePromoCode($strCode) {
if(!PackageFilter_PromoCodeTriggered::get()->find('PromoCodeTrigger',$strCode)) {
throw new PackagePromoCodeNotFoundException;
}
$arrParams = $this->GetPromoCodes();
$index = array_search($strCode, $arrParams);
if($index !== false) {
unset($arrParams[$index]);
}
$this->SetPromoCodes($arrParams);
NewPackage::PackageMatching($this);
}
public function GetPromoCodes() {
$arrParams = [];
if($strParams = $this->getOrderParam('TriggeredPromoCode')) {
$arrParams = unserialize($strParams);
}
return $arrParams;
}
protected function SetPromoCodes($arrCodes) {
$this->setOrderParam('TriggeredPromoCode',serialize(array_unique($arrCodes)));
return $this;
}
public function HasResourceManagementProduct() {
return SiteConfigOverride::CurrentSiteConfig()->EnableResourceManagement && $this->GetResourceManagementOrderItems()->first();
}
public function GetResourceManagementOrderItems() {
$alResourceManagementOrderItems = ArrayList::create();
foreach($this->OrderItems() as $orderItem)
if($orderItem->Product()->ClassName == 'ProductResource')
$alResourceManagementOrderItems->add($orderItem);
return $alResourceManagementOrderItems;
}
public function CanCheckIn() {
if($this->HasResourceManagementProduct() && $this->IsCheckedOut())
foreach($this->UniqueProductResourceOrderItems() as $orderItem) {
$resource = $orderItem->Events()->first()->Resource();
if($orderItem->GetRealQuantity() -
($resource->CheckedInQuantity($this) + $resource->WrittenOffQuantity($this)) > 0
)
return true;
}
}
public function CanCheckOut() {
return $this->HasResourceManagementProduct() && !$this->IsCheckedOut();
}
public function IsCheckedOut() {
foreach($this->GetResourceManagementOrderItems() as $orderItem)
if($orderItem->Events()->first()->CheckedOut)
return true;
}
public function IsCheckedIn() {
foreach($this->GetResourceManagementOrderItems() as $orderItem) {
$resource = $orderItem->Events()->first()->Resource();
$iProcessed = $resource->CheckedInQuantity($this) + $resource->WrittenOffQuantity($this);
if($orderItem->GetRealQuantity() - $iProcessed > 0)
return false;
}
return true;
}
public function IsPartiallyCheckedIn() {
foreach($this->GetResourceManagementOrderItems() as $orderItem) {
$resource = $orderItem->Events()->first()->Resource();
$iProcessed = $resource->CheckedInQuantity($this) + $resource->WrittenOffQuantity($this);
if($iProcessed > 0 && $iProcessed < $orderItem->GetRealQuantity())
return true;
}
return false;
}
public function MainStatusLabel() {
return $this->Status ? $this->Status : $this->MainStatus;
}
/**
* Get the custom status based on Order MainStatus
*
* First it will check the custom lables created by user
* if it can not be found it will try to find it based
* on the system status. If for some reason this also fail
* it will return the passed MainStatus
*
* @return string
*/
public function getRealStatus($strLabel) {
$strLabel = Convert::raw2sql($strLabel);
if ($customStatus = StatusCustomLabel::get()->filter(array(
'StatusSystemLabel.StatusName' => $strLabel,
'Status' => 1,
))->where("StatusCustomLabel.Label = '$strLabel' OR REPLACE(StatusCustomLabel.Label, ' ', '') = '$strLabel'")
->sort('Label', 'ASC')->first()) {
return $customStatus->Label;
} else if ($customStatus = StatusCustomLabel::get()->filter(array(
'StatusSystemLabel.StatusName' => $strLabel,
'Status' => 1,
))->where("StatusSystemLabel.StatusName = '$strLabel' OR REPLACE(StatusSystemLabel.StatusName, ' ', '') = '$strLabel'")
->sort('Label', 'ASC')->first()) {
return $customStatus->Label;
} else {
return $strLabel;
}
}
public function UpdateStatus() {
$this->Status = '';
if($this->MainStatus == 'Aborted') {
if($this->Transactions()->filter(array('Type' => 'Card', 'Status' => 'Aborted'))->first())
$this->Status = $this->getRealStatus('Payment Failure');
}
else if($this->MainStatus == 'Completed'){
if($this->HasResourceManagementProduct()) {
if($this->IsPartiallyCheckedIn())
$this->Status = $this->getRealStatus('Partially Checked In');
else if($this->CanCheckIn() || $this->CanCheckOut())
$this->Status = $this->IsCheckedOut() ? $this->getRealStatus('Checked Out') : $this->getRealStatus('Awaiting Checkout');
}
if($this->AmountDueToPay() != 0)
$this->Status = $this->TotalAmountOverdue == 0 ? $this->getRealStatus('Balance Outstanding') : $this->getRealStatus('Payment Overdue');
if($this->isInDB() && $this->ChildCareVouchers()->first())
$this->Status = $this->getRealStatus('Childcare Pending');
if($this->Invoiced)
$this->Status = $this->getRealStatus('Invoiced');
}
if(empty($this->Status))
$this->Status = $this->getRealStatus($this->MainStatus);
}
public function UniqueProductResourceOrderItems() {
$arrOrderItems = array();
foreach($this->GetResourceManagementOrderItems() as $orderItem) {
if($event = $orderItem->Events()->first())
if($resource = $event->Resource())
if(!isset($arrOrderItems[$resource->ID]))
$arrOrderItems[$resource->ID] = $orderItem;
}
return ArrayList::create(array_values($arrOrderItems));
}
public function CheckoutResource($resource) {
foreach($this->OrderItems()->filter('Events.ResourceID', $resource->ID) as $orderItem) {
$event = $orderItem->Events()->first();
$orderItem->Events()->add($event, array('CheckedOut' => SS_Datetime::now()->getValue()));
}
}
public function WriteoffResource($resource, $iCount) {
if($resource && $iCount > 0)
$resource->AddStockChange(-$iCount, $this);
}
public function CheckinResource($resource, $iCount) {
if($resource && $iCount > 0)
$resource->Type == 'Lending'
? $this->CheckinLendingResource($resource, $iCount)
: $this->CheckinDistributableResource($resource, $iCount);
}
protected function GetResourceOrderItemsAwaitingForCheckin($resource) {
return $this->OrderItems()
->filter('Events.ResourceID', $resource->ID)
->where('OrderItem_Events.CheckedIn IS NULL');
}
protected function CheckinDistributableResource($resource, $iCount) {
$orderItems = $this->GetResourceOrderItemsAwaitingForCheckin($resource);
if($orderItem = $orderItems->first()) {
$arrExtraFields = array();
$event = $orderItem->Events()->first();
if($iDifference = ($event->DistributableQuantity - $iCount)) {
// Partial check in
$newEvent = $event->duplicate(false)->write();
$orderItem->Events()->add($newEvent, array(
'CheckedIn' => SS_Datetime::now()->getValue(),
'CheckedOut' => $event->CheckedOut,
'DistributableQuantity' => $iCount,
));
$arrExtraFields = array('DistributableQuantity' => $iDifference);
} else {
// Full check in
$arrExtraFields = array('CheckedIn' => SS_Datetime::now()->getValue());
}
$orderItem->Events()->add($event, $arrExtraFields);
}
}
protected function CheckinLendingResource($resource, $iCount) {
foreach($this->GetResourceOrderItemsAwaitingForCheckin($resource)
->limit($iCount) as $orderItem)
$orderItem->Events()->add($orderItem->Events()->first(), array(
'CheckedIn' => SS_Datetime::now()->getValue(),
));
}
public function ResourceReturnDateTime() {
if($orderItem = $this->GetResourceManagementOrderItems()->first())
if($event = $orderItem->Events()->first())
return $event->EndDateTime;
}
public function ResourceCollectionDateTime() {
if($orderItem = $this->GetResourceManagementOrderItems()->first())
if($event = $orderItem->Events()->first())
return $event->StartDateTime;
}
public function AddChildCareVoucher($strAgent, $fAmount) {
if(MathUtils::CurrencyLessOrEqual($fAmount, $this->GetMaximumVouchersAmount())) {
if(!$voucherProvider = VoucherProvider::get()->filter(array(
'Type' => 'ChildCare',
'OrganisationName:nocase' => $strAgent,
))->first()) {
$voucherProvider = VoucherProvider::create(array(
'Type' => 'ChildCare',
'OrganisationName' => $strAgent,
));
$voucherProvider->write();
}
$iVoucherID = Voucher::create(array(
'Type' => 'ChildCare',
'Status' => 'Verification',
'VoucherProviderID' => $voucherProvider->ID,
))->write();
$transaction = Transaction::CreateNew(
'Voucher',
$this->ID,
$fAmount,
$voucherProvider->Name
);
$transaction->VoucherID = $iVoucherID;
$transaction->write();
$this->ReCalculate(true);
}
}
public function GetUnpaidCreditCardChargeItems() {
return $this->Charges("CreditCardChargePointID > 0 AND Status='Unpaid'");
}
/**
* Get Order and Purchaser details as array for template processing
*
* @return array
*/
public function GetOrderAndPurchaserDetailsAsArray() {
return array_merge($this->GetOrderDetailsAsArray(), $this->GetPurchaserDetailsAsArray());
}
/**
* Get order details as array for template processing
*
* @return array
*/
public function GetOrderDetailsAsArray() {
return array(
'Reference' => $this->Reference,
'PurchaseDate' => DateUtils::ConvertDateToPublicFormat($this->DateRegistered),
'Status' => _t('BookingLive.OrderStatus'.$this->Status,$this->Status),
'CustomerNotes' => $this->CustomerNotes(),
'Staff' => $this->Staff(),
'NotificationByEmail' => $this->getOrderParam('NotificationByEmail') == '1',
'HasPendingTransactions' => $this->HasPendingTransactions(),
);
}
/**
* Get purchaser details as array for template processing
* @return array
*/
public function GetPurchaserDetailsAsArray() {
if ($this->MainStatus == 'Pending' && $this->PendingPurchaserID) {
if ($this->Source == 'Staff' && $this->PurchaserID) {
return $this->Purchaser()->GetPurchaserDetailsAsArray();
}
$Purchsaser = $this->PendingPurchaser();
return [
'PurchaserEmail' => $Purchsaser->Email,
'PurchaserName' => $Purchsaser->FullName(),
];
} elseif ($this->PurchaserID) {
return $this->Purchaser()->GetPurchaserDetailsAsArray();
}
return [];
}
/**
* Return order total as array for template processing
*
* @param boolean $bIsHTML
* @param boolean $IncludeDeleteButtons
* @return array
*/
public function GetOrderTotalAsArray($bIsHTML = true, $IncludeDeleteButtons = false) {
return array(
'SubTotal' => OrderSummary::SubTotalContent($this, $bIsHTML, $IncludeDeleteButtons),
'ProductCharges'=> OrderSummary::ProductChargesContent($this, $bIsHTML, $IncludeDeleteButtons),
'Discounts' => OrderSummary::DiscountContent($this, $bIsHTML, $IncludeDeleteButtons),
'Transactions' => OrderSummary::TransactionContent($this, $bIsHTML, $IncludeDeleteButtons),
'Tax' => SiteConfig::current_site_config()->DisplayTax ? OrderSummary::TaxContent($this, $bIsHTML, $IncludeDeleteButtons) : '',
'OrderTotal' => OrderSummary::TotalContent($this, $bIsHTML, $IncludeDeleteButtons),
'FinalTotalCost' => number_format($this->FinalTotalCost,2,'.',''),
'Deposit' => OrderSummary::TotalDepositContent($this, $bIsHTML, $IncludeDeleteButtons),
'CurrencySymbol' => $this->CurrencySymbol(),
);
}
public function OrderEmailIcon() {
if (empty($_POST['IsInXLSExport2016'])) {
$strNotificationStatus = $this->getOrderParam('NotificationByEmail');
if ($strNotificationStatus !== '0' && $strNotificationStatus !== '1') {
$strNotificationStatus = $this->EmailNotification;
}
$color = 'rgba(2, 47, 71, 0.3)';
if ($strNotificationStatus == '1') {
$color = 'rgba(2, 47, 71, 1)';
}
return "";
}
}
public function CustomStatusesActionDropDown() {
$customStatuses = CustomStatusesField::CustomLabels($this->MainStatus);
$fActions = ActionsDropdownField::create('CustomStatuses');
$notFound = true;
foreach ($customStatuses->filter('Status',1) as $customStatus) {
if ($customStatus->Label == $this->Status) {
$fActions->addMainAttributes(array(
'label' => $customStatus->Label,
'style' => 'background-color: '.$customStatus->Color.' !important',
'class' => 'WhiteTextColor'
));
$notFound = false;
}
$fActions->addAction($customStatus->Label, array(
'class' => 'WhiteTextColor',
'data-link' => '#',
'style' => 'background-color: '.$customStatus->Color.' !important',
'data-id' => $this->ID
));
}
if ($notFound) {
$notFound = true;
foreach ($customStatuses->filter(array(
'Status' => 1,
'StatusSystemLabel.StatusName' => $this->Status
)) as $customStatus) {
$notFound = false;
$fActions->addMainAttributes(array(
'label' => $customStatus->Label,
'style' => 'background-color: '.$customStatus->Color,
'class' => 'WhiteTextColor'
));
}
if ($notFound) {
if ($customStatus = StatusCustomLabel::get()->filter(['StatusSystemLabel.StatusName' => $this->Status])->first()) {
$fActions->addMainAttributes(array(
'label' => $customStatus->Label,
'style' => 'background-color: '.$customStatus->Color,
'class' => 'WhiteTextColor'
));
}
}
}
return $fActions->forTemplate();
}
public function OrderSMSIcon() {
if (empty($_POST['IsInXLSExport2016'])) {
$iNotificationStatus = $this->SMSNotification;
$color = ' rgba(2, 47, 71, 0.3)';
if ($iNotificationStatus)
$color = 'rgba(2, 47, 71, 1)';
return "";
}
}
public function SetSimulatedDate($strDateTime, $bApply = false) {
if($strDateTime) {
$this->setOrderParam('SimulatedDate', date(MYSQLDATETIME, strtotime($strDateTime)));
if($bApply)
$this->ApplySimulatedDate();
}
return $this;
}
public function GetSimulatedDate() {
if($simulatedDate = $this->getOrderParam('SimulatedDate'))
return $simulatedDate;
}
public function OverridePricingDate($strPricingDate) {
if($strPricingDate)
$this->setOrderParam('OverridedPricingDate', date(MYSQLDATETIME, strtotime($strPricingDate)));
return $this;
}
public function GetOverridedPricingDate() {
if($overridePricingDate = $this->getOrderParam('OverridedPricingDate'))
return $overridePricingDate;
}
public function UnsetSimulatedDate() {
$this->unsetOrderParam('SimulatedDate');
$this->ClearSimulatedDate();
return $this;
}
public function UnsetOverridedPricingDate() {
$this->unsetOrderParam('OverridedPricingDate');
return $this;
}
public static function ApplySimulatedDate($order = null) {
if(!$order)
$order = Order::GetCurrentOrder('', false);
if($order && $strVirtualDate = $order->getOrderParam('SimulatedDate'))
SS_Datetime::set_mock_now($strVirtualDate);
return $order;
}
public static function ClearSimulatedDate() {
SS_Datetime::clear_mock_now();
}
public function IsSuperAdd() {
return $this->getOrderParam('SuperAddOrderID');
}
public function SuperAdd($iSuperAddOrderID) {
$orderCombineTo = Order::get()->byID($iSuperAddOrderID);
$iSeq = $orderCombineTo->GetNextSequence();
foreach ($this->OrderItems() as $item) {
$item->PendingParticipantsToParticipants(SiteConfigOverride::CurrentSiteConfig()->AutoCheckin);
$item->Sequence += $iSeq;
$item->OrderID = $orderCombineTo->ID;
$item->write();
}
Voucher::MakeEVouchers($orderCombineTo);
foreach ($this->Transactions() as $transaction) {
$transaction->OrderID = $orderCombineTo->ID;
$transaction->write();
}
$orderCombineTo->setOrderParam('SuperAddedOrderDetails', 'This order is super added from : '. $this->getTitle(). ' at '. SS_Datetime::now()->getValue());
$orderCombineTo->ReCalculate(true);
OrderNote::create(array(
'Type' => 'AdminNote',
'Note' => 'A temporary order was created ('. $this->getTitle().') to recalculate the basket after items where added',
'OrderID' => $orderCombineTo->ID
))->write();
$this->MainStatus = 'Cancelled';
$this->write();
OrderNote::create(array(
'Type' => 'AdminNote',
'Note' => 'The purpose of cancellation of this order is that, this has been combined to order : '. $orderCombineTo->getTitle(),
'OrderID' => $this->ID
))->write();
WebHook::process($orderCombineTo,'OrderUpdate');
}
public function iCalFeedLink() {
return Director::absoluteURL('icalfeed?r='.$this->Reference, true);
}
public function AddOrderNote($strNote, $strType = 'AdminNote') {
$this->OrderNotes()->add(OrderNote::create(array(
'Type' => $strType,
'Note' => $strNote,
)));
}
public function CanSendEmail() {
$nbep = $this->getOrderParam('NotificationByEmail');
if ($nbep === '0' || $nbep === '1') {
return $nbep == '1';
}
return $this->EmailNotification;
}
public function CanSendSMS() {
return $this->SMSNotification;
}
public static function GetCurrentTaskFlowOrder($taskFlow,$strSessionID='',$bCreateOrder=true, $bRecoverAbortedIfNoItems=true) {
$order = Order::GetCurrentOrder($strSessionID,$bCreateOrder,$bRecoverAbortedIfNoItems);
$currentTaskFlow = $order->CurrentTaskFlow();
if ($currentTaskFlow && $currentTaskFlow->ID != $taskFlow->ID) {
$order->Cancel = true;
$order->write();
$order = Order::GetCurrentOrder('',true,false);
}
$order->SetTaskFlowOrder($taskFlow);
return $order;
}
public function GetPrerequisites() {
$al = new ArrayList();
foreach($this->OrderItems() as $orderItem){
$product = $orderItem->Product();
if($product && $product->exists()) {
$al->merge($product->Prerequisites());
foreach ($product->ProductGroups() as $productGroup) {
$al->merge($productGroup->Prerequisites());
}
}
}
$al->merge(Prerequisite::get()->filter('Default',1));
return $al;
}
public function SwitchPurchaser(Member $member){
$this->update(array(
'SessionID' => session_id(),
'PendingPurchaserID' => 0,
'PurchaserID' => $member->ID,
))->write();
$this->ReCalculate(true);
//Maybe there is a package that will apply now
NewPackage::PackageMatching($this);
}
public function HasPendingTransactions() : bool {
return $this->Transactions()->filter('Status', 'Pending')->exists();
}
}