Проблема бизнеса

К нам обратился владелец крупного цветочного магазина с очень распространённой, но при этом крайне болезненной бизнес-проблемой. Каждый год, к ключевым праздникам — 14 февраля, 8 марта, 1 сентября — цены на цветы резко вырастают из-за сезонного спроса и высокой закупочной стоимости.

Однако покупатели начинают оформлять заказы заранее, иногда за 3–7 дней до праздника, когда действуют ещё обычные «непраздничные» цены. В итоге менеджерам приходилось лично связываться с клиентами и объяснять, что:

  • Цена заказа увеличится и потребуется доплата;
  • Букеты по указанной цене недоступны для праздничной даты;
  • Некоторые цветы отсутствуют в праздничные дни и будут заменены.
Последствия:
Это вызывало конфликты, недовольство клиентов, потери и репутационные риски. Магазин искал способ автоматизировать процесс и избежать ручных корректировок.
Ключевая задача:
Цена должна зависеть не от текущего дня, а от даты доставки, выбранной самим покупателем.

На первый взгляд решение кажется очевидным, однако движок OpenCart не поддерживает динамическую смену цены по выбранной дате — только по текущей. Из-за этого стандартные скидки OpenCart совершенно не подходят для праздничных сценариев.

Почему стандартные средства OpenCart не подходят

OpenCart применяет скидки по очень простой логике:

[code lang=php] if ($discount['date_start'] <= date('Y-m-d') && $discount['date_end'] >= date('Y-m-d')) { $price = $discount['price']; } [/code]
Проблема:
Скидка рассчитывается всегда исходя из текущей даты, а не из даты, которую выбирает покупатель.

В реальном кейсе это выглядело так:

  • 10 февраля покупатель оформляет заказ
  • Выбирает доставку на 14 февраля
  • Цена должна быть праздничной (дороже)
  • Но система применяет цену 10 февраля
Цена должна зависеть от даты, которую выбрал покупатель.
Если доставка выпадает на праздничный период — цена должна пересчитаться автоматически.

Наше решение: архитектура и преимущества

Главная идея:
Не изобретать свой механизм цен, а раскрыть потенциал стандартного OpenCart, добавив недостающий слой логики.

Мы использовали существующие сущности OpenCart и дополнили их «умным» поведением:

  • Вкладка «Скидки» — стала хранилищем праздничных цен, т.к. уже поддерживает диапазон дат (date_start / date_end) и разные группы покупателей.
  • Опция типа «Дата» — стала источником даты доставки, которую выбирает клиент.
  • AJAX-пересчет цены — срабатывает, когда клиент выбирает дату, и возвращает корректную цену.
  • Пересчёт цены в корзине и заказе — обязательный backend-контроль, чтобы никто не обошёл праздничную цену.
  • Автоматическое управление опцией «Дата» — если у товара появились скидки с датами, опция Дата включается автоматически.
  • Поддержка составных товаров (композитов) — праздничная цена рассчитывается из цен компонентов.
Вкладка «Скидки» — стала хранилищем праздничных цен
Вкладка «Скидки» — стала хранилищем праздничных цен
Опция типа «Дата» — стала источником даты доставки
Опция типа «Дата» — стала источником даты доставки

Почему такое решение оказалось идеальным

1. Минимальное вмешательство в ядро
Работа полностью через OCMOD, без переписи системных классов.
2. Совместимость
Работает с любыми версиями шаблона UniTheme / UniStore, любыми модулями корзины и SEO-расширениями.
3. Масштабируемость
Можно добавлять сколько угодно праздничных периодов и групп клиентов.
4. Прозрачность для менеджеров
Менеджер работает в стандартной админке OpenCart и не учится новому интерфейсу.

Как это выглядит с точки зрения данных

В админке

Менеджер создаёт скидку:

  • Цена — праздничная цена
  • Количество = 1
  • Дата начала / дата конца — период праздника
На витрине

Покупатель выбирает:

  • Доставить 14.02.2026

AJAX отправляет эту дату → PHP получает её → ищет скидку именно для этого дня → возвращает корректную цену.

В корзине

Даже если покупатель подменит AJAX-запрос, cart.php пересчитает цену заново.

Реализация: от backend к frontend

Изменения в модели (работа со скидками)

По умолчанию OpenCart отдаёт только активные на текущий день скидки. Для праздничной логики это неприемлемо — нам нужно видеть все будущие периоды скидок.

Поэтому мы добавили отдельный метод:

[code lang=php] public function getProductDiscountsWithDates($product_id) { $query = $this->db->query(" SELECT product_discount_id, product_id, customer_group_id, quantity, priority, price, date_start, date_end FROM " . DB_PREFIX . "product_discount WHERE product_id = '" . (int)$product_id . "' AND customer_group_id = '" . (int)$this->config->get('config_customer_group_id') . "' ORDER BY quantity ASC, priority ASC, price ASC "); return $query->rows; } [/code]
Теперь мы можем анализировать все периоды: будущие, текущие и без дат. И решать — какой период подходит под дату, выбранную клиентом.

Логика определения праздничной цены

Сначала нужно обработать дату от фронтенда. Фронтенд отдаёт дату в формате:
dd/mm/yyyy
Например:
14/02/2026
Backend должен перевести это в формат MySQL:
yyyy-mm-dd
Например: 2026-02-14


[code lang=php] if (preg_match('/^\d{2}/\d{2}/\d{4}$/', $posted_date)) { $parts = explode('/', $posted_date); if (checkdate($parts[1], $parts[0], $parts[2])) { $selected_date = $parts[2] . '-' . $parts[1] . '-' . $parts[0]; } } [/code]
Важно: модуль распознаёт оба формата — и dd/mm/yyyy, и yyyy-mm-dd — чтобы работать и с календарём, и с внутренними значениями.

Теперь, когда дата доставки обработана, мы ищем подходящий период:

[code lang=php] foreach ($discounts as $discount) { if (!empty($discount['date_start']) && !empty($discount['date_end'])) { if ($selected_date >= $discount['date_start'] && $selected_date <= $discount['date_end']) { $holiday_price = $discount['price']; $discount_period_info = sprintf( "Период праздничных цен: %s - %s", date('d.m.Y', strtotime($discount['date_start'])), date('d.m.Y', strtotime($discount['date_end'])) ); break; } } } [/code]
Результат: на любой выбранный день товар получает корректную праздничную цену. Если периодов несколько — берётся первый по приоритету.

Сохранение даты в сессию

Чтобы покупатель при переходе по товарам не выбирал дату заново — она сохраняется:

[code lang=php] $this->session->data['delivery_date'] = $selected_date; [/code]

Это позволяет:

  • ✔ подтягивать дату в календарь автоматически
  • ✔ использовать её в корзине
  • ✔ использовать в checkout
  • ✔ пересчитывать цены постоянно в одном контексте

Интеграция с корзиной и заказом (безопасность)

Даже если пользователь отключит JavaScript, все цены обязательно будут пересчитаны на сервере.

В system/library/cart/cart.php:

[code lang=php] if ($holiday_price !== null) { // применяем праздничную цену $price = $holiday_price; } else { // fallback: обычная цена или скидка по количеству } [/code]
Почему это важно:
Даже если злоумышленник попытается подменить JS или отправить запрос вручную — сервер всегда проставит правильную цену.

Backend защищает от:

  • подмены фронтенда
  • попыток оплатить товар по неактуальной цене
  • некорректных сумм в письмах
  • ошибок в заказах в админке

Динамическое обновление на фронтенде (AJAX, Pickadate)

Когда покупатель выбирает дату доставки — нужно мгновенно пересчитать цену, не перезагружая страницу. Для этого мы реализовали несколько логичных и аккуратных шагов.

1. Отправка опций и даты на сервер

При каждом изменении в карточке товара (в том числе выборе даты) вызывается AJAX-функция:

[code lang=js] function updateProductPrice() { let formData = $('#product input[type="text"], \ #product input[type="hidden"]:not("#uni_purchase_byoneclick_form_product input"), \ #product input[type="radio"]:checked, \ #product input[type="checkbox"]:checked, \ #product select, \ #product .uni-datepicker input[type="text"]').serialize(); $.ajax({ type: 'post', url: 'index.php?route=unitemplate/main/uni_functions/updatePrices', data: formData, dataType: 'json', cache: false, success: function(json) { // Обновление цены $('#product .sc-module-price').html(json['price']); if (json['special']) { $('#product .sc-module-price-old').html(json['price']); $('#product .sc-module-price').html(json['special']); } // Отображение информации о праздничном периоде $('.sc-holiday-price-info').remove(); if (json['discount_period']) { $('.datepicker-tooltip-info').append( '<div class="sc-holiday-price-info d-flex align-items-center mt-2 mb-2">' + '<i class="fa fa-gift text-danger me-2"></i>' + '<span class="fsz-12 fw-500 dark-text">' + json['discount_period'] + '</span>' + '</div>' ); } // Автозаполнение календаря, если пришла дата из сессии if (json['delivery_date']) { const [y, m, d] = json['delivery_date'].split('-'); const $dateInput = $('#product .uni-datepicker input[type="text"]'); if ($dateInput.length) { let picker = $dateInput.pickadate('picker'); if (picker && picker.get('select') === null) { picker.set('select', [parseInt(y), parseInt(m)-1, parseInt(d)]); } } } } }); } [/code]
Простыми словами:
При каждом изменении опций товар общается с сервером и получает актуальную цену по выбранной дате.

2. Инициализация календаря Pickadate

Мы подключаем кастомный календарь:

[code lang=js] productDatePicker = $dateInput.pickadate({ format: 'dd/mm/yyyy', formatSubmit: 'dd/mm/yyyy', firstDay: 1, max: 90, min: true, monthsFull: ['Январь','Февраль','Март','Апрель','Май','Июнь', 'Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'], weekdaysShort: ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'], today: 'Сегодня', clear: 'Очистить', close: 'Закрыть', hiddenName: true, onSet: function(ctx) { if (ctx.select || ctx.hasOwnProperty('clear')) { setTimeout(updateProductPrice, 50); } } }).pickadate('picker'); [/code]

3. Автоматический запуск при загрузке страницы

Если на товаре есть обязательная дата доставки — нужно рассчитать цену сразу.

[code lang=js] $(document).ready(function() { const $dateInput = $('#product .uni-datepicker input[type="text"]'); if ($dateInput.length > 0 && !$dateInput.val()) { updateProductPrice(); // первый расчет } }); [/code]

Это удобно, когда:

  • • клиент вернулся из корзины
  • • дата уже лежит в сессии
  • • нужно сразу показать праздничную цену

Особые случаи: составные товары и автоматизация опций

Автоматическая работа с составными товарами

Составные товары (букеты из нескольких компонентов) получают корректные праздничные цены автоматически.
[code lang=php] $composite_price = $sprice; foreach ($product_composites as $product_composite) { if ($has_discount_in_period) { $composite_price = $composite_price - ($ordinary_price * $component_quantity) + ($discount_price * $component_quantity); } } [/code]
  • Все комплекты получают праздничные цены автоматически
  • Администратору не нужно ничего менять вручную
  • Логика полностью встроена в OpenCart

Автоматическое управление опцией "Дата"

[code lang=php] if ($has_holiday_discount && !$has_date_option) { INSERT INTO product_option ... } elseif (!$has_holiday_discount && $has_date_option) { DELETE FROM product_option ... } [/code]
Опция "Дата" появляется только для товаров с праздничными скидками и удаляется при их отсутствии.

Интеграция с быстрой покупкой и checkout

[code lang=php] $custom_field[10] = $formatted_date; // Дата доставки $custom_field[4] = $formatted_date; // Дата самовывоза [/code]
  • Покупатель выбирает дату один раз
  • Все формы на сайте автоматически заполняются

Результат и выгоды для бизнеса

Устранили ручную работу

Менеджеры больше не тратят время на объяснение изменений цен и не вызывают недовольство клиентов. Все процессы автоматизированы.

Повысили доверие клиентов

Цена ясна сразу при выборе даты доставки. Никаких скрытых доплат и сюрпризов при оформлении заказа.

Масштабируемость

Система автоматически масштабируется на любые праздники и сезонные периоды. Просто добавляйте новые скидки в админке.

Ключевые достижения:
  • Полностью решена проблема праздничных цен
  • Работает через штатные механизмы OpenCart
  • Не ломает ядро и совместима с другими модулями
  • Поддерживает составные товары и автоматизацию
  • Запоминает дату доставки на всех этапах заказа
  • Обновляет цены в реальном времени без перезагрузки
  • Корректно рассчитывает цену на всех этапах заказа

Покупатель теперь всегда видит правильную цену для своей даты.
Менеджеры больше не перезванивают клиентам.
Бизнес получил автоматизацию и точность.

Заключение

Готовы автоматизировать сезонное ценообразование в вашем магазине?

Если вы сталкиваетесь с похожими проблемами в вашем интернет-магазине на OpenCart, команда Uni Opencart готова помочь вам создать эффективное решение.

Что мы предлагаем:
  • Индивидуальные доработки под ваш бизнес
  • Оптимизацию существующих решений
  • Техническую поддержку 24/7
  • Гарантию на все работы
Наши преимущества:
  • Более 10 лет опыта работы с OpenCart
  • Специализация на технических доработках
  • Минимальное вмешательство в ядро
  • Совместимость с популярными шаблонами
Важно: Каждое решение мы адаптируем под конкретные нужды вашего бизнеса. Наша цель — не просто написать код, а решить вашу бизнес-задачу эффективно и надежно.