Система интеграции строится на следующих принципах:
- Артикул — единый идентификатор товара в обеих системах
- REST API — универсальный интерфейс обмена данными
- Односторонняя синхронизация — 1С выступает источником данных (master), Mini App — получателем (slave)
- Ручной запуск — выгрузка инициируется администратором через панель управления
Часть 1: Реализация на стороне 1С:УТ
Структура HTTP-сервиса
В 1С:УТ создаем HTTP-сервис для предоставления данных номенклатуры. Сервис публикует метод получения товаров с фильтрацией по дате изменения.
// HTTP-сервис "MiniAppAPI"
// URL шаблон метода: /nomenclature
Функция НоменклатураGET(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Получаем параметры запроса
ДатаИзменения = Запрос.ПараметрыЗапроса.Получить("modified_since");
// Формируем запрос к базе
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.ПолнОе Наименование КАК ПолноеНаименование,
| ЦеныНоменклатуры.Цена КАК Цена,
| Номенклатура.Описание КАК Описание,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК ОстатокНаСкладе
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних() КАК ЦеныНоменклатуры
| ПО Номенклатура.Ссылка = ЦеныНоменклатуры.Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки() КАК Остатки
| ПО Номенклатура.Ссылка = Остатки.Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
| И Номенклатура.Артикул <> """"
| И Номенклатура.ДатаИзменения >= &ДатаИзменения";
Если ЗначениеЗаполнено(ДатаИзменения) Тогда
Запрос.УстановитьПараметр("ДатаИзменения", XMLЗначение(Тип("Дата"), ДатаИзменения));
Иначе
Запрос.УстановитьПараметр("ДатаИзменения", Дата(1, 1, 1));
КонецЕсли;
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
// Формируем JSON
МассивТоваров = Новый Массив;
Пока Выборка.Следующий() Цикл
Товар = Новый Структура;
Товар.Вставить("article", Выборка.Артикул);
Товар.Вставить("name", Выборка.Наименование);
Товар.Вставить("full_name", Выборка.ПолноеНаименование);
Товар.Вставить("price", Выборка.Цена);
Товар.Вставить("description", Выборка.Описание);
Товар.Вставить("stock_quantity", Выборка.ОстатокНаСкладе);
МассивТоваров.Добавить(Товар);
КонецЦикла;
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, МассивТоваров);
Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());
Возврат Ответ;
КонецФункции
Авторизация запросов
Для обеспечения безопасности используем базовую аутентификацию или токен доступа:
Функция ПроверитьАвторизацию(Запрос)
ЗаголовокАвторизации = Запрос.Заголовки.Получить("Authorization");
Если ЗаголовокАвторизации = Неопределено Тогда
Возврат Ложь;
КонецЕсли;
// Проверяем токен
ОжидаемыйТокен = Константы.TokenДляMiniApp.Получить();
Токен = СтрЗаменить(ЗаголовокАвторизации, "Bearer ", "");
Возврат Токен = ОжидаемыйТокен;
КонецФункции
Обработка изображений товаров
Изображения товаров можно передавать как Base64 или предоставлять URL для скачивания:
Функция ПолучитьURLИзображенияТовара(СсылкаНоменклатура)
// Получаем присоединенный файл
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ПрисоединенныеФайлы.Ссылка КАК Ссылка
|ИЗ
| Справочник.НоменклатураПрисоединенныеФайлы КАК ПрисоединенныеФайлы
|ГДЕ
| ПрисоединенныеФайлы.ВладелецФайла = &Номенклатура
| И ПрисоединенныеФайлы.ЭтоИзображение";
Запрос.УстановитьПараметр("Номенклатура", СсылкаНоменклатура);
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат "";
КонецЕсли;
Выборка = Результат.Выбрать();
Выборка.Следующий();
// Формируем публичную ссылку
АдресСервера = Константы.АдресПубликацииИБ.Получить();
URLИзображения = АдресСервера + "/images/" + Строка(Выборка.Ссылка.УникальныйИдентификатор());
Возврат URLИзображения;
КонецФункции
Часть 2: Реализация на стороне Telegram Mini App
Backend API (Node.js/Express)
На стороне Mini App создаем эндпоинт для инициации синхронизации:
// routes/admin/sync.js
const express = require('express');
const router = express.Router();
const axios = require('axios');
const Product = require('../../models/Product');
// Эндпоинт для синхронизации номенклатуры
router.post('/sync-nomenclature', async (req, res) => {
try {
const { lastSyncDate } = req.body;
// Запрос к 1С API
const response = await axios.get(
`${process.env.ONS_API_URL}/nomenclature`,
{
params: {
modified_since: lastSyncDate || null
},
headers: {
'Authorization': `Bearer ${process.env.ONS_API_TOKEN}`
},
timeout: 30000
}
);
const products = response.data;
let created = 0;
let updated = 0;
let errors = [];
// Обработка полученных товаров
for (const item of products) {
try {
// Ищем товар по артикулу
let product = await Product.findOne({ article: item.article });
if (product) {
// Обновляем существующий товар
product.name = item.name;
product.fullName = item.full_name;
product.price = item.price;
product.description = item.description;
product.stockQuantity = item.stock_quantity;
product.lastSyncDate = new Date();
await product.save();
updated++;
} else {
// Создаем новый товар
product = new Product({
article: item.article,
name: item.name,
fullName: item.full_name,
price: item.price,
description: item.description,
stockQuantity: item.stock_quantity,
isActive: true,
lastSyncDate: new Date()
});
await product.save();
created++;
}
} catch (err) {
errors.push({
article: item.article,
error: err.message
});
}
}
// Возвращаем результат синхронизации
res.json({
success: true,
summary: {
total: products.length,
created,
updated,
errors: errors.length
},
errors: errors.length > 0 ? errors : undefined
});
} catch (error) {
console.error('Sync error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при синхронизации с 1С',
error: error.message
});
}
});
module.exports = router;
Модель данных товара (Mongoose)
// models/Product.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
article: {
type: String,
required: true,
unique: true,
index: true
},
name: {
type: String,
required: true
},
fullName: {
type: String
},
price: {
type: Number,
required: true,
min: 0
},
description: {
type: String
},
imageUrl: {
type: String
},
stockQuantity: {
type: Number,
default: 0
},
isActive: {
type: Boolean,
default: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
lastSyncDate: {
type: Date
}
}, {
timestamps: true
});
module.exports = mongoose.model('Product', productSchema);
Административная панель (React)
Компонент кнопки синхронизации в админ-панели:
// components/Admin/SyncButton.jsx
import React, { useState } from 'react';
import axios from 'axios';
const SyncButton = () => {
const [syncing, setSyncing] = useState(false);
const [result, setResult] = useState(null);
const handleSync = async () => {
setSyncing(true);
setResult(null);
try {
// Получаем дату последней синхронизации
const lastSync = localStorage.getItem('lastNomenclatureSync');
const response = await axios.post('/api/admin/sync-nomenclature', {
lastSyncDate: lastSync
});
if (response.data.success) {
// Сохраняем время синхронизации
localStorage.setItem('lastNomenclatureSync', new Date().toISOString());
setResult({
type: 'success',
message: `Синхронизация завершена успешно!
Создано: ${response.data.summary.created}
Обновлено: ${response.data.summary.updated}
Ошибок: ${response.data.summary.errors}`
});
}
} catch (error) {
setResult({
type: 'error',
message: error.response?.data?.message || 'Ошибка синхронизации'
});
} finally {
setSyncing(false);
}
};
return (
<div className="sync-container">
<button
onClick={handleSync}
disabled={syncing}
className="sync-button"
>
{syncing ? 'Синхронизация...' : 'Выгрузить номенклатуру из 1С'}
</button>
{result && (
<div className={`sync-result ${result.type}`}>
{result.message}
</div>
)}
</div>
);
};
export default SyncButton;
Обработка конфликтов и особые случаи
Деактивация удаленных товаров
В 1С товары редко удаляются физически — обычно ставится пометка удаления. Рекомендуется реализовать мягкое удаление:
// Проверка неактивных товаров
router.post('/deactivate-missing', async (req, res) => {
const { activeArticles } = req.body;
// Деактивируем товары, которых нет в актуальном списке
const result = await Product.updateMany(
{
article: { $nin: activeArticles },
isActive: true
},
{
$set: { isActive: false }
}
);
res.json({ deactivated: result.modifiedCount });
});
Логирование синхронизации
// models/SyncLog.js
const syncLogSchema = new mongoose.Schema({
startTime: Date,
endTime: Date,
status: String, // 'success', 'partial', 'failed'
productsCreated: Number,
productsUpdated: Number,
errors: [{ article: String, message: String }],
initiatedBy: String
});
Настройка и запуск
Конфигурация 1С
- Опубликовать HTTP-сервис с внешним доступом
- Создать константу
TokenДляMiniApp
с уникальным токеном - Настроить права доступа для веб-сервиса
- Проверить доступность эндпоинта через Postman
Конфигурация Mini App
Переменные окружения (.env):
ONS_API_URL=https://your-1c-server.com/your-base/hs/MiniAppAPI
ONS_API_TOKEN=your-secret-token-here
SYNC_TIMEOUT=30000
Заключение
Предложенное решение позволяет гибко интегрировать Telegram Mini App с 1С:УТ, обеспечивая актуальность каталога товаров. Использование артикула как единого идентификатора упрощает сопоставление данных и минимизирует вероятность дублирования.
Ключевые преимущества подхода:
- Простота реализации — стандартные HTTP-сервисы 1С
- Гибкость — легко расширить дополнительными полями
- Контроль — ручной запуск синхронизации администратором
- Безопасность — токен-based авторизация
Дальнейшее развитие может включать автоматическую синхронизацию по расписанию, двустороннюю синхронизацию заказов и более сложную обработку остатков с учетом резервирования.
Вступайте в нашу телеграмм-группу Инфостарт