[ 'class' => LanguageBehavior::className(), ], 'images' => [ 'class' => SaveMultipleFileBehavior::className(), 'name' => 'imagesUpload', 'directory' => 'products', 'column' => 'image', 'links' => [ 'product_id' => 'product_id', 'id' => 'product_variant_id', ], 'model' => ProductImage::className(), ], 'multipleImage' => [ 'class' => MultipleImgBehavior::className(), 'links' => [ 'product_variant_id' => 'id', ], 'model' => ProductImage::className(), 'config' => [ 'caption' => 'image', 'delete_action' => 'variant/delete-image', 'id' => 'id', ], ], ]; } /** * @inheritdoc */ public function rules() { return [ [ [ 'product_id', 'product_unit_id', ], 'required', ], [ [ 'product_id', 'product_unit_id', ], 'integer', ], [ [ 'price', 'price_old', 'stock', ], 'number', ], [ [ 'sku', ], 'string', 'max' => 255, ], [ [ 'options', ], 'safe', ], [ [ 'product_unit_id' ], 'exist', 'skipOnError' => true, 'targetClass' => ProductUnit::className(), 'targetAttribute' => [ 'product_unit_id' => 'id' ], ], [ [ 'product_id' ], 'exist', 'skipOnError' => true, 'targetClass' => Product::className(), 'targetAttribute' => [ 'product_id' => 'id' ], ], ]; } /** * @inheritdoc */ public function attributeLabels() { return [ 'id' => Yii::t('product', 'Product Variant ID'), 'product_id' => Yii::t('product', 'Product ID'), 'sku' => Yii::t('product', 'Sku'), 'price' => Yii::t('product', 'Price'), 'price_old' => Yii::t('product', 'Price Old'), 'stock' => Yii::t('product', 'Stock'), 'product_unit_id' => Yii::t('product', 'Product Unit ID'), 'stock_caption' => Yii::t('product', 'Stock'), 'image' => Yii::t('product', 'Image'), 'images' => Yii::t('product', 'Images'), ]; } /** * @return \yii\db\ActiveQuery */ public function getProductUnit() { return $this->hasOne(ProductUnit::className(), [ 'id' => 'product_unit_id' ]); } /** * @return \yii\db\ActiveQuery */ public function getProduct() { return $this->hasOne(Product::className(), [ 'id' => 'product_id' ]); } /** * @return \yii\db\ActiveQuery */ public function getProductStocks() { return $this->hasMany(ProductStock::className(), [ 'product_variant_id' => 'id' ]); } /** * Get qunatity for current ProductVariant * If $recalculate set to true will recalculate stock via product_stock table * * @param bool $recalculate * * @return int */ public function getQuantity(bool $recalculate = false): int { if (!$recalculate) { return $this->stock; } else { $quantity = $this->getProductStocks() ->sum('quantity'); if (empty( $quantity )) { $this->stock = 0; } else { $this->stock = (int) $quantity; } $this->save(false, [ 'stock' ]); return $this->stock; } } /** * Get ProductStocks query woth preloaded Stocks for current ProductVariant * **Used in dynamic fields in product variant form** * * @return ActiveQuery */ public function getVariantStocks() { return $this->getProductStocks() ->joinWith('stock'); } /** * @return ActiveQuery */ public function getStocks() { return $this->hasMany(Stock::className(), [ 'id' => 'stock_id' ]) ->via('productStocks'); } /** * @return ActiveQuery */ public function getOptions() { return $this->hasMany(TaxOption::className(), [ 'id' => 'option_id' ]) ->viaTable('product_variant_option', [ 'product_variant_id' => 'id' ]); } /** * Get one variant's option whith needed conditions, or random if condition is empty * * @param array $conditions * * @return ActiveQuery */ public function getOption(array $conditions = []) { $query = $this->hasOne(TaxOption::className(), [ 'id' => 'option_id' ]) ->viaTable('product_variant_option', [ 'product_variant_id' => 'id' ]); foreach ($conditions as $condition) { if (!empty($condition) && is_array($condition)) { $query->andFilterWhere($condition); } } return $query; } /** * Get TaxOptions with preloaded TaxGroups for current ProductVariant * * @return ActiveQuery */ public function getFilters() { return $this->getOptions() ->joinWith('taxGroup.lang') ->joinWith('lang'); } /** * Get Product title concanated with current ProductVariant title * * @return string */ public function getFullname(): string { return $this->product->lang->title . ' ' . $this->lang->title; } /** * Set Options to override previous * * @param int[] $values */ public function setOptions($values) { $this->options = $values; } /** * Get all TaxGroups for current ProductVariant filled with $customOptions that satisfy current ProductVariant * * @param array $conditions * * @return \artweb\artbox\ecommerce\models\TaxGroup[] */ public function getProperties(array $conditions = []) { $groups = $options = []; foreach ($this->getOptions() ->with('lang') ->all() as $option) { /** * @var TaxOption $option */ $options[ $option->tax_group_id ][] = $option; } $query = TaxGroup::find() ->where([ 'tax_group.id' => array_keys($options) ]) ->orderBy([ 'sort' => SORT_ASC ]) ->with('lang'); if (!empty($conditions)) { foreach ($conditions as $condition) { $query->andFilterWhere($condition); } } foreach ( $query->all() as $group) { /** * @var TaxGroup $group */ if (!empty( $options[ $group->id ] )) { $group->customOptions = $options[ $group->id ]; $groups[] = $group; } } return $groups; } /** * Set stocks to override existing in product_stock table * * @param mixed $stocks */ public function setStocks($stocks) { $this->stocks = (array) $stocks; } /** * @return ActiveQuery */ public function getCategory() { return $this->hasOne(Category::className(), [ 'id' => 'category_id' ]) ->viaTable('product_category', [ 'product_id' => 'product_id' ]); } /** * @return ActiveQuery */ public function getCategories() { return $this->hasMany(Category::className(), [ 'id' => 'category_id' ]) ->viaTable('product_category', [ 'product_id' => 'product_id' ]); } /** * Get TaxGroups query for current ProductVariant according to level * * 0 - Product Tax Groups * * 1 - ProductVariant Tax Groups * * @param int $level * * @return ActiveQuery * @throws InvalidParamException */ public function getTaxGroupsByLevel(int $level = 0) { return $this->product->getTaxGroupsByLevel($level); } public function afterSave($insert, $changedAttributes) { parent::afterSave($insert, $changedAttributes); if (!empty( $this->options )) { $options = TaxOption::findAll($this->options); $this->unlinkAll('options', true); foreach ($options as $option) { $this->link('options', $option); } } if (!empty( $this->stocks )) { ProductStock::deleteAll([ 'product_variant_id' => $this->id ]); foreach ($this->stocks as $id => $quantity) { /** * @var ProductStock $productStock */ $productStock = ProductStock::find() ->where( [ 'product_variant_id' => $this->id, 'stock_id' => $id, ] ) ->one(); $productStock->quantity = $quantity; $productStock->save(); } } } }