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; field($model, 'name')->textInput(['maxlength' => true]) ?> + field($model, 'description')->widget(\mihaildev\ckeditor\CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ], ]); ?> + field($model, 'video')->textarea()->label('Video embeded'); ?> + field($model, 'brand_id')->dropDownList( ArrayHelper::map(ProductHelper::getBrands()->all(), 'brand_id', 'name'), [ @@ -30,21 +33,18 @@ use unclead\widgets\MultipleInputColumn; 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']), - ] - ]); - */?> + ]);*/?> field($model, 'variants')->widget(MultipleInput::className(), [ 'columns' => [ @@ -90,6 +90,16 @@ use unclead\widgets\MultipleInputColumn; ]); ?> + all() as $group) :?> + field($model, 'options')->checkboxList( + ArrayHelper::map($group->options, 'tax_option_id', 'ValueRenderFlash'), + [ + 'multiple' => true, + 'unselect' => null, + ] + )->label($group->name);?> + +
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'); 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; +?> +

name .' '. $product->variant->name?>

+ +
+
+
+ image)) :?> + <?= $product->name?> + + <?= $product->image->alt ? $product->image->alt : $product->name?> + + +
+ images)) :?> +
+ images as $image) :?> +
+ <?= $image->alt ? $image->alt : $product->name?> +
+ + + + +
+ + +
+ + +
+
+ Код: variant->sku?> + stock !== 0 ? ' есть в наличии' : ' нет в наличии'?> +
+ +
+
+
variant->price?>
+
грн.
+
+
1
+
+
+
+
-
+
+
+
+ + + + +
+
+
+ БЫСТРЫЙ ЗАКАЗ + + +
+
+ +
+

+ Доставка товара на следующий день после выставления счета. Мы доставим “День в
день” — уточните это у менеджера. +

+ Подробно о доставке +
+ +
+ +
+ +

Характеристики

+ + + + + +
+ +
+
+
    + +
  • Характеристики
  • + + description)) :?> +
  • Описание
  • + + description)) :?> +
  • Видео
  • + + +
+
+ +
+
    + +
  • +
    +
    name?>
    +
    + _options as $option) :?> ValueRenderHTML?> +
    +
    +
  • + +
+
+ + description)) :?> +
+ description?> +
+ + video)) :?> +
+ 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 @@
-
+
+
+ image)) :?> + + + <?= $product->image->alt ? $product->image->alt : $product->name?> + +
name?>
Бренд: brand->name?>
categoriesNames)?>
+ variant) :?>
variant->price?> грн.
+ добавить к сравнению
\ 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;
  • name?>
    - options as $option) :?> + _options as $option) :?>
    - + ValueRenderHTML?>
    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