first commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
|
||||
class GlobalDiscounts extends Window
|
||||
{
|
||||
protected $tableName = 'global_discounts';
|
||||
|
||||
/** @var \KupShop\GlobalDiscountsBundle\Util\GlobalDiscounts */
|
||||
private $globalDiscounts;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->globalDiscounts = ServiceContainer::getService(\KupShop\GlobalDiscountsBundle\Util\GlobalDiscounts::class);
|
||||
}
|
||||
|
||||
public function get_vars()
|
||||
{
|
||||
$vars = parent::get_vars();
|
||||
|
||||
if (isset($vars['body']['data']['filter'])) {
|
||||
$vars['body']['data']['filter'] = json_decode($vars['body']['data']['filter'], true);
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
$data = parent::getData();
|
||||
|
||||
if (getVal('Submit')) {
|
||||
$data['date_from'] = $this->prepareDateTime($data['date_from']);
|
||||
$data['date_to'] = $this->prepareDateTime($data['date_to']);
|
||||
$data['filter'] = json_encode(getVal('filter'));
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function handleUpdate()
|
||||
{
|
||||
if ($this->getAction() != 'add') {
|
||||
$discount = $this->getDiscount();
|
||||
// deactivate discount before update
|
||||
// discount filter can be changed so we need to deactivate discounts that are using old filter
|
||||
if (($discount['active'] ?? false) == 1) {
|
||||
$this->globalDiscounts->deactivateDiscount($discount);
|
||||
}
|
||||
}
|
||||
|
||||
$SQL = parent::handleUpdate();
|
||||
|
||||
if ($SQL) {
|
||||
$this->globalDiscounts->activateDiscounts();
|
||||
clearCache('insert_products', true);
|
||||
}
|
||||
|
||||
return $SQL;
|
||||
}
|
||||
|
||||
public function handleDelete()
|
||||
{
|
||||
$discount = $this->getDiscount();
|
||||
// deactivate discount before delete
|
||||
if (($discount['active'] ?? false) == 1) {
|
||||
$this->globalDiscounts->deactivateDiscount($discount);
|
||||
clearCache('insert_products', true);
|
||||
}
|
||||
|
||||
parent::handleDelete();
|
||||
}
|
||||
|
||||
private function getDiscount()
|
||||
{
|
||||
$discount = $this->getObject();
|
||||
$discount['filter'] = json_decode($discount['filter'] ?? '', true);
|
||||
|
||||
return $discount;
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalDiscounts::class;
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
$txt_str['GlobalDiscounts'] = [
|
||||
'toolbar_list' => 'Globální slevy',
|
||||
'toolbar_add' => 'Přidat globální slevu',
|
||||
|
||||
'titleAdd' => 'Přidat globální slevu',
|
||||
'titleEdit' => 'Upravit globální slevu',
|
||||
|
||||
'GlobalDiscount' => 'Globální sleva',
|
||||
|
||||
'name' => 'Název',
|
||||
'discount' => 'Sleva',
|
||||
'date_from' => 'Datum od',
|
||||
'date_to' => 'Datum do',
|
||||
];
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use KupShop\AdminBundle\AdminList\BaseList;
|
||||
|
||||
class GlobalDiscountsList extends BaseList
|
||||
{
|
||||
use AdminListSortable;
|
||||
|
||||
protected $template = 'list/GlobalDiscounts.tpl';
|
||||
protected $tableDef = [
|
||||
'id' => 'id',
|
||||
'fields' => [
|
||||
'Pořadí' => ['field' => 'position', 'render' => 'renderPosition', 'size' => '70px'],
|
||||
'Název' => ['field' => 'name'],
|
||||
'Sleva' => ['field' => 'discount', 'render' => 'renderDiscount'],
|
||||
'Datum od' => ['field' => 'date_from', 'render' => 'renderDateTime'],
|
||||
'Datum do' => ['field' => 'date_to', 'render' => 'renderDateTime'],
|
||||
'Aktivní' => ['field' => 'active', 'render' => 'renderBoolean'],
|
||||
],
|
||||
];
|
||||
protected $pageDivide = 999999;
|
||||
|
||||
public function renderDiscount($values, $column)
|
||||
{
|
||||
$discount = toDecimal($values['discount']);
|
||||
|
||||
return $discount->printFloatValue(-2).' %';
|
||||
}
|
||||
|
||||
public function handleRecalculate()
|
||||
{
|
||||
$globalDiscounts = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService(\KupShop\GlobalDiscountsBundle\Util\GlobalDiscounts::class);
|
||||
|
||||
$globalDiscounts->activateDiscounts();
|
||||
$globalDiscounts->deactivateDiscounts();
|
||||
$globalDiscounts->recalculateDiscounts();
|
||||
|
||||
clearCache('insert_products', true);
|
||||
|
||||
$this->returnOK('Přepočet proveden');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
$item = getVal('moved_item');
|
||||
|
||||
if (!empty($item)) {
|
||||
$this->saveList($item, 'global_discounts', 'position');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public function getQuery()
|
||||
{
|
||||
$qb = sqlQueryBuilder()->select('*')
|
||||
->from('global_discounts');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalDiscountsList::class;
|
||||
@@ -0,0 +1,7 @@
|
||||
{extends "[shared]/listSortable.tpl"}
|
||||
|
||||
{block buttons}
|
||||
<a href="launch.php?s=list.php&type=GlobalDiscounts&acn=recalculate"
|
||||
class="btn btn-danger"
|
||||
title="Provede kompletní přepočet slev, který se jinak provádí jednou denně o půlnoci.">Provést přepočet slev</a>
|
||||
{/block}
|
||||
@@ -0,0 +1,57 @@
|
||||
{extends "[shared]window.tpl"}
|
||||
|
||||
{block tabs}
|
||||
{windowTab id='flapGlobalDiscount' label=translate('GlobalDiscount')}
|
||||
{/block}
|
||||
|
||||
{block tabsContent}
|
||||
<div id="flapGlobalDiscount" class="tab-pane fade active in boxStatic">
|
||||
<h1 class="h4 main-panel-title">{'GlobalDiscount'|translate}</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'name'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control input-sm" name="data[name]" value="{$body.data.name}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'discount'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="data[discount]" value="{$body.data.discount}" required>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'date_from'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input autocomplete="off" id="date_from" type="text" class="form-control input-sm" name="data[date_from]" value="{$body.data.date_from|format_date:'d.n.Y H:i:s'}" required>
|
||||
{insert_calendar selector='#date_from' format='datetime'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'date_to'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input autocomplete="off" id="date_to" type="text" class="form-control input-sm" name="data[date_to]" value="{$body.data.date_to|format_date:'d.n.Y H:i:s'}" required>
|
||||
{insert_calendar selector='#date_to' format='datetime'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="discount_filter">
|
||||
{$filter = $body.data.filter}
|
||||
{include "block.productsFilter.tpl"}
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GlobalDiscountsBundle\AdminRegister;
|
||||
|
||||
use KupShop\AdminBundle\AdminRegister\AdminRegister;
|
||||
use KupShop\AdminBundle\AdminRegister\IAdminRegisterStatic;
|
||||
|
||||
class GlobalDiscountsAdminRegister extends AdminRegister implements IAdminRegisterStatic
|
||||
{
|
||||
public static function getMenu(): array
|
||||
{
|
||||
return [
|
||||
static::createMenuItem('productsMenu', [
|
||||
'name' => 'GlobalDiscounts',
|
||||
'left' => 's=menu.php&type=GlobalDiscounts', 'right' => 's=list.php&type=GlobalDiscounts',
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPermissions(): array
|
||||
{
|
||||
return [
|
||||
static::createPermissions('GlobalDiscounts', [\Modules::GLOBAL_DISCOUNTS], ['GLOBAL_DISCNT']),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GlobalDiscountsBundle\EventListener;
|
||||
|
||||
use KupShop\GlobalDiscountsBundle\Util\GlobalDiscounts;
|
||||
use KupShop\KupShopBundle\Event\CronEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class CronListener implements EventSubscriberInterface
|
||||
{
|
||||
private $globalDiscounts;
|
||||
|
||||
public function __construct(GlobalDiscounts $globalDiscounts)
|
||||
{
|
||||
$this->globalDiscounts = $globalDiscounts;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
CronEvent::RUN_EXPENSIVE => [
|
||||
['expensive', 200],
|
||||
],
|
||||
CronEvent::RUN_FREQUENT => [
|
||||
['frequent', 200],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function frequent()
|
||||
{
|
||||
$this->globalDiscounts->activateDiscounts();
|
||||
$this->globalDiscounts->deactivateDiscounts();
|
||||
}
|
||||
|
||||
public function expensive()
|
||||
{
|
||||
$this->globalDiscounts->activateDiscounts();
|
||||
$this->globalDiscounts->deactivateDiscounts();
|
||||
$this->globalDiscounts->recalculateDiscounts();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GlobalDiscountsBundle\EventListener;
|
||||
|
||||
use KupShop\CatalogBundle\Event\ProductEvent;
|
||||
use KupShop\GlobalDiscountsBundle\Util\GlobalDiscounts;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class ProductEventListener implements EventSubscriberInterface
|
||||
{
|
||||
private $globalDiscounts;
|
||||
|
||||
public function __construct(GlobalDiscounts $globalDiscounts)
|
||||
{
|
||||
$this->globalDiscounts = $globalDiscounts;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
ProductEvent::PRODUCT_CREATED => [
|
||||
['onProductCreated', 200],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function onProductCreated(ProductEvent $event)
|
||||
{
|
||||
$this->globalDiscounts->applyDiscountsOnProducts([$event->getProduct()->id]);
|
||||
|
||||
// TODO: tmp logging
|
||||
$logger = ServiceContainer::getService('logger');
|
||||
$logger->notice('GlobalDiscounts: New product created', ['product' => $event->getProduct()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GlobalDiscountsBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class GlobalDiscountsBundle extends Bundle
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: true
|
||||
|
||||
KupShop\GlobalDiscountsBundle\:
|
||||
resource: ../../{AdminRegister,EventListener,View,Util,Controller}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GlobalDiscountsBundle\Resources\upgrade;
|
||||
|
||||
class GlobalDiscountsUpgrade extends \UpgradeNew
|
||||
{
|
||||
public function check_tableExists()
|
||||
{
|
||||
return $this->checkTableExists('global_discounts');
|
||||
}
|
||||
|
||||
/** Create table 'global_discounts' */
|
||||
public function upgrade_tableExists()
|
||||
{
|
||||
sqlQuery('CREATE TABLE IF NOT EXISTS global_discounts (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
position INT DEFAULT NULL,
|
||||
name VARCHAR(60) NOT NULL,
|
||||
discount DECIMAL(15,4) DEFAULT 0,
|
||||
filter MEDIUMTEXT DEFAULT NULL,
|
||||
date_from DATETIME NOT NULL,
|
||||
date_to DATETIME NOT NULL,
|
||||
active TINYINT DEFAULT 0,
|
||||
PRIMARY KEY(id)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ENGINE = InnoDB;
|
||||
');
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
|
||||
public function check_DateNullable()
|
||||
{
|
||||
return $this->checkColumnIsNull('global_discounts', 'date_from', true);
|
||||
}
|
||||
|
||||
/** 'global_discounts': enable null values in date columns */
|
||||
public function upgrade_DateNullable()
|
||||
{
|
||||
sqlQuery('ALTER TABLE global_discounts MODIFY date_from DATETIME DEFAULT NULL');
|
||||
sqlQuery('ALTER TABLE global_discounts MODIFY date_to DATETIME DEFAULT NULL');
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
{
|
||||
"global_discounts": [
|
||||
{
|
||||
"id": 1,
|
||||
"position": 1,
|
||||
"name": "Activatable discount 1",
|
||||
"discount": "22.0000",
|
||||
"filter": "{\"producers\":[\"10\"],\"parametersValuesOperator\":\"AND\",\"price\":{\"min\":\"\",\"max\":\"\"},\"discount\":{\"min\":\"\",\"max\":\"\"}}",
|
||||
"date_from": "2019-03-12 00:00:00",
|
||||
"date_to": "2019-03-16 00:00:00",
|
||||
"active": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"position": 0,
|
||||
"name": "Activatable discount 2",
|
||||
"discount": "15.0000",
|
||||
"filter": "{\"producers\":[\"10\", \"12\"],\"parametersValuesOperator\":\"AND\",\"price\":{\"min\":\"\",\"max\":\"\"},\"discount\":{\"min\":\"\",\"max\":\"\"}}",
|
||||
"date_from": "2019-03-12 00:00:00",
|
||||
"date_to": "2019-03-16 00:00:00",
|
||||
"active": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"position": 2,
|
||||
"name": "Deactivable discount",
|
||||
"discount": "10.0000",
|
||||
"filter": "{\"producers\":[\"12\"],\"parametersValuesOperator\":\"AND\",\"price\":{\"min\":\"\",\"max\":\"\"},\"discount\":{\"min\":\"\",\"max\":\"\"}}",
|
||||
"date_from": "2019-03-11 00:00:00",
|
||||
"date_to": "2019-03-13 00:00:00",
|
||||
"active": 1
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"position": 3,
|
||||
"name": "Expired discount",
|
||||
"discount": "10.0000",
|
||||
"filter": "{\"producers\":[\"2817\"],\"parametersValuesOperator\":\"AND\",\"price\":{\"min\":\"\",\"max\":\"\"},\"discount\":{\"min\":\"\",\"max\":\"\"}}",
|
||||
"date_from": "2019-03-11 00:00:00",
|
||||
"date_to": "2019-03-13 00:00:00",
|
||||
"active": 0
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"position": 4,
|
||||
"name": "Active discount",
|
||||
"discount": "10.0000",
|
||||
"filter": "{\"producers\":[\"13\"],\"parametersValuesOperator\":\"AND\",\"price\":{\"min\":\"\",\"max\":\"\"},\"discount\":{\"min\":\"\",\"max\":\"\"}}",
|
||||
"date_from": "2019-03-12 00:00:00",
|
||||
"date_to": "2019-03-16 00:00:00",
|
||||
"active": 1
|
||||
}
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"id":1,
|
||||
"title":"NIKE Capri LACE Test Dlouheho Nazvu Produktu Test Dlouheho Produktu Tes",
|
||||
"code":"QO591",
|
||||
"ean":1001,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<p class=\"last\">Jenže kvůli všudy přítomné trávě jsou stíny balónků sotva vidět, natož aby šlo rozeznat, jakou barvu tyto stíny mají. Uvidět tak balónky náhodný kolemjdoucí, jistě by si pomyslel, že už tu takhle poletují snad tisíc let. Stále si víceméně drží výšku a ani do stran se příliš nepohybují. Proti slunci to vypadá, že se slunce pohybuje k západu rychleji než balónky, a možná to tak skutečně je. Nejeden filozof by mohl tvrdit, že balónky se sluncem závodí, ale fyzikové by to jistě vyvrátili. Z fyzikálního pohledu totiž balónky působí zcela nezajímavě.</p>",
|
||||
"parameters":"<ul style=\"line-height: 20.7999992370605px;\">\r\n\t<li><b>Složení</b>: WATERFEEL X-LIFE</li>\r\n\t<li><b>Boky</b>: 27</li>\r\n\t<li><b>Cílová skupina</b>: vhodné na trénink a pro aktivní plavání</li>\r\n\t<li><b>Pásek</b>: tkanička v pase pro snadné přizpůsobení velikosti</li>\r\n\t<li><b>Materiál</b>: pohodlný a měkký materiál s prodlouženou životností. Poskytuje neodolatelnou volnost pohybu a odolává chlorované vodě a slunečnímu záření</li>\r\n</ul>",
|
||||
"price":"826.4460",
|
||||
"price_common":"1200.0000",
|
||||
"vat":1,
|
||||
"discount":"20.00000000",
|
||||
"producer":10,
|
||||
"guarantee":24,
|
||||
"in_store":0,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":0,
|
||||
"campaign":"FS",
|
||||
"updated":"2015-02-16 11:18:43",
|
||||
"date_added":"2015-02-16 09:41:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"N",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":2,
|
||||
"title":"Adidas Mundial Goal Dalsi Test Velmi Dlouheho Produktoveho Nazvu",
|
||||
"code":"JQ671",
|
||||
"ean":1002,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<p class=\"last\">Jenže kvůli všudy přítomné trávě jsou stíny balónků sotva vidět, natož aby šlo rozeznat, jakou barvu tyto stíny mají. Uvidět tak balónky náhodný kolemjdoucí, jistě by si pomyslel, že už tu takhle poletují snad tisíc let. Stále si víceméně drží výšku a ani do stran se příliš nepohybují. Proti slunci to vypadá, že se slunce pohybuje k západu rychleji než balónky, a možná to tak skutečně je. Nejeden filozof by mohl tvrdit, že balónky se sluncem závodí, ale fyzikové by to jistě vyvrátili. Z fyzikálního pohledu totiž balónky působí zcela nezajímavě.</p>",
|
||||
"parameters":"<ul style=\"line-height: 20.7999992370605px;\">\r\n\t<li><b>Složení</b>: WATERFEEL X-LIFE</li>\r\n\t<li><b>Boky</b>: 27</li>\r\n\t<li><b>Cílová skupina</b>: vhodné na trénink a pro aktivní plavání</li>\r\n\t<li><b>Pásek</b>: tkanička v pase pro snadné přizpůsobení velikosti</li>\r\n\t<li><b>Materiál</b>: pohodlný a měkký materiál s prodlouženou životností. Poskytuje neodolatelnou volnost pohybu a odolává chlorované vodě a slunečnímu záření</li>\r\n</ul>",
|
||||
"price":"2066.1160",
|
||||
"price_common":"2800.0000",
|
||||
"vat":1,
|
||||
"discount":"10.00000000",
|
||||
"producer":10,
|
||||
"guarantee":24,
|
||||
"in_store":9,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":-1,
|
||||
"campaign":"D,L",
|
||||
"updated":"2015-02-16 11:18:47",
|
||||
"date_added":"2015-02-16 09:41:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"N",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":3,
|
||||
"title":"Wilson pure battle crew",
|
||||
"code":"MV934",
|
||||
"ean":null,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<p class=\"last\">Jenže kvůli všudy přítomné trávě jsou stíny balónků sotva vidět, natož aby šlo rozeznat, jakou barvu tyto stíny mají. Uvidět tak balónky náhodný kolemjdoucí, jistě by si pomyslel, že už tu takhle poletují snad tisíc let. Stále si víceméně drží výšku a ani do stran se příliš nepohybují. Proti slunci to vypadá, že se slunce pohybuje k západu rychleji než balónky, a možná to tak skutečně je. Nejeden filozof by mohl tvrdit, že balónky se sluncem závodí, ale fyzikové by to jistě vyvrátili. Z fyzikálního pohledu totiž balónky působí zcela nezajímavě.</p>",
|
||||
"parameters":"<ul style=\"line-height: 20.7999992370605px;\">\r\n\t<li><b>Složení</b>: WATERFEEL X-LIFE</li>\r\n\t<li><b>Boky</b>: 27</li>\r\n\t<li><b>Cílová skupina</b>: vhodné na trénink a pro aktivní plavání</li>\r\n\t<li><b>Pásek</b>: tkanička v pase pro snadné přizpůsobení velikosti</li>\r\n\t<li><b>Materiál</b>: pohodlný a měkký materiál s prodlouženou životností. Poskytuje neodolatelnou volnost pohybu a odolává chlorované vodě a slunečnímu záření</li>\r\n</ul>",
|
||||
"price":"661.1570",
|
||||
"price_common":"900.0000",
|
||||
"vat":1,
|
||||
"discount":"10.00000000",
|
||||
"producer":12,
|
||||
"guarantee":24,
|
||||
"in_store":-1,
|
||||
"pieces_sold":1,
|
||||
"delivery_time":-1,
|
||||
"campaign":"S,L,A",
|
||||
"updated":"2015-02-16 11:21:49",
|
||||
"date_added":"2014-09-26 08:27:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"N",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":4,
|
||||
"title":"Columbia Lay D Down",
|
||||
"code":"UQ372",
|
||||
"ean":null,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<p class=\"last\">Jenže kvůli všudy přítomné trávě jsou stíny balónků sotva vidět, natož aby šlo rozeznat, jakou barvu tyto stíny mají. Uvidět tak balónky náhodný kolemjdoucí, jistě by si pomyslel, že už tu takhle poletují snad tisíc let. Stále si víceméně drží výšku a ani do stran se příliš nepohybují. Proti slunci to vypadá, že se slunce pohybuje k západu rychleji než balónky, a možná to tak skutečně je. Nejeden filozof by mohl tvrdit, že balónky se sluncem závodí, ale fyzikové by to jistě vyvrátili. Z fyzikálního pohledu totiž balónky působí zcela nezajímavě.</p>",
|
||||
"parameters":"<ul style=\"line-height: 20.7999992370605px;\">\r\n\t<li><b>Složení</b>: WATERFEEL X-LIFE</li>\r\n\t<li><b>Boky</b>: 27</li>\r\n\t<li><b>Cílová skupina</b>: vhodné na trénink a pro aktivní plavání</li>\r\n\t<li><b>Pásek</b>: tkanička v pase pro snadné přizpůsobení velikosti</li>\r\n\t<li><b>Materiál</b>: pohodlný a měkký materiál s prodlouženou životností. Poskytuje neodolatelnou volnost pohybu a odolává chlorované vodě a slunečnímu záření</li>\r\n</ul>",
|
||||
"price":"4132.2310",
|
||||
"price_common":"6000.0000",
|
||||
"vat":1,
|
||||
"discount":"0.00000000",
|
||||
"producer":13,
|
||||
"guarantee":24,
|
||||
"in_store":5,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":-1,
|
||||
"campaign":"L",
|
||||
"updated":"2015-02-16 11:18:59",
|
||||
"date_added":"2014-09-26 08:27:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"N",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":5,
|
||||
"title":"Alpine Pro NEW SPIDER PTX",
|
||||
"code":"TO499",
|
||||
"ean":null,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<p class=\"last\">Jenže kvůli všudy přítomné trávě jsou stíny balónků sotva vidět, natož aby šlo rozeznat, jakou barvu tyto stíny mají. Uvidět tak balónky náhodný kolemjdoucí, jistě by si pomyslel, že už tu takhle poletují snad tisíc let. Stále si víceméně drží výšku a ani do stran se příliš nepohybují. Proti slunci to vypadá, že se slunce pohybuje k západu rychleji než balónky, a možná to tak skutečně je. Nejeden filozof by mohl tvrdit, že balónky se sluncem závodí, ale fyzikové by to jistě vyvrátili. Z fyzikálního pohledu totiž balónky působí zcela nezajímavě.</p>",
|
||||
"parameters":"<ul style=\"line-height: 20.7999992370605px;\">\r\n\t<li><b>Složení</b>: WATERFEEL X-LIFE</li>\r\n\t<li><b>Boky</b>: 27</li>\r\n\t<li><b>Cílová skupina</b>: vhodné na trénink a pro aktivní plavání</li>\r\n\t<li><b>Pásek</b>: tkanička v pase pro snadné přizpůsobení velikosti</li>\r\n\t<li><b>Materiál</b>: pohodlný a měkký materiál s prodlouženou životností. Poskytuje neodolatelnou volnost pohybu a odolává chlorované vodě a slunečnímu záření</li>\r\n</ul>",
|
||||
"price":"0.0000",
|
||||
"price_common":"1600.0000",
|
||||
"vat":1,
|
||||
"discount":"0.00000000",
|
||||
"producer":11,
|
||||
"guarantee":24,
|
||||
"in_store":3,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":-1,
|
||||
"campaign":"L",
|
||||
"updated":"2015-02-16 11:19:03",
|
||||
"date_added":"2015-02-16 09:41:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"N",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":6,
|
||||
"title":"Salomon XR MISSION W",
|
||||
"code":"NO206",
|
||||
"ean":null,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<p class=\"last\">Jenže kvůli všudy přítomné trávě jsou stíny balónků sotva vidět, natož aby šlo rozeznat, jakou barvu tyto stíny mají. Uvidět tak balónky náhodný kolemjdoucí, jistě by si pomyslel, že už tu takhle poletují snad tisíc let. Stále si víceméně drží výšku a ani do stran se příliš nepohybují. Proti slunci to vypadá, že se slunce pohybuje k západu rychleji než balónky, a možná to tak skutečně je. Nejeden filozof by mohl tvrdit, že balónky se sluncem závodí, ale fyzikové by to jistě vyvrátili. Z fyzikálního pohledu totiž balónky působí zcela nezajímavě.</p>",
|
||||
"parameters":"<ul style=\"line-height: 20.7999992370605px;\">\r\n\t<li><b>Složení</b>: WATERFEEL X-LIFE</li>\r\n\t<li><b>Boky</b>: 27</li>\r\n\t<li><b>Cílová skupina</b>: vhodné na trénink a pro aktivní plavání</li>\r\n\t<li><b>Pásek</b>: tkanička v pase pro snadné přizpůsobení velikosti</li>\r\n\t<li><b>Materiál</b>: pohodlný a měkký materiál s prodlouženou životností. Poskytuje neodolatelnou volnost pohybu a odolává chlorované vodě a slunečnímu záření</li>\r\n</ul>",
|
||||
"price":"2479.3390",
|
||||
"price_common":"0.0000",
|
||||
"vat":1,
|
||||
"discount":"0.00000000",
|
||||
"producer":null,
|
||||
"guarantee":24,
|
||||
"in_store":19,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":0,
|
||||
"campaign":"N,S,L",
|
||||
"updated":"2015-02-16 11:19:07",
|
||||
"date_added":"2015-02-16 09:41:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"N",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":7,
|
||||
"title":"Black and Decker Firestorm FSB14",
|
||||
"code":"FT572",
|
||||
"ean":null,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<ul>\r\n\t<li>Vhodná pro: <br />\r\n\tBlack and Decker BDG14SF-2 Black and Decker BDGL1440<br />\r\n\tBlack and Decker BDGL14K-2 Black and Decker CD142SK<br />\r\n\tBlack and Decker CD14SFK Black and Decker CDC140AK<br />\r\n\tBlack and Decker CDC1440K Black and Decker CP14K<br />\r\n\tBlack and Decker CP14KB Black and Decker EPC14CA<br />\r\n\tBlack and Decker EPC14CAB Black and Decker HP142K<br />\r\n\tBlack and Decker HP142KD Black and Decker HP146F2<br />\r\n\tBlack and Decker HP146F2B Black and Decker HP146F3B<br />\r\n\tBlack and Decker HP146F3K Black and Decker HP146FBH<br />\r\n\tBlack and Decker HP148F2 Black and Decker HP148F2B<br />\r\n\tBlack and Decker HP148F2K Black and Decker HP148F2R<br />\r\n\tBlack and Decker HP148F3B Black and Decker HP148F3K<br />\r\n\tBlack and Decker HP14K Black and Decker HP14KD<br />\r\n\tBlack and Decker HPD1400 Black and Decker HPD14K-2<br />\r\n\tBlack and Decker HPS1440 Black and Decker KC2002F<br />\r\n\tBlack and Decker KC2002FK Black and Decker NM14<br />\r\n\tBlack and Decker PS142K Black and Decker R143F2<br />\r\n\tBlack and Decker RD1440K Black and Decker RD1441K<br />\r\n\tBlack and Decker SX4000 Black and Decker SX5500<br />\r\n\tBlack and Decker SX6000 Black and Decker SX7000<br />\r\n\tBlack and Decker SX7500 Black and Decker SXR14<br />\r\n\tBlack and Decker XTC143BK </li>\r\n</ul>",
|
||||
"parameters":"<p><strong>Technické parametry:</strong> </p>\r\n\r\n<p>Kapacita: 3300 mAh <br />\r\nNapětí:14,4V<br />\r\nTyp: NiMH <br />\r\nHmotnost: 912g</p>",
|
||||
"price":"1035.5370",
|
||||
"price_common":"0.0000",
|
||||
"vat":1,
|
||||
"discount":30,
|
||||
"producer":13,
|
||||
"guarantee":24,
|
||||
"in_store":2,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":-1,
|
||||
"campaign":"",
|
||||
"updated":"2015-02-16 11:19:29",
|
||||
"date_added":"2015-02-16 09:45:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"N",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":8,
|
||||
"title":"METABO BS 14,4 Li set mobilní dílna",
|
||||
"code":"XV352",
|
||||
"ean":null,
|
||||
"short_descr":"METABO BS 14,4 Li set mobilní dílna",
|
||||
"long_descr":"<p><strong>METABO BS 14,4 Li</strong></p>\r\n\r\n<ul>\r\n\t<li>Rychloupínací sklíčidlo s aretací hřídele pro snadnou výměnu nástroje</li>\r\n\t<li>20 nastavitelných točivých momentů, plus stupeň vrtání</li>\r\n\t<li>Dvourychlostní převodovka pro ideální volbu pracovních otáček</li>\r\n\t<li>Patentovaný LED ukazatel stavu nabití článku</li>\r\n\t<li>Nově vylepšená elektronika Variospeed (V)</li>\r\n\t<li>Držáky bitů na obou stranách stroje</li>\r\n\t<li>Pravý a levý chod stroje</li>\r\n</ul>\r\n\r\n<p><strong>Součástí dodávky:</strong><br />\r\n2x akumulátor LI-ion 2,0 Ah, Nabíječka SC60 plus, kufr s příslušenstvím:</p>\r\n\r\n<ul>\r\n\t<li>Svinovací metr 5m</li>\r\n\t<li>Vrtáky do kovu (1,5-1,5-2-2-2,5-3-3,5-4-4,5-5-5,5-6-6,5)</li>\r\n\t<li>Vrtáky do dřeva (4-5-6-8-10)</li>\r\n\t<li>Ploché frézovací vrtáky do dřeva (16-20-22)</li>\r\n\t<li>Ulamovací nůž, čepel 18 mm</li>\r\n\t<li>3x držák bitů</li>\r\n\t<li>Šroubovací šestihranné nástavce 6-7-8-9-10-11-12-13</li>\r\n\t<li>bity 25 mm - PH1-PH2-PH2-PH3</li>\r\n\t<li>bity 25 mm - PZ1-PZ2-PZ2-PZ3</li>\r\n\t<li>bity 25 mm - H3-H4-H5-H6</li>\r\n\t<li>bity 25 mm - T15-T20-T25</li>\r\n\t<li>bity 25 mm - S4-S6-S7</li>\r\n\t<li>bity 50 mm - PH1, PH2, PH3, PZ1, PZ2, PZ3, S4, S6, T20, T25</li>\r\n\t<li>Záhlubník</li>\r\n</ul>",
|
||||
"parameters":"<p>druh akulumátorového článku: Li-Ion<br />\r\nnapětí akumulátoru: 14,4 V<br />\r\nkapacita akumulátoru: 2 Ah<br />\r\nmax. točivý moment měkký: 20 Nm<br />\r\nmax. točivý moment tvrdý: 40 Nm<br />\r\nnastavitelný točivý moment: 0,8 - 5 Nm<br />\r\nprůměr vrtáku do oceli: 10 mm/5 mm<br />\r\nprůměr vrtáku do dřeva: 20 mm/16 mm<br />\r\npočet otáček při volnoběhu: 0 - 450 / 0 - 1.600 /min<br />\r\nzávit vrtacího vřetena: 1/2\" -20 UNF<br />\r\nrozpětí sklíčidla: 1 - 10 mm</p>",
|
||||
"price":"4123.9670",
|
||||
"price_common":"0.0000",
|
||||
"vat":1,
|
||||
"discount":"0.00000000",
|
||||
"producer":13,
|
||||
"guarantee":24,
|
||||
"in_store":0,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":0,
|
||||
"campaign":"N",
|
||||
"updated":"2015-02-16 11:21:06",
|
||||
"date_added":"2015-02-16 09:38:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"Y",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":9,
|
||||
"title":"DeWALT DCD780C2 aku vrtačka",
|
||||
"code":"NL935",
|
||||
"ean":null,
|
||||
"short_descr":"DeWALT DCD780C2 aku vrtačka",
|
||||
"long_descr":"<ul>\r\n\t<li>Poslední generace kompaktní vrtačky/šroubováku XR Li-Ion 18 V disponuje NOVOU baterií s technologií XR Li-Ion</li>\r\n\t<li>Extrémně kompaktní a lehká konstrukce umožňuje použití v omezených prostorech</li>\r\n\t<li>Celokovová převodovka se dvěma převodovými stupni zaručuje delší provozní dobu a provozní životnost nářadí</li>\r\n\t<li>Možnost nastavení 14 poloh pro hodnotu momentu zaručuje optimalizovanou přesnost při šroubování různých šroubů a vrutů do různých materiálů</li>\r\n\t<li>Konstrukce inteligentního spouštěcího spínače zaručuje dokonalou ovladatelnost při práci</li>\r\n\t<li>Kompaktní rychloupínací sklíčidlo 13 mm s jednou objímkou a s automatickým zajištěním hřídele umožňuje rychlou a snadnou výměnu nástroje pouze jednou rukou</li>\r\n\t<li>Jasné bílé světlo LED diody se zpožděním poskytuje lepší viditelnost a funkci svítilny</li>\r\n\t<li>Zdokonalená ergonomická konstrukce a gumová rukojeť zvyšují komfort obsluhy</li>\r\n\t<li>Nasunovací baterie Li-Ion umožňuje velmi jednoduché vkládání a vyjímání</li>\r\n\t<li>Univerzální nabíječka pro použití s nasunovacími bateriemi XR Li-Ion s napájecím napětím 18 V, 14,4 V a 10,8 V</li>\r\n\t<li>Ocelová příchytka na řemen a silný magnetický držák nástrojů zaručují pevné a bezpečné uložení</li>\r\n\t<li>Součást inteligentní řady XR Lithium Ion určená pro efektivní a rychlé dokončení montážních úkolů</li>\r\n</ul>\r\n\r\n<p>\r\n<strong>Součástí dodávky:</strong><br />\r\n2x akumulátor LI-Ion 1,5 Ah, univerzální nabíječka XR, kufr, příchytka na řemen, magnetický držák nástrojů</p>",
|
||||
"parameters":"<p>Napájecí napětí 18 V<br />\r\nKapacita sklíčidla 1,5 - 13 mm<br />\r\nVýkon 350 W<br />\r\nOtáčky naprázdno 0 - 600 / 2000 ot./min<br />\r\nMax. kroutící moment 60 Nm<br />\r\nMax. průměr otvoru [dřevo] 38 mm<br />\r\nMax. průměr otvoru [kov] 13 mm<br />\r\nZávit vřetena <br />\r\nHmotnost 1,55 kg<br />\r\nDélka 190 mm<br />\r\nVýška 218 mm<br />\r\nHloubka 80 mm <br />\r\nBaterie 1,5 Ah</p>",
|
||||
"price":"5198.3470",
|
||||
"price_common":"0.0000",
|
||||
"vat":1,
|
||||
"discount":"0.00000000",
|
||||
"producer":14,
|
||||
"guarantee":24,
|
||||
"in_store":19,
|
||||
"pieces_sold":0,
|
||||
"delivery_time":0,
|
||||
"campaign":"S,A,Z",
|
||||
"updated":"2015-02-16 11:19:53",
|
||||
"date_added":"2015-02-16 09:39:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"Y",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":10,
|
||||
"title":"Black and Decker Core EGBL108K aku vrtačka",
|
||||
"code":"RD102",
|
||||
"ean":null,
|
||||
"short_descr":"Lorem ipsum dolor sit amet",
|
||||
"long_descr":"<p class=\"last\">Jenže kvůli všudy přítomné trávě jsou stíny balónků sotva vidět, natož aby šlo rozeznat, jakou barvu tyto stíny mají. Uvidět tak balónky náhodný kolemjdoucí, jistě by si pomyslel, že už tu takhle poletují snad tisíc let. Stále si víceméně drží výšku a ani do stran se příliš nepohybují. Proti slunci to vypadá, že se slunce pohybuje k západu rychleji než balónky, a možná to tak skutečně je. Nejeden filozof by mohl tvrdit, že balónky se sluncem závodí, ale fyzikové by to jistě vyvrátili. Z fyzikálního pohledu totiž balónky působí zcela nezajímavě.</p>",
|
||||
"parameters":"<ul>\r\n\t<li>list 1</li>\r\n\t<li>list 1</li>\r\n\t<li>list 1</li>\r\n</ul>",
|
||||
"price":"1748.7600",
|
||||
"price_common":"0.0000",
|
||||
"vat":2,
|
||||
"discount":"20.00000000",
|
||||
"producer":15,
|
||||
"guarantee":24,
|
||||
"in_store":-1,
|
||||
"pieces_sold":1,
|
||||
"delivery_time":-2,
|
||||
"campaign":"D",
|
||||
"updated":"2015-02-16 11:21:10",
|
||||
"date_added":"2015-02-16 09:37:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"Y",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
},
|
||||
{
|
||||
"id":11,
|
||||
"title":"iPhone wpj",
|
||||
"code":"GR573",
|
||||
"ean":null,
|
||||
"short_descr":"wpj",
|
||||
"long_descr":"<p class=\"last\">Mobilní telefon Apple A6 Dual-core 1.3GHz, dotykový 4\" Retina 1136x640, interní paměť 8GB, WiFi 802.11a/b/g/n, Bluetooth 4.0, 8 Mpx fotoaparát s LED bleskem, GPS, Gyroskop, Akcelerometr, Lightning rozhraní, iOS 8</p>",
|
||||
"parameters":"<p><strong>Barvy plné života</strong></p>\r\n\r\n<p>Každá funkce a vlastnost telefonu iPhone 5C vám vylepší všechny zážitky. Díky<br />\r\npestrým barevným povrchům si můžete vybrat barvu, která vám sedne nejvíce<br />\r\n- dokonce i domovská stránka a tapety jsou navrženy v barvě vašeho iPhonu.</p>\r\n\r\n<p><strong>Apple A6 - záruka neomezených možností</strong></p>\r\n\r\n<p>Spouštějte oblíbené aplikace. Procházejte si sociální sítě. Stahujte si a sledujte HD videa. Hrajte dechberoucí hry. Zkrátka všechno, co rádi na iPhonu děláte - navíc s rychlostí, kterou očekáváte.</p>\r\n\r\n<p><strong>Zbrusu nový iOS 8</strong></p>\r\n\r\n<p>Jasnější a jednodušší ikony. Prostředí, které působí živě a plynule. Nové funkce,<br />\r\nkteré naplno využívají potenciál skrytý uvnitř telefonu. Světově nejpokročilejší<br />\r\noperační systém iOS 8 je perfektním společníkem nového iPhonu 5C.</p>",
|
||||
"price":"1652.0660",
|
||||
"price_common":"0.0000",
|
||||
"vat":1,
|
||||
"discount":80.04000092,
|
||||
"producer":17,
|
||||
"guarantee":36,
|
||||
"in_store":23,
|
||||
"pieces_sold":10,
|
||||
"delivery_time":0,
|
||||
"campaign":"N,S,D,L,M,Z",
|
||||
"updated":"2015-02-16 11:21:12",
|
||||
"date_added":"2015-02-13 13:56:00",
|
||||
"figure":"Y",
|
||||
"show_raw_price":"N",
|
||||
"position":null,
|
||||
"meta_title":null,
|
||||
"meta_description":null,
|
||||
"meta_keywords":null,
|
||||
"show_in_feed":"Y",
|
||||
"max_cpc":0,
|
||||
"in_store_min":null,
|
||||
"note":null,
|
||||
"weight":null,
|
||||
"price_buy":null,
|
||||
"bonus_points":null,
|
||||
"data":null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GlobalDiscountsBundle\Tests;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use KupShop\GlobalDiscountsBundle\Util\GlobalDiscounts;
|
||||
|
||||
class GlobalDiscountsTest extends \DatabaseTestCase
|
||||
{
|
||||
/** @var GlobalDiscounts */
|
||||
private $globalDiscounts;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->globalDiscounts = $this->get(GlobalDiscounts::class);
|
||||
|
||||
$this->makeDiscountsDateActive([1, 2, 5]);
|
||||
}
|
||||
|
||||
public function testApplyDiscountsOnProduct()
|
||||
{
|
||||
$product = new \Product();
|
||||
$product->createFromDB(8);
|
||||
|
||||
$product2 = new \Product();
|
||||
$product2->createFromDB(9);
|
||||
|
||||
$this->assertEquals(0, $product->discount->asFloat());
|
||||
$this->assertEquals(0, $product2->discount->asFloat());
|
||||
|
||||
$this->globalDiscounts->applyDiscountsOnProducts([8, 9]);
|
||||
|
||||
$product->createFromDB(8);
|
||||
$product2->createFromDB(9);
|
||||
|
||||
$this->assertEquals(10, $product->discount->asFloat());
|
||||
$this->assertEquals(0, $product2->discount->asFloat());
|
||||
}
|
||||
|
||||
public function testRecalculateDiscounts()
|
||||
{
|
||||
$product = new \Product();
|
||||
$product->createFromDB(7);
|
||||
|
||||
$this->assertEquals(30, $product->discount->asFloat());
|
||||
|
||||
$this->globalDiscounts->recalculateDiscounts();
|
||||
|
||||
$product->createFromDB(7);
|
||||
|
||||
$this->assertEquals(10, $product->discount->asFloat());
|
||||
}
|
||||
|
||||
public function testDeactivateDiscounts()
|
||||
{
|
||||
$this->assertCount(1, $this->globalDiscounts->getDeactivableDiscounts());
|
||||
|
||||
$this->globalDiscounts->deactivateDiscounts();
|
||||
|
||||
$this->assertEmpty($this->globalDiscounts->getDeactivableDiscounts());
|
||||
}
|
||||
|
||||
public function testActivateDiscounts()
|
||||
{
|
||||
$this->assertCount(2, $this->globalDiscounts->getActiveDiscounts());
|
||||
|
||||
$this->globalDiscounts->activateDiscounts();
|
||||
|
||||
$this->assertEmpty($this->globalDiscounts->getActivatableDiscounts());
|
||||
$this->assertCount(4, $this->globalDiscounts->getActiveDiscounts());
|
||||
}
|
||||
|
||||
public function testDeactivateDiscount()
|
||||
{
|
||||
$this->assertCount(2, $this->globalDiscounts->getActiveDiscounts());
|
||||
|
||||
$this->globalDiscounts->deactivateDiscount(
|
||||
$this->globalDiscounts->getDiscount(3)
|
||||
);
|
||||
|
||||
$products = $this->selectSQL('products', ['producer' => 12], ['id']);
|
||||
|
||||
foreach ($products as $product) {
|
||||
$productObj = new \Product();
|
||||
$productObj->createFromDB($product['id']);
|
||||
|
||||
$this->assertEquals(0, $productObj->discount->asFloat());
|
||||
}
|
||||
|
||||
$this->assertCount(1, $this->globalDiscounts->getActiveDiscounts());
|
||||
}
|
||||
|
||||
public function testActivateDiscount()
|
||||
{
|
||||
$this->assertCount(2, $this->globalDiscounts->getActiveDiscounts());
|
||||
|
||||
$discount = $this->globalDiscounts->getDiscount(1);
|
||||
$this->globalDiscounts->activateDiscount($discount);
|
||||
|
||||
$products = $this->selectSQL('products', ['producer' => 10], ['id']);
|
||||
|
||||
foreach ($products as $product) {
|
||||
$productObj = new \Product();
|
||||
$productObj->createFromDB($product['id']);
|
||||
|
||||
if (isset($productObj->campaign_codes['FS'])) {
|
||||
$this->assertNotEquals($discount['discount'], $productObj->discount->asFloat());
|
||||
} else {
|
||||
$this->assertEquals($discount['discount'], $productObj->discount->asFloat());
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertCount(3, $this->globalDiscounts->getActiveDiscounts());
|
||||
}
|
||||
|
||||
public function testGetActivatableDiscounts()
|
||||
{
|
||||
$discounts = $this->globalDiscounts->getActivatableDiscounts();
|
||||
|
||||
$this->assertCount(2, $discounts);
|
||||
}
|
||||
|
||||
public function testGetActiveDiscounts()
|
||||
{
|
||||
$discounts = $this->globalDiscounts->getActiveDiscounts();
|
||||
|
||||
$this->assertCount(2, $discounts);
|
||||
}
|
||||
|
||||
public function testGetDeactivableDiscounts()
|
||||
{
|
||||
$discounts = $this->globalDiscounts->getDeactivableDiscounts();
|
||||
|
||||
$this->assertCount(1, $discounts);
|
||||
}
|
||||
|
||||
private function makeDiscountsDateActive(array $discountIds)
|
||||
{
|
||||
sqlQuery('UPDATE global_discounts SET date_from = (NOW() - INTERVAL 1 DAY), date_to = (NOW() + INTERVAL 1 DAY) WHERE id IN (:ids)', ['ids' => $discountIds], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
}
|
||||
|
||||
protected function getDataSet()
|
||||
{
|
||||
return $this->getJsonDataSetFromFile();
|
||||
}
|
||||
}
|
||||
166
bundles/KupShop/GlobalDiscountsBundle/Util/GlobalDiscounts.php
Normal file
166
bundles/KupShop/GlobalDiscountsBundle/Util/GlobalDiscounts.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GlobalDiscountsBundle\Util;
|
||||
|
||||
use KupShop\CatalogBundle\Util\ProductsFilterSpecs;
|
||||
use Query\Operator;
|
||||
|
||||
class GlobalDiscounts
|
||||
{
|
||||
private $filterSpecs;
|
||||
|
||||
public function __construct(ProductsFilterSpecs $filterSpecs)
|
||||
{
|
||||
$this->filterSpecs = $filterSpecs;
|
||||
}
|
||||
|
||||
public function activateDiscounts()
|
||||
{
|
||||
foreach ($this->getActivatableDiscounts() as $discount) {
|
||||
$this->activateDiscount($discount);
|
||||
}
|
||||
}
|
||||
|
||||
public function deactivateDiscounts()
|
||||
{
|
||||
$deactivableDiscounts = $this->getDeactivableDiscounts();
|
||||
|
||||
foreach ($deactivableDiscounts as $discount) {
|
||||
$this->deactivateDiscount($discount);
|
||||
}
|
||||
|
||||
if (!empty($deactivableDiscounts)) {
|
||||
$this->recalculateDiscounts();
|
||||
}
|
||||
}
|
||||
|
||||
public function recalculateDiscounts()
|
||||
{
|
||||
foreach ($this->getActiveDiscounts() as $discount) {
|
||||
$this->updateProductsDiscount($discount['discount'], $discount['filter']);
|
||||
}
|
||||
}
|
||||
|
||||
public function activateDiscount(array $discount): bool
|
||||
{
|
||||
$this->updateProductsDiscount($discount['discount'], $discount['filter']);
|
||||
$this->updateDiscount($discount['id'], ['active' => 1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deactivateDiscount(array $discount): bool
|
||||
{
|
||||
$this->updateProductsDiscount(0, $discount['filter']);
|
||||
$this->updateDiscount($discount['id'], ['active' => 0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function applyDiscountsOnProducts(array $productIds)
|
||||
{
|
||||
foreach ($this->getActiveDiscounts() as $discount) {
|
||||
$this->updateProductsDiscount($discount['discount'], $discount['filter'], $productIds);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateProductsDiscount($discountValue, $filter, ?array $productIds = null): void
|
||||
{
|
||||
$specs = $this->filterSpecs->getSpecs($filter);
|
||||
|
||||
$update = sqlQueryBuilder()->update('products', 'p')
|
||||
->set('p.discount', ':discount')
|
||||
->setParameter(':discount', $discountValue)
|
||||
->andWhere($specs)
|
||||
->andWhere(Operator::not(Operator::findInSet(['FS'], 'p.campaign')));
|
||||
|
||||
if ($productIds !== null) {
|
||||
$update->andWhere(Operator::inIntArray($productIds, 'p.id'));
|
||||
}
|
||||
|
||||
$update->execute();
|
||||
}
|
||||
|
||||
public function updateDiscount(int $discountId, array $values)
|
||||
{
|
||||
sqlQueryBuilder()->update('global_discounts')
|
||||
->directValues($values)
|
||||
->where(Operator::equals(['id' => $discountId]))
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function getDiscount(int $discountId)
|
||||
{
|
||||
$discount = $this->getQueryBuilder()->select('*')
|
||||
->where(Operator::equals(['id' => $discountId]))
|
||||
->execute()->fetch();
|
||||
|
||||
if (!$discount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->prepareDiscount($discount);
|
||||
}
|
||||
|
||||
public function getActiveDiscounts(): array
|
||||
{
|
||||
$discounts = $this->getQueryBuilder(true)
|
||||
->execute()->fetchAll();
|
||||
|
||||
if (!$discounts) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(function ($x) { return $this->prepareDiscount($x); }, $discounts);
|
||||
}
|
||||
|
||||
public function getActivatableDiscounts(): array
|
||||
{
|
||||
$discounts = $this->getQueryBuilder(false)
|
||||
->andWhere('(date_from <= :date AND date_to >= :date) OR (date_from IS NULL AND date_to IS NULL) OR (date_from <= :date AND date_to IS NULL) OR (date_from IS NULL AND date_to >= :date)')
|
||||
->setParameter('date', new \DateTime(), 'datetime')
|
||||
->execute()->fetchAll();
|
||||
|
||||
if (!$discounts) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(function ($x) { return $this->prepareDiscount($x); }, $discounts);
|
||||
}
|
||||
|
||||
public function getDeactivableDiscounts(): array
|
||||
{
|
||||
$discounts = $this->getQueryBuilder(true)
|
||||
->andWhere('date_from >= :date OR date_to <= :date')
|
||||
->orderBy('position')
|
||||
->setParameter('date', new \DateTime(), 'datetime')
|
||||
->execute()->fetchAll();
|
||||
|
||||
if (!$discounts) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(function ($x) { return $this->prepareDiscount($x); }, $discounts);
|
||||
}
|
||||
|
||||
protected function getQueryBuilder($active = null)
|
||||
{
|
||||
$qb = sqlQueryBuilder()->from('global_discounts')
|
||||
->select('*')
|
||||
->orderBy('position')
|
||||
->sendToMaster();
|
||||
|
||||
if ($active !== null) {
|
||||
$qb->andWhere(Operator::equals(['active' => $active ? 1 : 0]));
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function prepareDiscount($discount)
|
||||
{
|
||||
$discount['filter'] = json_decode($discount['filter'], true);
|
||||
|
||||
return $discount;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user