R
Раскрой Панелей
🌐 Тонкий / Веб-клиент 🖥️ Толстый — UI ⚡ Толстый — UI + API 🤖 Без UI 🏭 On-Premise

⚡ Толстый клиент — UI + API Полная интеграция

⏱ 3–5 часов 📝 ~150 строк ObjectScript 📋 Версия 1С: 8.3+ 🔑 API-ключ: обязателен
Максимальная интеграция. HTML-поле для менеджера + прямые HTTP-запросы из 1С: авто-смена статусов после оплаты, синхронизация склада ликвидных остатков, опциональные вебхуки. Всё в одном модуле.
Что добавляется сверх UI
  • Статус заказа меняется автоматически (paid → in_production)
  • Склад ликвидных остатков синхронизируется с 1С
  • Регламентное задание для периодической синхронизации
  • Получение данных расчёта программно (без UI)
  • Подписка на вебхуки (order.completed, remnant.added)
Требования
  • Толстый клиент (не работает в тонком/вебе)
  • Разрешение «Использование внешних программ и интернет-ресурсов»
  • Доступ к raskroy.app:443 с сервера 1С
  • API-ключ с уровнем pro или enterprise

Полный модуль «РаскройПанелей»

Вставьте весь код в один общий модуль. Флаги: Клиент (управляемое) + Сервер (для HTTP-функций).
РаскройПанелей — полный модуль (~150 строк)
// ═══════════════════════════════════════════════════════════════
// РаскройПанелей — Толстый клиент, UI + API
// Флаги модуля: Клиент (УФ) + Сервер
// ═══════════════════════════════════════════════════════════════

// ── Настройки ────────────────────────────────────────────────────
BASE_URL  = "https://raskroy.app";
API_TOKEN = "rsk_ВАШ_КЛЮЧ";

// ════════════════════════ КЛИЕНТСКАЯ ЧАСТЬ ══════════════════════

// Открыть расчёт в HTML-поле (вызывается с формы)
// Режим: "sandwich" (сэндвич-панели, по умолчанию) или "windowsill" (подоконники)
&НаКлиенте
Процедура ОткрытьРасчёт(ПолеHTML, НомерСчёта, Контрагент, Детали, Стратегия = "optimal", Режим = "sandwich") Экспорт
    ДеталиСтрока = "";
    Для Каждого Д Из Детали Цикл
        ДеталиСтрока = ДеталиСтрока + ?(ДеталиСтрока="","",",")
            + Д.ИД+":"+Строка(Д.Ширина)+":"+Строка(Д.Высота)+":"+Строка(Д.Количество);
    КонецЦикла;
    URL = BASE_URL + "/?source=1c&invoice="
        + КодироватьСтроку(НомерСчёта, СпособКодированияСтроки.URLВАдресеСтроки)
        + "&client="   + КодироватьСтроку(Контрагент, СпособКодированияСтроки.URLВАдресеСтроки)
        + "&strategy=" + Стратегия + "&mode=" + Режим
        + "&token="    + API_TOKEN
        + "&parts="    + ДеталиСтрока;
    ПолеHTML.Перейти(URL);
КонецПроцедуры

// Пример вызова из формы счёта:
//   ОткрытьРасчёт(Элементы.HTML, Объект.Номер, Объект.Контрагент.Наименование,
//                 ДеталиСэндвича, "optimal", "sandwich");        // сэндвич
//   ОткрытьРасчёт(Элементы.HTML, Объект.Номер, Объект.Контрагент.Наименование,
//                 ДеталиПодоконников, "standard", "windowsill"); // подоконники

// Получить ответ (вызывается из ПриПолученииСообщенияОтВебСтраницы)
&НаКлиенте
Процедура ОбработатьОтветКлиент(Форма, JSONСтрока) Экспорт
    Чтение = Новый ЧтениеJSON;
    Чтение.УстановитьСтроку(JSONСтрока);
    Данные = ПрочитатьJSON(Чтение);
    Если Данные.type <> "raskroy.fillInvoice" Тогда Возврат; КонецЕсли;

    // Заполнить строку табличной части — ключевое слово зависит от режима
    // Данные.mode = "sandwich" | "windowsill" (получаем от веб-интерфейса)
    ТабЧасть = Форма.Объект.Товары;
    КлючНоменклатуры = ?(Данные.mode = "windowsill", "подоконник", "сэндвич");
    Стр = Неопределено;
    Для Каждого С Из ТабЧасть Цикл
        Если СтрНайти(НРег(С.Номенклатура.Наименование),КлючНоменклатуры)>0 Тогда
            Стр=С; Прервать; КонецЕсли;
    КонецЦикла;
    Если Стр=Неопределено Тогда Стр=ТабЧасть.Добавить(); КонецЕсли;

    Стр.Количество  = Данные.sheets_used;
    Стр.Комментарий = "Раскрой: "+Данные.sheets_used+" л. КПД "+Данные.efficiency_pct+"%";
    Сообщить("✓ Заполнено: "+Данные.sheets_used+" листов");

    // Обновить статус на сервере (если счёт уже оплачен — сразу в производство)
    ОбновитьСтатус(Строка(Форма.Объект.Ссылка), "issued");
КонецПроцедуры

// ════════════════════════ СЕРВЕРНАЯ ЧАСТЬ ═══════════════════════

// Обновить статус заказа через API
&НаСервере
Процедура ОбновитьСтатус(ЗаказID, Статус, Контрагент = "", Примечание = "") Экспорт
    Попытка
        Соединение = Новый HTTPСоединение("raskroy.app",,,,,, Новый ЗащищённоеСоединениеOpenSSL);
        Запрос = Новый HTTPЗапрос("/api/v1/orders/"+ЗаказID+"/status");
        Запрос.Заголовки.Вставить("Content-Type", "application/json");
        Запрос.Заголовки.Вставить("X-API-Key",    API_TOKEN);
        Тело = Новый Структура("status,client_name,production_note",
            Статус, Контрагент, Примечание);
        Запрос.УстановитьТелоИзСтроки(_ВJSON(Тело));
        Соединение.ВызватьHTTP(Запрос, "PATCH");
    Исключение
        ЖурналРегистрации.Записать("РаскройПанелей",, "Ошибка обновления статуса: "+ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

// Получить данные заказа (invoice-формат, без SVG)
&НаСервере
Функция ПолучитьДанныеЗаказа(ЗаказID) Экспорт
    Попытка
        Соединение = Новый HTTPСоединение("raskroy.app",,,,,, Новый ЗащищённоеСоединениеOpenSSL);
        Запрос = Новый HTTPЗапрос("/api/v1/orders/"+ЗаказID+"/invoice");
        Запрос.Заголовки.Вставить("X-API-Key", API_TOKEN);
        Ответ = Соединение.Получить(Запрос);
        Если Ответ.КодСостояния = 200 Тогда
            Возврат _ИзJSON(Ответ.ПолучитьТелоКакСтроку());
        КонецЕсли;
    Исключение
        ЖурналРегистрации.Записать("РаскройПанелей",, ОписаниеОшибки());
    КонецПопытки;
    Возврат Неопределено;
КонецФункции

// Синхронизировать склад остатков 1С → сервис
// ОстаткиСписок — Массив структур {ИД1С, Ширина, Высота}
&НаСервере
Процедура СинхронизироватьСклад(ОстаткиСписок) Экспорт
    РемнантыMassив = Новый Массив;
    Для Каждого Ост Из ОстаткиСписок Цикл
        Рем = Новый Структура;
        Рем.Вставить("w", Ост.Ширина);
        Рем.Вставить("h", Ост.Высота);
        Рем.Вставить("external_meta", Новый Структура("1c_ref", Ост.ИД1С));
        РемнантыMassив.Добавить(Рем);
    КонецЦикла;

    Тело = Новый Структура("remnants", РемнантыMassив);

    Попытка
        Соединение = Новый HTTPСоединение("raskroy.app",,,,,, Новый ЗащищённоеСоединениеOpenSSL);
        Запрос = Новый HTTPЗапрос("/api/v1/remnants/bulk");
        Запрос.Заголовки.Вставить("Content-Type", "application/json");
        Запрос.Заголовки.Вставить("X-API-Key", API_TOKEN);
        Запрос.УстановитьТелоИзСтроки(_ВJSON(Тело));
        Ответ = Соединение.ОтправитьДляОбработки(Запрос);
        Д = _ИзJSON(Ответ.ПолучитьТелоКакСтроку());
        ЖурналРегистрации.Записать("РаскройПанелей",, "Синхронизировано остатков: "+Д.processed);
    Исключение
        ЖурналРегистрации.Записать("РаскройПанелей",, "Ошибка синхронизации: "+ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

// ── Вспомогательные ──────────────────────────────────────────────
&НаСервере
Функция _ВJSON(Объект)
    ЗаписьJSON = Новый ЗаписьJSON; ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, Объект); Возврат ЗаписьJSON.Закрыть();
КонецФункции
&НаСервере
Функция _ИзJSON(Строка)
    Ч = Новый ЧтениеJSON; Ч.УстановитьСтроку(Строка); Возврат ПрочитатьJSON(Ч);
КонецФункции

Код формы счёта

МодульФормы.СчётПокупателю (обработчики)
&НаКлиенте
Процедура РаскройСэндвичаНажатие(Элемент)
    Детали = Новый Массив;
    Счётчик = 1;
    Для Каждого Стр Из Объект.Товары Цикл
        Если СтрНайти(НРег(Стр.Номенклатура.Наименование),"сэндвич")>0 Тогда
            Д = Новый Структура("ИД,Ширина,Высота,Количество",
                "P"+Счётчик, Стр.Номенклатура.Ширина,
                Стр.Номенклатура.Высота, Стр.Количество);
            Детали.Добавить(Д); Счётчик+=1;
        КонецЕсли;
    КонецЦикла;
    РаскройПанелей.ОткрытьРасчёт(Элементы.ПолеРаскрой,
        Строка(Объект.Ссылка), Строка(Объект.Контрагент), Детали);
КонецПроцедуры

&НаКлиенте
Процедура ПолеРаскройПриПолученииСообщенияОтВебСтраницы(Элемент, ТипСообщения, Данные)
    Если ТипСообщения<>"notify" Или Данные="" Тогда Возврат; КонецЕсли;
    Попытка
        РаскройПанелей.ОбработатьОтветКлиент(ЭтотОбъект, Данные);
    Исключение
        Сообщить("Ошибка: "+ОписаниеОшибки(), СтатусСообщения.Важное);
    КонецПопытки;
КонецПроцедуры

Регламентное задание — синхронизация склада

Создайте регламентное задание в конфигураторе (Общие → Регламентные задания). Запуск: раз в час или в конце рабочего дня.
РегЗадание.СинхронизацияСкладаРаскрой
// Метод регламентного задания
&НаСервере
Процедура ВыполнитьСинхронизациюСклада() Экспорт

    // Выбираем ликвидные остатки сэндвич-панелей из регистра 1С
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ
    |    ОстаткиРегистр.Номенклатура.Код КАК ИД1С,
    |    ОстаткиРегистр.Номенклатура.Ширина КАК Ширина,
    |    ОстаткиРегистр.Номенклатура.Высота КАК Высота,
    |    ОстаткиРегистр.КоличествоОстаток КАК Количество
    |ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ОстаткиРегистр
    |ГДЕ ОстаткиРегистр.Склад = &Склад
    |    И ОстаткиРегистр.Номенклатура.ВидТовара.Наименование ПОДОБНО ""%сэндвич%""
    |    И ОстаткиРегистр.КоличествоОстаток > 0";
    Запрос.УстановитьПараметр("Склад", /* ваш склад */ Неопределено);

    Результат = Запрос.Выполнить().Выбрать();
    ОстаткиСписок = Новый Массив;

    Пока Результат.Следующий() Цикл
        Ост = Новый Структура("ИД1С, Ширина, Высота",
            Результат.ИД1С, Результат.Ширина, Результат.Высота);
        ОстаткиСписок.Добавить(Ост);
    КонецЦикла;

    Если ОстаткиСписок.Количество() > 0 Тогда
        РаскройПанелей.СинхронизироватьСклад(ОстаткиСписок);
    КонецЕсли;

КонецПроцедуры
Скорректируйте имя регистра накопления и поля запроса под вашу конфигурацию. В УТ — ТоварыНаСкладах, в ERP — ТоварыОрганизаций.

Тестирование