Version: current

      1 <?php
      2 // Объект Заказ - хранит всю информацию о заказе.
      3 class BookingOrder extends DataObject {
      4     static $db = array(
      5         'StartDate' => 'Date',
      6         'EndDate' => 'Date',
      7         'ContactPhone' => 'Varchar',
      8         'Email' => 'Varchar(100)',
      9         'InTime' => 'Time', // Время заезда
     10         'OutTime' => 'Time', // Время выезда
     11         'Comment' => 'Text',
     12         'AgreeWithRulesAndPolicy' => 'Boolean',  // флаг "я согласен с правилами бронирования и политикой конфиденциальности"
     13         'Price' => 'Decimal(10,2)',
     14         'Discount' => 'Decimal(10,2)',
     15         'Status' => "Enum('Basket, New, Query, Paid, Complete, MemberCancelled, AdminCancelled')", // статус заказа  новый (ожидает подтверждения), подтвержден (ожидает оплаты), оплачен, выполнен, отменен пользователем, отменен администратором
     16
     17         'FIOList' => 'Text', // список всех жильцов номера - скрытое поле для поиска в админке
     18         'HashLink' => 'Varchar',
     19         'SubsiteID' => 'Int', // подсайт заказа
     20     );
     21
     22     static $has_one = array(
     23         'PaymentMethod' => 'PaymentMethod', // способ платежа
     24     );
     25
     26     static $has_many = array(
     27         'Rooms' => 'RoomOrder', // заказанные номера
     28         'OrderServices' => 'OrderServiceOrder', // заказанные услуги заказа
     29         'OrderLog' => 'BookingOrder_StatusLog' // история изменения статуса заказа
     30     );
     31
     32     static $default_sort = 'ID DESC';
     33
     34     static $defaults = array(
     35         'Status' => 'Basket',
     36     );
     37
     38     static $summary_fields = array('StartDate', 'EndDate', 'ContactPhone', 'Email', 'StatusTitle');
     39
     40     static $searchable_fields = array('StartDate', 'EndDate', 'FIOList', 'ContactPhone', 'Email', 'Price', 'Status' => array('filter'=> 'ExactMatchMultiFilter'));
     41
     42     static $field_labels = array(
     43         'StatusTitle' => 'Статус'
     44     );
     45
     46     static $use_payments = true;
     47     static $use_shipping = false;
     48
     49     // статусы заказа, учитываемые в работе
     50     static $accounted_statuses = array('Query', 'Paid', 'Complete');
     51
     52     static function get_current_order() {
     53         $orderID = (int)Session::get('CurrentBookingOrderID');
     54         if ($orderID) {
     55             $order = DataObject::get_one('BookingOrder', "ID = {$orderID}"); // AND Status = 'Basket' - регулируем по CurrentBookingOrderID, а не по статусу
     56             return $order;
     57         }
     58         return false;
     59     }
     60
     61     /**
     62      * Локализованное название статуса
     63      *
     64      * Локализация с помощью файла переводов, константы вида "BookingOrder.Status_{$status}"
     65      *
     66      * @param string $status - код статуса
     67      *
     68      * @return string
     69      */
     70     static function status_title($status) {
     71         return _t('BookingOrder.Status_' . $status, $status);
     72     }
     73
     74     function canView($member = null) {
     75         return Permission::check('CMS_ACCESS_BookingOrderAdmin', 'any', $member);
     76     }
     77
     78     function canEdit($member = null) {
     79         return Permission::check('CMS_ACCESS_BookingOrderAdmin', 'any', $member);
     80     }
     81
     82     function canDelete($member = null) {
     83         return Permission::check('CMS_ACCESS_BookingOrderAdmin', 'any', $member);
     84     }
     85
     86     function canCreate($member = null) {
     87         return Permission::check('CMS_ACCESS_BookingOrderAdmin', 'any', $member);
     88     }
     89
     90     static function LocalStatuses() {
     91         $rs = array();
     92         foreach(singleton('BookingOrder')->dbObject('Status')->enumValues() as $value) {
     93             $rs[$value] = self::status_title($value);
     94         }
     95         return $rs;
     96     }
     97
     98     function OrderingMember() {
     99         return $this->Rooms()->First()->MainPerson();
    100     }
    101
    102     // Длина заказа в днях
    103     function OrderLength() {
    104         return RoomRate::period_length($this->StartDate, $this->EndDate);
    105     }
    106
    107     // Список заказанных номеров с сортировкой по полю Sort связанного Номера и по количеству мест в номере
    108     function OrderRooms() { // JOIN SiteTree_Live ON SiteTree_Live.ID = RoomOrder.RoomID
    109         $rs = DataObject::get('RoomOrder', "OrderID = {$this->ID}", "RoomOrder.Sort, RoomRate.PersonCount", 'JOIN RoomRate ON RoomRate.ID = RoomOrder.RateID');
    110         return $rs;
    111     }
    112
    113     // Локализованное название статуса
    114     function StatusTitle() {
    115         return self::status_title($this->Status);
    116     }
    117
    118     //  Совместимость с модулем оплат.
    119     function getGrandTotal() {
    120         return $this->Price - $this->Discount;
    121     }
    122
    123     // флаг может ли пользователь отменить заказ
    124     function canCancel() {
    125         // только со статусов Новый и Подтвержден
    126         if (($this->Status != "New") && ($this->Status != "Query")) {
    127             return false;
    128         }
    129
    130         // только если в списке тарифов заказа нет Невозвратных
    131         if ($this->Rooms() && $this->Rooms()->Count()) {
    132             foreach($this->Rooms() as $roomOrder) {
    133                 if ($roomOrder->Rate()->NoReturn) {
    134                     return false;
    135                 }
    136             }
    137         }
    138         return true;
    139     }
    140
    141     function OrderPaymentLink() {
    142         return BookingPage::find_links('payment/' . $this->HashLink);
    143     }
    144
    145     // ссылка на печать ваучера
    146     function PrintVoucherLink() {
    147         return BookingPage::find_links('print_voucher/' . $this->HashLink);
    148     }
    149
    150     // ссылка на загрузку ваучера как файла
    151     function DownloadVoucherLink() {
    152         return BookingPage::find_links('download_voucher/' . $this->HashLink);
    153     }
    154
    155     // ссылка на отмену заказа
    156     function CancelLink() {
    157         if ($this->canCancel()) {
    158             return BookingPage::find_links('cancel_order/' . $this->HashLink);
    159         }
    160     }
    161
    162     function getCMSFields() {
    163         $fields = parent::getCMSFields();
    164         $statuses = self::LocalStatuses();
    165         array_shift($statuses);
    166
    167         $fields->replaceField('Status', new DropdownField('Status', $this->fieldLabel('Status'), $statuses));
    168         //$fields->replaceField('Price', new LabelField('Price', $this->Price));
    169         $fields->replaceField('Price', $fields->dataFieldByName('Price')->performReadonlyTransformation());
    170
    171         $fields->insertAfter(new ReadonlyField('TotalPrice', _t('BookingOrder.TotalPrice', 'TotalPrice'), $this->GrandTotal), 'Discount');
    172         $fields->removeByName('FIOList');
    173         $fields->removeByName('AgreeWithRulesAndPolicy');
    174         $fields->removeByName('HashLink');
    175         $fields->removeByName('SubsiteID');
    176
    177         if ($this->isInDB()) {
    178             $fields->dataFieldByName('OrderLog')->setPermissions(array('view'));
    179         }
    180
    181         return $fields;
    182     }
    183
    184     // Количество тарифа номера в заказе
    185     function getOrderRoomsCount($rateID) {
    186         if ($roomOrders = $this->Rooms("RateID = {$rateID}")) {
    187             return $roomOrders->Count();
    188         }
    189         return false;
    190     }
    191
    192     // Количество доп.мест тарифа
    193     function getAdditionalPlacesValue($rateID) {
    194         if ($roomOrder = $this->Rooms()->find('RateID', $rateID)) {
    195             return $roomOrder->AdditionalPlacesCount;
    196         }
    197         return false;
    198     }
    199
    200     function saveOrderData($data) {
    201         if (isset($data['dateArrival'])) {
    202             $this->StartDate = $data['dateArrival'];
    203         }
    204         if (isset($data['dateDeparture'])) {
    205             $this->EndDate = $data['dateDeparture'];
    206         }
    207
    208         // Цену бы наверное нужно пересчитывать??
    209         /*
    210         if (isset($data['price'])) {
    211             $this->Price = (float)$data['price'];
    212         }
    213         */
    214         if (isset($data['roomsList'])) {
    215             foreach($this->Rooms() as $room) {
    216                 $room->delete();
    217             }
    218             $this->Rooms()->removeAll();
    219             foreach($data['roomsList'] as $index=>$room) {
    220                 $roomOrder = new RoomOrder();
    221                 $roomOrder->OrderID = $this->ID;
    222                 $roomOrder->RoomID = $room['room-id'];
    223                 $roomOrder->RateID = $room['rate-id'];
    224                 if (isset($room['additional-count'])) {
    225                     $roomOrder->AdditionalPlacesCount = (int)$room['additional-count'];
    226                 }
    227                 $roomOrder->write();
    228                 // без этого в $this->calculateOrderCost() не видно $this->Rooms()
    229                 $this->Rooms()->add($roomOrder);
    230
    231
    232                 // Сорханяем поле Sort для правильной сортировки заказов на номера
    233                 //$roomOrder->Sort = $roomOrder->Room()->Sort;
    234
    235                 //$roomOrder->Sort = (isset($room['room-pos'])) ? (int)$room['room-pos'] : 0;
    236                 $roomOrder->Sort = $index;
    237
    238                 $roomOrder->write();
    239
    240                 if (isset($room['services'])) {
    241                     foreach($roomOrder->RoomServices() as $roomService) {
    242                         $roomService->delete();
    243                     }
    244                     $roomOrder->RoomServices()->removeAll();
    245                     foreach($room['services'] as $roomService) {
    246                         $roomServiceOrder = DataObject::get_one('RoomServiceOrder', "RoomServiceID = " . $roomService['service-id'] . " AND RoomOrderID = {$roomOrder->ID}");
    247                         if (!$roomServiceOrder) {
    248                             $roomServiceOrder = new RoomServiceOrder();
    249                             $roomServiceOrder->RoomServiceID = $roomService['service-id'];
    250                             $roomServiceOrder->RoomOrderID = $roomOrder->ID;
    251                         }
    252                         $roomServiceOrder->Count = $roomService['service-count'];
    253                         $roomServiceOrder->write();
    254                         // без этого в $this->calculateOrderCost() не видно $this->Rooms()->RoomServices()
    255                         $roomOrder->RoomServices()->add($roomServiceOrder);
    256                     }
    257                 }
    258                 // считаем стоимость заказа номера и сохраняем
    259                 $roomOrder->calcRoomOrderCost();
    260                 $roomOrder->write();
    261             }
    262         }
    263         if (isset($data['servicesList'])) {
    264             foreach($this->OrderServices() as $orderService) {
    265                 $orderService->delete();
    266             }
    267             $this->OrderServices()->removeAll();
    268             //$oldOrderServiceIDs = $this->OrderServices()->getIdList();
    269             foreach($data['servicesList'] as $orderServiceData) {
    270                 if ($orderService = DataObject::get_by_id('OrderService', (int)$orderServiceData['id'])) {
    271                     if ($orderService->HasAdditionalField) {
    272                         for($i = 0; $i < $orderServiceData['count']; $i++) {
    273                             $orderServiceOrder = new OrderServiceOrder();
    274                             $orderServiceOrder->OrderServiceID = $orderService->ID;
    275                             $orderServiceOrder->OrderID = $this->ID;
    276                             $orderServiceOrder->Count = 1;
    277                             if (isset($orderServiceData['add-info'])) {
    278                                 $orderServiceOrder->AdditionalFieldData = $orderServiceData['add-info'][$i];
    279                             }
    280                             $orderServiceOrder->write();
    281                             // без этого в $this->calculateOrderCost() не видно $this->OrderServices()
    282                             $this->OrderServices()->add($orderServiceOrder);
    283                         }
    284                     } else {
    285                         $orderServiceOrder = new OrderServiceOrder();
    286                         $orderServiceOrder->OrderServiceID = $orderService->ID;
    287                         $orderServiceOrder->OrderID = $this->ID;
    288                         $orderServiceOrder->Count = $orderServiceData['count'];
    289                         $orderServiceOrder->write();
    290                         // без этого в $this->calculateOrderCost() не видно $this->OrderServices()
    291                         $this->OrderServices()->add($orderServiceOrder);
    292                     }
    293
    294                 }
    295             }
    296             /* ПОКА РЕШИЛ, ЧТО УДАЛЯЕМ ВСЕ
    297             // удаляем отсутствующие ..
    298             foreach($oldOrderServiceIDs as $ID) {
    299                 DataObject::delete_by_id('OrderServiceOrder', $ID);
    300             }
    301             */
    302         }
    303         $subsiteID = 0;
    304         if (class_exists('Subsite')) {
    305             $subsiteID = Subsite::currentSubsiteID();
    306         }
    307         $this->SubsiteID = $subsiteID;
    308         $this->write();
    309         /*
    310         $this->calculateOrderCost();
    311
    312         $this->write();
    313         */
    314         Session::set('CurrentBookingOrderID', $this->ID);
    315     }
    316
    317     function calculateOrderCost() {
    318         $sum = 0;
    319         foreach($this->Rooms() as $roomOrder) {
    320             $sum += $roomOrder->RoomOrderCost;
    321             if ($roomOrder->RoomServices() && $roomOrder->RoomServices()->Count()) {
    322                 foreach($roomOrder->RoomServices() as $roomService) {
    323                     $sum += $roomService->RoomServicePrice();
    324                 }
    325             }
    326         }
    327         if ($this->OrderServices() && $this->OrderServices()->Count()) {
    328             foreach($this->OrderServices() as $orderService) {
    329                 $sum += $orderService->OrderServicePrice();
    330             }
    331         }
    332         $this->Price = $sum;
    333         return $this->Price;
    334     }
    335
    336     function onBeforeWrite() {
    337         parent::onBeforeWrite();
    338         if (!$this->HashLink) {
    339             $this->HashLink = md5(time() . serialize($this->data()) . rand());
    340         }
    341         //if ($this->isChanged('StartDate') || $this->isChanged('EndDate')) {
    342             foreach($this->Rooms() as $roomOrder) {
    343                 $roomOrder->calcRoomOrderCost();
    344                 $roomOrder->write();
    345             }
    346             $this->calculateOrderCost();
    347         //}
    348     }
    349
    350     function onAfterWrite() {
    351         // новый заказ
    352         if ($this->isChanged('ID')) {
    353             $log = new BookingOrder_StatusLog();
    354             $log->Status = $this->StatusTitle();
    355             $log->Note = ($this->Note) ? $this->Note : _t('BookingOrder.OrderCreated', 'BookingOrder was created');
    356             $log->OrderID = $this->ID;
    357             $log->write();
    358         }
    359         // Если поменяли статус
    360         else if ($this->isChanged('Status') && ($this->Status != 'New')) {
    361             $log = new BookingOrder_StatusLog();
    362             $log->Status = $this->StatusTitle();
    363             $log->Note = ($this->Note) ? $this->Note : _t('BookingOrder.StatusChanged', 'BookingOrder was changed');
    364             $log->OrderID = $this->ID;
    365             $log->write();
    366             $this->sendOrderStatus();
    367         }
    368
    369         // если меняем способ оплаты или Скидку - то пересоздаем платеж
    370         if (class_exists('Payment') && ($this->isChanged('PaymentMethodID', 2) || $this->isChanged('Discount', 2))) {
    371             if ($this->Payments() && $this->Payments()->Count()) {
    372                 foreach($this->owner->Payments() as $payment) {
    373                     if (($payment->Status == 'Pending') || ($payment->Status == 'Incomplete')) {
    374                         $payment->delete();
    375                     }
    376                 }
    377             }
    378             // создаем новый платеж, только если заказа не новый (не сменил стату с Basket на ...)
    379             if (!$this->isChanged('Status') || (($changedFields = $this->getChangedFields()) && ($changedFields['Status']['before'] != 'Basket'))) {
    380                 if ($this->PaymentMethodID && ($method = $this->PaymentMethod())) {
    381                     $p = $method->createPayment($this, 'Payment');
    382                 }
    383             }
    384         }
    385         parent::onAfterWrite();
    386     }
    387
    388     function OnBeforeDelete() {
    389         foreach ($this->OrderLog() as $log) {
    390             $log->delete();
    391         }
    392         parent::OnBeforeDelete();
    393     }
    394
    395     function sendReceipt() {
    396         if (class_exists('Subsite') && $this->SubsiteID && ($subsite = DataObject::get_by_id('Subsite', $this->SubsiteID))) {
    397             i18n::set_locale($subsite->Language);
    398         }
    399         $email = new Email();
    400         $bookingPage = DataObject::get_one('BookingPage');
    401         $from = ($bookingPage && $bookingPage->AdminEmail) ? $bookingPage->AdminEmail : Email::getAdminEmail();
    402         $email->setFrom($from);
    403         $to = $this->Email;
    404         $email->setSubject(_t('BookingOrder.ReceiptMailSubject'));
    405         $email->setTemplate('BookingOrderClientReceipt');
    406         $email->populateTemplate(array(
    407             'Order' => $this
    408         ));
    409         // письмо клиенту
    410         if ($to) {
    411             $email->setTo($to);
    412             $email->send();
    413         }
    414         i18n::set_locale('ru_RU');
    415         // письмо манагеру
    416         $email = new Email();
    417         $email->setFrom($from);
    418         $email->setTo($from);
    419         $email->setSubject(_t('BookingOrder.ManagerReceiptMailSubject'));
    420         $email->setTemplate('BookingOrderManagerReceipt');
    421         $email->populateTemplate(array(
    422             'Order' => $this
    423         ));
    424         $email->send();
    425     }
    426
    427     function sendOrderStatus() {
    428         $paymentLink = $this->OrderPaymentLink();
    429         if (class_exists('Subsite') && $this->SubsiteID && ($subsite = DataObject::get_by_id('Subsite', $this->SubsiteID))) {
    430             i18n::set_locale($subsite->Language);
    431             // почему-то при выставлении языка здесь не меняется BookingPage::find_links, поэтому добавляем отдельно
    432             $paymentLink = $subsite->domain() . $paymentLink;
    433         }
    434         $email = new Email();
    435         $bookingPage = DataObject::get_one('BookingPage');
    436         $from = ($bookingPage && $bookingPage->AdminEmail) ? $bookingPage->AdminEmail : Email::getAdminEmail();
    437         $email->setFrom($from);
    438         $to = $this->Email;
    439         $email->setSubject(_t('BookingOrder.StatusChangeMailSubject'));
    440         $email->setTemplate('BookingOrderClientChangeStatus');
    441
    442         $email->populateTemplate(array(
    443             'Order' => $this,
    444             'OrderPaymentLink' => $paymentLink,
    445         ));
    446         // письмо клиенту
    447         if ($to) {
    448             $email->setTo($to);
    449             $email->send();
    450         }
    451         i18n::set_locale('ru_RU');
    452         /* Не уверен, что манагеру нужно письмо об изменении статуса
    453         // письмо манагеру
    454         $email->setTo($from);
    455         $email->setTemplate('BookingOrderManagerChangeStatus');
    456         $email->send();
    457         */
    458     }
    459 }
    460
    461 /**
    462  * Запись об изменении статуса заказа
    463  *
    464  * @author inxo, dvp
    465  */
    466 class BookingOrder_StatusLog extends DataObject {
    467
    468     static $db = array(
    469         'Status' => 'Varchar(255)', // Новый статус
    470         'Note' => 'Text', // Комментарий к изменению
    471     );
    472
    473     static $has_one = array(
    474         'Creator' => 'Member',  // Автор изменения
    475         'Order' => 'BookingOrder', // Связанный заказ
    476     );
    477
    478     static $summary_fields = array(
    479         'Created', 'Status', 'Note'
    480     );
    481
    482     static $searchable_fields = array(
    483         'OrderID' => array(
    484             'field' => 'NumericField',
    485             'filter' => 'ExactMatchFilter'
    486         ),
    487     );
    488
    489     static $default_sort = 'ID';
    490
    491     function fieldLabels($includerelations = true) {
    492         $labels = parent::fieldLabels($includerelations);
    493         $labels['OrderID'] = _t('BookingOrder.db_ID', 'BookingOrder ID');
    494         $labels['Created'] = _t('BookingOrder_StatusLog.db_Created', 'Status time');
    495         return $labels;
    496     }
    497
    498     function getCMSFields() {
    499         $fields = parent::getCMSFields();
    500         $fields->replaceField('OrderID', new ReadonlyField('OrderID', $this->fieldLabel('BookingOrder'), $this->OrderID));
    501         $fields->removeByName('CreatorID');
    502         return $fields;
    503     }
    504
    505     function canEdit() {
    506         return false;
    507     }
    508
    509     function canDelete() {
    510         return false;
    511     }
    512
    513     function onBeforeWrite() {
    514         if (!$this->ID) {
    515             $this->CreatorID = (Member::currentUserID()) ? Member::currentUserID() : 1;
    516         }
    517         parent::onBeforeWrite();
    518     }
    519 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -