Bundle add to cart added
This commit is contained in:
parent
7352a97797
commit
557cbb1db1
|
|
@ -221,11 +221,6 @@
|
|||
|
||||
inject: ['$validator'],
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
titleInputName: function () {
|
||||
if (this.option.id)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace Webkul\Product\Helpers;
|
||||
|
||||
/**
|
||||
* Bundle Option Helper
|
||||
*
|
||||
* @author Jitendra Singh <jitendra@webkul.com>
|
||||
* @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())
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ class ConfigurableOption extends AbstractProduct
|
|||
* Returns the allowed variants JSON
|
||||
*
|
||||
* @param Product $product
|
||||
* @return float
|
||||
* @return array
|
||||
*/
|
||||
public function getConfigurationConfig($product)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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' => [
|
||||
|
|
|
|||
|
|
@ -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' => [
|
||||
|
|
|
|||
|
|
@ -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' => [
|
||||
|
|
|
|||
|
|
@ -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' => [
|
||||
|
|
|
|||
|
|
@ -48,21 +48,11 @@
|
|||
{!! view_render_event('bagisto.shop.products.view.quantity.before', ['product' => $product]) !!}
|
||||
|
||||
@if ($product->getTypeInstance()->showQuantityBox())
|
||||
<div class="quantity control-group" :class="[errors.has('quantity') ? 'has-error' : '']">
|
||||
|
||||
<label class="required">{{ __('shop::app.products.quantity') }}</label>
|
||||
|
||||
<input class="control quantity-change" value="-" style="width: 35px; border-radius: 3px 0px 0px 3px;" onclick="updateQunatity('remove')" readonly>
|
||||
|
||||
<input name="quantity" id="quantity" class="control quantity-change" value="1" v-validate="'required|numeric|min_value:1'" style="width: 60px; position: relative; margin-left: -4px; margin-right: -4px; border-right: none;border-left: none; border-radius: 0px;" data-vv-as=""{{ __('shop::app.products.quantity') }}"" readonly>
|
||||
|
||||
<input class="control quantity-change" value="+" style="width: 35px; padding: 0 12px; border-radius: 0px 3px 3px 0px;" onclick=updateQunatity('add') readonly>
|
||||
|
||||
<span class="control-error" v-if="errors.has('quantity')">@{{ errors.first('quantity') }}</span>
|
||||
</div>
|
||||
<quantity-changer></quantity-changer>
|
||||
@else
|
||||
<input type="hidden" name="quantity" value="1">
|
||||
@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 @@
|
|||
</form>
|
||||
</script>
|
||||
|
||||
<script type="text/x-template" id="quantity-changer-template">
|
||||
<div class="quantity control-group" :class="[errors.has(controlName) ? 'has-error' : '']">
|
||||
<label class="required">{{ __('shop::app.products.quantity') }}</label>
|
||||
|
||||
<button type="button" class="decrease" @click="decreaseQty()">-</button>
|
||||
|
||||
<input :name="controlName" class="control" :value="qty" v-validate="'required|numeric|min_value:1'" data-vv-as=""{{ __('shop::app.products.quantity') }}"" readonly>
|
||||
|
||||
<button type="button" class="increase" @click="increaseQty()">+</button>
|
||||
|
||||
<span class="control-error" v-if="errors.has(controlName)">@{{ errors.first(controlName) }}</span>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
Vue.component('product-view', {
|
||||
|
|
@ -128,7 +134,7 @@
|
|||
|
||||
data: function() {
|
||||
return {
|
||||
is_buy_now: 0
|
||||
is_buy_now: 0,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -148,13 +154,59 @@
|
|||
setTimeout(function() {
|
||||
document.getElementById('product-form').submit();
|
||||
}, 0);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('quantity-changer', {
|
||||
template: '#quantity-changer-template',
|
||||
|
||||
inject: ['$validator'],
|
||||
|
||||
props: {
|
||||
controlName: {
|
||||
type: String,
|
||||
default: 'quantity'
|
||||
},
|
||||
|
||||
quantity: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
qty: this.quantity
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
quantity: function (val) {
|
||||
this.qty = val;
|
||||
|
||||
this.$emit('onQtyUpdated', this.qty)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
decreaseQty: function() {
|
||||
if (this.qty > 1)
|
||||
this.qty = parseInt(this.qty) - 1;
|
||||
|
||||
this.$emit('onQtyUpdated', this.qty)
|
||||
},
|
||||
|
||||
increaseQty: function() {
|
||||
this.qty = parseInt(this.qty) + 1;
|
||||
|
||||
this.$emit('onQtyUpdated', this.qty)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
var addTOButton = document.getElementsByClassName('add-to-buttons')[0];
|
||||
document.getElementById('loader').style.display="none";
|
||||
|
|
@ -196,23 +248,5 @@
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
function updateQunatity(operation) {
|
||||
var quantity = document.getElementById('quantity').value;
|
||||
|
||||
if (operation == 'add') {
|
||||
quantity = parseInt(quantity) + 1;
|
||||
} else if (operation == 'remove') {
|
||||
if (quantity > 1) {
|
||||
quantity = parseInt(quantity) - 1;
|
||||
} else {
|
||||
alert('{{ __('shop::app.products.less-quantity') }}');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("quantity").value = quantity;
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
@if ($product->type == 'bundle')
|
||||
|
||||
{!! view_render_event('bagisto.shop.products.view.bundle-options.before', ['product' => $product]) !!}
|
||||
|
||||
<bundle-option-list></bundle-option-list>
|
||||
|
||||
{!! view_render_event('bagisto.shop.products.view.bundle-options.after', ['product' => $product]) !!}
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/x-template" id="bundle-option-list-template">
|
||||
<div class="bundle-options-wrapper">
|
||||
<div class="bundle-option-list">
|
||||
<h3>{{ __('shop::app.products.customize-options') }}</h3>
|
||||
|
||||
<bundle-option-item
|
||||
v-for="(option, index) in options"
|
||||
:option="option"
|
||||
:key="index"
|
||||
:index="index"
|
||||
@onProductSelected="productSelected(option, $event)">
|
||||
</bundle-option-item>
|
||||
</div>
|
||||
|
||||
<div class="bundle-summary">
|
||||
<h3>{{ __('shop::app.products.your-customization') }}</h3>
|
||||
|
||||
<quantity-changer></quantity-changer>
|
||||
|
||||
<div class="control-group">
|
||||
<label>{{ __('shop::app.products.total-amount') }}</label>
|
||||
|
||||
<div class="bundle-price">
|
||||
@{{ formated_total_price | currency(currency_options) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="bundle-items">
|
||||
<li v-for="(option, index) in options">
|
||||
@{{ option.label }}
|
||||
|
||||
<div class="selected-products">
|
||||
<div v-for="(product, index1) in option.products" v-if="product.is_default">
|
||||
@{{ product.qty + ' x ' + product.name }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-template" id="bundle-option-item-template">
|
||||
<div class="bundle-option-item">
|
||||
<div class="control-group" :class="[errors.has('bundle_options[' + option.id + '][]') ? 'has-error' : '']">
|
||||
<label class="required">@{{ option.label }}</label>
|
||||
|
||||
<div v-if="option.type == 'select'">
|
||||
<select class="control" :name="'bundle_options[' + option.id + '][]'" v-model="selected_product" v-validate="'required'" :data-vv-as="option.label + '"'">
|
||||
<option value="">{{ __('shop::app.products.choose-selection') }}</option>
|
||||
<option v-for="(product, index2) in option.products" :value="product.id">
|
||||
@{{ product.name + ' + ' + product.price.final_price.formated_price }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if="option.type == 'radio'">
|
||||
<span class="radio" v-for="(product, index2) in option.products">
|
||||
<input type="radio" :name="'bundle_options[' + option.id + '][]'" v-model="selected_product" v-validate="'required'" :data-vv-as="'"' + option.label + '"'" :value="product.id" :id="'bundle_options[' + option.id + '][]'">
|
||||
<label class="radio-view" :for="'bundle_options[' + option.id + '][]'"></label>
|
||||
|
||||
@{{ product.name }}
|
||||
|
||||
<span class="price">
|
||||
+ @{{ product.price.final_price.formated_price }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="option.type == 'checkbox'">
|
||||
<span class="checkbox" v-for="(product, index2) in option.products">
|
||||
<input type="checkbox" :name="'bundle_options[' + option.id + '][]'" :value="product.id" v-model="selected_product" v-validate="'required'" :data-vv-as="'"' + option.label + '"'" :id="'bundle_options[' + option.id + '][]'">
|
||||
<label class="checkbox-view" :for="'bundle_options[' + option.id + '][]'"></label>
|
||||
|
||||
@{{ product.name }}
|
||||
|
||||
<span class="price">
|
||||
+ @{{ product.price.final_price.formated_price }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="option.type == 'multiselect'">
|
||||
<select class="control" :name="'bundle_options[' + option.id + '][]'" v-model="selected_product" v-validate="'required'" :data-vv-as="'"' + option.label + '"'" multiple>
|
||||
<option v-for="(product, index2) in option.products" :value="product.id">
|
||||
@{{ product.name + ' + ' + product.price.final_price.formated_price }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<span class="control-error" v-if="errors.has('bundle_options[' + option.id + '][]')">
|
||||
@{{ errors.first('bundle_options[' + option.id + '][]') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="option.type == 'select' || option.type == 'radio'">
|
||||
<quantity-changer
|
||||
:control-name="'bundle_option_qty[' + option.id + ']'"
|
||||
:quantity="product_qty"
|
||||
@onQtyUpdated="qtyUpdated($event)">
|
||||
</quantity-changer>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
Vue.component('bundle-option-list', {
|
||||
|
||||
template: '#bundle-option-list-template',
|
||||
|
||||
inject: ['$validator'],
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
config: @json(app('Webkul\Product\Helpers\BundleOption')->getBundleConfig($product)),
|
||||
|
||||
options: [],
|
||||
|
||||
currency_options: @json(core()->getAccountJsSymbols())
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
formated_total_price: function() {
|
||||
var total = 0;
|
||||
|
||||
for (var key in this.options) {
|
||||
for (var key1 in this.options[key].products) {
|
||||
if (! this.options[key].products[key1].is_default)
|
||||
continue;
|
||||
|
||||
total += this.options[key].products[key1].qty * this.options[key].products[key1].price.final_price.price;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
},
|
||||
|
||||
created: function() {
|
||||
for (var key in this.config.options) {
|
||||
this.options.push(this.config.options[key])
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
productSelected: function(option, value) {
|
||||
var selectedProductIds = Array.isArray(value) ? value : [value];
|
||||
|
||||
for (var key in option.products) {
|
||||
option.products[key].is_default = selectedProductIds.indexOf(option.products[key].id) > -1 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('bundle-option-item', {
|
||||
template: '#bundle-option-item-template',
|
||||
|
||||
props: ['index', 'option'],
|
||||
|
||||
inject: ['$validator'],
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
selected_product: (this.option.type == 'checkbox' || this.option.type == 'multiselect') ? [] : null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
product_qty: function() {
|
||||
return this.option.products[this.selected_product]
|
||||
? this.option.products[this.selected_product].qty
|
||||
: 0;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
selected_product: function (value) {
|
||||
this.$emit('onProductSelected', value)
|
||||
}
|
||||
},
|
||||
|
||||
created: function() {
|
||||
for (var key1 in this.option.products) {
|
||||
if (! this.option.products[key1].is_default)
|
||||
continue;
|
||||
|
||||
if (this.option.type == 'checkbox' || this.option.type == 'multiselect') {
|
||||
this.selected_product.push(this.option.products[key1].id)
|
||||
} else {
|
||||
this.selected_product = this.option.products[key1].id
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
qtyUpdated: function(qty) {
|
||||
if (! this.option.products[this.selected_product])
|
||||
return;
|
||||
|
||||
this.option.products[this.selected_product].qty = qty;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endif
|
||||
Loading…
Reference in New Issue