Комплект в Simpla
Рассмотри задачу продажи товаров в комплекте.
Комплект состоит из товаров каталога. Выгода заключается в том, что цена комплекта ниже, чем сумма цен товаров купленных отдельно.
Рассмотрим реализацию в Simpla CMS
Реализация состоит в копировании функционала вариантов и сопуствующих товаров.
Нам потребуется:
- Добавить таблицу комлекта и изменить таблицу products;
- Доработать api товаров и добавить методы для работы с комплектами;
- Доработать админку для работы с комплектами;
- Сверстать отображение.
- Изменить оформление заказа
- Обновить автокомплиттер
Создадим таблицу s_products_kit и обновим s_products
CREATE TABLE IF NOT EXISTS `s_products_kit` ( `id` int(11) NOT NULL, `main_product_id` int(11) NOT NULL DEFAULT '0', `product_id` int(11) DEFAULT '0', `variant_id` int(11) DEFAULT NULL, `product_name` varchar(255) NOT NULL DEFAULT '', `variant_name` varchar(255) NOT NULL, `price` float(10,2) NOT NULL DEFAULT '0.00', `price_old` float(10,2) NOT NULL, `amount` int(11) NOT NULL DEFAULT '0', `position` int(11) NOT NULL, `sku` varchar(255) NOT NULL ) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8; ALTER TABLE `s_products_kit` ADD PRIMARY KEY (`id`), ADD KEY `product_id` (`main_product_id`); MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=20; ALTER TABLE `s_products` ADD COLUMN `kit` TINYINT(1) SET DEFAULT 0;
Дополним api
В файле /api/Products.php добавим методы для работы с комплектами:
public function get_products_kit($filter = array()) { $product_id_filter = ''; if(!empty($filter['main_product_id'])) $product_id_filter = $this->db->placehold('AND v.main_product_id in(?@)', (array)$filter['main_product_id']); if(!empty($filter['id'])) $variant_id_filter = $this->db->placehold('AND v.id in(?@)', (array)$filter['id']); if(!$product_id_filter) return array(); $query = $this->db->placehold("SELECT * FROM __products_kit AS v WHERE 1 $product_id_filter ORDER BY v.position "); $this->db->query($query); return $this->db->results(); } public function get_product_kit($id) { if(empty($id)) return false; $query = $this->db->placehold("SELECT * FROM __products_kit WHERE id=?", $id); $this->db->query($query); $product_kit = $this->db->result(); return $product_kit; } public function update_product_kit($id, $product_kit) { $query = $this->db->placehold("UPDATE __products_kit SET ?% WHERE id=? LIMIT 1", $product_kit, intval($id)); $this->db->query($query); return $id; } public function add_product_kit($product_kit) { $query = $this->db->placehold("INSERT INTO __products_kit SET ?%", $product_kit); $this->db->query($query); return $this->db->insert_id(); } public function delete_product_kit($id) { if(!empty($id)) { $query = $this->db->placehold("DELETE FROM __products_kit WHERE id = ? LIMIT 1", intval($id)); $this->db->query($query); } }
и изменим уже имеющиеся:
public function get_products($filter = array()) { ... $visible_filter = ''; $is_featured_filter = ''; $is_kit_filter = ''; //выбираем комплекты ... if(isset($filter['featured'])) $is_featured_filter = $this->db->placehold('AND p.featured=?', intval($filter['featured'])); if(isset($filter['kit'])) $is_kit_filter = $this->db->placehold('AND p.kit=?', intval($filter['kit'])); ... $query = "SELECT p.id, p.url, p.brand_id, p.name, p.annotation, p.body, p.position, p.created as created, p.visible, p.featured, p.meta_title, p.meta_keywords, p.meta_description, b.name as brand, b.url as brand_url, cpp.url as kit_url, cpp.name as kit_name, cpp.id as kit_id FROM __products p $category_id_filter LEFT JOIN __brands b ON p.brand_id = b.id LEFT JOIN __products_kit cp ON cp.main_product_id = p.id LEFT JOIN __products cpp ON cp.product_id = cpp.id WHERE 1 $product_id_filter $brand_id_filter $features_filter $keyword_filter $is_featured_filter $is_kit_filter $discounted_filter $in_stock_filter $visible_filter $group_by ORDER BY $order $sql_limit"; ... } public function count_products($filter = array()) { ... $is_featured_filter = ''; $is_kit_filter = ''; ... if(isset($filter['featured'])) $is_featured_filter = $this->db->placehold('AND p.featured=?', intval($filter['featured'])); if(isset($filter['kit'])) $is_kit_filter = $this->db->placehold('AND p.kit=?', intval($filter['kit'])); ... $query = "SELECT count(distinct p.id) as count FROM __products AS p $category_id_filter WHERE 1 $brand_id_filter $product_id_filter $keyword_filter $is_featured_filter $is_kit_filter $in_stock_filter $discounted_filter $visible_filter $features_filter "; ... } public function get_product($id) { ... $query = "SELECT DISTINCT p.id, p.url, p.brand_id, p.name, p.annotation, p.body, p.position, p.created as created, p.visible, p.featured, p.kit, p.meta_title, p.meta_keywords, p.meta_description, cpp.url as kit_url, cpp.name as kit_name, cpp.id as kit_id FROM __products AS p LEFT JOIN __products_kit cp ON cp.main_product_id = p.id LEFT JOIN __products cpp ON cp.product_id = cpp.id WHERE $filter GROUP BY p.id LIMIT 1"; $this->db->query($query); $product = $this->db->result(); return $product; ... } public function delete_product($id) { ... // Удаляем товар из связанных с другими $query = $this->db->placehold("DELETE FROM __related_products WHERE related_id=?", intval($id)); $this->db->query($query); // Удаляем товары коллекции $collection = $this->get_collection_products($id); foreach($collection as $c) $this->delete_collection_product($id, $c->related_id); // Удаляем товар из коллекции с другими $query = $this->db->placehold("DELETE FROM __products_collection WHERE related_id=?", intval($id)); $this->db->query($query); ... } public function duplicate_product($id) { ... $product->created = null; unset($product->kit_url); unset($product->kit_name); unset($product->kit_id); ... }
Поправим админку
Внесем правки в файл /simpla/ProductAdmin.php:
... // Связанные товары if(is_array($this->request->post('related_products'))) { foreach($this->request->post('related_products') as $p) { $rp[$p] = new stdClass; $rp[$p]->product_id = $product->id; $rp[$p]->related_id = $p; } $related_products = $rp; } $product->kit = 0; // Товары комплект $kit = $this->request->post('kit'); if(!empty($kit)) { $product->kit = 1;// Товар является комплектом if(is_array($this->request->post('kit'))) { foreach($this->request->post('kit') as $n=>$ki) { foreach($ki as $i=>$v) { if(empty($kit[$i])) $kit[$i] = new stdClass; $kit_temp[$i]->$n = $v; } } } } $kit = $kit_temp; ... // Связанные товары $query = $this->db->placehold('DELETE FROM __related_products WHERE product_id=?', $product->id); $this->db->query($query); if(is_array($related_products)) { $pos = 0; foreach($related_products as $i=>$related_product) $this->products->add_related_product($product->id, $related_product->related_id, $pos++); } // Комплект if(!empty($kit)) { $kit_ids = array(); foreach($kit as $index=>&$kit_product) { if(!empty($kit_product->id)) $this->products->update_product_kit($kit_product->id, $kit_product); else { $kit_product->main_product_id = $product->id; $kit_product->id = $this->products->add_product_kit($kit_product); } $kit_product = $this->products->get_product_kit($kit_product->id); if(!empty($kit_product->id)) $kit_product_ids[] = $kit_product->id; } // Удалить непереданные товары комплекта $current_products_kit = $this->products->get_products_kit(array('main_product_id'=>$product->id)); foreach($current_products_kit as $current_product_kit) if(!in_array($current_product_kit->id, $kit_product_ids)) $this->products->delete_product_kit($current_product_kit->id); // Отсортировать варианты asort($kit_product_ids); $i = 0; foreach($kit_product_ids as $kit_id) { $this->products->update_product_kit($kit_product_ids[$i], array('position'=>$kit_id)); $i++; } } ... if(is_array($options)) { ... } // Товары комплета $products_kit = $this->products->get_products_kit(array('main_product_id'=>$product->id)); if(!empty($products_kit)) { foreach($products_kit as &$p_k) $p_kit[$p_k->product_id] = &$p_k; $temp_variants = $this->variants->get_variants(array('product_id'=>array_keys($p_kit))); foreach($temp_variants as $temp_variant) $p_kit[$temp_variant->product_id]->variants[] = $temp_variant; } ... $this->design->assign('related_products', $related_products); $this->design->assign('products_kit', $products_kit); ...
Поправим шаблон отображения /simpla/design/html/product.tpl
$(function() { ... $('#set_checkbox:checkbox').change(function() { if ( $(this).attr("checked") ) { $('#set_block').show(); } else { $('#set_block').hide(); } }); // Добавление товара в комплект var new_set = $('#new_set').clone(true).on('keyup', price_calc); $('#new_set').remove().removeAttr('id'); $("input#set_products").autocomplete({ serviceUrl:'ajax/search_products.php', minChars:0, noCache: false, onSelect: function(suggestion){ $("input#set_products").val('').focus().blur(); new_item = $(new_set).clone(true).appendTo('#kits').fadeIn('slow'); new_item.find('input[name="kit[product_name][]"]').val(suggestion.data.name); new_item.find('input[name="kit[product_id][]"]').val(suggestion.data.id); // Сумма для комплекта $('li.variant_discount > input').val( function(i, oldval) { if(isNaN(parseFloat(oldval))) { oldval = 0; } return parseFloat(oldval) + parseFloat(suggestion.data.price); }); $.get( 'ajax/variants_products.php', { product_id: suggestion.data.id }).done(function( data ) { new_item.find('select[name="kit[variant_id][]"]').append(data); }); new_item.find('input[name="kit[price][]"]').val(suggestion.data.price); new_item.find('input[name="kit[price_old][]"]').val(suggestion.data.price); }, formatResult: function(suggestions, currentValue){ var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'); var pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')'; return (suggestions.data.image?"<img align=absmiddle src='"+suggestions.data.image+"'> ":'') + suggestions.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>'); } }); // Удаление товара из комплекта $('a.del_kit').click(function() { if($("#kits ul").size()>0) { $(this).closest("ul").fadeOut(200, function() { $(this).remove(); }); } return false; }); $('#kits > ul').on('keyup', price_calc); ... }); ... function price_calc(event) { var variant_price = 0; var variant_discount = 0; var kit_price_old = 0; var kit_amount = 0; var kit_price = 0; if(event.srcElement.name === 'kit[price][]' || event.srcElement.name === 'kit[amount][]') { $(event.target).closest('div').find('ul').each(function() { kit_price_old = $(this).find('[name="kit[price_old][]"]').val(); kit_amount = $(this).find('[name="kit[amount][]"]').val(); kit_price = $(this).find('[name="kit[price][]"]').val(); variant_price += kit_amount * kit_price; variant_discount += kit_amount * kit_price_old; }); } // Сумма для комплекта $('li.variant_price > input').val(variant_price); $('li.variant_discount > input').val(variant_discount); } </script> ... <div class="checkbox"> <input name=featured value="1" type="checkbox" id="featured_checkbox" {if $product->featured}checked{/if}/> <label for="featured_checkbox">Рекомендуемый</label> </div> <div class="checkbox"> <input name=kit value='1' type="checkbox" id="set_checkbox" {if $product->kit}checked{/if}/> <label for="set_checkbox">Комплект</label> </div> ... <!-- Варианты товара --> ... <!-- Варианты товара (The End)--> <!-- Комплект товара --> <div id="set_block" {if !$product->kit}style='display:none;'{/if}> <ul id="header"> <li class="set_move"></li> <li class="set_name">Название варианта</li> <li class="set_sku">Вариант</li> <li class="set_price">Цена, {$currency->sign}</li> <li class="set_amount">Кол-во</li> </ul> <div id="kits"> {foreach from=$products_kit item=kit} <ul> <li class="set_move"><div class="move_zone"></div></li> <li class="set_name"> <input name="kit[id][]" type="hidden" value="{$kit->id|escape}" /> <input name="kit[product_name][]" type="" value="{$kit->product_name|escape}" /> <input name="kit[product_id][]" type="hidden" value="{$kit->product_id|escape}" /> <a class="del_kit" href=""><img src="design/images/cross-circle-frame.png" alt="" /></a> </li> <li class="set_sku"> <select name="kit[variant_id][]"> {foreach from=$kit->variants item=kit_variant} <option value="{$kit_variant->id}">{if $kit_variant->name}{$kit_variant->name}{else}{$kit_variant->id}{/if}</option> {/foreach} </select> </li> <li class="set_price"> <input name="kit[price][]" type="text" value="{$kit->price|escape}" /> <input name="kit[price_old][]" type="hidden" value="{$kit->price_old|escape}" /> </li> <li class="set_amount"><input name="kit[amount][]" type="text" value="{if $kit->amount|escape}{$kit->amount|escape}{else}0{/if}" />{$settings->units}</li> </ul> {/foreach} </div> <ul id=new_set style='display:none;'> <li class="variant_move"><div class="move_zone"></div></li> <li class="set_name"> <input name="kit[product_name][]" type="" value="" /> <input name="kit[product_id][]" type="hidden" value="" /> <a class="del_kit" href=""><img src="design/images/cross-circle-frame.png" alt="" /></a> </li> <li class="set_sku"> <select name="kit[variant_id][]"> </select> </li> <li class="set_price"> <input name="kit[price][]" type="text" value="" /> <input name="kit[price_old][]" type="hidden" value="" /> </li> <li class="set_amount"><input name="kit[amount][]" type="text" value="1" />{$settings->units}</li> </ul> <input class="button_green button_save" type="submit" name="" value="Сохранить" /> <span class="add_kit" ><input type=text name=collection_sug id='set_products' class="input_autocomplete" placeholder='Выберите товар чтобы добавить его' /></span> </div> <!-- Комплект товара (The End)-->
Добавим загрузку варианта. Создаем файл /simpla/ajax/variants_products.php
<?php require_once('../../api/Simpla.php'); $simpla = new Simpla(); $limit = 100; $product_id = $simpla->request->get('product_id', 'integer'); $simpla->db->query('SELECT * FROM __variants WHERE product_id = ?', $product_id); $variants = $simpla->db->results(); $options = ""; foreach($variants as $variant) { $options .= "<option value='".$variant->id."' data-price='".$variant->price."'>".($variant->name?$variant->name:$variant->id)."</option>"; } header("Content-type: application/json; charset=UTF-8"); header("Cache-Control: must-revalidate"); header("Pragma: no-cache"); header("Expires: -1"); print json_encode($options);
Изменить файл /simpla/ajax/search_products.php
... $simpla->db->query('SELECT p.id, p.name, i.filename as image, v.price FROM __products p LEFT JOIN __images i ON i.product_id=p.id AND i.position=(SELECT MIN(position) FROM __images WHERE product_id=p.id LIMIT 1) LEFT JOIN __variants v ON v.product_id = p.id WHERE p.name LIKE "%'.mysql_real_escape_string($keyword).'%" ORDER BY p.name LIMIT ?', $limit); $products = $simpla->db->results(); ...
Поправим стили /simpla/design/css/style.css
/* Комплект товара */ #set_block { background-color: #f0f0f0; border: 1px dotted #d0d0d0; clear: both; overflow: hidden; padding-top: 15px; padding-bottom: 15px; margin-bottom: 20px; width: 940px; } #set_block ul { overflow: hidden; padding-bottom: 2px; padding-left:20px; padding-top: 2px; clear: left; } #set_block ul#header { height: 20px; margin-bottom: 0; padding-bottom: 0; } #set_block ul#header li { font-size: 13px; padding-left: 3px; margin-left: -2px; } #set_block ul li { float: left; display: block; height: 31px; overflow: hidden; } #set_block ul li input { height: 20px; font-size: 14px; color: #636363; margin-top: 0px; padding-top: 2px; margin-left: 2px; } #set_block li.set_move { width: 20px; height:16px; padding-top: 5px;} #set_block li.set_name { width: 270px; padding-left: 5px; } #set_block li.set_name input { font-size: 16px; color: #000; width: 220px; float: left; margin-right: 2px; } #set_block li.set_name a.del_variant { display: block; padding-top: 6px; } #set_block li.set_sku { width: 120px; } #set_block li.set_sku input { width: 95px; } #set_block li.set_price { width: 120px; } #set_block li.set_price input { width: 95px; font-size: 15px; font-weight: bold; color: #000; } #set_block li.set_discount { width: 95px; } #set_block li.set_discount input { width: 70px; } #set_block li.set_amount { width: 90px; font-size: 11px; } #set_block li.set_amount input { width: 35px; margin-right: 2px; } #set_block li.set_download { width: 180px;} #set_block li.set_download input{ width: 170px; font-size: 9px; margin-top: 2px;} #set_block li.set_download .add_attachment img{ margin-top: 4px;} #set_block span.add { margin-left: 30px; clear: left; margin-top: 5px; display: block; background-repeat: no-repeat; padding-left: 20px; background-image: url(../images/plus-circle.png); } #set_block .variant_move div{ width:20px; height: 16px; } #set_block span.add_kit { margin-left: 8px; clear: left; margin-top: 5px; display: block; background-repeat: no-repeat; padding-left: 40px; }
Отображаем комплект
Отображение карточки комплекта /view/ProductView.php
if(!empty($related_ids)) { ... } // Товары коллекции $products_kit = $this->products->get_products_kit(array('main_product_id'=>$product->id)); if(!empty($products_kit)) { foreach($products_kit as &$p_k) $p_kit[$p_k->product_id] = &$p_k; $temp_products = $this->products->get_products(array('id'=>array_keys($p_kit))); foreach($temp_products as $temp_product) { $p_kit[$temp_product->id]->url = $temp_product->url; $p_kit[$temp_product->id]->name = $temp_product->name; } $temp_images = $this->products->get_images(array('product_id'=>array_keys($p_kit))); foreach($temp_images as $temp_image) { $p_kit[$temp_image->product_id]->image = $temp_image->filename; } }
заменить
return $this->design->fetch('product.tpl');
на
if($product->kit) { $this->design->assign('products_kit', $products_kit); return $this->design->fetch('products_kit.tpl'); } else { return $this->design->fetch('product.tpl'); }
Шаблон отображения /public_html/design/spy-tv/html/products_kit.tpl
{foreach name=products_kit item=product_kit from=$products_kit} <div class="product"> <!-- Фото товара --> <div class="image"> {if $product_kit->image} <a href="/products/{$product_kit->url}"><img src="{$product_kit->image|resize:35:35}" alt="{$product_kit->name|escape}"/></a> {else} <a href="/products/{$product_kit->url}"><img src="design/{$settings->theme|escape}/images/nofoto.gif"/></a> {/if} </div> <!-- Фото товара (The End) --> <!-- Название товара --> <h4><a data-product="{$product_kit->id}" href="products/{$product_kit->url}">{$product_kit->name|escape}</a></h4> <!-- Название товара (The End) --> <!-- Цена товара --> <table> <tr class="variant"> <td> {if $product_kit->price_old > 0}<span class="compare_price">{$product_kit->price_old|convert}</span>{/if} <span class="price">{$product_kit->price|convert} <span class="currency">{$currency->sign|escape}</span></span> </td> </tr> </table> <!-- Цена товара (The End) --> </div> {/foreach}
Заказ комплекта
Далее код описан для измененной работы корзины, код требует доработки для работы с simpla из коробки /public_html/view/CartView.php
// Старая реализация Добавляем товары к заказу /*foreach($this->request->post('amounts') as $variant_id=>$amount) { $this->orders->add_purchase(array('order_id'=>$order_id, 'variant_id'=>intval($variant_id), 'amount'=>intval($amount))); }*/ // Добавляем товары к заказу foreach($this->request->post('amounts') as $variant_id=>$amount) { if($product->kit) //Если среди товаров есть комплект, то в покупку мы кладем товары комплекта { $products_kit = $this->products->get_products_kit(array('main_product_id'=>$product->id)); foreach($products_kit as $p_k) { $this->orders->add_purchase(array('order_id'=>$order_id, 'variant_id'=>intval($p_k->variant_id), 'amount'=>($amount * intval($p_k->amount)), 'price'=>intval($p_k->price))); } } else { $this->orders->add_purchase(array('order_id'=>$order_id, 'variant_id'=>intval($variant_id), 'amount'=>intval($amount))); } }