diff --git a/common/config/main.php b/common/config/main.php
index 2366da3..025fb4a 100644
--- a/common/config/main.php
+++ b/common/config/main.php
@@ -100,6 +100,27 @@ return [
'model' => '\common\modules\product\models\ProductCategory',
]
],
+ 'product_option' => [
+ 'name' => Yii::t('product', 'Properties'),
+ 'field' => 'options',
+ 'entity1' => [
+ 'model' => '\common\modules\product\models\Product',
+ 'label' => 'Product',
+ 'listField' => 'fullname',
+ 'key' => 'product_id',
+ 'linked_key' => 'product_id',
+ ],
+ 'entity2' => [
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'label' => 'Option',
+ 'listField' => 'ValueeRenderFlash',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'option_id',
+ ],
+ 'via' => [
+ 'model' => 'common\modules\product\models\ProductOption',
+ ]
+ ],
'tax_group_to_category' => [
'name' => Yii::t('product', 'Характеристики по категориям'),
'field' => 'group_to_category',
diff --git a/common/modules/product/controllers/ManageController.php b/common/modules/product/controllers/ManageController.php
index 8ecad53..f7bb879 100644
--- a/common/modules/product/controllers/ManageController.php
+++ b/common/modules/product/controllers/ManageController.php
@@ -33,6 +33,24 @@ class ManageController extends Controller
}
public function actionTest() {
+ return;
+ foreach(Product::find()->all() as $product) {
+ if (!$product->variant) {
+ $product->save();
+ $variantModel = new ProductVariant();
+ $variantModel->product_id = $product->product_id;
+ $variantModel->name = 'test-'. uniqid();
+ $variantModel->sku = $variantModel->name;
+ $variantModel->price = rand(5, 200000);
+ $variantModel->price_old = rand(0, 5) > 3 ? $variantModel->price* (1+rand(0, 10) / 10) : $variantModel->price;
+ $variantModel->product_unit_id = rand(1, 5);
+ $variantModel->stock = rand(0, 50);
+ $variantModel->save();
+ }
+ }
+
+ return;
+
$categories = Category::find()->where(['depth' => 2])->all();
$cats_ids = [];
foreach($categories as $cat) {
@@ -122,8 +140,11 @@ class ManageController extends Controller
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->product_id]);
} else {
+ $groups = $model->category->getTaxGroups();
+
return $this->render('update', [
'model' => $model,
+ 'groups' => $groups,
]);
}
}
diff --git a/common/modules/product/models/Product.php b/common/modules/product/models/Product.php
index e55b350..e8d11a3 100644
--- a/common/modules/product/models/Product.php
+++ b/common/modules/product/models/Product.php
@@ -2,6 +2,7 @@
namespace common\modules\product\models;
+use common\behaviors\Slug;
use common\modules\rubrication\models\TaxOption;
use Yii;
use common\modules\relation\relationBehavior;
@@ -23,6 +24,9 @@ class Product extends \yii\db\ActiveRecord
{
/** @var array $variants */
public $_variants = [];
+
+ /** @var array $_images */
+ public $imagesUpload = [];
/**
* @inheritdoc
*/
@@ -32,9 +36,16 @@ class Product extends \yii\db\ActiveRecord
[
'class' => relationBehavior::className(),
'relations' => [
- 'product_categories' => 'entity1' // Product category
+ 'product_categories' => 'entity1', // Product category
+ 'product_option' => 'entity1' // Product category
]
],
+ [
+ 'class' => Slug::className(),
+ 'in_attribute' => 'name',
+ 'out_attribute' => 'alias',
+ 'translit' => true
+ ]
];
}
@@ -54,7 +65,10 @@ class Product extends \yii\db\ActiveRecord
return [
[['brand_id'], 'integer'],
[['name'], 'string', 'max' => 150],
- [['categories', 'variants'], 'safe'],
+ [['alias'], 'string', 'max' => 250],
+ [['categories', 'variants', 'options'], 'safe'],
+ [['imagesUpload'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg, gif'],
+ [['description', 'video'], 'safe'],
// [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => Product::className(), 'targetAttribute' => ['product_id' => 'product_id']],
];
}
@@ -70,6 +84,8 @@ class Product extends \yii\db\ActiveRecord
'brand_id' => Yii::t('product', 'Brand'),
'categories' => Yii::t('product', 'Categories'), // relation behavior field
'category' => Yii::t('product', 'Category'), // relation behavior field
+ 'image' => Yii::t('product', 'Image'),
+ 'images' => Yii::t('product', 'Images'),
];
}
@@ -97,6 +113,10 @@ class Product extends \yii\db\ActiveRecord
return $this->hasMany(ProductImage::className(), ['product_id' => 'product_id']);
}
+ public function setImages($images) {
+ $this->_images = $images;
+ }
+
/**
* @return \yii\db\ActiveQuery
*/
@@ -146,6 +166,10 @@ class Product extends \yii\db\ActiveRecord
return $categories->one();
}
+ public function getOptions() {
+ return $this->getRelations('product_option');
+ }
+
/**
* @inheritdoc
* @return ProductQuery the active query used by this AR class.
@@ -159,11 +183,18 @@ class Product extends \yii\db\ActiveRecord
{
parent::afterSave($insert, $changedAttributes);
+ foreach($this->imagesUpload as $image) {
+ $image->saveAs('/images/items/' . $image->baseName .'_'. uniqid() . '.' . $image->extension);
+ }
+
$todel = [];
foreach ($this->variants ? : [] as $_variant) {
$todel[$_variant->product_variant_id] = $_variant->product_variant_id;
}
foreach ($this->_variants as $_variant) {
+ if (!is_array($_variant)) {
+ return;
+ }
if (!empty($_variant['product_variant_id'])) {
unset($todel[$_variant['product_variant_id']]);
$model = ProductVariant::findOne($_variant['product_variant_id']);
diff --git a/common/modules/product/models/ProductOption.php b/common/modules/product/models/ProductOption.php
new file mode 100644
index 0000000..02d1601
--- /dev/null
+++ b/common/modules/product/models/ProductOption.php
@@ -0,0 +1,65 @@
+ true, 'targetClass' => Product::className(), 'targetAttribute' => ['product_id' => 'product_id']],
+ [['option_id'], 'exist', 'skipOnError' => true, 'targetClass' => TaxOption::className(), 'targetAttribute' => ['option_id' => 'tax_option_id']],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'product_id' => Yii::t('product', 'Product ID'),
+ 'option_id' => Yii::t('product', 'Option ID'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getProduct()
+ {
+ return $this->hasOne(Product::className(), ['product_id' => 'product_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getOption()
+ {
+ return $this->hasOne(TaxOption::className(), ['tax_option_id' => 'option_id']);
+ }
+}
diff --git a/common/modules/product/models/ProductSearch.php b/common/modules/product/models/ProductSearch.php
index e75df3f..f59ea6a 100644
--- a/common/modules/product/models/ProductSearch.php
+++ b/common/modules/product/models/ProductSearch.php
@@ -6,6 +6,7 @@ use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use common\modules\product\models\Product;
+use yii\web\NotFoundHttpException;
/**
* ProductSearch represents the model behind the search form about `common\modules\product\models\Product`.
@@ -70,7 +71,7 @@ class ProductSearch extends Product
public static function findByAlias($alias) {
/** @var ProductQuery $query */
- $query = Category::find();
+ $query = Product::find();
$query->byAlias($alias);
if (($model = $query->one()) !== null) {
return $model;
diff --git a/common/modules/product/views/manage/_form.php b/common/modules/product/views/manage/_form.php
index 984e985..8ba47c5 100644
--- a/common/modules/product/views/manage/_form.php
+++ b/common/modules/product/views/manage/_form.php
@@ -20,6 +20,9 @@ use unclead\widgets\MultipleInputColumn;
= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
+ = $form->field($model, 'description')->widget(\mihaildev\ckeditor\CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ], ]); ?>
+ = $form->field($model, 'video')->textarea()->label('Video embeded'); ?>
+
= $form->field($model, 'brand_id')->dropDownList(
ArrayHelper::map(ProductHelper::getBrands()->all(), 'brand_id', 'name'),
[
@@ -30,21 +33,18 @@ use unclead\widgets\MultipleInputColumn;
= $form->field($model, 'categories')->dropDownList(
ArtboxTreeHelper::treeMap(ProductHelper::getCategories(), 'category_id', 'name'),
[
-// 'prompt' => Yii::t('product', 'Select category'),
'multiple' => true
]
) ?>
- field($model, 'images[]')->widget(FileInput::classname(), [
+
+
+ field($model, 'imagesUpload[]')->widget(FileInput::classname(), [
'options' => [
'accept' => 'image/*',
'multiple' => true,
],
- 'pluginOptions' => [
-// 'uploadUrl' => \yii\helpers\Url::to(['/site/file-upload']),
- ]
- ]);
- */?>
+ ]);*/?>
= $form->field($model, 'variants')->widget(MultipleInput::className(), [
'columns' => [
@@ -90,6 +90,16 @@ use unclead\widgets\MultipleInputColumn;
]);
?>
+ all() as $group) :?>
+ = $form->field($model, 'options')->checkboxList(
+ ArrayHelper::map($group->options, 'tax_option_id', 'ValueRenderFlash'),
+ [
+ 'multiple' => true,
+ 'unselect' => null,
+ ]
+ )->label($group->name);?>
+
+
= Html::submitButton($model->isNewRecord ? Yii::t('product', 'Create') : Yii::t('product', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
diff --git a/common/modules/product/views/manage/update.php b/common/modules/product/views/manage/update.php
index 9ff3851..9e6060e 100644
--- a/common/modules/product/views/manage/update.php
+++ b/common/modules/product/views/manage/update.php
@@ -18,6 +18,7 @@ $this->params['breadcrumbs'][] = Yii::t('product', 'Update');
= $this->render('_form', [
'model' => $model,
+ 'groups' => $groups,
]) ?>
diff --git a/common/modules/rubrication/helpers/RubricationHelper.php b/common/modules/rubrication/helpers/RubricationHelper.php
index 39e7cbb..34c7717 100644
--- a/common/modules/rubrication/helpers/RubricationHelper.php
+++ b/common/modules/rubrication/helpers/RubricationHelper.php
@@ -40,4 +40,12 @@ class RubricationHelper {
return $module->types;
}
+
+ public function checkboxList($items, $options = [])
+ {
+ $this->adjustLabelFor($options);
+ $this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
+
+ return $this;
+ }
}
\ No newline at end of file
diff --git a/common/modules/rubrication/models/TaxGroup.php b/common/modules/rubrication/models/TaxGroup.php
index 84e1c48..2ac7345 100644
--- a/common/modules/rubrication/models/TaxGroup.php
+++ b/common/modules/rubrication/models/TaxGroup.php
@@ -23,6 +23,7 @@ use Yii;
*/
class TaxGroup extends \yii\db\ActiveRecord
{
+ public $_options = [];
/**
* @inheritdoc
*/
diff --git a/console/migrations/m160304_065108_product.php b/console/migrations/m160304_065108_product.php
index bdbdf39..17dc750 100644
--- a/console/migrations/m160304_065108_product.php
+++ b/console/migrations/m160304_065108_product.php
@@ -69,7 +69,10 @@ class m160304_065108_product extends Migration
$this->createTable('{{%product}}', [
'product_id' => $this->primaryKey(),
'name' => $this->string(255)->notNull(),
+ 'alias' => $this->string(255),
'brand_id' => $this->integer(),
+ 'description' => $this->text(),
+ 'video' => $this->text(),
], $tableOptions);
$this->addForeignKey('fki_product_id', 'product_category', 'product_id', 'product', 'product_id', 'NO ACTION', 'NO ACTION');
diff --git a/console/migrations/m160324_075409_product_option.php b/console/migrations/m160324_075409_product_option.php
new file mode 100644
index 0000000..4fab544
--- /dev/null
+++ b/console/migrations/m160324_075409_product_option.php
@@ -0,0 +1,44 @@
+db->driverName === 'mysql') {
+ // Only for MySQL
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+
+ // @todo https://habrahabr.ru/post/138947/
+ } elseif ($this->db->driverName === 'pgsql') {
+ // Only for PostgreSQL
+ // @todo use intarray field for tax_options
+ }
+
+ $this->createTable('{{%product_option}}', [
+ 'product_id' => $this->integer()->notNull(),
+ 'option_id' => $this->integer()->notNull(),
+ ], $tableOptions);
+ $this->addPrimaryKey('product_option_pkey', 'product_option', ['product_id', 'option_id']);
+ $this->addForeignKey('product_option_product_fkey', 'product_option', 'product_id', 'product', 'product_id', 'NO ACTION', 'NO ACTION');
+ $this->addForeignKey('product_option_option_fkey', 'product_option', 'option_id', 'tax_option', 'tax_option_id', 'NO ACTION', 'NO ACTION');
+ }
+
+ public function down()
+ {
+ $this->dropTable('{{%product_option}}');
+ }
+
+ /*
+ // Use safeUp/safeDown to run migration code within a transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
diff --git a/frontend/controllers/CatalogController.php b/frontend/controllers/CatalogController.php
index d8cb123..4d60cb5 100644
--- a/frontend/controllers/CatalogController.php
+++ b/frontend/controllers/CatalogController.php
@@ -7,8 +7,11 @@ use common\modules\product\models\Category;
use common\modules\product\models\CategorySearch;
use common\modules\product\models\Product;
use common\modules\product\models\ProductCategory;
+use common\modules\product\models\ProductOption;
use common\modules\product\models\ProductSearch;
use common\modules\product\models\ProductVariant;
+use common\modules\rubrication\models\TaxGroup;
+use common\modules\rubrication\models\TaxOption;
use yii\data\ActiveDataProvider;
use yii\data\Pagination;
use yii\data\Sort;
@@ -56,10 +59,34 @@ class CatalogController extends \yii\web\Controller
]);
$all_count = $query->count();
+ $brandsQuery = Brand::find()
+ ->innerJoinWith('products')
+ ->innerJoin(ProductCategory::tableName(), ProductCategory::tableName() .'.product_id='. Product::tableName() .'.product_id')
+ ->where([
+ ProductCategory::tableName() .'.category_id' => $category->category_id
+ ])
+ ->groupBy(Brand::tableName() .'.brand_id');
+ $brands = $brandsQuery->all();
+ $brands_count = $brandsQuery->count();
+
+ $optionsQuery = TaxOption::find()
+// ->select([TaxOption::tableName() .'.tax_option_id', TaxOption::tableName() .'.alias'])
+ ->innerJoin(ProductOption::tableName(), ProductOption::tableName() .'.option_id='. TaxOption::tableName() .'.tax_option_id')
+ ->innerJoin(ProductCategory::tableName(), ProductCategory::tableName() .'.product_id='. ProductOption::tableName() .'.product_id')
+ ->where([
+ ProductCategory::tableName() .'.category_id' => $category->category_id
+ ])
+ ->groupBy(TaxOption::tableName() .'.tax_option_id');
+ $all_options = [];
+ foreach($optionsQuery->all() as $_option) {
+ $all_options[] = $_option;
+ }
+
$priceQuery = clone $query;
$priceMin = $priceMinCurr = $priceQuery->min(ProductVariant::tableName() .'.price');
$priceMax = $priceMaxCurr = $priceQuery->max(ProductVariant::tableName() .'.price');
+ // Prices
if (($price_interval = \Yii::$app->request->get('price_interval')) != false) {
$price_interval = explode(';', $price_interval);
$price_interval = [
@@ -75,21 +102,40 @@ class CatalogController extends \yii\web\Controller
$priceMaxCurr = $price_interval[1];
}
}
+
+ $groups = [];
+ foreach($category->getTaxGroups()->all() as $_group) {
+ $groups[$_group->tax_group_id] = $_group;
+ }
+ foreach ($all_options as $option) {
+ $groups[$option->tax_group_id]->_options[] = $option;
+ }
+ foreach($groups as $i => $group) {
+ if (empty($group->_options))
+ unset($groups[$i]);
+ }
+
+ // Options
+ if (($options = \Yii::$app->request->get('option')) != false) {
+ $query->innerJoin(ProductOption::tableName(), ProductOption::tableName() .'.product_id='. Product::tableName() .'.product_id');
+ $query->innerJoin(TaxOption::tableName(), TaxOption::tableName() .'.tax_option_id='. ProductOption::tableName() .'.option_id');
+ foreach($options as $group_alias => $option_alias) {
+ $group = TaxGroup::find()->where(['like', 'alias', $group_alias])->one();
+ if (!$group) {
+ continue;
+ }
+ $query->andWhere([TaxOption::tableName() .'.tax_group_id' => $group->tax_group_id, TaxOption::tableName() .'.alias' => $option_alias]);
+ }
+ }
+
$count = $query->count();
+
$pages = new Pagination(['totalCount' => $count, 'pageSize' => $per_page]);
$query->offset($pages->offset)
->orderBy($sort->orders)
->limit($pages->limit);
$products = $query->all();
- $brandsQuery = Brand::find()->innerJoinWith('products')->innerJoin(ProductCategory::tableName(), ProductCategory::tableName() .'.product_id='. Product::tableName() .'.product_id')->where([
- ProductCategory::tableName() .'.category_id' => $category->category_id
- ])->groupBy(Brand::tableName() .'.brand_id');
- $brands = $brandsQuery->all();
- $brands_count = $brandsQuery->count();
-
- $groups = $category->getTaxGroups()->all();
-
return $this->render(
'products',
[
@@ -107,6 +153,7 @@ class CatalogController extends \yii\web\Controller
'brands' => $brands,
'brands_count' => $brands_count,
'groups' => $groups,
+ 'options' => $options,
]
);
}
@@ -118,7 +165,22 @@ class CatalogController extends \yii\web\Controller
if (empty($product->product_id)) {
throw new HttpException(404 ,'Page not found');
}
- return $this->render('product');
+ $groups = [];
+ foreach($product->category->getTaxGroups()->all() as $_group) {
+ $groups[$_group->tax_group_id] = $_group;
+ }
+ foreach ($product->options as $option) {
+ $groups[$option->tax_group_id]->_options[] = $option;
+ }
+ foreach($groups as $i => $group) {
+ if (empty($group->_options))
+ unset($groups[$i]);
+ }
+
+ return $this->render('product', [
+ 'product' => $product,
+ 'properties' => $groups,
+ ]);
}
public function actionBrands()
diff --git a/frontend/views/catalog/product.php b/frontend/views/catalog/product.php
new file mode 100644
index 0000000..b5b3d84
--- /dev/null
+++ b/frontend/views/catalog/product.php
@@ -0,0 +1,164 @@
+title = $product->name;
+foreach($product->category->getParents()->all() as $parent) {
+ $this->params['breadcrumbs'][] = ['label' => $parent->name, 'url' => ['catalog/category', 'alias' => $parent->alias]];
+}
+$this->params['breadcrumbs'][] = ['label' => $product->category->name, 'url' => ['catalog/category', 'alias' => $product->category->alias]];
+$this->params['breadcrumbs'][] = $product->name .' #'. $product->variant->sku;
+?>
+= $product->name .' '. $product->variant->name?>
+
+
+
+
+ image)) :?>
+

+
+

+
+
+
+ images)) :?>
+
+ images as $image) :?>
+
+

+
+
+
+

+

+
+
+
+
+
+
+
+
+
Код: = $product->variant->sku?>
+
= $product->stock !== 0 ? ' есть в наличии' : ' нет в наличии'?>
+
+
+
+
+
= $product->variant->price?>
+
грн.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Доставка товара на следующий день после выставления счета. Мы доставим “День в
день” — уточните это у менеджера.
+
+
Подробно о доставке
+
+
+
+
+
+
+
Характеристики
+
+
+ -
+
+
= $group->name?>
+
+ _options as $option) :?> = $option->ValueRenderHTML?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Характеристики
+
+ description)) :?>
+ - Описание
+
+ description)) :?>
+ - Видео
+
+
+
+
+
+
+
+
+ -
+
+
= $group->name?>
+
+ _options as $option) :?> = $option->ValueRenderHTML?>
+
+
+
+
+
+
+
+ description)) :?>
+
+ = $product->description?>
+
+
+ video)) :?>
+
+ = $product->video?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/views/catalog/product_item.php b/frontend/views/catalog/product_item.php
index f9c37c2..fa90e92 100644
--- a/frontend/views/catalog/product_item.php
+++ b/frontend/views/catalog/product_item.php
@@ -4,12 +4,21 @@
\ No newline at end of file
diff --git a/frontend/views/catalog/products.php b/frontend/views/catalog/products.php
index 3f042e6..9589979 100644
--- a/frontend/views/catalog/products.php
+++ b/frontend/views/catalog/products.php
@@ -74,9 +74,9 @@ $this->params['breadcrumbs'][] = $category->name;
= $group->name?>
- options as $option) :?>
+ _options as $option) :?>
diff --git a/frontend/web/images/no_photo.png b/frontend/web/images/no_photo.png
new file mode 100644
index 0000000..aef9e08
Binary files /dev/null and b/frontend/web/images/no_photo.png differ
diff --git a/frontend/web/images/no_photo_big.png b/frontend/web/images/no_photo_big.png
new file mode 100644
index 0000000..fc71420
Binary files /dev/null and b/frontend/web/images/no_photo_big.png differ
--
libgit2 0.21.4