1 <?php
2
3 4 5 6 7 8
9 class Order extends DataObject {
10
11 static $db = array(
12 'ClientName' => 'Text',
13 'Email' => 'Varchar',
14 'Phone' => 'Varchar',
15 'ClientNotes' => 'Text',
16 'IP' => 'Varchar',
17 'HashLink' => 'Varchar',
18
19 'ItemsTotal' => 'CatalogPrice',
20
21 'Discount' => 'CatalogPrice',
22 'GrandTotal' => 'CatalogPrice',
23
24 'AdminNotes' => 'Text',
25 'Status' => "Enum('Unpaid,Query,Paid,Processing,Sent,Complete,AdminCancelled,MemberCancelled')",
26 );
27
28 static $has_one = array(
29 'Member' => 'Member',
30
31
32
33 );
34
35 static $has_many = array(
36 'Items' => 'OrderItem',
37 'OrderLog' => 'Order_StatusLog',
38 );
39
40 static $defaults = array(
41 'Status' => 'Unpaid',
42 );
43
44 static $casting = array(
45 'TotalPrice' => 'CatalogPrice',
46 'StatusTitle' => 'Varchar',
47 );
48
49 static $searchable_fields = array(
50 'ID' => array(
51 'field' => 'NumericField',
52 'filter' => 'ExactMatchFilter'
53 ),
54 'ClientName' => array(
55 'field' => 'TextField',
56 'filter' => 'PartialMatchFilter',
57 ),
58 'Status' => array(
59 'field' => 'DropdownField',
60 'filter' => 'ExactMatchFilter',
61 ),
62 'LastEdited' => array(
63 'field' => 'DateField',
64 'filter' => 'GreaterThanFilter',
65 "title" => 'Не раньше'
66 ),
67 );
68
69 static $default_sort = 'ID DESC';
70 static $summary_fields = array('ID', 'ClientName', 'Created', 'StatusTitle', 'GrandTotal', 'Items');
71
72 static $required_fields = array('ClientName', 'Phone');
73
74 static $use_shipping = false;
75 static $use_payments = false;
76
77 static $can_cancel_before_payment = true;
78 static $can_cancel_before_processing = false;
79 static $can_cancel_before_sending = false;
80 static $can_cancel_after_sending = false;
81
82 83 84 85 86
87 static function set_required_fields($val) { self::$required_fields = $val; }
88
89 90 91 92 93 94 95 96 97
98 static function status_title($status) {
99 return _t('Order.Status_' . $status, $status);
100 }
101
102 static function get_by_hash($hash) {
103 $hash = Convert::raw2sql($hash);
104 return DataObject::get_one('Order', "HashLink='{$hash}'");
105 }
106
107 function fieldLabels($includerelations = true) {
108 $labels = parent::fieldLabels($includerelations);
109
110 $labels['ID'] = _t('Order.db_ID', 'Order ID');
111 $labels['Created'] = _t('Order.db_Created', 'Created');
112 $labels['LastEdited'] = _t('Order.db_LastEdited', 'Changed');
113 $labels['StatusTitle'] = _t('Order.db_Status', 'Status');
114 return $labels;
115 }
116
117 118 119 120 121 122 123 124
125 function getRequredFields() {
126 $fields = self::$required_fields;
127 $this->extend('updateRequiredFields', $fields);
128 return $fields;
129 }
130
131 132 133 134 135 136 137 138
139 function getContactFields() {
140 $fields = new FieldSet(
141 $f = new TextField('ClientName', _t('OrderForm.ClientName', 'ClientName')),
142 new PhoneField('Phone', _t('OrderForm.Phone', 'Phone')),
143 new EmailField('Email', _t('OrderForm.Email', 'Email'))
144 );
145 $f->setAutocomplete('name');
146 $this->extend('updateContactFields', $fields);
147 return $fields;
148 }
149
150 151 152 153 154 155 156 157
158 function getSummaryFields() {
159 $fields = new FieldSet(
160 new TextareaField('ClientNotes', _t('OrderForm.ClientNotes', 'Notes'))
161 );
162 $siteConfig = SiteConfig::current_site_config();
163 if ($siteConfig->hasMethod('SiteAgreementField') && ($rulesField = $siteConfig->SiteAgreementField())) {
164 $fields->push($rulesField);
165 }
166 $this->extend('updateSummaryFields', $fields);
167 return $fields;
168 }
169
170 171 172 173 174 175 176 177
178 function getFrontendFields() {
179 $fields = new FieldSet(
180 new HeaderField('hdrContacts', _t('OrderForm.ContactsHeader', 'Contacts'), 2 , true)
181 );
182 foreach ($this->getContactFields() as $item) {
183 $fields->push($item);
184 }
185
186 $fields->push(new HeaderField('hdrSummary', _t('OrderForm.SummaryHeader', 'Summary'), 2 , true));
187 foreach ($this->getSummaryFields() as $item) {
188 $fields->push($item);
189 }
190 $siteConfig = SiteConfig::current_site_config();
191 if ($siteConfig->hasMethod('SiteAgreementField') && ($rulesField = $siteConfig->SiteAgreementField())) {
192 $fields->push($rulesField);
193 }
194 $this->extend('updateFrontendFields', $fields);
195 return $fields;
196 }
197
198 function getCMSFields() {
199 DataObject::disableCMSFieldsExtensions();
200 $fields = parent::getCMSFields();
201 DataObject::enableCMSFieldsExtensions();
202
203 $fields->removeByName('HashLink');
204 $fields->removeByName('IP');
205
206
207 $fields->replaceField('ClientName', new TextField('ClientName', $this->fieldLabel('ClientName')));
208 $fields->replaceField('Email', new EmailField('Email', $this->fieldLabel('Email')));
209
210
211 foreach(array('ItemsTotal', 'GrandTotal') as $fieldName) {
212 $fields->replaceField($fieldName, new ReadonlyField($fieldName, $this->fieldLabel($fieldName)));
213 }
214 foreach(array('Discount', 'ItemsTotal', 'GrandTotal') as $fieldName) {
215 $f = $fields->dataFieldByName($fieldName);
216 $f->setTitle(sprintf('%s (%s)', $f->Title(), $this->Currency()));
217 }
218
219
220
221
222
223 $fields->addFieldToTab('Root.Main', new HeaderField('hdrManage', _t('Order.ManageHeader', 'Manage order')), 'AdminNotes');
224
225 $statusValues = $this->dbObject('Status')->enumValues();
226 unset($statusValues['MemberCancelled']);
227 foreach ($statusValues as $statusItem => $statusName) {
228 $statusValues[$statusItem] = Order::status_title($statusItem);
229 }
230 $fields->replaceField('Status', new DropdownField('Status', $this->fieldLabel('Status'), $statusValues ));
231 $fields->addFieldToTab('Root.Main', new TextareaField('Note', _t('Order_StatusLog.db_Note', 'Status change note')));
232
233
234 $table = new ComplexTableField($this, 'Products', 'OrderItem', null, null, 'OrderID = ' . $this->ID);
235 $table->setPermissions(array('add', 'edit', 'delete'));
236 $table->setPopupCaption(_t('OrderAdmin.EditItem', 'Edit Item'));
237 $fields->replaceField('Items', $table);
238
239
240 $log = $fields->dataFieldByName('OrderLog');
241 $log->setPermissions(array());
242 $log->setPagesize(30);
243
244
245 $fields->removeByName('MemberID');
246
247 if ($this->MemberID && $this->Member()->ID) {
248 $fields->findOrMakeTab('Root.Member', _t('Order.tab_Member', 'Client'));
249 $fields->addFieldToTab('Root.Member', new HeaderField('hdrMember', _t('Order.MemberHeader', 'Client')));
250
251 foreach (array('Title', 'Email', 'Phone', 'Created', 'LastVisited', 'NumVisit', 'Discount') as $name) {
252 $fields->addFieldToTab('Root.Member', new TextLiteralField('Member' . $name, $this->Member()->fieldLabel($name), $this->Member()->$name));
253 }
254
255
256 $fields->addFieldToTab('Root.Member', new HeaderField('hdrStatistic', _t('Order.StatisticHeader', 'Order Statistic')));
257
258 $fields->addFieldToTab('Root.Member', new LiteralField(
259 'OrdersCount',
260 sprintf(_t('Order.OrdersCount', 'Orders total count: %d'), $this->Member()->Orders()->Count())
261 ));
262
263 $fields->addFieldToTab('Root.Member', new LiteralField(
264 'OrdersSum',
265 sprintf(
266 _t('Order.OrdersSum', 'Orders total price: %.2f %s'),
267 array_sum($this->Member()->Orders()->column('GrandTotal')),
268 $this->Currency()
269 )
270 ));
271
272 $orderFields = $this->summaryFields();
273 unset($orderFields['ClientName']);
274 unset($orderFields['Items']);
275 $table = new ComplexTableField($this->Member(), 'Orders', 'Order', $orderFields, null, 'MemberID = ' . $this->MemberID);
276 $table->setPermissions(array());
277 $fields->addFieldToTab('Root.Member', $table);
278 }
279
280 $this->extend('updateCMSFields', $fields);
281 return $fields;
282 }
283
284 function onBeforeWrite() {
285
286 if (!$this->isInDB()) {
287 $this->MemberID = (Member::currentUserID()) ? Member::currentUserID() : 0;
288 $this->IP = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
289 }
290
291 if (!$this->HashLink) {
292 $this->HashLink = md5(time() . serialize($this->data()) . rand());
293 }
294
295
296 if ($this->getTotalPrice() != $this->ItemsTotal)
297 $this->ItemsTotal = $this->getTotalPrice();
298
299 if ($this->isChanged('Discount') && $this->Discount < 0) {
300 $this->Discount = 0;
301 }
302
303
304
305
306
307
308 $this->GrandTotal = $this->calculateGrandTotal();
309
310 parent::onBeforeWrite();
311 }
312
313 function onAfterWrite() {
314
315 if ($this->isChanged('ID')) {
316 $log = new Order_StatusLog();
317 $log->Status = $this->StatusTitle();
318 $log->Note = ($this->Note) ? $this->Note : _t('Order.OrderCreated', 'Odrder was created');
319 $log->OrderID = $this->ID;
320 $log->write();
321 }
322
323 else if ($this->isChanged('Status')) {
324 $log = new Order_StatusLog();
325 $log->Status = $this->StatusTitle();
326 $log->Note = ($this->Note) ? $this->Note : _t('Order.StatusChanged', 'Odrder was changed');
327 $log->OrderID = $this->ID;
328 $log->write();
329
330
331 $changedFields = $this->getChangedFields();
332 $note = $log->Note;
333 $this->extend('OnAfterChangeStatus', Member::CurrentUser(), $changedFields['Status'], $note);
334 }
335 parent::onAfterWrite();
336 }
337
338 function OnBeforeDelete() {
339 foreach ($this->OrderLog() as $log) {
340 $log->delete();
341 }
342 parent::OnBeforeDelete();
343 }
344
345 346 347 348 349 350 351
352 function MinTotalPrice($real=false) {
353 if ($real) {
354 return SiteConfig::current_site_config()->CartMinimalOrderPrice;
355 }
356 return DBField::create('CatalogPrice', SiteConfig::current_site_config()->CartMinimalOrderPrice);
357 }
358
359 360 361 362 363 364 365
366 function CanCheckout($price=null) {
367 if (!$this->exists())
368 return false;
369
370 $minPrice = $this->MinTotalPrice(1);
371 if ($minPrice == 0)
372 return true;
373
374 if (!$price) {
375 $price = $this->getTotalPrice();
376 }
377
378 return ($price >= $minPrice);
379 }
380
381
382 function calculateGrandTotal() {
383 $itemsTotal = $this->getTotalPrice();
384 $discount = $this->Discount;
385 $grandTotal = $itemsTotal - $discount;
386 $this->extend('updateGrandTotal', $grandTotal);
387 return $grandTotal;
388 }
389
390
391 function getGrandTotal() {
392 return (isset($this->record['GrandTotal']) && $this->record['GrandTotal']) ? $this->record['GrandTotal'] : $this->calculateGrandTotal();
393 }
394
395 function exists() {
396 if ($this->Items() && $this->Items()->Count() > 0) return true;
397 parent::exists();
398 }
399
400 401 402 403 404 405 406
407 function validate() {
408 $result = new ValidationResult();
409 foreach (self::getRequredFields() as $name) {
410 if (!$this->hasValue($name)) {
411 $result->error(sprintf(_t('Order.FieldRequired', 'Field "%s" is required'), $this->fieldLabel($name)));
412 }
413 }
414 if (!$this->Items() || $this->Items()->Count() == 0) {
415 $result->error(_t('Order.ItemsRequired', 'Not items in cart'));
416 }
417 if ($result->valid()) {
418 $this->extend('updateValidationResult', $result);
419 }
420 return $result;
421 }
422
423 424 425 426 427 428 429
430 function Currency() {
431 return SiteConfig::current_site_config()->CatalogCurrency;
432 }
433
434
435 function getTotalPrice() {
436 $ceil = SiteConfig::current_site_config()->CatalogDiscountCeil;
437 $result = 0;
438 if ($items = $this->Items()) {
439 foreach ($items as $item) {
440 $price = $item->TotalPrice;
441 $result += ($ceil) ? ceil($price) : round($price, 2);
442 }
443 }
444 return $result;
445 }
446
447
448 function getTotalQuantity() {
449 $total = 0;
450 if ($this->Items()) {
451 foreach ($this->Items() as $item) {
452 $total += $item->getQuantity();
453 }
454 }
455 return $total;
456 }
457
458
459 function StatusTitle() {
460 return self::status_title($this->Status);
461 }
462
463
464 function ItemName() {
465 return Convert::number2name($this->getTotalQuantity(), _t('Cart.ItemName1', 'product'), _t('Cart.ItemName2', 'products'), _t('Cart.ItemName3', 'products'));
466 }
467
468 469 470 471 472
473 function updateForAjax(array &$js) {
474 $js[] = array('total' => array(
475 'Quantity' => $this->getTotalQuantity(),
476 'TotalPrice' => $this->getTotalPrice(),
477 'ItemName' => $this->ItemName(),
478 ));
479 }
480
481 public function canCancel() {
482 switch ($this->Status) {
483 case 'Unpaid':
484 case 'Query':
485 return self::$can_cancel_before_payment;
486 case 'Paid': return self::$can_cancel_before_processing;
487 case 'Processing': return self::$can_cancel_before_sending;
488 case 'Sent':
489 case 'Complete':
490 return self::$can_cancel_after_sending;
491 default : return false;
492 }
493 }
494
495
496 function Items() {
497 if ($this->ID) {
498 return $this->itemsFromDatabase();
499 } else {
500 $items = Cart::get_items();
501 if ($items){
502 return $this->createItems($items);
503 }
504 }
505 }
506
507 private function childClass() {
508 $class = $this->stat('has_many');
509 return $class['Items'];
510 }
511
512 513 514 515 516 517 518 519
520 function createItems(array $items, $write = false) {
521 if ($write) {
522 foreach ($items as $item) {
523 $item->OrderID = $this->ID;
524 $item->write();
525 }
526 }
527 return $write ? $this->itemsFromDatabase() : $this->createDataObjectSet($items);
528 }
529
530 531 532 533 534
535 protected function itemsFromDatabase() {
536 return DataObject::get('OrderItem', "\"OrderID\" = '$this->ID'");
537 }
538
539 540 541 542 543 544 545
546 protected function createDataObjectSet($items) {
547 foreach ($items as $item) {
548 $item->_id = $item->_productId;
549 }
550 return new DataObjectSet($items);
551 }
552
553 function Link() {
554 return ($this->MemberID && CartSiteConfig::CartRegistraionAvailable()) ? ProfilePage::find_link('order/' . $this->HashLink) : CheckoutPage::find_link('order/' . $this->HashLink);
555 }
556
557 function PrintLink() {
558 return ($this->MemberID && CartSiteConfig::CartRegistraionAvailable()) ? ProfilePage::find_link('printorder/' . $this->HashLink) : CheckoutPage::find_link('printorder/' . $this->HashLink);
559 }
560
561 function CancelLink() {
562 if ($this->canCancel()) {
563 return ($this->MemberID && CartSiteConfig::CartRegistraionAvailable()) ? ProfilePage::find_link('cancel_order/' . $this->HashLink) : false;
564 }
565 return false;
566 }
567 }
568
569 570 571 572 573
574 class Order_StatusLog extends DataObject {
575
576 static $db = array(
577 'Status' => 'Varchar(255)',
578 'Note' => 'Text',
579 );
580
581 static $has_one = array(
582 'Creator' => 'Member',
583 'Order' => 'Order',
584 );
585
586 static $summary_fields = array(
587 'Created', 'Status', 'Note'
588 );
589
590 static $searchable_fields = array(
591 'OrderID' => array(
592 'field' => 'NumericField',
593 'filter' => 'ExactMatchFilter'
594 ),
595 );
596
597 static $default_sort = 'ID';
598
599 function fieldLabels($includerelations = true) {
600 $labels = parent::fieldLabels($includerelations);
601 $labels['OrderID'] = _t('Order.db_ID', 'Order ID');
602 $labels['Created'] = _t('Order_StatusLog.db_Created', 'Status time');
603 return $labels;
604 }
605
606 function getCMSFields() {
607 $fields = parent::getCMSFields();
608 $fields->replaceField('OrderID', new ReadonlyField('OrderID', $this->fieldLabel('Order'), $this->OrderID));
609 return $fields;
610 }
611
612 function canEdit() {
613 return false;
614 }
615
616 function canDelete() {
617 return false;
618 }
619
620 function onBeforeWrite() {
621 if (!$this->ID) {
622 $this->CreatorID = (Member::currentUserID()) ? Member::currentUserID() : 1;
623 }
624 parent::onBeforeWrite();
625 }
626
627 function Link() {
628 return $this->Order()->Link();
629 }
630
631 function PrintLink() {
632 return $this->Order()->PrintLink();
633 }
634 }
635
636 class Order_CancelForm extends Form {
637
638 function __construct($controller, $name, $orderID) {
639 $fields = new FieldSet(
640 new HiddenField('OrderID', '', $orderID)
641 );
642 $actions = new FieldSet(
643 new FormAction('doCancel', 'Cancel Order')
644 );
645 parent::__construct($controller, $name, $fields, $actions);
646 }
647
648 function doCancel($data, $form) {
649 $SQL_data = Convert::raw2sql($data);
650 $order = DataObject::get_by_id('Order', $SQL_data['OrderID']);
651 $order->Status = 'MemberCancelled';
652 $order->write();
653 Director::redirectBack();
654 return;
655 }
656 }
657
658
[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.
-