Разработка формы заявки на возврат товара 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Разработка формы заявки на возврат товара 1С-Битрикс
Средняя
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    811
  • 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 Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    745
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Разработка формы заявки на возврат товара 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);
    }
}

Клиентская часть: step-by-step форма

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 недели.