All pastes #2080330 Raw Edit

ololo

public text v1 · immutable
#2080330 ·published 2011-09-08 10:55 UTC
rendered paste body
<?php
/**
 * Класс определяет модель запросов. Запросы хранятся в таблице "requests".
 *
 * Следующие атрибуты доступны в модели, они соответствуют полям в таблице 'requests':
 * @property string $id Идентификатор запроса
 * @property string $users_id
 * @property string $plantations_id
 * @property string $types_id
 * @property string $sorts_id
 * @property integer $boxes_amount
 * @property integer $boxes_deal
 * @property string $exp_time
 * @property string $exp_date
 * @property string $status
 * @property string $create_date
 * @property string $update_date
 * @property string $uniq_number
 * @property string $aport_delivery
 * @property string $order_type
 * @property integer $order_id
 * @property Money $size_50
 * @property Money $size_60
 * @property Money $size_70
 * @property Money $size_80
 * @property Money $size_90
 * @property Money $size_100
 * @property Money $size_100p
 * @property integer $size_mask
 * @property integer $submarks_id
 * @property integer $agent_id
 *
 * @package MarketPlace
 * @subpackage Requests
 * @version SVN: $Id$
 * @todo В правила валидации обязательно включить проверку на то, что заказ, к которому приписывается заявка сделан мной
 */
class Requests extends BFActiveRecord {

    /**
     * Статус заявки находящейся на Market Place
     *
     * @var string
     */
    const STATUS_ON_MARKET = 'on_market';

    /**
     * Статус заявки снятой с Market Place
     *
     * @var string
     */
    const STATUS_OFF_MARKET = 'off_market';

    /**
     * Статус для истекшей заявки
     *
     * @var string
     */
    const STATUS_EXPIRED = 'expired';

    /**
     * Статус для полностью выполненной заявки
     *
     * @var string
     */
    const STATUS_EXECUTED = 'executed';

    /**
     * Статус для полностью выполненной заявки
     *
     * @var string
     */
    const STATUS_DEAL = 'deal';

    /**
     * заявка от продавца
     *
     * @var string
     */
    const TYPE_SELLER = 'seller';

    /**
     * Заявка от покупателя
     *
     * @var string
     */
    const TYPE_BUYER = 'buyer';

    /**
     * Идентификатор агента для заявок продавца
     * Поскольку для заявок продавца не требуется агент - должна использоваться эта константа
     *
     * @var integer
     */
    const ID_AGENT_FOR_SELLERS_REQUEST = -1;

    /**
     * Идентификатор сабмаркировки для заявок продавца
     * Поскольку для заявок продавца не требуется сабмаркировки - должна использоваться эта константа
     *
     * @var integer
     */
    const ID_SUBMARK_FOR_SELLERS_REQUEST = -1;

    /**
     * Предыдущий статус заявки
     *
     * @var string
     */
    public $old_status = self::STATUS_OFF_MARKET;

    /**
     * Предыдущее значение кол-ва ящиков в заявке
     * Используется для кеширования данных и последующих вычислениях при сохранении
     *
     * @var integer
     */
    private $old_amount;

    /**
     * Исходное значение кол-ва проданных ящиков
     * Используется для кеширования данных и последующих вычислениях при сохранении
     *
     * @var integer
     */
    private $old_deal;

    /**
     * Исходный номер заказа для заявки
     *
     * @var integer
     */
    private $old_order = -1;

    /**
     * Максимальное количество одновременно отбираемых заявок
     *
     * @var integer
     */
    public $limit = DEFAULT_ROW_LIMIT;

    /**
     * С какого элемента начинать выборку
     *
     * @var integer
     */
    public $offset = 0;

    /**
     * Идентификатор встречной заявки.По-умолчанию null.Если покупаем с рынка, то здесь будет идентификатор заявки для которой мы создаём встречную.
     *
     * @var integer
     */
    public $oppositeRequestId = null;

    /**
     * Имя плантации
     *
     * @var string
     */
    public $farm_name;

    /**
     * Имя сорта
     *
     * @var string
     */
    public $sort_name;

    /**
     * Оставить заявки только от тех, с кем у меня отношения
     * @var boolean
     */
    public $onlyTrusted;

    /**
     * Скрыть конкурентов (не выводить заявки от той роли, что и моя), но мои показать
     * @var boolean
     */
    public $hideCompetitors;

    /**
     * Геттер для приватного свойства $old_amount
     */
    public function getOld_amount() {
        return $this->old_amount;
    }

    /**
     * Геттер для приватного свойства $old_deal
     */
    public function getOld_deal() {
        return $this->old_deal;
    }

    /**
     * Возвращает ссылку на объект модели
     *
     * @return Requests
     */
    public static function model($className = __CLASS__) {
        return parent::model($className);
    }

    /**
     * Определяет имя таблицы, в которой хранятся запросы
     *
     * @return string
     */
    public function tableName() {
        return 'requests';
    }

    /**
     * Определяет правила валидации входящих данных для определения значения атрибутов модели.
     *
     * @return array
     */
    public function rules() {
        return array(
            array(
                'users_id, plantations_id, types_id, sorts_id, boxes_amount, exp_time, exp_date, status, uniq_number, aport_delivery, order_id',
                'required'
            ),
            array(
                'order_id, size_mask, submarks_id',
                'numerical',
            //'integerOnly' => true
            ),
            /*array (
                'size_50, size_60, size_70, size_80, size_90, size_100, size_100p, size_std, size_fncy, size_sel',
                'numerical'
            ),*/
            array(
                'users_id, plantations_id, types_id, sorts_id, boxes_amount, status, uniq_number, order_type',
                'length',
                'max' => 15
            ),
            array(
                'update_date',
                'safe'
            ),
            // The following rule is used by search().
            // Please remove those attributes that should not be searched.
            array(
                'id, users_id, plantations_id, types_id, sorts_id, boxes_amount, exp_time, exp_date, status, create_date, update_date, uniq_number, aport_delivery, order_type, order_id, size_50, size_60, size_70, size_80, size_90, size_100, size_100p, size_std, size_fncy, size_sel, size_mask, submarks_id',
                'safe',
                'on' => 'search'
            )
        );
    }

    /**
     * Определяет связи с другими моделями в системе
     *
     * @return array
     */
    public function relations() {
        return array(
            'order' => array(
                self::BELONGS_TO,
                'Orders',
                'order_id'
            ),
            'sorts' => array(
                // связь с моделью {@link Sizes}
                self::BELONGS_TO,
                'Sorts',
                'sorts_id'
            ),
            'types' => array(
                // связь с моделью {@link Types}
                self::BELONGS_TO,
                'Types',
                'types_id'
            ),
            'plantation' => array(
                // связь с моделью {@link Plantation}
                self::BELONGS_TO,
                'Plantations',
                'plantations_id'
            ),
            'sizemask' => array(
                // связь с моделью {@link Sizes}
                self::BELONGS_TO,
                'Sizes',
                'size_mask'
            ),
            'sumboxes' => array(
                self::STAT,
                'Requests',
                'boxes_amount',
            //'select' => ' SUM(boxes_amount) '
            ),
            'requests_stats' => array(
                // связь с моделью {@link RequestsStats}
                self::BELONGS_TO,
                'RequestsStats',
                'plantations_id, types_id, sorts_id',
            ),
            'users' => array(
                // связь с моделью {@link RequestsStats}
                self::BELONGS_TO,
                'Users',
                'users_id',
            ),
            'agents' => array(
                // связь с моделью {@link RequestsStats}
                self::BELONGS_TO,
                'Users',
                'agent_id',
            ),
            'submarks' => array(
                // связь с моделью {@link RequestsStats}
                self::BELONGS_TO,
                'Submarks',
                'submarks_id',
            ),
        );
    }

    /**
     * Определяет имена атрибутов модели
     *
     * @return array
     */
    public function attributeLabels() {
        return array(
            'id' => 'ID',
            'users_id' => Yii::t('tableItems', 'Users'),
            'plantations_id' => Yii::t('tableItems', 'Plantation'),
            'farm_name' => Yii::t('tableItems', 'Plantation'),
            'plantation.name' => Yii::t('tableItems', 'Plantation'),
            'types_id' => Yii::t('tableItems', 'Type'),
            'sorts_id' => Yii::t('tableItems', 'Sort'),
            'sorts.name' => Yii::t('tableItems', 'Sort'),
            'sort_name' => Yii::t('tableItems', 'Sort'),
            'boxes_amount' => Yii::t('tableItems', 'HB'),
            'exp_time' => Yii::t('requests', 'Time to exp'),
            'exp_date' => Yii::t('requests', 'Exp Date'),
            'status' => Yii::t('tableItems', 'Status'),
            'create_date' => Yii::t('tableItems', 'Create Date'),
            'update_date' => Yii::t('tableItems', 'Update Date'),
            'uniq_number' => Yii::t('requests', 'Offer id'),
            'aport_delivery' => Yii::t('requests', 'Aport Delivery'),
            'order_type' => Yii::t('requests', 'Order Type'),
            'order_id' => Yii::t('requests', 'Order Group'),
            'size_50' => Yii::t('tableItems', '50'),
            'size_60' => Yii::t('tableItems', '60'),
            'size_70' => Yii::t('tableItems', '70'),
            'size_80' => Yii::t('tableItems', '80'),
            'size_90' => Yii::t('tableItems', '90'),
            'size_100' => Yii::t('tableItems', '100'),
            'size_100p' => Yii::t('tableItems', '100+'),
            'size_std' => Yii::t('tableItems', 'Std'),
            'size_fncy' => Yii::t('tableItems', 'Fncy'),
            'size_sel' => Yii::t('tableItems', 'Sel'),
            'size_mask' => Yii::t('tableItems', 'Size Mask'),
            'submarks_id' => Yii::t('requests', 'Submark'),
        );
    }

    /**
     * Организует поиск и возвращает список объектов моделей, соответствующих критериям поиска.
     *
     * @return CActiveDataProvider
     */
    public function search($with = array(), $order = '') {
        $criteria = new CDbCriteria();

        $criteria->compare('id', $this->id);
        $criteria->compare('users_id', $this->users_id);
        $criteria->compare('plantations_id', $this->plantations_id, false);
        $criteria->compare('types_id', $this->types_id, false);
        $criteria->compare('sorts_id', $this->sorts_id, false);
        $criteria->compare('boxes_amount', $this->boxes_amount);

        $criteria->compare('exp_time', $this->exp_time);
        $criteria->compare('exp_date', $this->exp_date);
        if (is_array( $this->status))
            $criteria->addInCondition('t.status', $this->status);
        else
            $criteria->addSearchCondition('t.status', $this->status);

        $criteria->compare('create_date', $this->create_date, true);
        $criteria->compare('update_date', $this->update_date, true);
        $criteria->compare('uniq_number', $this->uniq_number, true);
        $criteria->compare('aport_delivery', $this->aport_delivery, true);
        $criteria->compare('order_type', $this->order_type);
        $criteria->compare('order_id', $this->order_id);

        $criteria->compare('size_50', $this->size_50);
        $criteria->compare('size_60', $this->size_60);
        $criteria->compare('size_70', $this->size_70);
        $criteria->compare('size_80', $this->size_80);
        $criteria->compare('size_90', $this->size_90);
        $criteria->compare('size_100', $this->size_100);
        $criteria->compare('size_100p', $this->size_100p);

        $criteria->compare('size_mask', $this->size_mask);
        $criteria->compare('submarks_id', $this->submarks_id);

        if (!empty($order)) {
            $criteria->order = $order;
        }

        $aWith = $this->relations();
        foreach ($with as $condition) {
            if (in_array($condition, $aWith)) {
                $criteria->with[$condition] = array(
                    'select' => 'name'
                );
            }
        }

        if ($this->hideCompetitors) {
            // Скрыть конкурентов (не выводить заявки от той роли, что и моя), но мои показать
            if (Yii::app()->user->role == Users::ROLE_BUYER) {
                $criteria->addCondition('(`order_type`="' . self::TYPE_SELLER. '" OR `users_id`=' . Yii::app()->user->id . ')', 'AND');
            }
            elseif (Yii::app()->user->role == Users::ROLE_SELLER) {
                $criteria->addCondition('(`order_type`="' . self::TYPE_BUYER. '" OR `users_id`=' . Yii::app()->user->id . ')', 'AND');
            }
        }

        if ($this->onlyTrusted) {
            $criteria->with['users'] = array(
                'join' => 'LEFT JOIN relations r1 ON users.id = r1.u_1_id LEFT JOIN relations r2 ON users.id = r2.u_2_id',
                'condition' => "(r1.type =  'b2s' AND r1.status =  'on' AND r1.u_2_id =" . Yii::app()->user->id . ") OR (r2.type =  'b2s' AND r2.status =  'on' AND r2.u_1_id =" . Yii::app()->user->id . ")"
            );
        }
        $criteria->limit = $this->limit;
        $criteria->offset = $this->offset;

        return new CActiveDataProvider(get_class($this), array(
            'criteria' => $criteria,
            'pagination' => criteriaToPaginator::convert($criteria)
        ));
    }

    /**
     * Список полей, которые нужно привести к/из UTC
     * afterFind - при выводе все timestamp, а также некоторые time
     * преобразуются из UTC (в которой хранятся), к пользовательской timezone
     * beforeSave - перед сохранением некоторые time преобразуются из
     * пользовательской timezone в UTC
     * по-умолчанию поля типа time закоментированы
     * @return array
     */
    public function convertDateTime() {
        return array(
            'afterFind' => array(
                //'exp_time',
                'exp_date',
                'create_date',
                'update_date',
            ),
            'beforeSave' => array(
            //'exp_time',
            ),
        );
    }

    /**
     * Возвращает список плантаций для текущего пользователя
     *
     * @return array
     */
    public function getPlantationsList() {
        $oPlantation = new Plantations();
        return CHtml::listData($oPlantation->getPlantationsForUser(Yii::app()->user->id)->getData(), 'id', 'name');
    }

    /**
     * Возвращает список заказов текущего пользователя
     *
     * @return array
     */
    public function getOrdersList() {
        $oOrders = new Orders();
        return CHtml::listData($oOrders->getOrdersForUser(Yii::app()->user->id)->getData(), 'id', 'name');
    }

    /**
     * Возвращает список типов цветов по указанной плантации
     *
     * @param integer $iPlantation Идентификатор плантации
     * @return array
     */
    public function getTypesListByPlantation($iPlantation = -1) {
        if (empty($iPlantation) or $iPlantation == -1)
            return array();
        return PriceList::model()->getTypeForPlantation($iPlantation);
    }

    /**
     * Строит список сортов по идентификатору плантации и типу
     *
     * @param integer $iType Тип, для которого нужно построить список
     * @param integer $iPlantation Идентификатор плантации
     * @return array
     */
    public function getSortsListByTypesAndPlantation($iType, $iPlantation) {
        $aReturn = PriceList::model()->getSortForPlantation($iPlantation, $iType);
        if (!empty($aReturn))
            return $aReturn['names'];
        else
            return array();
    }

    /**
     * Метод вызывается перед валидацией данных и подготавливает данные запроса к валидации
     *
     * @param CModelEvent $oEvent
     */
    public function onBeforeValidate($oEvent) {
        if (!isset($this->order_id)) {
            throw new CHttpException(400, 'Invalid request. Please do not repeat this request again.');
        }

        $oOrder = new Orders();
        $aOrder = $oOrder->findAllByPk($this->order_id);

        if (count($aOrder) != 1) {
            throw new CHttpException(400, 'Invalid request. Please do not repeat this request again.');
        }

        $oExpOrderDate = DateTime::createFromFormat(MYSQL_DATETIME_FORMAT, $aOrder[0]->exp_date);
        // рассчитываем протухание
        $oExpReqDate = new DateTime();
        $aExpTime = explode(':', $this->exp_time);
        if (!isset($aExpTime[1])) {
            $aExpTime[1] = 0; // можем ввести только часы
        }
        $oExpReqDate = $oExpReqDate->add(new DateInterval('PT' . $aExpTime[0] . 'H' . $aExpTime[1] . 'M'));

        if ($oExpReqDate > $oExpOrderDate) {
            $oExpReqDate = $oExpOrderDate;
        }
        $this->exp_date = $oExpReqDate->format(MYSQL_DATETIME_FORMAT);
        $this->aport_delivery = $aOrder[0]->air_date;
        $this->uniq_number = $this->generateUniqNumber();

        if (!($this->size_50 instanceof Money))
            $this->size_50 = new Money($this->size_50, Currency::USD_CURRENCY, false);
        if (!($this->size_60 instanceof Money))
            $this->size_60 = new Money($this->size_60, Currency::USD_CURRENCY, false);
        if (!($this->size_70 instanceof Money))
            $this->size_70 = new Money($this->size_70, Currency::USD_CURRENCY, false);
        if (!($this->size_80 instanceof Money))
            $this->size_80 = new Money($this->size_80, Currency::USD_CURRENCY, false);
        if (!($this->size_90 instanceof Money))
            $this->size_90 = new Money($this->size_90, Currency::USD_CURRENCY, false);
        if (!($this->size_100 instanceof Money))
            $this->size_100 = new Money($this->size_100, Currency::USD_CURRENCY, false);
        if (!($this->size_100p instanceof Money))
            $this->size_100p = new Money($this->size_100p, Currency::USD_CURRENCY, false);

        return parent::onBeforeValidate($oEvent);
    }

    /**
     * Сохраняет запрос и отправляет (если необходимо) информацию о нем в очередь.
     * Внимание! Выполнение метода должно проходить в рамках транзакции!!!
     *
     * Необходимость:
     *  - запрос доступен для покупки
     *  - запрос был от продавца,
     *  - в нем еще есть ящики для покупки
     *  - если заявка новая - для нее будет поставлена задача на протухание
     */
    public function saveAndSendToQueue() {
        $bReturn = false;
        $q = new QueueTask();
        $isNew = false;

        if ($this->getIsNewRecord()) {
            $isNew = true;
        }

        if (!($id = parent::save())) {
            $bReturn = false;
        }
        else {
            if ($isNew) {
                $date = new DateTime($this->exp_date);
                if ($q->add('ProcessingRequestExpired', array(
                    $this->id
                ), $date->getTimestamp(), //date(MYSQL_DATETIME_FORMAT, $this->exp_date),
                QueueTask::LEVEL_HIGHT)) {
                    $bReturn = true;
                }
            }
            if ($this->status === self::STATUS_ON_MARKET and $this->boxes_amount > 0 and $this->order_type == self::TYPE_SELLER) {

                if ($q->add('ProcessingDeal', array(
                    $this->id
                ), time(), QueueTask::LEVEL_IMMEDIATE)) {
                    $bReturn = true;
                }
                else {
                    $bReturn = false;
                }
            }
            else {
                $bReturn = true;
            }
        }
        unset($q);

        return $bReturn;
    }

    /**
     * Возвращает тип запроса (по сути - кто его сделал)
     *
     * @param string $sUserRole Роль пользователя
     * @return string
     * @exception Для ролей, отличных от покупателя или продавца будет брошено исключение
     */
    public function getOrderType($sUserRole) {
        if ($sUserRole == Users::ROLE_BUYER) {
            return self::TYPE_BUYER;
        }
        elseif ($sUserRole == Users::ROLE_SELLER) {
            return self::TYPE_SELLER;
        }
        else {
            throw new CHttpException(400, 'Invalid request. Please do not repeat this request again.');
        }
    }

    /**
     * Генерирует уникальный идентификатор запроса
     * @link http://redmine.biflorica.com/projects/bif/wiki/Идентификатор_сделки
     *
     * @return string
     */
    private function generateUniqNumber() {
        $oSequence = new Sequence();
        return $oSequence->getUniqNumber(Yii::app()->user->role);
    }

    /**
     * Возвращает true, если заявка выполнена, иначе - false
     *
     * @return boolean
     */
    public function isCompleted() {
        return ($this->boxes_amount == 0);
    }

    /**
     * Выставляет или снимает (в зависимости от текущего статуса) заявку на маркет.
     * Если заявка просрочена или уже куплена будет возвращено false
     *
     * @return boolean
     */
    public function moveToOrFromMarketPlace() {
        if ($this->status !== self::STATUS_OFF_MARKET and $this->status !== self::STATUS_ON_MARKET) {
            return false;
        }

        $order = Orders::model()->findByPk($this->order_id);
        if (is_null($order)) {
            return false;
        }
        if ($this->status == self::STATUS_OFF_MARKET) {
            $this->status = self::STATUS_ON_MARKET;
            $iDeltaBoxes = array(
                $this->boxes_amount,
                -$this->boxes_amount
            );
        }
        elseif ($this->status == self::STATUS_ON_MARKET) {
            $this->status = self::STATUS_OFF_MARKET;
            $iDeltaBoxes = array(
                -$this->boxes_amount,
                $this->boxes_amount
            );
        }

        $bReturn = $this->saveAndSendToQueue();

        if ($bReturn) {
            $order->changeCountMarketRequests($iDeltaBoxes[0], $iDeltaBoxes[1]);
        }

        return $bReturn;
    }

    /**
     * Клонирует объект
     *
     * @param Requests $oFrom Объект, от которого происходит клонирование
     */
    public function cloneFrom(Requests $oFrom) {
        $aAtributes = $oFrom->getAttributes();

        if ($aAtributes['boxes_amount']==0)
        {
            $aAtributes['boxes_amount'] = $aAtributes['boxes_deal'];
            $aAtributes['boxes_deal'] = 0;
        }
        $aAtributes['uniq_number'] = $this->generateUniqNumber();
        $aAtributes['update_date'] = null;
        $aAtributes['status'] = self::STATUS_OFF_MARKET;
        unset($aAtributes['create_date']);
        unset($aAtributes['id']);

        $this->setAttributes($aAtributes, false);

        return $this->saveAndSendToQueue();
    }

    /**
     * Добавляет коробки обратно в заявку (после отмены сделки)
     *
     * @param integer $boxes_amount количество коробок
     * @param array $dealData Массив с исходным цена для сделки. Пример array('size_50'=>'1.56', и т.д. для всех размеров)
     */
    public function addCanceledBoxesFromDeal($boxes_amount, $dealData) {
        $currentTime = new DateTime("now");

        // Проверим изменились ли исходные данные заявки
        $isReqChanged = false;
        foreach (FlowersSizes::getAllSize() as $size) {
            $size = 'size_' . $size;
            if (!empty($this->$size) AND !empty($dealData->$size) AND $this->$size != $dealData->$size) {
                $isReqChanged = true;
                break;
            }
        }

        if ($isReqChanged) {
            // исходная заявка изменилась
            // Значит создаём новую заявку
            $newReq = clone $this;
            $newReq->boxes_amount = $this->boxes_amount + $boxes_amount;
            $newReq->boxes_deal = $newReq->boxes_deal - $boxes_amount;
            $newReq->status = (($newReq->order_type == Requests::TYPE_SELLER AND Yii::app()->user->role == Users::ROLE_BUYER) OR ($newReq->order_type == Requests::TYPE_BUYER AND Yii::app()->user->role == Users::ROLE_SELLER)) ? Requests::STATUS_ON_MARKET : Requests::STATUS_OFF_MARKET;
            $newReq->create_date = $currentTime->format(MYSQL_DATETIME_FORMAT);
            foreach (FlowersSizes::getAllSize() as $size) {
                $newReq->{'size_' . $size} = $dealData->{'size_' . $size};
            }
            $newReq->save();
        }
        else {
            // исходная заявка не изменилась
            // Значит просто добавим ящики
            $this->boxes_amount = $this->boxes_amount + $boxes_amount;
            $this->boxes_deal = $this->boxes_deal - $boxes_amount;
            $this->status = (($this->order_type == Requests::TYPE_SELLER AND Yii::app()->user->role == Users::ROLE_BUYER) OR ($this->order_type == Requests::TYPE_BUYER AND Yii::app()->user->role == Users::ROLE_SELLER)) ? Requests::STATUS_ON_MARKET : Requests::STATUS_OFF_MARKET;
            $this->update_date = $currentTime->format(MYSQL_DATETIME_FORMAT);
            $this->save();
        }
    }

    /**
     * Вызывается перед сохранение данных, которые и приводятся к правильному виду
     *
     * @todo Вынести проверку заказа в валидацию
     * @return boolean
     */
    public function beforeSave() {

        $this->size_50 = $this->size_50->getValue();
        $this->size_60 = $this->size_60->getValue();
        $this->size_70 = $this->size_70->getValue();
        $this->size_80 = $this->size_80->getValue();
        $this->size_90 = $this->size_90->getValue();
        $this->size_100 = $this->size_100->getValue();
        $this->size_100p = $this->size_100p->getValue();
        $this->size_mask = $this->countSizeMask();

        // Проверка на то, что заявка попадает в заказ того пользователя, который с ней работает
        if (!Orders::model()->isBelongsToUser($this->order_id, $this->users_id)) {
            $this->addError('order_id', 'Access denied');
            return false;
        }

        // пересчитать кол-во ящиков для всего заказа
        $oOrder = Orders::model()->findByPk($this->order_id);
        /* @var $oOrder Orders */
        if (!$this->getIsNewRecord()) { // изменение существующей заявки
            Yii::app()->getComponent('ChangeBoxesCountHelper')->prepareBoxesChange($this);
        }
        else {
            // создание новой заявки
            if ($this->status == self::STATUS_ON_MARKET) {
                $result = $oOrder->changeCountMarketRequests($this->boxes_amount, null);
                if (!$result)
                    return false;
            }
            elseif ($this->status == self::STATUS_OFF_MARKET) {
                $result = $oOrder->changeCountMarketRequests(null, $this->boxes_amount);
                if (!$result)
                    return false;
            }
        }

        return parent::beforeSave();
    }

    /**
     * Вызывается перед удалением заявки.
     * Используется для изменения кол-ва ящиков в заказе
     *
     */
    public function beforeDelete() {
        parent::beforeDelete();

        $this->boxes_amount = 0;
        $this->boxes_deal = 0;
        Yii::app()->getComponent('ChangeBoxesCountHelper')->prepareBoxesChange($this);

        return true;
    }

    /**
     * Вычисляет size_mark
     * @return integer
     */
    private function countSizeMask() {
        $sizeMask = 0;
        if ($this->size_50 > 0) {
            $sizeMask += 1;
        }
        if ($this->size_60 > 0) {
            $sizeMask += 2;
        }
        if ($this->size_70 > 0) {
            $sizeMask += 4;
        }
        if ($this->size_80 > 0) {
            $sizeMask += 8;
        }
        if ($this->size_90 > 0) {
            $sizeMask += 16;
        }
        if ($this->size_100 > 0) {
            $sizeMask += 32;
        }
        if ($this->size_100p > 0) {
            $sizeMask += 64;
        }
        return $sizeMask;
    }

    /**
     * Высчитывает размер цветов в заявке по ее маске
     *
     */
    public function getSizeByMask() {
        $size = array();
        $this->getMaxSize($this->size_mask, 64, $size);

        return $size;
    }

    /**
     * Высчитывает размер цветов в заявке по ее маске
     * Используется для отделения основного метода от рекурсивной реализации
     */
    private function getMaxSize($size, $factor, &$aSize=array()) {
        if ($factor<1)
            return;
        if ($size>$factor) {
            $aSize[] = $factor;
            $this->getMaxSize($size-$factor, $factor/2, $aSize);
        }
        else {
            $this->getMaxSize($size, $factor/2, $aSize);
        }
    }

    /**
     * Организует сохранение запроса
     *
     * @param boolean $runValidation
     * @param array $attributes
     */
    public function save($runValidation = true, $attributes = null) {
        if (empty($this->oppositeRequestId) and $this->status == self::STATUS_ON_MARKET and $this->order_type == self::TYPE_SELLER and $this->isNewRecord) {
            return $this->saveAndSendToQueue();
        }
        else {
            return parent::save($runValidation, $attributes);
        }
    }

    /**
     * Подготавливает данные для работы - приводит типы и т.д.
     */
    function afterSave() {
        $return = parent::afterSave();
        if ($return === false)
            return false;

        $this->size_50 = new Money($this->size_50);
        $this->size_60 = new Money($this->size_60);
        $this->size_70 = new Money($this->size_70);
        $this->size_80 = new Money($this->size_80);
        $this->size_90 = new Money($this->size_90);
        $this->size_100 = new Money($this->size_100);
        $this->size_100p = new Money($this->size_100p);

        RequestsStats::model()->updateByRequest($this);

        if ($this->order_type == Requests::TYPE_SELLER) {
            RequestsDaily::model()->updateByRequest($this);
        }

        return $return;
    }

    /**
     * Метод вызывается после поиска данных и подготавливает данные
     *
     */
    public function afterFind() {
        $return = parent::afterFind();

        $this->size_50 = new Money($this->size_50);
        $this->size_60 = new Money($this->size_60);
        $this->size_70 = new Money($this->size_70);
        $this->size_80 = new Money($this->size_80);
        $this->size_90 = new Money($this->size_90);
        $this->size_100 = new Money($this->size_100);
        $this->size_100p = new Money($this->size_100p);

        // закешили данные, при сохранении будем смотреть что поменялось
        $this->old_amount = $this->boxes_amount;
        $this->old_status = $this->status;
        $this->old_deal = $this->boxes_deal;
        $this->old_order = $this->order_id;

        return $return;
    }

    /**
     * Проверяет название размера
     *
     * @param string $name
     * @return boolean
     */
    public function checkSize($name) {
        return (isset($this->{$name}) && $this->{$name}->getValue() > 0);
    }

    /**
     * Получается статистические данные по конкретному заказу
     *
     * @param array $iOrderId номер заказ
     * @return array
     */
    public function buildStats($aOrderId) {
        $rows = Yii::app()->db->createCommand("SELECT COUNT(*) as count, status FROM `requests` WHERE order_id IN(" . implode(',',$aOrderId) . ") GROUP BY `status`")->queryAll();
        foreach ($rows as $row) {
            $result[$row['status']] = $row['count'];
        }
        return array(
            'on_market' => (isset($result['on_market'])) ? $result['on_market'] : 0,
            'off_market' => (isset($result['off_market'])) ? $result['off_market'] : 0,
            'deals' => (isset($result['executed'])) ? $result['executed'] : 0,
            'cancel' => (isset($result['expired'])) ? $result['expired'] : 0,
        );
    }

    public function isExpired()
    {
        return $this->status==self::STATUS_EXPIRED;
    }

    public function isOffMarket()
    {
        return $this->status==self::STATUS_OFF_MARKET;
    }
}