first commit

This commit is contained in:
2025-08-02 16:30:27 +02:00
commit 23646bfcee
14851 changed files with 1750626 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\Actions;
use KupShop\AdminBundle\Admin\Actions\ActionResult;
use KupShop\PreordersBundle\Entity\UserPreorder;
class CreateOrderAction extends UserPreorderAction
{
private ?bool $enabled = null;
public function execute(&$data, array $config, string $type): ActionResult
{
return new ActionResult(true);
}
public function getLaunchHandler(): string
{
return "nw('preordersCreateOrder', '', 'ID={$this->getId()}&acn=edit',);";
}
public function hasDialog(): bool
{
return false;
}
protected function checkEnabled(): bool
{
$script = str_replace('.php', '', (string) getVal('s', default: ''));
if (!in_array($script, $this->getTypes())) {
return false;
}
$ids = explode('-', $this->getId() ?? '');
if (count($ids) < 2) {
return false;
}
if ($this->enabled === null) {
[$userId, $dateId] = $ids;
$preorder = new UserPreorder((int) $userId, (int) $dateId);
$date = $preorder->getPreorderDate();
$this->enabled = \DateTimeImmutable::createFromFormat('Y-m-d', $date['date_end'])
<= (new \DateTimeImmutable())->sub(\DateInterval::createFromDateString('1 day'));
}
return $this->enabled;
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\Actions;
use KupShop\AdminBundle\Admin\Actions\ActionResult;
use KupShop\AdminBundle\Admin\Actions\IAction;
use KupShop\PreordersBundle\Service\OrderCreator;
class CreateOrderListAction extends UserPreorderAction implements IAction
{
public function __construct(
private readonly OrderCreator $orderCreator,
) {
}
public function execute(&$data, array $config, string $type): ActionResult
{
$userPreorder = $this->getUserPreorder();
$date = $userPreorder->getPreorderDate();
$enabled = \DateTimeImmutable::createFromFormat('Y-m-d', $date['date_end'])
<= (new \DateTimeImmutable())->sub(\DateInterval::createFromDateString('1 day'));
if (!$enabled) {
return new ActionResult(false, 'Objednávku nelze vytvořit z předobjednávky uživatele, která je stále otevřená.');
}
$itemsToAdd = [];
foreach ($userPreorder->getItems() as $item) {
$piecesRemaining = $item['pieces'] - $item['pieces_sent'];
if ($piecesRemaining <= 0) {
continue;
}
$itemsToAdd[$item['id']] = $piecesRemaining;
}
$order = $this->orderCreator->createOrder($userPreorder, $itemsToAdd);
return new ActionResult(true, "Objednávka {$order->order_no} úspěšně vytvořena.");
}
public function showInMassEdit(): bool
{
return true;
}
protected function checkEnabled(): bool
{
return getVal('s') === 'listEdit.php' || getVal('acn') === 'getActionSnippet';
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\Actions;
use KupShop\AdminBundle\Admin\Actions\ActionResult;
use KupShop\PreordersBundle\Service\DynamicContexts;
use KupShop\PreordersBundle\Service\PreorderItemsUpdater;
class RecalculatePreorderPiecesAction extends UserPreorderAction
{
public function __construct(
private readonly PreorderItemsUpdater $itemsUpdater,
private readonly DynamicContexts $contexts,
) {
}
public function execute(&$data, array $config, string $type): ActionResult
{
try {
$userPreorder = $this->getUserPreorder();
$this->contexts->activateUserPreorder($userPreorder);
$this->itemsUpdater->recalculatePiecePrices($userPreorder);
} catch (\Throwable $e) {
return new ActionResult(false, $e->getMessage());
}
return new ActionResult(true);
}
protected function checkEnabled(): bool
{
return true;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\Actions;
use KupShop\AdminBundle\Admin\Actions\AbstractAction;
use KupShop\AdminBundle\Admin\Actions\IAction;
use KupShop\PreordersBundle\Entity\UserPreorder;
abstract class UserPreorderAction extends AbstractAction implements IAction
{
private ?UserPreorder $userPreorder = null;
public function getTypes(): array
{
return ['preordersUsers'];
}
public function getName(): string
{
$split = explode('\\', static::class);
$className = lcfirst($split[count($split) - 1]);
return translate($className, 'preordersUsers', false, true);
}
protected function getUserPreorder(): UserPreorder
{
return $this->userPreorder ?? new UserPreorder(...array_map(intval(...), explode('-', $this->getId())));
}
public function isVisible(): bool
{
return $this->checkEnabled();
}
public function isEnabled(): bool
{
return $this->checkEnabled();
}
abstract protected function checkEnabled(): bool;
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\Tabs;
use KupShop\AdminBundle\Admin\WindowTab;
class PreordersSettingsTab extends WindowTab
{
protected $title = 'flapPreorders';
protected $template = 'tabs/settingsTab.tpl';
public static function getTypes(): array
{
return [
'settings' => 1,
];
}
public function getLabel(): string
{
return 'Předobjednávky';
}
}

View File

@@ -0,0 +1,32 @@
<?php
$txt_str['preorders'] = [
'titleAdd' => 'Definice nové předobjednávky',
'titleEdit' => 'Předobjednávka',
'toolbar_add' => 'Vytvořit novou předobjednávku',
'toolbar_list' => 'Předobjednávky',
'flapMain' => 'Nastavení',
'flapProducts' => 'Produkty',
'flapDiscountLevels' => 'Slevové hladiny',
'flapDates' => 'Termíny',
'name' => 'Název',
'usersGroup' => 'Skupiny uživatelů',
'pricelist' => 'Ceník',
'pricelistEmptyChoice' => 'Žádný ceník',
'emptyChoice' => 'Nevybráno',
'priceLevel' => 'Cenová hladina',
'addPreorderDate' => 'Přidat termín předobjednávky',
'priceFrom' => 'Cena od',
'priceTo' => 'Cena do',
'addDynamicPrice' => 'Přidat hladinu',
'base' => 'Základní',
'preorder' => 'Předobjednávka',
'preorders' => 'Předobjednávky',
'minPrice' => 'Minimální hodnota předobjednávky',
'multipleDatesSinglePage' => 'Více termínů na jedné stránce',
'multipleDatesSinglePageTooltip' => 'Zákazníkům se budou všechny termíny patřící k jedné předobjednávce zobrazovat na jedné stránce.',
'showHidden' => 'Umožnit předobjednávat skryté produkty',
];

View File

@@ -0,0 +1,6 @@
<?php
$txt_str['preordersCreateOrder'] = [
'titleEdit' => 'Vytvoření objednávky',
'buttonAddValue' => 'Přidat všechny produkty do objednávky',
];

View File

@@ -0,0 +1,37 @@
<?php
$txt_str['preordersDates'] = [
'titleAdd' => 'Nový termín předobjednávky',
'titleEdit' => 'Úprava termínu',
'toolbar_list' => 'Termíny předobjednávek',
'toolbar_add' => 'Přidat termín předobjednávky',
'MenuHelp' => 'Nápověda',
'flapMain' => 'Nastavení',
'flapPreordersCreated' => 'Vytvořené předobjednávky',
'dateEnd' => 'Datum ukončení pro zákazníky',
'dateName' => 'Název termínu',
'dateStart' => 'Datum otevření termínu',
'dateShipment' => 'Datum expedice',
'preorder' => 'Předobjednávka',
'preorderCreateBtn' => 'Vytvořit novou',
'description' => 'Popis termínu',
'labelProducts' => 'Produkty',
'addProduct' => 'Přidat produkt',
'customerName' => 'Zákazník',
'priceTotal' => 'Celková cena',
'priceRemaining' => 'Cena zbylého zboží',
'userAddress' => 'Doručovací adresa',
'dateStartTooltip' => 'Zákazníci předobjednávku uvidí a budou moci objednávat od tohoto dne.',
'dateEndTooltip' => 'Zákazníci mohou objednávat do půlnoci tohoto dne.',
'dateShipmentTooltip' => 'Slouží pouze pro informování zákazníků, kdy mohou předobjednané produkty očekávat.',
'flapProducts' => 'Produkty',
'date_end' => 'Datum ukončení předobjednávání',
'date_start' => 'Datum zahájení předobjednávání',
'date_shipment' => 'Datum expedice',
];

View File

@@ -0,0 +1,86 @@
<?php
$txt_str['preordersItems'] = [
'toolbar_list' => 'Seznam produktů',
'order' => 'Objednávka č.',
'name' => 'Jméno a příjmení',
'price' => 'Cena objednávky',
'totalPieces' => 'Celkem předobjednáno',
'sentPieces' => 'Kusů odesláno',
'totalPrice' => 'Celková cena',
'photo' => 'Fotka produktu',
'title' => 'Název',
'code' => 'Kód',
'vat' => 'Daň',
'discount' => 'Sleva z ceny',
'producer' => 'Výrobce',
'inStoreMin' => 'Min. skladem',
'piecesInStore' => 'Kusů na skladě',
'piecesSold' => 'Prodáno',
'deliveryTime' => 'Dostupnost',
'campaign' => 'Kampaň',
'statusNews' => 'Novinky',
'statusDiscount' => 'Akce / Slevy',
'statusSoldout' => 'Výprodeje',
'statusLeadPage' => 'Na úvodní straně',
'statusBestSeller' => 'Nejprodávanější',
'dateAdded' => 'Datum přidání',
'figure' => 'Zobrazení zboží',
'annotation' => 'Anotace',
'descriptionLead' => 'Popis zboží',
'descriptionProperties' => 'Rozšiřující popis',
'allParameteres' => 'Všechny parametry',
'activeParameteres' => 'Použité v sekcích a u výrobce',
'producerParameteres' => 'Použité u výrobce',
'parameteresInSections' => 'Použité v sekci',
'noParameters' => 'Žádné parametry',
'sectionRoot' => 'Sekce',
'filterProduct' => 'Podle produktu',
'products' => 'Předobjednané předměty',
'productList' => 'Seznam produktů',
'addProduct' => 'Přidat produkt',
'help' => 'Nápověda',
'filter' => 'Filtrování',
'filterSection' => 'Podle sekcí',
'filterProducer' => 'Podle výrobců',
'filterCampaign' => 'Podle kampaně',
'filterHidden' => 'Nezobrazované produkty',
'filterOld' => 'Ukončený prodej',
'filterMisplaced' => 'Nezařazené produkty',
'filterNoImg' => 'Produkty bez obrázků',
'filterImg' => 'Produkty s obrázky',
'filterIncompleteRelated' => 'Neúplný stav souvisejícího zboží',
'filterInStore' => 'Produkty skladem',
'massEdit' => 'Hromadná úprava',
'massEditProduct' => 'Hromadná úprava produktů',
'editationPrice' => 'Úprava cen',
'editationStock' => 'Úprava skladu',
'editationText' => 'Úprava textů',
'visibleProductsWithHiddenProducer' => 'Viditelné produkty se skrytým výrobcem',
'search' => 'Vyhledávání',
'searchId' => 'Podle čísla',
'searchCode' => 'Podle kódu',
'searchName' => 'Podle názvu',
'searchEan' => 'Podle EANu',
'inclOld' => 'Včetně ukončeného prodeje',
'searchCodeOrId' => 'Podle kódu / čísla',
'supplierCodes' => 'Kódy dodavatelů',
'generateCode' => 'Vygenerovat kód',
'generateEan' => 'Vygenerovat EAN',
'priceWithoutVat' => 'MOC bez DPH',
'priceForDiscount' => 'Cena pro slevu',
'normalPrice' => 'MOC s DPH',
'variation' => 'Varianta',
'shortDescription' => 'Popis',
'longDescription' => 'Dlouhý popis',
'stockInDate' => 'Datum naskladnění',
'showOnlyRemaining' => 'Zobrazit pouze neuspokojené',
'pieces_sent' => 'ks odesláno',
'pieces' => 'ks předobjednáno',
'currency' => 'Měna',
'preorder' => 'Předobjednávka',
'category' => 'Kategorie',
];

View File

@@ -0,0 +1,41 @@
<?php
$txt_str['preordersUsers'] = [
'toolbar_list' => 'Seznam uživatelů',
'toolbar_add' => 'Přidat termín předobjednávky',
'titleEdit' => 'Shrnutí předobjednávky uživatele',
'flapMain' => 'Shrnutí předobjednávky uživatele',
'flapMessages' => 'Komunikace',
'flapOrders' => 'Vytvořené objednávky',
'titleUser' => 'Předobjednávka uživatele',
'titleOrderedProducts' => 'Objednané zboží',
'user' => 'Uživatel',
'dateTitle' => 'Termín',
'item' => 'Název',
'piecePrice' => 'Jednotková cena',
'piecesSent' => 'Ks (odesláno / celkem)',
'priceRemaining' => 'Cena (zbývá / celkem)',
'piecesToAdd' => 'ks přidat do objednávky',
'saveItemPieces' => 'Uložit počty ks produktů',
'createOrderAction' => 'Vytvořit objednávku z předobjednávky',
'recalculatePreorderPiecesAction' => 'Přepočítat jednotkové ceny',
'recalculateAreYouSure' => 'Opravdu chcete předpočíat jednotkové ceny podle aktuálního nastavení předobjednávky?',
'messageDate' => 'Datum',
'messageText' => 'Text zprávy',
'messageSentBy' => 'Odeslal',
'newMessage' => 'Nová zpráva',
'notification' => 'E-mail odeslán',
'previousMessages' => 'Historie komunikace',
'notificationNotNotified' => 'Ne',
'notificationEmailNotified' => 'Ano',
'dateShipmentAbbr' => 'exp.:',
'createOrderListAction' => 'Vytvořit objednávku',
'variationNotSelected' => 'Položka <a href="#preorder-item-%d">"%s"</a> nemá vybranou variantu, ale produkt má více variant. Při vytváření objednávky bude vybrána první varianta, případně ji změňte v objednávce.',
'variationNotSelectedTooltip' => 'Položka nemá vybranou variantu.',
];

View File

@@ -0,0 +1,60 @@
<?php
namespace KupShop\PreordersBundle\Admin\lists;
use KupShop\AdminBundle\AdminList\BaseList;
use Query\Operator as Op;
use Query\QueryBuilder;
class PreordersDatesList extends BaseList
{
protected ?string $tableAlias = 'pd';
protected $tableDef = [
'id' => 'id',
'fields' => [
'Číslo' => ['field' => 'id', 'spec' => 'pd.id'],
'Název předobjednávky' => ['field' => 'name', 'spec' => 'po.name'],
'Popis' => ['field' => 'description', 'spec' => 'pd.description', 'render' => 'renderHTML'],
'Datum otevření' => ['field' => 'date_start', 'spec' => 'pd.date_start', 'render' => 'renderDate'],
'Datum skrytí' => ['field' => 'date_end', 'spec' => 'pd.date_end', 'render' => 'renderDate'],
'Datum expedice' => ['field' => 'date_shipment', 'spec' => 'pd.date_shipment'],
'Cena zbylého zboží' => ['field' => 'price_remaining', 'spec' => 'totals.price_remaining', 'render' => 'renderPrice'],
],
];
public function getQuery()
{
$qb = sqlQueryBuilder()
->from('preorders_dates', 'pd')
->leftJoin('pd', 'preorders', 'po', 'pd.id_preorder = po.id');
$totals = sqlQueryBuilder()
->select(
'pi.id_preorder_date AS id',
'SUM(pi.piece_price * currencies.rate * (pi.pieces - pi.pieces_sent)) AS price_remaining',
)
->from('preorders_items', 'pi')
->leftJoin('pi', 'currencies', 'currencies', 'pi.currency = currencies.id')
->groupBy('pi.id_preorder_date');
$qb->leftJoinSubQuery('pd', $totals, 'totals', 'totals.id = pd.id');
return $this->getFilterQueryParams($qb);
}
public function getFilterQueryParams(QueryBuilder $qb): QueryBuilder
{
if ($ids = getVal('id_preorder_date')) {
$qb->andWhere(Op::inIntArray($ids, 'pd.id_preorder_date'));
}
if ($ids = getVal('id_preorder')) {
$qb->andWhere(Op::inIntArray($ids, 'pd.id_preorder'));
}
return $qb;
}
}
return PreordersDatesList::class;

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\lists;
use KupShop\AdminBundle\AdminList\BaseList;
use Query\Operator;
use Query\QueryBuilder;
class PreordersItemsList extends BaseList
{
protected $tableName = 'preorders_items';
protected ?string $tableAlias = 'pi';
protected $tableDef = [
'id' => 'id',
'fields' => [
'Název' => ['field' => 'title', 'size' => 3, 'type' => 'products', 'spec' => 'p.title, p.id AS id'],
'Varianta' => ['field' => 'variation_title', 'spec' => 'COALESCE(pv.title, "Bez varianty") AS variation_title'],
'Kód' => ['field' => 'code', 'spec' => 'COALESCE(pv.code, p.code) AS code'],
'EAN' => ['field' => 'ean', 'spec' => 'COALESCE(pv.ean, p.ean) AS ean'],
'Číslo produktu' => ['field' => 'p.id'],
'Celkem (ks)' => ['field' => 'pieces', 'spec' => 'SUM(pi.pieces) AS pieces'],
'Zbývá odeslat (ks)' => ['field' => 'pieces_remaining', 'spec' => 'SUM(pi.pieces - pi.pieces_sent) AS pieces_remaining'],
'Odesláno (ks)' => ['field' => 'pieces_sent', 'spec' => 'SUM(pi.pieces_sent) AS pieces_sent'],
'Průměrná cena za ks' => ['field' => 'average_price', 'spec' => 'AVG(pi.piece_price * currencies.rate) AS average_price', 'render' => 'renderPrice'],
'Celková cena' => ['field' => 'total_price', 'spec' => 'SUM(pi.piece_price * pi.pieces * currencies.rate) AS total_price', 'render' => 'renderPrice'],
'Produktová cena (bez DPH)' => ['field' => 'product_price', 'spec' => 'COALESCE(pv.price, p.price) AS product_price', 'render' => 'renderPrice'],
'Rozdíl předobjednávkové a produktové ceny' => ['field' => 'price_diff', 'spec' => '(AVG(pi.piece_price * currencies.rate) - COALESCE(pv.price, p.price)) AS price_diff', 'render' => 'renderPrice'],
'Na skladě' => ['field' => 'in_store', 'spec' => 'COALESCE(pv.in_store, p.in_store) AS in_store'],
],
];
public function getQuery(): QueryBuilder
{
$qb = sqlQueryBuilder()
->from($this->tableName, $this->tableAlias)
->leftJoin('pi', 'products', 'p', 'pi.id_product = p.id')
->leftJoin('pi', 'products_variations', 'pv', 'pi.id_variation = pv.id')
->leftJoin('pi', 'currencies', 'currencies', 'pi.currency = currencies.id')
->leftJoin('pi', 'preorders_dates', 'pd', 'pi.id_preorder_date = pd.id')
->addGroupBy('pi.id_product')
->addGroupBy('pi.id_variation');
$qb->andWhere($this->getFilterSpec());
return $qb;
}
private function getFilterSpec(): \Closure
{
return function (QueryBuilder $qb) {
$equals = [];
if ($preorder = getVal('id_preorder')) {
$equals[] = Operator::inIntArray($preorder, 'pd.id_preorder');
}
if ($date = getVal('id_preorder_date')) {
$equals[] = Operator::inIntArray($date, 'pi.id_preorder_date');
}
if ($user = getVal('id_user')) {
$equals[] = Operator::inIntArray($user, 'pi.id_user');
}
if (getVal('only_remaining', default: 'N') === 'Y') {
$qb->andWhere('(pi.pieces - pi.pieces_sent) <> 0');
}
$qb->andWhere(Operator::andX($equals));
};
}
}
return PreordersItemsList::class;

View File

@@ -0,0 +1,34 @@
<?php
namespace KupShop\PreordersBundle\Admin\lists;
use KupShop\AdminBundle\AdminList\BaseList;
class PreordersList extends BaseList
{
protected $tableDef = [
'id' => 'id',
'fields' => [
'Název' => ['field' => 'name', 'spec' => 'p.name'],
'Číslo' => ['field' => 'id', 'spec' => 'p.id'],
'Konec posledního termínu' => ['field' => 'pd.last_end', 'render' => 'renderDate', 'spec' => 'pd.last_end'],
'Počet termínů' => ['field' => 'count', 'spec' => 'COALESCE(pd.date_count, 0) AS count'],
],
];
public function getQuery()
{
$counts = sqlQueryBuilder()
->select('pd.id_preorder',
'COUNT(pd.id) AS date_count',
'MAX(pd.date_end) AS last_end')
->from('preorders_dates', 'pd')
->groupBy('pd.id_preorder');
return sqlQueryBuilder()
->from('preorders', 'p')
->leftJoinSubQuery('p', $counts, 'pd', 'p.id = pd.id_preorder');
}
}
return PreordersList::class;

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\lists;
use Doctrine\DBAL\ParameterType;
use Query;
global $cfg;
require_once $cfg['Path']['shared_version'].'admin/lists/OrdersList.php';
class PreordersOrdersCreatedList extends \OrdersList
{
public function customizeTableDef($tableDef): array
{
$tableDef = parent::customizeTableDef($tableDef);
foreach ($tableDef['fields'] as &$field) {
if ($field['translation_section'] ?? false) {
continue;
}
$field['translation_section'] = 'orders';
}
return $tableDef;
}
public function getFilterQuery(): Query\QueryBuilder
{
$qb = parent::getFilterQuery();
$qb->andWhere("JSON_VALUE(o.note_admin, '$.preorders.id_preorder_date') IS NOT NULL");
if ($id = getVal('id_preorder_date')) {
$qb->andWhere("JSON_VALUE(o.note_admin, '$.preorders.id_preorder_date') = :id_preorder_date")
->addParameters(['id_preorder_date' => $id], ['id_preorder_date' => ParameterType::INTEGER]);
}
return $qb;
}
}
return PreordersOrdersCreatedList::class;

View File

@@ -0,0 +1,159 @@
<?php
namespace KupShop\PreordersBundle\Admin\lists;
use Doctrine\DBAL\ParameterType;
use KupShop\AdminBundle\AdminList\BaseList;
use KupShop\KupShopBundle\Util\HtmlBuilder\HTML;
use Query\Operator as Op;
use Query\QueryBuilder;
class PreordersUsersList extends BaseList
{
protected $showMassEdit = true;
protected $tableDef = [
'fields' => [
'Uživatel' => ['field' => 'user', 'spec' => "CONCAT_WS(' ', IF(u.firm = '', '', CONCAT(u.firm, ',')), u.name, u.surname) AS user"],
'Číslo předobjednávky' => ['field' => 'po.id_preorder', 'spec' => 'po.id_preorder'],
'Číslo termínu' => ['field' => 'id_preorder_date', 'spec' => 'po.id_preorder_date'],
'Název předobjednávky' => ['field' => 'preorder', 'spec' => 'po.name AS preorder'],
'Termín' => ['field' => 'preorder_date', 'spec' => "CONCAT(DATE_FORMAT(date_start, '%d.%m.%Y'), ' - ', DATE_FORMAT(date_end, '%d.%m.%Y')) AS preorder_date"],
'Začátek termínu' => ['field' => 'date_start', 'spec' => 'po.date_start', 'render' => 'renderDate'],
'Konec termínu' => ['field' => 'date_end', 'spec' => 'po.date_end', 'render' => 'renderDate'],
'Datum expedice' => ['field' => 'po.date_shipment', 'spec' => 'po.date_shipment'],
'Celková cena' => ['field' => 'price_total', 'spec' => 'SUM(po.piece_price * po.pieces) AS price_total', 'render' => 'renderPrice', 'fieldType' => BaseList::TYPE_PRICE, 'editable' => 'N'],
'Cena zbylého zboží' => ['field' => 'price_remaining', 'spec' => 'SUM(po.piece_price * (po.pieces - po.pieces_sent)) AS price_remaining', 'render' => 'renderPrice', 'fieldType' => BaseList::TYPE_PRICE, 'editable' => 'N'],
'Zprávy od zákazníka' => ['field' => 'history', 'spec' => 'pud.history', 'render' => 'renderUserMessages'],
],
'class' => 'getRowClass',
];
public function getRowClass($values): string
{
$class = '';
if ($values['price_remaining'] > 0) {
$class .= ' danger';
} elseif ($values['pieces'] <= 0) {
$class .= ' info';
} else {
$class .= ' success';
}
return $class;
}
public function renderPreorderDate($values, $column): string
{
$value = $this->renderCell($values, $column);
return "{$value} ({$this->renderDate($values, ['field' => 'date_start'])} {$this->renderDate($values, ['field' => 'date_end'])})";
}
public function renderUserMessages($values, $column)
{
$messages = json_decode($values['history'] ?: '[]', true);
$count = count(array_filter($messages, fn (array $m) => empty($m['admin'])));
if ($count > 0) {
return HTML::create('strong')
->text($count);
}
return $count;
}
public function getQuery(): QueryBuilder
{
$preordersQb = sqlQueryBuilder()
->select('pi.*', 'po.name', 'pd.date_end', 'pd.date_start', 'pd.date_shipment', 'po.id AS id_preorder')
->from('preorders_items', 'pi')
->leftJoin('pi', 'preorders_dates', 'pd', 'pi.id_preorder_date = pd.id')
->leftJoin('pd', 'preorders', 'po', 'pd.id_preorder = po.id');
$qb = sqlQueryBuilder()
->select("CONCAT(u.id, '-', COALESCE(po.id_preorder_date, '')) AS id", 'po.currency')
->from('users', 'u')
->leftJoinSubQuery('u', $preordersQb, 'po', 'po.id_user = u.id')
->leftJoin('po', 'preorders_users_data', 'pud', 'po.id_user = pud.id_user AND po.id_preorder_date = pud.id_preorder_date')
->andWhere("u.figure = 'Y'")
->addGroupBy('u.id')
->addGroupBy('po.id_preorder_date');
return $this->applyCustomQueryFilters($qb);
}
public function applyCustomQueryFilters(QueryBuilder $qb): QueryBuilder
{
$andEq = [];
if (getVal('id_preorder_date')) {
$andEq['po.id_preorder_date'] = array_map(intval(...), getVal('id_preorder_date'));
}
if (getVal('id_preorder')) {
$andEq['po.id_preorder'] = array_map(intval(...), getVal('id_preorder'));
}
if (getVal('id_user')) {
$andEq['u.id'] = array_map(intval(...), getVal('id_user'));
}
if (!empty($andEq)) {
foreach ($andEq as $key => $ids) {
$qb->andWhere(Op::inIntArray($ids, $key));
}
}
if (!getVal('allUsers')) {
$qb->andWhere('po.pieces > 0');
}
if ($groups = getVal('id_users_group')) {
$permittedIds = sqlQueryBuilder()->select('id_user')
->from('users_groups_relations')
->andWhere(Op::inIntArray(
array_map(intval(...), $groups),
'id_group',
));
$qb->andWhere('u.id IN ('.$permittedIds->getSQL().')')
->addParameters($permittedIds->getParameters(), $permittedIds->getParameterTypes());
}
if ($user = getVal('user')) {
if (ctype_digit($user)) {
$qb->andWhere('u.ico = :user_ico')
->addParameters(['user_ico' => $user], [ParameterType::INTEGER]);
} else {
$userLikeFilter = '%'.htmlspecialchars($user).'%';
$qb->andWhere(Op::like([
'u.firm' => $userLikeFilter,
'u.name' => $userLikeFilter,
'u.surname' => $userLikeFilter,
], 'OR'));
}
}
if (getVal('onlyClosed')) {
$qb->andWhere('po.date_end <= NOW()');
}
if (getVal('onlyUnsent')) {
$ids = sqlQueryBuilder()->select('DISTINCT pi.id_preorder_date')
->from('preorders_items', 'pi')
->where('pi.pieces - pi.pieces_sent > 0')
->execute()
->fetchFirstColumn();
$qb->andWhere(Op::inIntArray($ids, 'po.id'));
}
return $qb;
}
}
return PreordersUsersList::class;

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\lists;
use Doctrine\DBAL\Query\QueryBuilder;
use KupShop\I18nBundle\Admin\lists\TranslateList;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\PreordersBundle\Translations\PreordersDatesTranslation;
class TranslatePreordersDatesList extends TranslateList
{
protected $listType = 'translatePreordersDates';
protected $template = 'translateList/preordersDates.tpl';
public function __construct()
{
$this->translation = ServiceContainer::getService(PreordersDatesTranslation::class);
parent::__construct();
}
protected function createQueryBuilder(): QueryBuilder
{
$qb = parent::createQueryBuilder();
$qb->leftJoin('pd', 'preorders', 'po', 'pd.id_preorder = po.id')
->addSelect('po.name AS preorder');
return $qb;
}
}
return TranslatePreordersDatesList::class;

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace KupShop\PreordersBundle\Admin\lists;
use KupShop\I18nBundle\Admin\lists\TranslateList;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\PreordersBundle\Translations\PreordersTranslation;
class TranslatePreordersList extends TranslateList
{
protected $listType = 'translatePreorders';
protected $template = 'translateList/preorders.tpl';
public function __construct()
{
$this->translation = ServiceContainer::getService(PreordersTranslation::class);
parent::__construct();
}
}
return TranslatePreordersList::class;

View File

@@ -0,0 +1,86 @@
<?php
namespace KupShop\PreordersBundle\Admin;
class preorders extends \Window
{
protected $tableName = 'preorders';
public function get_vars(): array
{
$vars = parent::get_vars();
$body = &$vars['body'];
$data = &$body['data'];
if (!empty($data['settings'])) {
$this->sanitiseSettings($data['settings']);
}
if (!empty($data['min_price'])) {
$data['min_price'] = toDecimal($data['min_price']);
}
return $vars;
}
public function getData()
{
$data = parent::getData();
if (!empty($data['settings'])) {
$this->sanitiseSettings($data['settings']);
}
return $data;
}
protected function sanitiseSettings(&$settings): void
{
if (is_string($settings)) {
$settings = json_decode($settings, true);
}
if (!empty($settings['dynamic_prices'])) {
$this->sanitiseDynamicPrices($settings['dynamic_prices']);
}
if (!empty($settings['users_groups'])) {
$settings['users_groups'] = array_map('intval', $settings['users_groups']);
}
if (getVal('Submit')) {
$settings = json_encode($settings);
}
}
protected function sanitiseDynamicPrices(&$dynamicPrices): void
{
$dynamicPrices = array_filter($dynamicPrices, function ($val) {
return (empty($val['price_from']) || ctype_digit($val['price_from']))
&& (empty($val['price_to']) || ctype_digit($val['price_to']))
&& (ctype_digit($val['id_price_level'] ?? 'NaN') || ctype_digit($val['id_pricelist'] ?? 'NaN'));
});
usort($dynamicPrices, function ($a, $b) {
if (empty($a['price_from'])) {
return -1;
}
if (empty($b['price_from'])) {
return 1;
}
if (empty($a['price_to'])) {
return 1;
}
if (empty($b['price_to'])) {
return -1;
}
return $a['price_from'] < $b['price_from'] ? -1 : 1;
});
}
}
return preorders::class;

View File

@@ -0,0 +1,76 @@
<?php
namespace KupShop\PreordersBundle\Admin;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\PreordersBundle\Entity\UserPreorder;
use KupShop\PreordersBundle\Service\DynamicContexts;
use KupShop\PreordersBundle\Service\OrderCreator;
class preordersCreateOrder extends \Window
{
protected $tableName = 'preorders_items';
private readonly DynamicContexts $contexts;
private ?UserPreorder $userPreorder = null;
public function __construct()
{
$this->contexts = ServiceContainer::getService(DynamicContexts::class);
}
private function getUserPreorder(): UserPreorder
{
if ($this->userPreorder === null) {
[$userId, $dateId] = explode('-', $this->getID());
$this->userPreorder = new UserPreorder((int) $userId, (int) $dateId);
}
return $this->userPreorder;
}
protected function getObject()
{
return $this->getUserPreorder()->getTemplateVars();
}
public function get_vars()
{
$vars = parent::get_vars();
$this->contexts->activateUserPreorder($this->getUserPreorder());
return $vars;
}
public function hasRights($name = null): bool
{
switch ($name) {
case \Window::RIGHT_DUPLICATE:
case \Window::RIGHT_DELETE:
return false;
default:
return true;
}
}
public function handleUpdate(): true
{
$vars = $this->get_vars();
$data = $vars['body']['data'];
$items = isset($data['items']) ? array_filter($data['items']) : [];
if (!empty($items)) {
/** @var OrderCreator $orderCreator */
$orderCreator = ServiceContainer::getService(OrderCreator::class);
$userPreorder = new UserPreorder((int) $data['id_user'], (int) $data['id_preorder_date']);
$order = $orderCreator->createOrder($userPreorder, $items);
$this->returnOK("Objednávka {$order->order_no} úspěšně vytvořena.");
}
return true;
}
}
return preordersCreateOrder::class;

View File

@@ -0,0 +1,50 @@
<?php
namespace KupShop\PreordersBundle\Admin;
class preordersDates extends \Window
{
protected $tableName = 'preorders_dates';
protected $nameField;
public function get_vars()
{
$vars = parent::get_vars();
$body = &$vars['body'];
$data = &$body['data'];
$acn = $this->getAction();
if ($acn === 'add' && getVal('id_preorder')) {
$data['id_preorder'] = intval(getVal('id_preorder'));
}
if (!empty($data['date_start'])) {
$data['date_start'] = \DateTime::createFromFormat('Y-m-d', $data['date_start']);
}
if (!empty($data['date_end'])) {
$data['date_end'] = \DateTime::createFromFormat('Y-m-d', $data['date_end']);
}
return $vars;
}
public function getData()
{
$data = parent::getData();
if (getVal('Submit')) {
if (!empty($data['date_start'])) {
$data['date_start'] = $this->prepareDate($data['date_start']);
}
if (!empty($data['date_end'])) {
$data['date_end'] = $this->prepareDate($data['date_end']);
}
}
return $data;
}
}
return preordersDates::class;

View File

@@ -0,0 +1,198 @@
<?php
namespace KupShop\PreordersBundle\Admin;
use KupShop\AdminBundle\Admin\UserMessagesAdminInterface;
use KupShop\KupShopBundle\Email\UserMessagesInterface;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\PreordersBundle\Email\UserPreorderEmail;
use KupShop\PreordersBundle\Email\UserPreorderMessageEmail;
use KupShop\PreordersBundle\Entity\NotifyState;
use KupShop\PreordersBundle\Entity\UserPreorder;
use KupShop\PreordersBundle\Entity\UserPreorderMessage;
use KupShop\PreordersBundle\Service\DynamicContexts;
use Query\Operator as Op;
class preordersUsers extends \Window implements UserMessagesAdminInterface
{
protected $tableName = 'preorders_items';
private ?UserPreorder $userPreorder = null;
private readonly DynamicContexts $contextsService;
public function __construct()
{
$this->contextsService = ServiceContainer::getService(DynamicContexts::class);
}
private function getUserPreorder(): UserPreorder
{
if ($this->userPreorder === null) {
[$userId, $preorderDateId] = explode('-', $this->getID());
$this->userPreorder = new UserPreorder((int) $userId, (int) $preorderDateId);
}
return $this->userPreorder;
}
protected function getObject(): array
{
$up = $this->getUserPreorder()->getTemplateVars();
usort($up['items'], fn ($i, $j) => $i['title'] <=> $j['title']);
return $up;
}
public function get_vars(): array
{
$updateKeys = array_intersect(array_keys($_POST), ['SaveItemPieces', 'AddNewMessage']);
if (!empty($updateKeys)) {
$this->forceUpdate();
}
$vars = parent::get_vars();
$body = &$vars['body'];
$data = &$body['data'];
$userPreorder = $this->getUserPreorder();
$this->contextsService->activateUserPreorder($userPreorder);
$body['isClosed'] = \DateTimeImmutable::createFromFormat('Y-m-d', $data['date_end'])
<= (new \DateTimeImmutable())->sub(\DateInterval::createFromDateString('1 day'));
$body['messages'] = $userPreorder->getMessages();
$body['custom_data'] = $userPreorder->getCustomData();
$body['errors'] = $this->getItemErrors($data['items']);
return $vars;
}
private function getItemErrors(array $items): array
{
$errors = [];
foreach ($items as $item) {
if (($item['has_variations'] ?? false) && empty($item['id_variation'])) {
$errors[] = sprintf(
translate('variationNotSelected', 'preordersUsers'),
(int) $item['id'],
$item['product_title'],
);
}
}
return $errors;
}
public function hasRights($name = null): bool
{
switch ($name) {
case \Window::RIGHT_DUPLICATE:
return false;
default:
return true;
}
}
public function handleUpdate(): void
{
$userPreorder = $this->getUserPreorder();
$data = $this->getData();
if (!empty($data['user_address'])) {
$customData = $userPreorder->getCustomData();
$customData->set('user_address', $data['user_address']);
$customData->save();
}
if (getVal('SaveItemPieces', null, false) && $items = getVal('items')) {
foreach ($items as $id => $item) {
$userPreorder->setItem(
$id,
null,
null,
$item['pieces'],
$item['pieces_sent'],
);
}
$this->returnOK('Kusy produktů uloženy.');
}
if (getVal('AddNewMessage')) {
$this->handleSendMessage();
}
$this->returnOK();
}
private function handleSendMessage(): void
{
$userMessages = $this->getUserPreorder()->getMessages();
$text = getVal('new_message');
if (!$text) {
$this->returnError('Zpráva nebyla odeslána - žádný text.');
}
$admin = getAdminUser();
$newMessage = new UserPreorderMessage(
text: $text,
admin: $admin['login'] ?? 'NEZNÁMÝ ADMIN',
);
$userMessages->add($newMessage);
if ($exception = $userMessages->save()) {
$this->returnError('Nepodařilo se uložit zprávu. '.$exception->getMessage());
}
if (!getVal('message_do_not_notify')) {
if ($statusMessage = getVal('status_message')) {
$emailService = ServiceContainer::getService(UserPreorderMessageEmail::class);
$emailService->setUserMessage($statusMessage);
} else {
$emailService = ServiceContainer::getService(UserPreorderEmail::class);
}
$email = $emailService->setUserPreorder($this->getUserPreorder())->setMessage($newMessage);
$emailRes = $emailService->sendEmail($email->getEmail());
if (!$emailRes) {
$this->returnError('Nepodařilo se poslat e-mail uživateli.');
}
$newMessage->notified = NotifyState::EmailNotified;
$userMessages->save();
}
$this->returnOK('Zpráva úspěšně odeslána.');
}
public function handleDelete(): void
{
$userPreorder = $this->getUserPreorder();
$items = $userPreorder->getItems(true);
$ids = array_column($items, 'id');
sqlQueryBuilder()->delete('preorders_items')
->andWhere(Op::inIntArray($ids, 'id'))
->execute();
sqlQueryBuilder()->delete('preorders_users_data')
->andWhere(Op::equals([
'id_preorder_date' => $userPreorder->preorderDateId,
'id_user' => $userPreorder->userId,
]))
->execute();
redirect('launch.php?s=preordersUsers.php&acn=erased');
}
public function getUserEmails(): UserMessagesInterface
{
return ServiceContainer::getService(UserPreorderMessageEmail::class)->setUserPreorder($this->userPreorder);
}
}
return preordersUsers::class;

View File

@@ -0,0 +1,4 @@
<p style="width: 100%; text-align: center; margin-bottom: 2rem;">
Tato akce převede <strong>celou</strong> předobjednávku do objednávky. Tato akce nelze vrátit zpět.
<br>Opravdu chcete pokračovat?
</p>

View File

@@ -0,0 +1,4 @@
<div class="row mb-3">
<p class="col-lg-6 text-center">{'recalculateAreYouSure'|translate:'preordersUsers'}</p>
</div>

View File

@@ -0,0 +1 @@
{extends file="[shared]/menu.tpl"}

View File

@@ -0,0 +1,62 @@
{extends file="[shared]/menu.tpl"}
{block name="menu-items"}
<ul class="nav nav-pills nav-stacked">
<li class="nav-header"><i class="glyphicon glyphicon-tags"></i><span>{translate_type type=$type}</span></li>
<li><a href="javascript:nf('', 'launch.php?s=list.php&amp;type={$type}');"><i class="glyphicon glyphicon-list"></i>
<span>{'toolbar_list'|translate}</span></a></li>
<li>
<ul class="nav-sub nav-pills">
<form class="form-inline" target="mainFrame" method="get">
<input type="hidden" name="type" value="preordersItems"/>
<input type="hidden" name="s" value="list.php"/>
<div style="flex-direction: column;" class="d-flex form-group">
<div class="form-group">
<select class="selecter"
name="id_user[]"
multiple
data-autocomplete="users"
data-placeholder="Vyberte uživatele"
></select>
</div>
<div class="form-group">
<select class="selecter"
name="id_preorder[]"
multiple
data-autocomplete="preorders"
data-autocomplete-params="entity_type=preorders"
data-placeholder="Vyberte předobjednávky"
></select>
</div>
<div class="form-group">
<select class="selecter"
name="id_preorder_date[]"
multiple
data-autocomplete="preorders"
data-autocomplete-params="entity_type=preorders_dates"
data-placeholder="Vyberte termíny"
></select>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" class="input" name="only_remaining" id="only_remaining" value="Y">
<label for="only_remaining">Pouze neodeslané</label>
</div>
</div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary" title="Vyhledat uživatele">
<i class="glyphicon glyphicon-search"></i>
<span>Vyhledat</span>
</button>
</div>
</div>
</form>
</ul>
</li>
</ul>
{/block}

View File

@@ -0,0 +1,85 @@
{extends file="[shared]/menu.tpl"}
{block name="menu-items"}
<ul class="nav nav-pills nav-stacked">
<li class="nav-header"><i class="glyphicon glyphicon-tags"></i><span>{translate_type type=$type}</li>
<li><a href="javascript:nf('', 'launch.php?s=list.php&amp;type={$type}');"><i class="glyphicon glyphicon-list"></i>
<span>{'toolbar_list'|translate}</span></a></li>
<li>
<ul class="nav-sub nav-pills">
<form class="form-inline" target="mainFrame" method="get">
<input type="hidden" name="type" value="preordersUsers"/>
<input type="hidden" name="s" value="list.php"/>
<div style="flex-direction: column;" class="d-flex form-group">
<div class="form-group">
<select class="selecter"
name="id_user[]"
multiple
data-autocomplete="users"
data-placeholder="Vyberte uživatele"
></select>
</div>
<div class="form-group">
<select class="selecter"
name="id_preorder[]"
multiple
data-autocomplete="preorders"
data-autocomplete-params="entity_type=preorders"
data-placeholder="Vyberte předobjednávky"
></select>
</div>
<div class="form-group">
<select class="selecter"
name="id_preorder_date[]"
multiple
data-autocomplete="preorders"
data-autocomplete-params="entity_type=preorders_dates"
data-placeholder="Vyberte termíny"
></select>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" class="input" name="onlyClosed" id="onlyClosed">
<label for="onlyClosed">Pouze z uzavřených termínů</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" class="input" name="onlyUnsent" id="onlyUnsent">
<label for="onlyUnsent">Pouze s neodeslanými produkty</label>
</div>
</div>
<div style="flex-direction: column; margin-top: 1em;" class="d-flex form-group">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" class="input" name="allUsers" id="allUsers">
<label for="allUsers">Zobrazit všechny uživatele</label>
</div>
</div>
<div class="form-group">
<select class="selecter"
name="id_users_group[]"
multiple
data-autocomplete="users_groups"
data-placeholder="Vyberte skupiny uživatelů"
></select>
</div>
</div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary" title="Vyhledat uživatele">
<i class="glyphicon glyphicon-search"></i>
<span>Vyhledat</span>
</button>
</div>
</div>
</form>
</ul>
</li>
</ul>
{/block}

View File

@@ -0,0 +1,31 @@
<div id="flapPreorders" class="tab-pane fade in boxFlex">
<div class="row bottom-space">
<div class="col-md-12">
<h1 class="h4 main-panel-title">{'preorders'|translate:'preorders'}</h1>
</div>
</div>
{if isSuperuser()}
<div class="form-group">
<div class="col-md-4 control-label">
<label>{'multipleDatesSinglePage'|translate:'preorders'}</label>
<span class="glyphicon glyphicon-flash m-l-1" style="color: #aab2bd;" title="Vidí pouze superadmin"></span>
<a class="help-tip" data-toggle="tooltip" title="{'multipleDatesSinglePageTooltip'|translate:'preorders'}"><i class="bi bi-question-circle"></i></a>
</div>
<div class="col-md-1">
{print_toggle name="preorders][multiple_dates" value=$dbcfg.preorders.multiple_dates|default:'N'}
</div>
</div>
{else}
<input type="hidden" value="{$dbcfg.preorders.multiple_dates|default:'N'}" name="data[preorders][multiple_dates]">
{/if}
<div class="form-group">
<div class="col-md-4 control-label">
<label>{'showHidden'|translate:'preorders'}</label>
</div>
<div class="col-md-1">
{print_toggle name="preorders][show_hidden" value=$dbcfg.preorders.show_hidden|default:'N'}
</div>
</div>
</div>

View File

@@ -0,0 +1,5 @@
{extends "[I18nBundle]/list/translate.tpl"}
{block translationObjectTitle}
<strong>({$object.id})</strong> {$object.name}
{/block}

View File

@@ -0,0 +1,5 @@
{extends "[I18nBundle]/list/translate.tpl"}
{block translationObjectTitle}
<strong>({$object.id})</strong> {$object.preorder} {$object.date_start|date_format:'%d.%m.%Y'} - {$object.date_end|date_format:'%d.%m.%Y'}
{/block}

View File

@@ -0,0 +1,250 @@
{extends file="[shared]/window.tpl"}
{block tabs}
{windowTab id='flapMain'}
{if $body.data.id}
{windowTab id='flapProducts'}
{windowTab id='flapDiscountLevels'}
{windowTab id='flapDates'}
{/if}
{/block}
{block tabsContent}
<div id="flapMain" class="tab-pane fade active in boxStatic box">
<div class="row">
<div class="col-xs-1">
<div class="wpj-form-group">
<label for="id_preorder">Číslo</label>
<input id="id_preorder" class="form-control text-center" disabled value="{$body.data.id}" style="max-width: 3em;">
</div>
</div>
<div class="col-xs-5">
<div class="wpj-form-group">
<label>
{'name'|translate}
<a class="help-tip" data-html="true" data-placement="right"
data-toggle="tooltip" title="" data-original-title="Zobrazí se uživatelům v prostředí předobjednávek">
<i class="bi bi-question-circle"></i>
</a>
</label>
<input type="text" name="data[name]" id="name" class="form-control" value="{$body.data.name}">
</div>
</div>
<div class="col-xs-6">
<div class="wpj-form-group" id="date-fields">
<label>{'usersGroup'|translate}</label>
<select
name="data[settings][users_groups][]"
multiple="multiple"
class="selecter"
data-autocomplete="users_groups"
data-preload="usersGroups"
>
{foreach $body.data.settings.users_groups as $group}
<option value="{$group}" selected>{$group}</option>
{/foreach}
</select>
</div>
</div>
{block "custom-data"}{/block}
</div>
<div class="row">
<div class="col-xs-6">
<div class="wpj-form-group">
<label>{'base'|translate} {lcfirst('pricelist'|translate)}</label>
<select
name="data[id_pricelist]"
class="selecter"
data-autocomplete="priceLists"
data-preload="priceLists"
>
<option value="">{'pricelistEmptyChoice'|translate}</option>
{if $body.data.id_pricelist}
<option value="{$body.data.id_pricelist}" selected>{$body.data.id_pricelist}</option>
{/if}
</select>
</div>
</div>
<div class="col-xs-6">
<div class="wpj-form-group">
<label>{'base'|translate} {lcfirst('priceLevel'|translate)}</label>
<select
name="data[id_price_level]"
class="selecter"
data-autocomplete="priceLevels"
data-preload="priceLevels"
>
<option value="">{'priceLevelStandard'|translate:'users'}<option>
{if $body.data.id_price_level}
<option value="{$body.data.id_price_level}" selected>{$body.data.id_price_level}</option>
{/if}
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="wpj-form-group">
<label for="input--min_price">{'minPrice'|translate}</label>
<div class="input-group">
<input id="input--min_price" type="text" class="form-control" name="data[min_price]"
value="{if $body.data.min_price}{$body.data.min_price->asFloat()|string_format:"%.2f"}{/if}">
<span class="input-group-addon">{$dbcfg.currency}</span>
<span class="input-group-addon">{'withoutTax'|translate:'choice'}</span>
</div>
</div>
</div>
</div>
</div>
<div id="flapProducts" class="tab-pane fade boxFlex">
{$productsSortable = true}
{include 'block.productsFilter.tpl' filter=$body.data.settings.products_filter
filterInputName='data[settings][products_filter]' filterName='products_filter'}
</div>
<div id="flapDiscountLevels" class="tab-pane fade boxStatic box">
<div class="row bottom-space">
<div class="col-xs-3">
<button type="button" class="btn btn-block btn-success" data-form-add>
<i class="bi bi-plus-lg m-r-1"></i>{'addDynamicPrice'|translate}
</button>
</div>
</div>
<div class="wpj-panel wpj-panel-default">
<div class="wpj-panel-heading">
<div class="row">
<div class="col-xs-2">
<strong class="small">{'priceFrom'|translate}</strong>
</div>
<div class="col-xs-2">
<strong class="small">{'priceTo'|translate}</strong>
</div>
<div class="col-xs-3">
<strong class="small">{'priceLevel'|translate}</strong>
</div>
<div class="col-xs-3">
<strong class="small">{'pricelist'|translate}</strong>
</div>
</div>
</div>
<div class="wpj-list-group">
<div class="wpj-list-group-item" style="display: none;" data-form-new>
<div class="row">
<div class="col-xs-2">
<div class="input-group">
<input class="form-control" name="data[settings][dynamic_prices][@][price_from]"
placeholder="{'priceFrom'|translate}">
</div>
</div>
<div class="col-xs-2">
<div class="input-group">
<input class="form-control" name="data[settings][dynamic_prices][@][price_to]"
placeholder="{'priceTo'|translate}">
</div>
</div>
<div class="col-xs-3">
<select
class="selecter"
name="data[settings][dynamic_prices][@][id_price_level]"
data-autocomplete="priceLevels"
>
<option selected value="">{'priceLevelStandard'|translate:'users'}</option>
</select>
</div>
<div class="col-xs-3">
<select
class="selecter"
name="data[settings][dynamic_prices][@][id_pricelist]"
data-autocomplete="priceLists"
>
<option value="">{'pricelistEmptyChoice'|translate}</option>
</select>
</div>
<div class="col-xs-2 text-right">
<button type="button" class="btn btn-sm btn-danger" data-form-delete>
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
{foreach $body.data.settings.dynamic_prices as $dynamicPrice}
<div class="wpj-list-group-item" data-form-item>
<div class="row">
<div class="col-xs-2">
<div class="input-group">
<input class="form-control" name="data[settings][dynamic_prices][{$dynamicPrice@index}][price_from]"
value="{$dynamicPrice.price_from}"
placeholder="{'priceFrom'|translate}">
</div>
</div>
<div class="col-xs-2">
<div class="input-group">
<input class="form-control" name="data[settings][dynamic_prices][{$dynamicPrice@index}][price_to]"
value="{$dynamicPrice.price_to}"
placeholder="{'priceTo'|translate}">
</div>
</div>
<div class="col-xs-3">
<select
class="selecter"
name="data[settings][dynamic_prices][{$dynamicPrice@index}][id_price_level]"
data-autocomplete="priceLevels"
data-autocomplete-params="allow_empty=1"
data-preload="priceLevels"
>
{if $dynamicPrice.id_price_level}
<option selected value="{$dynamicPrice.id_price_level}">{$dynamicPrice.id_price_level}</option>
{/if}
</select>
</div>
<div class="col-xs-3">
<select
class="selecter"
name="data[settings][dynamic_prices][{$dynamicPrice@index}][id_pricelist]"
data-autocomplete="priceLists"
data-preload="priceLists"
>
<option value="">{'pricelistEmptyChoice'|translate}</option>
{if $dynamicPrice.id_pricelist}
<option selected value="{$dynamicPrice.id_pricelist}">{$dynamicPrice.id_pricelist}</option>
{/if}
</select>
</div>
<div class="col-xs-2 text-right">
<button type="button" class="btn btn-sm btn-danger" data-form-delete>
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
{/foreach}
</div>
</div>
</div>
<div id="flapDates" class="tab-pane fade active in boxStatic box">
<div class="row bottom-space">
<div class="col-xs-3">
<a class="btn btn-block btn-success"
href="javascript:nw('preordersDates', null, 'id_preorder={$body.data.id}');"><span class="bi bi-plus-lg m-r-1"></span>{'addPreorderDate'|translate}</a>
</div>
</div>
<iframe class="on-demand boxFlex" height="600"
data-src="launch.php?s=list.php&type=preordersDates&id_preorder[]={$body.data.id}"></iframe>
</div>
{/block}
{block onready append}
initForm({
selector: '#flapDiscountLevels',
jsonFriendly: true,
beforeAdd(callback) {
var $form = callback();
window.preloadAutocompletes($form);
return false;
},
});
{/block}

View File

@@ -0,0 +1,75 @@
{extends file="[shared]/window.tpl"}
{block tabsContent}
<div class="row">
<div class="col-xs-4 pull-right">
<div class="wpj-form-group">
<a href="#" data-add-all class="btn btn-success btn-block"><span
class="bi bi-plus-lg m-r-1"></span>{'buttonAddValue'|translate}</a>
</div>
</div>
</div>
<div class="wpj-panel wpj-panel-default">
<div class="wpj-panel-heading">
<div class="row">
<div class="col-xs-5">
<small class="text-muted">{'item'|translate:'preordersUsers'}</small>
</div>
<div class="col-xs-2">
<small class="text-muted">{'piecePrice'|translate:'preordersUsers'}</small>
</div>
<div class="col-xs-2">
<small class="text-muted">{'piecesSent'|translate:'preordersUsers'}</small>
</div>
<div class="col-xs-2">
<small class="text-muted">{'piecesToAdd'|translate:'preordersUsers'}</small>
</div>
<div class="col-xs-1">
<small class="text-muted"></small>
</div>
</div>
</div>
<div class="wpj-list-group">
{foreach $body.data.items as $item}
{if $item.pieces_sent >= $item.pieces}{continue}{/if}
<div class="wpj-list-group-item">
<div class="row">
<div class="col-xs-5">
<strong><a href="javascript:nw('products','{$item.id_product}');" class="text-dark">{$item.title}</a></strong>
</div>
<div class="col-xs-2">
{$item.piece_price|format_price}
</div>
<div class="col-xs-2">
{$item.pieces_sent} / {$item.pieces}
</div>
<div class="col-xs-2">
<div class="input-group">
<input class="form-control" name="data[items][{$item.id}]"
aria-label="{'piecesToAdd'|translate}">
<div class="input-group-btn">
<button type="button" data-add-all-count="{$item.pieces - $item.pieces_sent}" class="btn btn-success">Vše
</button>
</div>
</div>
</div>
<div class="col-xs-1">
<small class="text-muted"></small>
</div>
</div>
</div>
{/foreach}
</div>
</div>
<script>
$('button[data-add-all-count]').on('click', function () {
var $btn = $(this);
var count = $btn.data('add-all-count');
$btn.parent('.input-group-btn').siblings('input').val(count);
});
$('[data-add-all]').on('click', function (e) {
$('button[data-add-all-count]').click();
});
</script>
{/block}

View File

@@ -0,0 +1,150 @@
{extends file="[shared]/window.tpl"}
{block tabs}
{windowTab id='flapMain'}
{if $body.data.id}
{windowTab id='flapPreordersCreated'}
{windowTab id='flapProducts'}
{/if}
{/block}
{block tabsContent}
<div id="flapMain" class="tab-pane fade active in boxStatic box">
<div class="row wpj-form-group-flex">
<div class="col-xs-1">
<div class="wpj-form-group">
<label for="id_preorder_date">Číslo</label>
<input id="id_preorder_date" disabled value="{$body.data.id}" class="form-control text-center" style="max-width: 3em;">
</div>
</div>
<div class="col-sm-5"></div>
<div class="col-xs-4">
<div class="wpj-form-group">
<label>
<a href="javascript:void showPreorder();">{'preorder'|translate}</a>
</label>
<script>
function showPreorder() {
var preorderId = $('select[name="data[id_preorder]"] option:selected').attr('value');
nw('preorders', preorderId);
}
</script>
<select
name="data[id_preorder]"
class="selecter"
data-preload="preorders"
data-autocomplete="preorders"
data-autocomplete-params="entity_type=preorders"
>
{if $body.data.id_preorder}
<option selected value="{$body.data.id_preorder}">{$body.data.id_preorder}</option>
{/if}
</select>
</div>
</div>
<div class="col-xs-2">
<div class="wpj-form-group">
<a href="javascript:nw('preorders');" class="btn btn-block btn-success"><span class="bi bi-plus-lg m-r-1"></span>{'preorderCreateBtn'|translate}</a>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="wpj-form-group">
<label>
{'dateStart'|translate}
<a class="help-tip"
data-toggle="tooltip"
title="{'dateStartTooltip'|translate}"
>
<i class="bi bi-question-circle"></i>
</a>
</label>
<div class="input-group">
<input type="text" name="data[date_start]" id="date_start" class="form-control input-sm"
value="{$body.data.date_start|format_date:'d.m.Y'}">
{insert_calendar selector='#date_start' format='date'}
<label for="date_start" class="input-group-addon">
<span class="bi bi-calendar-week"></span>
</label>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="wpj-form-group">
<label>
{'dateEnd'|translate}
<a class="help-tip"
data-toggle="tooltip"
title="{'dateEndTooltip'|translate}"
>
<i class="bi bi-question-circle"></i>
</a>
</label>
<div class="input-group">
<input type="text" name="data[date_end]" id="date_end" class="form-control input-sm"
value="{$body.data.date_end|format_date:'d.m.Y'}">
{insert_calendar selector='#date_end' format='date'}
<label for="date_end" class="input-group-addon">
<span class="bi bi-calendar-week"></span>
</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="wpj-form-group">
<label>
{'dateShipment'|translate}
<a class="help-tip"
data-toggle="tooltip"
title="{'dateShipmentTooltip'|translate}"
>
<i class="bi bi-question-circle"></i>
</a>
</label>
<input type="text" name="data[date_shipment]" id="date_shipment" class="form-control input-sm"
value="{$body.data.date_shipment}">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="wpj-form-group">
<label for="description">{'description'|translate}</label>
<textarea
id="description"
name="data[description]"
rows="3"
class="form-control input-sm"
>{$body.data.description}</textarea>
</div>
</div>
</div>
</div>
<div id="flapPreordersCreated" class="tab-pane fade active in boxStatic box">
<iframe data-src="launch.php?s=list.php&type=preordersUsers&id_preorder_date[]={$body.data.id}" height="520" class="on-demand"></iframe>
</div>
<div id="flapProducts" class="tab-pane fade active in boxStatic box">
<iframe data-src="launch.php?s=list.php&type=preordersItems&id_preorder_date[]={$body.data.id}" height="520" class="on-demand"></iframe>
</div>
<script type="text/javascript">
$(function () {
$('[data-shipment-clear]').on('click', function () {
var selector = $(this).data('shipment-clear');
$(selector).val('');
});
});
</script>
{/block}

View File

@@ -0,0 +1,276 @@
{extends file="[shared]/window.tpl"}
{block tabs}
{windowTab id='flapMain'}
{windowTab id='flapMessages'}
{if $body.isClosed}
{windowTab id='flapOrders'}
{/if}
{/block}
{block js append}
<script src="./static/js/EmailMessages.js"></script>
{/block}
{block tabsContent}
<div id="flapMain" class="tab-pane fade active in boxStatic box">
<div class="m-b-1">
<h4>{'titleUser'|translate}</h4>
</div>
<div class="well well-lg">
<div class="row">
<div class="col-xs-3">
<p class="m-b-0">
{'user'|translate}<br>
<a href="javascript:nw('users', {$body.data.id_user});" class="text-large text-bold text-dark">
{$body.data.user_firm}
</a>
</p>
</div>
<div class="col-xs-2">
<p class="m-b-0">
{'preorder'|translate:'preordersDates'}<br>
<a href="javascript:nw('preorders', {$body.data.id_preorder});" class="text-large text-bold text-dark">
{$body.data.name}
</a>
</p>
</div>
<div class="col-xs-3">
<p class="m-b-0">
{'dateTitle'|translate}<br>
<a href="javascript:nw('preordersDates', {$body.data.id_preorder_date});" class="text-large text-bold text-dark" title="{$body.data.date_description}">
{$body.data.date_start|format_date:'d.m.Y'} {$body.data.date_end|format_date:'d.m.Y'}
{if $body.data.date_shipment}
<br>
<small class="text-muted" style="font-weight: normal;">({'dateShipmentAbbr'|translate} {$body.data.date_shipment})</small>
{/if}
</a>
</p>
</div>
<div class="col-xs-2">
<p class="m-b-0">
{'priceTotal'|translate:'preordersDates'}<br>
<strong class="text-large">{$body.data.price_total|format_price}</strong>
</p>
</div>
<div class="col-xs-2">
<p class="m-b-0">
{'priceRemaining'|translate:'preordersDates'}<br>
<strong class="text-large">{$body.data.price_remaining|format_price}</strong>
</p>
</div>
</div>
{ifmodule USER_ADDRESSES}
<div class="row">
<div class="col-xs-12">
<label>{'userAddress'|translate:'preordersDates'}</label>
</div>
<div class="col-xs-12">
<select class="selecter" name="data[user_address]" data-autocomplete="UserAddresses" data-placeholder="--"
data-preload="UserAddresses"
data-autocomplete-params="id_user={$body.data.id_user}">
{if $body.custom_data.user_address}
<option value="{$body.custom_data.user_address}" selected>{$body.custom_data.user_address}</option>
{/if}
</select>
</div>
</div>
{/ifmodule}
</div>
{if $body.errors|count}
<div class="row">
<div class="col-xs-12">
{foreach $body.errors as $error}
<div class="alert alert-danger">
<span>{$error nofilter}</span>
</div>
{/foreach}
</div>
</div>
{/if}
<div class="d-flex align-items-center justify-content-between m-b-1">
<h4>{'titleOrderedProducts'|translate}</h4>
<button name="SaveItemPieces" class="btn btn-primary" type="submit" value="SaveItemPieces">{'saveItemPieces'|translate}</button>
</div>
<div class="wpj-panel wpj-panel-default">
<div class="wpj-panel-heading">
<div class="row">
<div class="col-xs-5">
<small>{'item'|translate}</small>
</div>
<div class="col-xs-2">
<small>{'piecePrice'|translate}</small>
</div>
<div class="col-xs-2">
<small>{'piecesSent'|translate}</small>
</div>
<div class="col-xs-3 text-right">
<small>{'priceRemaining'|translate}</small>
</div>
</div>
</div>
<div class="wpj-list-group">
{foreach $body.data.items as $item}
{$formatParams = "currency=`$item.currency`"}
<div id="preorder-item-{$item.id}" class="wpj-list-group-item">
<div class="row">
<div class="col-xs-5">
<a href="javascript:nw('products','{$item.id_product}');">{$item.product_title} {$item.variation_title}</a>
{if $item.has_variations and $item.id_variation === null}
<a class="text-danger m-l-1" data-toggle="tooltip" title=""
data-original-title="{'variationNotSelectedTooltip'|translate}">
<i class="bi bi-exclamation-circle"></i>
</a>
{/if}
</div>
<div class="col-xs-2">
{$item.piece_price|format_price:$formatParams}
</div>
<div class="col-xs-2">
<div class="d-flex align-items-center">
<input class="form-control" name="items[{$item.id}][pieces_sent]" value="{$item.pieces_sent}">
&nbsp;/&nbsp;
<input class="form-control" name="items[{$item.id}][pieces]" value="{$item.pieces}">
</div>
</div>
<div class="col-xs-3 text-right">
{$item.price_remaining|format_price:$formatParams} / {$item.price_total|format_price:$formatParams}
</div>
</div>
</div>
{/foreach}
</div>
</div>
<hr />
<div class="d-flex align-items-center justify-content-between">
<h4>Metadata</h4>
</div>
<div class="row">
<div class="col-sm-4">
<div class="d-flex justify-content-between align-items-center m-b-1">
<label>Jazyk:</label>
<code>{$body.custom_data.language}</code>
</div>
<div class="d-flex justify-content-between align-items-center m-b-1">
<label>Doména:</label>
<code><a href="https://{$body.custom_data.domain}/" target="_blank">{$body.custom_data.domain}</a></code>
</div>
<div class="d-flex justify-content-between align-items-center m-b-1">
<label>Měna:</label>
<code>{$body.custom_data.currency}</code>
</div>
</div>
<div class="col-sm-4">
<div class="d-flex justify-content-between align-items-center m-b-1">
<label>Naposledy uloženo:</label>
<code>{$body.custom_data.last_saved_at|date_format:'%d.%m.%Y %H:%I %Z'}</code>
</div>
<div class="d-flex justify-content-between align-items-center m-b-1">
<label>IP Adresa:</label>
<code>{$body.custom_data.ip_address}</code>
</div>
<div class="d-flex justify-content-between align-items-center m-b-1" >
<label>Prohlížeč:</label>
<code title="{$body.custom_data.user_agent}">{$body.custom_data.user_agent|truncate:30:'...'}</code>
</div>
</div>
</div>
</div>
<div id="flapMessages" class="tab-pane fade in boxStatic box">
<div class="m-b-1">
<h4>{'previousMessages'|translate}</h4>
</div>
<div class="col-md-12 m-b-2">
<div class="row m-b-2">
<div class="wpj-panel wpj-panel-default">
<div class="wpj-panel-heading">
<div class="row">
<div class="col-xs-2">
<small class="text-muted">{'notification'|translate}</small>
</div>
<div class="col-xs-2 text-center">
<small class="text-muted">{'messageDate'|translate}</small>
</div>
<div class="col-xs-6 text-center">
<small class="text-muted">{'messageText'|translate}</small>
</div>
<div class="col-xs-2 text-right">
<small class="text-muted">{'messageSentBy'|translate}</small>
</div>
</div>
</div>
<div class="wpj-list-group">
{foreach $body.messages as $message}
<div class="wpj-list-group-item">
<div class="row">
<div class="col-xs-2">
{$message.notified->getMessage()}
</div>
<div class="col-xs-2 text-center">
{$message.date|date_format:'d.m.Y H:i'}
</div>
<div class="col-xs-6 text-center">
{$message.text nofilter}
</div>
<div class="col-xs-2 text-right">
{if $message.admin}
{$message.admin}
{else}
{$body.data.user_firm}
{/if}
</div>
</div>
</div>
{/foreach}
</div>
</div>
</div>
</div>
<div class="d-flex flex-row justify-content-end">
<div class="col-xs-6">
<h5>{'newMessage'|translate}</h5>
<div class="m-b-1">
<textarea id="message" name="new_message" wrap="soft"></textarea>
{insert_wysiwyg target="message" type="BasicTable"
config="toolbarStartupExpanded : false,startupFocus : false,removePlugins : 'elementspath', height:'150px', resize_minHeight:'100px', resize_enabled:false, ignoreEmptyParagraph:true, startupOutlineBlocks:false"}
</div>
<div>
<input type="hidden" name="status_message" value="" id="status_comment">
{include "block.UserMessages.tpl"}
{renderMessageButtons}
</div>
<div class="d-flex flex-row justify-content-between">
<div class="checkbox">
<input type="checkbox" class="check" value="Y" name="message_do_not_notify" id="donotnotify">
<label for="donotnotify"><strong>{'negation'|translate:'orders'}</strong>{'sendEmail'|translate:'orders'}</label>
</div>
<button class="btn btn-primary" type="submit" name="AddNewMessage" value="SaveItemPieces">Odeslat zprávu</button>
</div>
</div>
</div>
</div>
<div id="flapOrders" class="tab-pane fade in boxStatic box">
<iframe
class="on-demand boxFlex" height="600"
src="launch.php?s=list.php&type=preordersOrdersCreated&userId={$body.data.id_user}&id_preorder_date={$body.data.id_preorder_date}"></iframe>
</div>
{/block}