Розробка форми заявки на повернення товару 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка форми заявки на повернення товару 1С-Бітрікс
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Розробка форми заявки на повернення товару 1С-Бітрікс

Стандартний компонент bitrix:sale.order.return.edit працює, але в реальних проєктах його не вистачає: він не підтримує завантаження фотографій дефекту, немає покрокового інтерфейсу (step-by-step wizard), немає можливості вказати різні причини для кожної позиції замовлення. При 50–100 зверненнях із приводу повернень на день незручна форма — це прямі втрати часу менеджерів на уточнення по телефону.

Кастомна форма заявки на повернення будується поверх API модуля sale і має вирішувати конкретні задачі: зібрати достатньо інформації з першого звернення, не перевантажити покупця, автоматично створити заявку в системі з усіма потрібними даними.

Структура wizard-форми

Оптимальний UX для форми повернення — 3–4 кроки:

  1. Вибір замовлення — покупець обирає з власної історії замовлень, доступних для повернення
  2. Вибір товарів і причин — галочками обирає позиції, для кожної вказує причину і кількість
  3. Додаткова інформація — коментар, завантаження фото/документів
  4. Підтвердження — підсумковий екран із даними заявки та інструкціями

Крок 1: доступні для повернення замовлення

Повернення можливе лише по оплачених замовленнях у визначений період (зазвичай 14 днів за законом). Завантажуємо список:

namespace Local\Returns;

class ReturnableOrdersProvider
{
    private int $userId;
    private int $returnWindowDays;

    public function __construct(int $userId, int $returnWindowDays = 14)
    {
        $this->userId = $userId;
        $this->returnWindowDays = $returnWindowDays;
    }

    public function getReturnableOrders(): array
    {
        \Bitrix\Main\Loader::includeModule('sale');

        $dateFrom = new \Bitrix\Main\Type\Date();
        $dateFrom->add('-' . $this->returnWindowDays . ' days');

        $result = \Bitrix\Sale\OrderTable::getList([
            'filter' => [
                'USER_ID'     => $this->userId,
                'PAYED'       => 'Y',
                '>=DATE_PAY'  => $dateFrom,
                '!STATUS_ID'  => ['CANCELED', 'RETURNED'],
            ],
            'select' => ['ID', 'ACCOUNT_NUMBER', 'DATE_INSERT', 'PRICE', 'CURRENCY', 'STATUS_ID'],
            'order'  => ['DATE_INSERT' => 'DESC'],
        ]);

        $orders = [];
        while ($row = $result->fetch()) {
            // Перевіряємо: немає чи вже повного повернення по цьому замовленню
            if (!$this->hasFullReturn($row['ID'])) {
                $orders[] = $row;
            }
        }

        return $orders;
    }

    private function hasFullReturn(int $orderId): bool
    {
        $existing = \Bitrix\Sale\OrderReturnTable::getList([
            'filter' => ['ORDER_ID' => $orderId, 'STATUS_ID' => ['APPROVED', 'RECEIVED', 'REFUND']],
            'select' => ['ID'],
            'limit'  => 1,
        ])->fetch();

        return (bool)$existing;
    }
}

Крок 2: позиції замовлення з вибором причини

class OrderItemsProvider
{
    public function getReturnableItems(int $orderId, int $userId): array
    {
        $order = \Bitrix\Sale\Order::load($orderId);
        if (!$order || $order->getUserId() !== $userId) {
            throw new \RuntimeException('Order not found or access denied');
        }

        $items = [];
        foreach ($order->getBasket() as $item) {
            // Рахуємо вже повернену кількість
            $returnedQty = $this->getReturnedQuantity($orderId, $item->getId());
            $availableQty = $item->getQuantity() - $returnedQty;

            if ($availableQty <= 0) continue;

            $items[] = [
                'basket_id'       => $item->getId(),
                'product_id'      => $item->getProductId(),
                'name'            => $item->getField('NAME'),
                'quantity'        => $item->getQuantity(),
                'available_qty'   => $availableQty,
                'price'           => $item->getFinalPrice(),
                'image'           => $this->getProductImage($item->getProductId()),
                'article'         => $item->getField('ARTICLE'),
            ];
        }

        return $items;
    }

    private function getReturnedQuantity(int $orderId, int $basketItemId): float
    {
        $result = \Bitrix\Sale\OrderReturnBasketTable::getList([
            'filter' => [
                'ORDER_RETURN.ORDER_ID' => $orderId,
                'BASKET_ID'             => $basketItemId,
                'ORDER_RETURN.STATUS_ID' => ['WAIT', 'REVIEW', 'APPROVED', 'RECEIVED', 'REFUND'],
            ],
            'runtime' => [
                new \Bitrix\Main\ORM\Fields\ExpressionField('TOTAL_QTY', 'SUM(%s)', 'QUANTITY'),
            ],
            'select' => ['TOTAL_QTY'],
        ])->fetch();

        return (float)($result['TOTAL_QTY'] ?? 0);
    }
}

Клієнтська частина: покрокова форма

React-компонент для покрокової форми (або Vue — за вибором):

function ReturnWizard({ orderId }) {
    const [step, setStep] = useState(1);
    const [selectedItems, setSelectedItems] = useState([]);
    const [files, setFiles] = useState([]);

    const returnReasons = [
        { id: 'defect',     label: 'Виробничий брак' },
        { id: 'wrong_item', label: 'Надіслали не той товар' },
        { id: 'damaged',    label: 'Пошкоджено при доставці' },
        { id: 'not_fit',    label: 'Не підійшов' },
        { id: 'other',      label: 'Інша причина' },
    ];

    const canProceed = selectedItems.some(item => item.selected && item.reason);

    async function submitReturn() {
        const formData = new FormData();
        formData.append('order_id', orderId);
        formData.append('sessid', BX.bitrix_sessid());
        formData.append('items', JSON.stringify(selectedItems.filter(i => i.selected)));

        files.forEach((file, i) => formData.append(`files[${i}]`, file));

        const res = await fetch('/local/api/return-submit.php', {
            method: 'POST',
            body: formData,
        });
        const data = await res.json();

        if (data.success) {
            setStep(4); // Success screen
        }
    }

    // ... рендер кроків
}

Серверний обробник фінального відправлення

// /local/api/return-submit.php
require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');

header('Content-Type: application/json');

if (!\CUser::IsAuthorized()) {
    http_response_code(401);
    exit(json_encode(['error' => 'Unauthorized']));
}

if (!\bitrix_sessid_check($_POST['sessid'] ?? '')) {
    http_response_code(403);
    exit(json_encode(['error' => 'Invalid session']));
}

$orderId = (int)($_POST['order_id'] ?? 0);
$items   = json_decode($_POST['items'] ?? '[]', true);
$userId  = (int)\CUser::GetID();

// Валідуємо, що замовлення належить користувачу
$validator = new \Local\Returns\ReturnValidator($userId);
if (!$validator->canReturnOrder($orderId)) {
    exit(json_encode(['success' => false, 'error' => 'Замовлення недоступне для повернення']));
}

// Завантажуємо прикріплені файли
$fileIds = [];
$uploader = new \Local\Upload\FileUploader();
foreach ($_FILES as $key => $file) {
    if (strpos($key, 'files') === 0 && $file['error'] === UPLOAD_ERR_OK) {
        try {
            $result    = $uploader->handle($file);
            $fileIds[] = $result['id'];
        } catch (\Exception $e) {
            // Логуємо, але не перериваємо
        }
    }
}

// Створюємо заявку на повернення
$manager   = new \Local\Returns\ReturnManager();
$returnId  = $manager->createReturn($orderId, $items, 'MONEY');

// Прикріплюємо файли до заявки
if ($fileIds) {
    \Local\Returns\ReturnAttachments::attach($returnId, $fileIds);
}

// Надсилаємо повідомлення
\Local\Returns\Notifications::sendToCustomer($returnId);
\Local\Returns\Notifications::sendToManager($returnId);

exit(json_encode([
    'success'   => true,
    'return_id' => $returnId,
    'message'   => 'Заявку #' . $returnId . ' створено. Розглянемо протягом 2 робочих днів.',
]));

Вкладення до заявки: розширення таблиці

Стандартна система повернень Бітрікс не зберігає прикріплені файли. Розширюємо через Highload-блок:

class ReturnAttachmentTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'local_return_attachments'; }

    public static function getMap(): array
    {
        return [
            new \Bitrix\Main\ORM\Fields\IntegerField('ID',        ['primary' => true, 'autocomplete' => true]),
            new \Bitrix\Main\ORM\Fields\IntegerField('RETURN_ID'),
            new \Bitrix\Main\ORM\Fields\IntegerField('FILE_ID'),  // b_file.ID
            new \Bitrix\Main\ORM\Fields\DatetimeField('CREATED_AT'),
        ];
    }
}

Склад робіт

  • Крок 1: список замовлень, придатних для повернення, з перевіркою періоду та статусу
  • Крок 2: вибір позицій із причинами повернення та кількістю
  • Крок 3: завантаження фотографій/документів через FileUploader
  • Крок 4: підтвердження, інструкції з відправлення товару
  • Серверний обробник: валідація, створення повернення через Sale API
  • Email-повідомлення: покупцю (підтвердження) + менеджеру (нова заявка)
  • Сторінка «Мої повернення» в особистому кабінеті зі статусами

Терміни: повна форма з wizard і завантаженням файлів — 2–4 тижні.