From 557cbb1db16785e2ba02394b0c7039b714b46263 Mon Sep 17 00:00:00 2001 From: jitendra Date: Fri, 30 Aug 2019 17:09:15 +0530 Subject: [PATCH] Bundle add to cart added --- .../accordians/bundle-items.blade.php | 5 - .../Http/Controllers/CategoryController.php | 3 +- packages/Webkul/Checkout/src/Cart.php | 11 +- packages/Webkul/Checkout/src/Models/Cart.php | 2 +- packages/Webkul/Core/src/Core.php | 22 ++ ...e_product_bundle_option_products_table.php | 1 + .../Product/src/Helpers/BundleOption.php | 111 +++++++++ .../src/Helpers/ConfigurableOption.php | 2 +- .../src/Models/ProductBundleOptionProduct.php | 10 +- .../Webkul/Product/src/Models/ProductFlat.php | 8 + .../src/Repositories/ProductRepository.php | 3 +- .../Webkul/Product/src/Type/AbstractType.php | 75 ++++-- packages/Webkul/Product/src/Type/Bundle.php | 187 ++++++++++++++- .../Webkul/Product/src/Type/Configurable.php | 36 ++- .../Webkul/Product/src/Type/Downloadable.php | 27 +-- packages/Webkul/Product/src/Type/Grouped.php | 7 + packages/Webkul/Product/src/Type/Simple.php | 17 +- packages/Webkul/Product/src/Type/Virtual.php | 9 +- .../Webkul/Sales/src/Models/OrderItem.php | 10 +- .../src/Repositories/InvoiceRepository.php | 4 +- .../OrderItemInventoryRepository.php | 4 +- .../src/Repositories/OrderItemRepository.php | 50 ++-- .../src/Repositories/OrderRepository.php | 6 +- packages/Webkul/Shop/package.json | 1 + .../Shop/src/Resources/assets/js/app.js | 4 + .../Shop/src/Resources/assets/sass/app.scss | 192 +++++++++++++--- .../Webkul/Shop/src/Resources/lang/ar/app.php | 6 +- .../Webkul/Shop/src/Resources/lang/en/app.php | 6 +- .../Webkul/Shop/src/Resources/lang/fa/app.php | 6 +- .../Shop/src/Resources/lang/pt_BR/app.php | 6 +- .../Resources/views/products/view.blade.php | 98 +++++--- .../products/view/bundle-options.blade.php | 217 ++++++++++++++++++ 32 files changed, 961 insertions(+), 185 deletions(-) create mode 100644 packages/Webkul/Product/src/Helpers/BundleOption.php create mode 100644 packages/Webkul/Shop/src/Resources/views/products/view/bundle-options.blade.php diff --git a/packages/Webkul/Admin/src/Resources/views/catalog/products/accordians/bundle-items.blade.php b/packages/Webkul/Admin/src/Resources/views/catalog/products/accordians/bundle-items.blade.php index c3e336b0f..7e264377f 100644 --- a/packages/Webkul/Admin/src/Resources/views/catalog/products/accordians/bundle-items.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/catalog/products/accordians/bundle-items.blade.php @@ -221,11 +221,6 @@ inject: ['$validator'], - data: function() { - return { - } - }, - computed: { titleInputName: function () { if (this.option.id) diff --git a/packages/Webkul/Category/src/Http/Controllers/CategoryController.php b/packages/Webkul/Category/src/Http/Controllers/CategoryController.php index 809fcaf6f..418788379 100755 --- a/packages/Webkul/Category/src/Http/Controllers/CategoryController.php +++ b/packages/Webkul/Category/src/Http/Controllers/CategoryController.php @@ -2,9 +2,10 @@ namespace Webkul\Category\Http\Controllers; +use Illuminate\Support\Facades\Event; use Webkul\Category\Repositories\CategoryRepository; use Webkul\Category\Models\CategoryTranslation; -use Illuminate\Support\Facades\Event; +use Webkul\Attribute\Repositories\AttributeRepository; /** * Catalog category controller diff --git a/packages/Webkul/Checkout/src/Cart.php b/packages/Webkul/Checkout/src/Cart.php index d208d476d..545590ea0 100755 --- a/packages/Webkul/Checkout/src/Cart.php +++ b/packages/Webkul/Checkout/src/Cart.php @@ -657,10 +657,7 @@ class Cart { return false; } else { foreach ($cart->items as $item) { - if ($item->product_flat->getTypeInstance()->getMinimalPrice($item) == $item->price) - continue; - - $price = ! is_null($item->custom_price) ? $item->custom_price : $productFlat->getTypeInstance()->getMinimalPrice(); + $price = ! is_null($item->custom_price) ? $item->custom_price : $item->base_price; $this->cartItemRepository->update([ 'price' => core()->convertPrice($price), @@ -871,8 +868,10 @@ class Cart { 'additional' => $data['additional'], ]; - if (isset($data['child']) && $data['child']) { - $finalData['child'] = $this->prepareDataForOrderItem($data['child']); + if (isset($data['children']) && $data['children']) { + foreach ($data['children'] as $child) { + $finalData['children'][] = $this->prepareDataForOrderItem($child); + } } return $finalData; diff --git a/packages/Webkul/Checkout/src/Models/Cart.php b/packages/Webkul/Checkout/src/Models/Cart.php index 1660e7b83..01cddea2b 100755 --- a/packages/Webkul/Checkout/src/Models/Cart.php +++ b/packages/Webkul/Checkout/src/Models/Cart.php @@ -14,7 +14,7 @@ class Cart extends Model implements CartContract protected $hidden = ['coupon_code']; - protected $with = ['items', 'items.child']; + protected $with = ['items', 'items.children']; /** * To get relevant associated items with the cart instance diff --git a/packages/Webkul/Core/src/Core.php b/packages/Webkul/Core/src/Core.php index a82b84dbc..9bfc791a3 100755 --- a/packages/Webkul/Core/src/Core.php +++ b/packages/Webkul/Core/src/Core.php @@ -436,6 +436,28 @@ class Core } } + /** + * Format and convert price with currency symbol + * + * @return array + */ + public function getAccountJsSymbols() + { + $formater = new \NumberFormatter( app()->getLocale(), \NumberFormatter::CURRENCY ); + + $pattern = $formater->getPattern(); + + $pattern = str_replace("¤", "%s", $pattern); + + $pattern = str_replace("#,##0.00", "%v", $pattern); + + return [ + 'symbol' => core()->currencySymbol(core()->getCurrentCurrencyCode()), + 'decimal' => $formater->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), + 'format' => $pattern + ]; + } + /** * Format price with base currency symbol * diff --git a/packages/Webkul/Product/src/Database/Migrations/2019_08_20_170528_create_product_bundle_option_products_table.php b/packages/Webkul/Product/src/Database/Migrations/2019_08_20_170528_create_product_bundle_option_products_table.php index 5cb07480d..0343bdc48 100644 --- a/packages/Webkul/Product/src/Database/Migrations/2019_08_20_170528_create_product_bundle_option_products_table.php +++ b/packages/Webkul/Product/src/Database/Migrations/2019_08_20_170528_create_product_bundle_option_products_table.php @@ -16,6 +16,7 @@ class CreateProductBundleOptionProductsTable extends Migration Schema::create('product_bundle_option_products', function (Blueprint $table) { $table->increments('id'); $table->integer('qty')->default(0); + $table->boolean('is_user_defined')->default(1); $table->boolean('is_default')->default(0); $table->integer('sort_order')->default(0); diff --git a/packages/Webkul/Product/src/Helpers/BundleOption.php b/packages/Webkul/Product/src/Helpers/BundleOption.php new file mode 100644 index 000000000..da383ce41 --- /dev/null +++ b/packages/Webkul/Product/src/Helpers/BundleOption.php @@ -0,0 +1,111 @@ + + * @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com) + */ +class BundleOption extends AbstractProduct +{ + /** + * Product + * + * @var Product + */ + protected $product; + + /** + * Returns bundle option config + * + * @param Product $product + * @return array + */ + public function getBundleConfig($product) + { + $this->product = $product; + + return [ + 'options' => $this->getOptions() + ]; + } + + /** + * Returns bundle options + * + * @return array + */ + public function getOptions() + { + $options = []; + + foreach ($this->product->bundle_options as $option) { + $options[$option->id] = $this->getOptionItemData($option); + } + + return $options; + } + + /** + * Get formed data from bundle option + * + * @param ProductBundleOption $option + * @return array + */ + private function getOptionItemData($option) + { + return [ + 'id' => $option->id, + 'label' => $option->label, + 'type' => $option->type, + 'products' => $this->getOptionProducts($option), + 'sort_order' => $option->sort_order + ]; + } + + /** + * Get formed data from bundle option product + * + * @param ProductBundleOption $option + * @return array + */ + private function getOptionProducts($option) + { + $products = []; + + foreach ($option->bundle_option_products as $index => $bundleOptionProduct) { + $products[$bundleOptionProduct->id] = [ + 'id' => $bundleOptionProduct->id, + 'qty' => $bundleOptionProduct->qty, + 'price' => $this->getProductPrices($bundleOptionProduct->product), + 'name' => $bundleOptionProduct->product->name, + 'product_id' => $bundleOptionProduct->product_id, + 'is_default' => $bundleOptionProduct->is_default + ]; + } + + return $products; + } + + /** + * Returns bundle product prices + * + * @param Product $product + * @return array + */ + public function getProductPrices($product) + { + return [ + 'regular_price' => [ + 'price' => core()->convertPrice($product->price), + 'formated_price' => core()->currency($product->price) + ], + 'final_price' => [ + 'price' => core()->convertPrice($product->getTypeInstance()->getMinimalPrice()), + 'formated_price' => core()->currency($product->getTypeInstance()->getMinimalPrice()) + ] + ]; + } +} \ No newline at end of file diff --git a/packages/Webkul/Product/src/Helpers/ConfigurableOption.php b/packages/Webkul/Product/src/Helpers/ConfigurableOption.php index 4b11f98a9..5831cac13 100755 --- a/packages/Webkul/Product/src/Helpers/ConfigurableOption.php +++ b/packages/Webkul/Product/src/Helpers/ConfigurableOption.php @@ -71,7 +71,7 @@ class ConfigurableOption extends AbstractProduct * Returns the allowed variants JSON * * @param Product $product - * @return float + * @return array */ public function getConfigurationConfig($product) { diff --git a/packages/Webkul/Product/src/Models/ProductBundleOptionProduct.php b/packages/Webkul/Product/src/Models/ProductBundleOptionProduct.php index a659105aa..2faa5efce 100644 --- a/packages/Webkul/Product/src/Models/ProductBundleOptionProduct.php +++ b/packages/Webkul/Product/src/Models/ProductBundleOptionProduct.php @@ -9,7 +9,15 @@ class ProductBundleOptionProduct extends Model implements ProductBundleOptionPro { public $timestamps = false; - protected $fillable = ['qty', 'sort_order', 'is_default', 'product_bundle_option_id', 'product_id']; + protected $fillable = ['qty', 'is_user_defined', 'sort_order', 'is_default', 'product_bundle_option_id', 'product_id']; + + /** + * Get the bundle option that owns this resource. + */ + public function bundle_option() + { + return $this->belongsTo(ProductBundleProductProxy::modelClass()); + } /** * Get the product that owns the image. diff --git a/packages/Webkul/Product/src/Models/ProductFlat.php b/packages/Webkul/Product/src/Models/ProductFlat.php index 549bcc056..5e84da1ce 100644 --- a/packages/Webkul/Product/src/Models/ProductFlat.php +++ b/packages/Webkul/Product/src/Models/ProductFlat.php @@ -175,6 +175,14 @@ class ProductFlat extends Model implements ProductFlatContract return $this->product->grouped_products(); } + /** + * Get the bundle options that owns the product. + */ + public function bundle_options() + { + return $this->product->bundle_options(); + } + /** * Retrieve product attributes * diff --git a/packages/Webkul/Product/src/Repositories/ProductRepository.php b/packages/Webkul/Product/src/Repositories/ProductRepository.php index 4ef0147ce..60edc094a 100755 --- a/packages/Webkul/Product/src/Repositories/ProductRepository.php +++ b/packages/Webkul/Product/src/Repositories/ProductRepository.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Webkul\Attribute\Repositories\AttributeRepository; use Webkul\Core\Eloquent\Repository; use Webkul\Product\Repositories\ProductFlatRepository; +use Webkul\Product\Models\ProductAttributeValue; /** * Product Repository @@ -171,7 +172,7 @@ class ProductRepository extends Repository foreach($aliases as $table => $alias) { $query1 = $query1->orWhere(function($query2) use($qb, $table, $alias) { - foreach ($this->attribute->getProductDefaultAttributes(array_keys(request()->input())) as $code => $attribute) { + foreach ($this->attributeRepository->getProductDefaultAttributes(array_keys(request()->input())) as $code => $attribute) { $aliasTemp = $alias . $attribute->code; $qb = $qb->leftJoin('product_attribute_values as ' . $aliasTemp, $table . '.id', '=', $aliasTemp . '.product_id'); diff --git a/packages/Webkul/Product/src/Type/AbstractType.php b/packages/Webkul/Product/src/Type/AbstractType.php index 0d5c096c8..0b1588aea 100644 --- a/packages/Webkul/Product/src/Type/AbstractType.php +++ b/packages/Webkul/Product/src/Type/AbstractType.php @@ -69,6 +69,41 @@ abstract class AbstractType */ protected $product; + /** + * Is a composite product type + * + * @var boolean + */ + protected $isComposite = false; + + /** + * Is a stokable product type + * + * @var boolean + */ + protected $isStockable = true; + + /** + * Show quantity box + * + * @var boolean + */ + protected $showQuantityBox = false; + + /** + * Is product have sufficient quantity + * + * @var boolean + */ + protected $haveSufficientQuantity = true; + + /** + * Product can be moved from wishlist to cart or not + * + * @var boolean + */ + protected $canBeMovedFromWishlistToCart = true; + /** * Create a new product type instance. * @@ -213,7 +248,17 @@ abstract class AbstractType */ public function isStockable() { - return true; + return $this->isStockable; + } + + /** + * Return true if this product can have inventory + * + * @return boolean + */ + public function isComposite() + { + return $this->isComposite; } /** @@ -222,7 +267,7 @@ abstract class AbstractType */ public function haveSufficientQuantity($qty) { - return true; + return $this->haveSufficientQuantity; } /** @@ -232,7 +277,7 @@ abstract class AbstractType */ public function showQuantityBox() { - return false; + return $this->showQuantityBox; } /** @@ -251,7 +296,7 @@ abstract class AbstractType */ public function canBeMovedFromWishlistToCart($item) { - return true; + return $this->canBeMovedFromWishlistToCart; } /** @@ -315,6 +360,16 @@ abstract class AbstractType return $this->product->price; } + /** + * Get product minimal price + * + * @return float + */ + public function getFinalPrice() + { + return $this->getMinimalPrice(); + } + /** * Returns the product's minimal price * @@ -370,7 +425,7 @@ abstract class AbstractType if ($this->isStockable() && ! $this->haveSufficientQuantity($data['quantity'])) return trans('shop::app.checkout.cart.quantity.inventory_warning'); - $price = $this->getMinimalPrice(); + $price = $this->getFinalPrice(); $products = [ [ @@ -408,16 +463,6 @@ abstract class AbstractType return $data; } - /** - * Check if product can be configured - * - * @return boolean - */ - public function canConfigure() - { - return false; - } - /** * * @param array $options1 diff --git a/packages/Webkul/Product/src/Type/Bundle.php b/packages/Webkul/Product/src/Type/Bundle.php index 9db8973f0..6fea5307b 100644 --- a/packages/Webkul/Product/src/Type/Bundle.php +++ b/packages/Webkul/Product/src/Type/Bundle.php @@ -8,6 +8,7 @@ use Webkul\Product\Repositories\ProductAttributeValueRepository; use Webkul\Product\Repositories\ProductInventoryRepository; use Webkul\Product\Repositories\ProductImageRepository; use Webkul\Product\Repositories\ProductBundleOptionRepository; +use Webkul\Product\Repositories\ProductBundleOptionProductRepository; use Webkul\Product\Helpers\ProductImage; /** @@ -60,6 +61,13 @@ class Bundle extends AbstractType */ protected $productBundleOptionRepository; + /** + * ProductBundleOptionProductRepository instance + * + * @var ProductBundleOptionProductRepository + */ + protected $productBundleOptionProductRepository; + /** * Product Image helper instance * @@ -86,16 +94,24 @@ class Bundle extends AbstractType 'admin::catalog.products.accordians.product-links' ]; + /** + * Is a composite product type + * + * @var boolean + */ + protected $isComposite = true; + /** * Create a new product type instance. * - * @param Webkul\Attribute\Repositories\AttributeRepository $attributeRepository - * @param Webkul\Product\Repositories\ProductRepository $productRepository - * @param Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValueRepository - * @param Webkul\Product\Repositories\ProductInventoryRepository $productInventoryRepository - * @param Webkul\Product\Repositories\ProductImageRepository $productImageRepository - * @param Webkul\Product\Repositories\ProductBundleOptionRepository $productBundleOptionRepository - * @param Webkul\Product\Helpers\ProductImage $productImageHelper + * @param Webkul\Attribute\Repositories\AttributeRepository $attributeRepository + * @param Webkul\Product\Repositories\ProductRepository $productRepository + * @param Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValueRepository + * @param Webkul\Product\Repositories\ProductInventoryRepository $productInventoryRepository + * @param Webkul\Product\Repositories\ProductImageRepository $productImageRepository + * @param Webkul\Product\Repositories\ProductBundleOptionRepository $productBundleOptionRepository + * @param Webkul\Product\Repositories\ProductBundleOptionProductRepository $productBundleOptionProductRepository + * @param Webkul\Product\Helpers\ProductImage $productImageHelper * @return void */ public function __construct( @@ -105,6 +121,7 @@ class Bundle extends AbstractType ProductInventoryRepository $productInventoryRepository, productImageRepository $productImageRepository, ProductBundleOptionRepository $productBundleOptionRepository, + ProductBundleOptionProductRepository $productBundleOptionProductRepository, ProductImage $productImageHelper ) { @@ -118,6 +135,8 @@ class Bundle extends AbstractType ); $this->productBundleOptionRepository = $productBundleOptionRepository; + + $this->productBundleOptionProductRepository = $productBundleOptionProductRepository; } /** @@ -135,4 +154,158 @@ class Bundle extends AbstractType return $product; } + + /** + * Get product minimal price + * + * @return float + */ + public function getMinimalPrice() + { + return 0; + } + + /** + * Get product maximam price + * + * @return float + */ + public function getMaximamPrice() + { + return 0; + } + + /** + * Get product final price + * + * @return float + */ + public function getFinalPrice() + { + return 0; + } + + /** + * Get product minimal price + * + * @return string + */ + public function getPriceHtml() + { + } + + /** + * Add product. Returns error message if can't prepare product. + * + * @param array $data + * @return array + */ + public function prepareForCart($data) + { + if (! isset($data['bundle_options'])) + return trans('shop::app.checkout.cart.item.error-add'); + + $products = parent::prepareForCart($data); + + foreach ($this->getCartChildProducts($data) as $productId => $data) { + $product = $this->productRepository->find($productId); + + $cartProduct = $product->getTypeInstance()->prepareForCart($data); + + if (is_string($cartProduct)) + return $cartProduct; + + $cartProduct[0]['parent_id'] = $this->product->id; + + $products = array_merge($products, $cartProduct); + + $products[0]['price'] += $cartProduct[0]['total']; + $products[0]['base_price'] += $cartProduct[0]['base_total']; + $products[0]['total'] += $cartProduct[0]['total']; + $products[0]['base_total'] += $cartProduct[0]['base_total']; + $products[0]['weight'] += $cartProduct[0]['weight']; + $products[0]['total_weight'] += $cartProduct[0]['total_weight']; + $products[0]['base_total_weight'] += $cartProduct[0]['base_total_weight']; + } + + return $products; + } + + /** + * Add product. Returns error message if can't prepare product. + * + * @param array $data + * @return array + */ + public function getCartChildProducts($data) + { + $products = []; + + foreach ($data['bundle_options'] as $optionId => $optionProductIds) { + foreach ($optionProductIds as $optionProductId) { + $optionProduct = $this->productBundleOptionProductRepository->findOneWhere([ + 'id' => $optionProductId, + 'product_bundle_option_id' => $optionId + ]); + + $qty = $data['bundle_option_qty'][$optionId] ?? $optionProduct->qty; + + if (! isset($products[$optionProduct->product_id])) { + $products[$optionProduct->product_id] = [ + 'product_id' => $optionProduct->product_id, + 'quantity' => $qty, + ]; + } else { + $products[$optionProduct->product_id] = array_merge($products[$optionProduct->product_id], [ + 'quantity' => $products[$optionProduct->product_id]['quantity'] + $qty + ]); + } + } + } + + return $products; + } + + /** + * + * @param array $options1 + * @param array $options2 + * @return boolean + */ + public function compareOptions($options1, $options2) + { + if ($this->product->id != $options2['product_id']) + return false; + + return $options1['bundle_options'] == $options2['bundle_options']; + } + + /** + * Returns additional information for items + * + * @param array $data + * @return array + */ + public function getAdditionalOptions($data) + { + foreach ($data['bundle_options'] as $optionId => $optionProductIds) { + $option = $this->productBundleOptionRepository->find($optionId); + + $labels = []; + + foreach ($optionProductIds as $optionProductId) { + $optionProduct = $this->productBundleOptionProductRepository->find($optionProductId); + + $labels[] = $optionProduct->product->name; + } + + $data['attributes'][$option->id] = [ + 'attribute_name' => $option->label, + 'option_id' => $option->id, + 'option_label' => implode(', ', $labels), + ]; + } + + return $data; + } } \ No newline at end of file diff --git a/packages/Webkul/Product/src/Type/Configurable.php b/packages/Webkul/Product/src/Type/Configurable.php index 30421e70f..035706674 100644 --- a/packages/Webkul/Product/src/Type/Configurable.php +++ b/packages/Webkul/Product/src/Type/Configurable.php @@ -32,6 +32,20 @@ class Configurable extends AbstractType 'admin::catalog.products.accordians.product-links' ]; + /** + * Is a composite product type + * + * @var boolean + */ + protected $isComposite = true; + + /** + * Show quantity box + * + * @var boolean + */ + protected $showQuantityBox = true; + /** * @param array $data * @return Product @@ -264,16 +278,6 @@ class Configurable extends AbstractType { return $cartItem->child->product->getTypeInstance()->haveSufficientQuantity($cartItem->quantity); } - - /** - * Return true if this product can have inventory - * - * @return boolean - */ - public function showQuantityBox() - { - return true; - } /** * Returns validation rules @@ -380,7 +384,7 @@ class Configurable extends AbstractType if (! $childProduct->haveSufficientQuantity($data['quantity'])) return trans('shop::app.checkout.cart.quantity.inventory_warning'); - $price = $this->getMinimalPrice(); + $price = $this->getFinalPrice(); $products = [ [ @@ -410,16 +414,6 @@ class Configurable extends AbstractType return $products; } - /** - * Check if product can be configured - * - * @return boolean - */ - public function canConfigure() - { - return true; - } - /** * * @param array $options1 diff --git a/packages/Webkul/Product/src/Type/Downloadable.php b/packages/Webkul/Product/src/Type/Downloadable.php index 2094e0733..3d77152ad 100644 --- a/packages/Webkul/Product/src/Type/Downloadable.php +++ b/packages/Webkul/Product/src/Type/Downloadable.php @@ -95,6 +95,13 @@ class Downloadable extends AbstractType 'admin::catalog.products.accordians.product-links' ]; + /** + * Is a stokable product type + * + * @var boolean + */ + protected $isStockable = false; + /** * Create a new product type instance. * @@ -168,16 +175,6 @@ class Downloadable extends AbstractType return false; } - /** - * Return true if this product can have inventory - * - * @return boolean - */ - public function isStockable() - { - return false; - } - /** * Returns validation rules * @@ -210,16 +207,6 @@ class Downloadable extends AbstractType return parent::prepareForCart($data); } - /** - * Check if product can be configured - * - * @return boolean - */ - public function canConfigure() - { - return true; - } - /** * * @param array $options1 diff --git a/packages/Webkul/Product/src/Type/Grouped.php b/packages/Webkul/Product/src/Type/Grouped.php index bc97f9d8e..f32a13ae6 100644 --- a/packages/Webkul/Product/src/Type/Grouped.php +++ b/packages/Webkul/Product/src/Type/Grouped.php @@ -88,6 +88,13 @@ class Grouped extends AbstractType 'admin::catalog.products.accordians.product-links' ]; + /** + * Is a composite product type + * + * @var boolean + */ + protected $isComposite = true; + /** * Create a new product type instance. * diff --git a/packages/Webkul/Product/src/Type/Simple.php b/packages/Webkul/Product/src/Type/Simple.php index 7d63f9295..ac41266fa 100644 --- a/packages/Webkul/Product/src/Type/Simple.php +++ b/packages/Webkul/Product/src/Type/Simple.php @@ -29,6 +29,13 @@ class Simple extends AbstractType 'admin::catalog.products.accordians.product-links' ]; + /** + * Show quantity box + * + * @var boolean + */ + protected $showQuantityBox = true; + /** * Return true if this product type is saleable * @@ -54,16 +61,6 @@ class Simple extends AbstractType { return $qty <= $this->totalQuantity() ? true : (core()->getConfigData('catalog.inventory.stock_options.backorders') ? true : false); } - - /** - * Return true if this product can have inventory - * - * @return boolean - */ - public function showQuantityBox() - { - return true; - } /** * @return integer diff --git a/packages/Webkul/Product/src/Type/Virtual.php b/packages/Webkul/Product/src/Type/Virtual.php index 66ecd7ffc..4f04b4084 100644 --- a/packages/Webkul/Product/src/Type/Virtual.php +++ b/packages/Webkul/Product/src/Type/Virtual.php @@ -29,12 +29,9 @@ class Virtual extends AbstractType ]; /** - * Return true if this product can have inventory + * Is a stokable product type * - * @return boolean + * @var boolean */ - public function isStockable() - { - return false; - } + protected $isStockable = false; } \ No newline at end of file diff --git a/packages/Webkul/Sales/src/Models/OrderItem.php b/packages/Webkul/Sales/src/Models/OrderItem.php index 5db36e11f..a7d540bee 100755 --- a/packages/Webkul/Sales/src/Models/OrderItem.php +++ b/packages/Webkul/Sales/src/Models/OrderItem.php @@ -8,7 +8,7 @@ use Webkul\Product\Models\Product; class OrderItem extends Model implements OrderItemContract { - protected $guarded = ['id', 'child', 'created_at', 'updated_at']; + protected $guarded = ['id', 'child', 'children', 'created_at', 'updated_at']; protected $casts = [ 'additional' => 'array', @@ -129,6 +129,14 @@ class OrderItem extends Model implements OrderItemContract return $this->hasOne(OrderItemProxy::modelClass(), 'parent_id'); } + /** + * Get the children items. + */ + public function children() + { + return $this->hasMany(self::class, 'parent_id'); + } + /** * Get the invoice items record associated with the order item. */ diff --git a/packages/Webkul/Sales/src/Repositories/InvoiceRepository.php b/packages/Webkul/Sales/src/Repositories/InvoiceRepository.php index d7d698dfc..b83b6dbce 100755 --- a/packages/Webkul/Sales/src/Repositories/InvoiceRepository.php +++ b/packages/Webkul/Sales/src/Repositories/InvoiceRepository.php @@ -136,10 +136,8 @@ class InvoiceRepository extends Repository 'product_type' => $orderItem->product_type, 'additional' => $orderItem->additional, ]); - - if ($orderItem->type == 'configurable' && $orderItem->child) { - $childOrderItem = $orderItem->child; + foreach ($orderItem->children as $childOrderItem) { $invoiceItem->child = $this->invoiceItemRepository->create([ 'invoice_id' => $invoice->id, 'order_item_id' => $childOrderItem->id, diff --git a/packages/Webkul/Sales/src/Repositories/OrderItemInventoryRepository.php b/packages/Webkul/Sales/src/Repositories/OrderItemInventoryRepository.php index 90857c0d1..44e5b4791 100755 --- a/packages/Webkul/Sales/src/Repositories/OrderItemInventoryRepository.php +++ b/packages/Webkul/Sales/src/Repositories/OrderItemInventoryRepository.php @@ -38,9 +38,9 @@ class OrderItemInventoryRepository extends Repository $product = $orderItem->type == 'configurable' ? $orderItem->child->product : $orderItem->product; - if (!$product) { + if (! $product) return ; - } + $inventories = $product->inventory_sources()->orderBy('priority', 'asc')->get(); foreach ($inventories as $inventorySource) { diff --git a/packages/Webkul/Sales/src/Repositories/OrderItemRepository.php b/packages/Webkul/Sales/src/Repositories/OrderItemRepository.php index f01c22201..5bedfa4f1 100755 --- a/packages/Webkul/Sales/src/Repositories/OrderItemRepository.php +++ b/packages/Webkul/Sales/src/Repositories/OrderItemRepository.php @@ -38,7 +38,7 @@ class OrderItemRepository extends Repository unset($data['product']); } - return $this->model->create($data); + return parent::create($data); } /** @@ -88,26 +88,38 @@ class OrderItemRepository extends Repository if (! $orderedQuantity = $orderItem->qty_ordered) return; - $product = $orderItem->type == 'configurable' ? $orderItem->child->product : $orderItem->product; + $products = []; - if (! $product) - return; - - - $orderedInventory = $product->ordered_inventories() - ->where('channel_id', $orderItem->order->channel->id) - ->first(); - - if ($orderedInventory) { - $orderedInventory->update([ - 'qty' => $orderedInventory->qty + $orderItem->qty_ordered - ]); + if ($orderItem->product->getTypeInstance()->isComposite()) { + foreach ($orderItem->children as $child) { + if (! $child->product) + continue; + + $products[] = $child->product; + } } else { - $product->ordered_inventories()->create([ - 'qty' => $orderItem->qty_ordered, - 'product_id' => $product->id, - 'channel_id' => $orderItem->order->channel->id, - ]); + if (! $orderItem->product) + return; + + $products[] = $orderItem->product; + } + + foreach ($products as $product) { + $orderedInventory = $product->ordered_inventories() + ->where('channel_id', $orderItem->order->channel->id) + ->first(); + + if ($orderedInventory) { + $orderedInventory->update([ + 'qty' => $orderedInventory->qty + $orderItem->qty_ordered + ]); + } else { + $product->ordered_inventories()->create([ + 'qty' => $orderItem->qty_ordered, + 'product_id' => $product->id, + 'channel_id' => $orderItem->order->channel->id, + ]); + } } } diff --git a/packages/Webkul/Sales/src/Repositories/OrderRepository.php b/packages/Webkul/Sales/src/Repositories/OrderRepository.php index cf07f617c..7546d8ad7 100755 --- a/packages/Webkul/Sales/src/Repositories/OrderRepository.php +++ b/packages/Webkul/Sales/src/Repositories/OrderRepository.php @@ -102,8 +102,10 @@ class OrderRepository extends Repository foreach ($data['items'] as $item) { $orderItem = $this->orderItemRepository->create(array_merge($item, ['order_id' => $order->id])); - if (isset($item['child']) && $item['child']) { - $orderItem->child = $this->orderItemRepository->create(array_merge($item['child'], ['order_id' => $order->id, 'parent_id' => $orderItem->id])); + if (isset($item['children']) && $item['children']) { + foreach ($item['children'] as $child) { + $this->orderItemRepository->create(array_merge($child, ['order_id' => $order->id, 'parent_id' => $orderItem->id])); + } } $this->orderItemRepository->manageInventory($orderItem); diff --git a/packages/Webkul/Shop/package.json b/packages/Webkul/Shop/package.json index b9aec46f8..9f3d3d1df 100755 --- a/packages/Webkul/Shop/package.json +++ b/packages/Webkul/Shop/package.json @@ -18,6 +18,7 @@ "vue": "^2.1.10" }, "dependencies": { + "accounting": "^0.4.1", "ez-plus": "^1.2.1", "vee-validate": "2.0.0-rc.26", "vue-flatpickr": "^2.3.0", diff --git a/packages/Webkul/Shop/src/Resources/assets/js/app.js b/packages/Webkul/Shop/src/Resources/assets/js/app.js index 6f47555f0..ea5e616c8 100755 --- a/packages/Webkul/Shop/src/Resources/assets/js/app.js +++ b/packages/Webkul/Shop/src/Resources/assets/js/app.js @@ -4,6 +4,7 @@ window.VeeValidate = require("vee-validate"); window.axios = require("axios"); require("./bootstrap"); require("ez-plus/src/jquery.ez-plus.js"); +var accounting = require('accounting'); Vue.use(VeeValidate); Vue.prototype.$http = axios @@ -12,6 +13,9 @@ window.eventBus = new Vue(); Vue.component("image-slider", require("./components/image-slider.vue")); Vue.component("vue-slider", require("vue-slider-component")); +Vue.filter('currency', function (value, argument) { + return accounting.formatMoney(value, argument); +}) $(document).ready(function () { diff --git a/packages/Webkul/Shop/src/Resources/assets/sass/app.scss b/packages/Webkul/Shop/src/Resources/assets/sass/app.scss index f3f7821ec..a660ae318 100755 --- a/packages/Webkul/Shop/src/Resources/assets/sass/app.scss +++ b/packages/Webkul/Shop/src/Resources/assets/sass/app.scss @@ -170,6 +170,24 @@ input { animation: spin 1s ease-in-out infinite; } +.radio { + margin: 10px 0 0px 0px !important; +} + +.checkbox { + margin: 10px 0 0px 0px; + + .checkbox-view { + height: 16px !important; + width: 16px !important; + background-image: url("../images/checkbox.svg") !important; + } + + input:checked + .checkbox-view { + background-image: url("../images/checkbox-checked.svg") !important; + } +} + .pull-right { float:right; } @@ -411,6 +429,7 @@ input { font-size: 14px; color: $btn-background-color; box-shadow: 1px 1px 1px $shadow-color1; + font-weight: 500; &.sale { background: $disc-price; @@ -1409,16 +1428,6 @@ section.slider-block { .checkbox { margin: 0; - - .checkbox-view { - height: 16px; - width: 16px; - background-image: url("../images/checkbox.svg"); - } - - input:checked + .checkbox-view { - background-image: url("../images/checkbox-checked.svg"); - } } .color-swatch { @@ -1928,6 +1937,108 @@ section.product-detail { } } + .bundle-options-wrapper { + .bundle-option-list { + padding: 15px 0; + border-top: solid 1px rgba(162, 162, 162, 0.2); + + h3 { + font-size: 16px; + margin: 0; + color: #242424; + } + + .bundle-option-item { + border-bottom: solid 1px rgba(162, 162, 162, 0.2); + padding: 15px 0; + width: 100%; + display: inline-block; + + &:last-child { + border-bottom: 0; + padding-bottom: 0; + } + + .control-group { + margin-bottom: 0; + color: #5E5E5E; + + label { + color: #242424; + } + + .control { + color: #5E5E5E; + } + + .price { + margin-left: 15px; + } + } + + .quantity { + border-top: 0; + padding-bottom: 0; + + &.has-error { + button { + border-color: #FC6868; + color: #FC6868; + } + } + } + + .control-error { + float: left; + width: 100%; + } + + &.has-error { + button { + border-color: #FC6868; + color: #FC6868; + } + } + } + } + + .bundle-summary { + padding: 15px 0; + border-top: solid 1px rgba(162, 162, 162, 0.2); + + h3 { + font-size: 16px; + margin: 0; + color: #242424; + } + + .quantity { + border-top: 0; + } + + .bundle-price { + font-weight: 600; + font-size: 24px; + color: #FF6472; + margin-top: 10px; + } + + ul.bundle-items { + li { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + + .selected-products { + color: #5E5E5E; + } + } + } + } + } + .full-description { * { max-width: 100%; @@ -2013,15 +2124,6 @@ section.product-detail { } } } - - .quantity-change { - cursor: pointer; - text-align: center; - - &:focus { - border-color: $border-color !important; - } - } } } } @@ -2302,14 +2404,6 @@ section.cart { margin-top: 18px; margin-right: 15px; } - - .quantity-change { - cursor: pointer; - - &:focus { - border-color: $border-color !important; - } - } } } @@ -2319,6 +2413,50 @@ section.cart { } } +.quantity { + display: inline-block !important; + + label { + margin-bottom: 10px; + } + + button { + width: 40px; + height: 38px; + font-size: 16px; + background: #ffffff; + border: 1px solid #C7C7C7; + float: left; + cursor: pointer; + + &.decrease { + border-radius: 3px 0px 0px 3px; + } + + &.increase { + border-radius: 0px 3px 3px 0px; + } + } + + &.control-group { + .control { + text-align: center; + float: left; + width: 60px; + height: 38px; + margin: 0; + border: 1px solid #C7C7C7; + border-right: none; + border-left: none; + border-radius: 0px; + + &:focus { + border-color: #C7C7C7; + } + } + } +} + .order-summary { h3 { font-size: 16px; diff --git a/packages/Webkul/Shop/src/Resources/lang/ar/app.php b/packages/Webkul/Shop/src/Resources/lang/ar/app.php index f6d12460a..4fa8106ec 100644 --- a/packages/Webkul/Shop/src/Resources/lang/ar/app.php +++ b/packages/Webkul/Shop/src/Resources/lang/ar/app.php @@ -324,7 +324,11 @@ return [ 'out-of-stock' => 'خارج الأسهم', 'view-all' => 'عرض الكل', 'less-quantity' => 'Quantity can not be less than one.', - 'starting-at' => 'Starting at' + 'starting-at' => 'Starting at', + 'customize-options' => 'Customize Options', + 'choose-selection' => 'Choose a selection', + 'your-customization' => 'Your Customization', + 'total-amount' => 'Total Amount' ], 'wishlist' => [ diff --git a/packages/Webkul/Shop/src/Resources/lang/en/app.php b/packages/Webkul/Shop/src/Resources/lang/en/app.php index be664b123..95b05e85b 100755 --- a/packages/Webkul/Shop/src/Resources/lang/en/app.php +++ b/packages/Webkul/Shop/src/Resources/lang/en/app.php @@ -354,7 +354,11 @@ return [ 'sample' => 'Sample', 'name' => 'Name', 'qty' => 'Qty', - 'starting-at' => 'Starting at' + 'starting-at' => 'Starting at', + 'customize-options' => 'Customize Options', + 'choose-selection' => 'Choose a selection', + 'your-customization' => 'Your Customization', + 'total-amount' => 'Total Amount' ], 'wishlist' => [ diff --git a/packages/Webkul/Shop/src/Resources/lang/fa/app.php b/packages/Webkul/Shop/src/Resources/lang/fa/app.php index 75cc79c3f..8d4a2709a 100644 --- a/packages/Webkul/Shop/src/Resources/lang/fa/app.php +++ b/packages/Webkul/Shop/src/Resources/lang/fa/app.php @@ -334,7 +334,11 @@ return [ 'view-all' => 'مشاهده همه', 'select-above-options' => 'لطفا ابتدا گزینه های بالا را انتخاب کنید', 'less-quantity' => 'کمیت نمی تواند کمتر از یک باشد.', - 'starting-at' => 'Starting at' + 'starting-at' => 'Starting at', + 'customize-options' => 'Customize Options', + 'choose-selection' => 'Choose a selection', + 'your-customization' => 'Your Customization', + 'total-amount' => 'Total Amount' ], 'wishlist' => [ diff --git a/packages/Webkul/Shop/src/Resources/lang/pt_BR/app.php b/packages/Webkul/Shop/src/Resources/lang/pt_BR/app.php index 889c88211..4f35993a5 100755 --- a/packages/Webkul/Shop/src/Resources/lang/pt_BR/app.php +++ b/packages/Webkul/Shop/src/Resources/lang/pt_BR/app.php @@ -329,7 +329,11 @@ return [ 'view-all' => 'Ver Tudo', 'select-above-options' => 'Por favor, selecione as opções acima primeiro.', 'less-quantity' => 'Quantity can not be less than one.', - 'starting-at' => 'Starting at' + 'starting-at' => 'Starting at', + 'customize-options' => 'Customize Options', + 'choose-selection' => 'Choose a selection', + 'your-customization' => 'Your Customization', + 'total-amount' => 'Total Amount' ], 'wishlist' => [ diff --git a/packages/Webkul/Shop/src/Resources/views/products/view.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view.blade.php index f0560275a..7d4d62f14 100755 --- a/packages/Webkul/Shop/src/Resources/views/products/view.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/products/view.blade.php @@ -48,21 +48,11 @@ {!! view_render_event('bagisto.shop.products.view.quantity.before', ['product' => $product]) !!} @if ($product->getTypeInstance()->showQuantityBox()) -
- - - - - - - - - - @{{ errors.first('quantity') }} -
+ @else @endif + {!! view_render_event('bagisto.shop.products.view.quantity.after', ['product' => $product]) !!} @@ -71,6 +61,8 @@ @include ('shop::products.view.downloadable') @include ('shop::products.view.grouped-products') + + @include ('shop::products.view.bundle-options') {!! view_render_event('bagisto.shop.products.view.description.before', ['product' => $product]) !!} @@ -118,6 +110,20 @@ + + @endpush \ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/view/bundle-options.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view/bundle-options.blade.php new file mode 100644 index 000000000..8dd9f4f1d --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view/bundle-options.blade.php @@ -0,0 +1,217 @@ +@if ($product->type == 'bundle') + + {!! view_render_event('bagisto.shop.products.view.bundle-options.before', ['product' => $product]) !!} + + + + {!! view_render_event('bagisto.shop.products.view.bundle-options.after', ['product' => $product]) !!} + + @push('scripts') + + + + + + @endpush +@endif \ No newline at end of file