Импорт ТОРГ-12 из Excel в складскую программу StoreHouse 5

Привет, индустрия! Я бухгалтер‑калькулятор в общепите. Программа, с которой я работаю, называется StoreHouse5. Каждый день через мои руки проходят тонны первички, приходных накладных от поставщиков, чаще в бумажном виде.

Все вокруг кричат про тотальную цифровую трансформацию, маркировку и ЭДО. Наш ресторан купил красивое готовое решение для импорта документов/поставок, из ЕГАИС в StoreHouse, и импорт документов/поставок из ЭДО в StoreHouse, и другое.

Круто? Да, но только на бумаге.

Крупные федеральные поставщики присылают документы в ЭДО или ЕГАИС, каждый день. Эти документы спокойно импортируются в StoreHouse. А вот мелкие локальные контрагенты (крафтовые производители зелени, сыроварни, поставщики свежего мяса) до сих пор живут в прошлом веке. Им ЭДО не обязателен. Максимум, на что они способны — забить накладную в свой Excel и скинуть менеджеру в Telegram, самый плохой вариант — это мягкие накладные, написанные непонятным почерком.

Итог: каждый день бухгалтер‑калькулятор вынужден руками вбивать по 100–300 строк в StoreHouse. Глаза слезятся, время уходит, риск опечататься в ценах или количестве — огромный. В какой‑то момент мне это надоело, и я решил: хватит это терпеть, в век всеобщей цифровизации!

Я никогда не был программистом, один бы такое не написал. Взял в соавторы ИИ‑ассистента DeepSeek — китайская нейросеть с открытым исходным кодом, четко объяснил ему логику работы калькулятора, дал прочитать статьи разработчиков с их сайта. Арендовал у дилера сервер SH5 с WebApi, с правами администратора. И мы начали кодить и тестить. На разработку, тесты и полировку ушло 3 месяца неторопливого труда. По вечерам, дома, после работы, когда было желание покодить с нейросетью. Сегодня моя система работает, вместо 2 часов монотонного ада, внесения товаров из excel в StoreHouse, я нажимаю одну кнопку — и 100 строк ТОРГ-12 мгновенно улетают в StoreHouse.

Рассказываю, как устроен мой «Робот‑Бухгалтер».

Идея и архитектура: почему нельзя просто залить Excel в SH5

Каждый, кто хоть раз работал со словарями Store House 5, знает главную боль: разница в наименованиях.
У фермера в Excel написано «Сыр Моцарелла ф‑ма 1кг», а в номенклатуре SH5 этот товар заведен как «Сыр Моцарелла». SH5 в стандартном режиме не умеет импортировать справочники товаров из Excel напрямую.

По документации SH5 умеет загружать только документы, и то через сервис rkDExch — из почты. Прямой импорт товаров из Excel — только для видов алкоголя, и то с версии 5.153.811. Мне это не подходило. Мне нужно было решение, которое умеет сопоставлять товары, помнить эти связи и не плодить дубли. Мы с ИИ спроектировали классический ETL‑процесс (Extract‑Transform‑Load) и развернули безопасный контур:

  • Extract (Извлечение): Программа забирает Excel‑файл, присланный поставщиком из определенной папки.

  • Transform (Трансформация): Данные проверяются через промежуточную базу сопоставлений (PostgreSQL).

  • Load (Загрузка): Чистый, проверенный, распарсенный документ отправляется напрямую в систему, по json запросу.

Чтобы не рисковать работающей базой данных ресторана, я арендовал у нашего дилера R‑Keeper тестовый сервер StoreHouse 5 в облаке. Вся остальная экосистема крутилась локально на моем домашнем компьютере.

Стек технологий, или из чего собран Робот‑Бухгалтер

Весь софт написан на Python 3.12 и собран полностью из бесплатных библиотек с открытым исходным кодом:

  • Streamlit — моя главная находка. Он позволил мне, человеку без знаний HTML, CSS и JavaScript, развернуть полноценный, красивый и современный веб‑интерфейс прямо в браузере.

  • PostgreSQL — надежная база данных. Она стала тем самым «умным мостом», где хранятся связи между названиями товаров у поставщика и внутренними кодами StoreHouse. Также она хранит всю историю загрузок и детализацию по каждой накладной, что позволит в будущем, пользоваться аналитикой самого streamlit.

  • Pandas + openpyxl/xlrd — математическое ядро, которое за долю секунды парсит и переваривает любые таблицы.xls и.xlsx.

  • SH5 Web API + Requests — официальный шлюз, через который мой скрипт общается с сервером R‑Keeper с помощью HTTP‑запросов.

Как это работает на практике: победа над рутиной

Ежедневный процесс работы теперь выглядит модно и технологично:

  • Я сохраняю Excel‑файл ТОРГ-12 от поставщика из Telegram в папку IN, приложение смотрит эту папку.

  • Открываю браузер, где меня встречает лаконичная вебка моего приложения. Слева отображаются все файлы в папке для загрузки. Выбираю файл, указываю поставщика и склад получателя (если тот же поставщик, то выбирать уже не надо, вебка запоминает последнее действие). Отображается номер накладной, дата, и сумма. Если ТТН принята без расхождений нажимаю кнопку «Отправить в Store House 5». Если с расхождениями, то в этом же веб документе, меняю количество, сумма сама пересчитывается.

  • Я сохраняю Excel‑файл ТОРГ-12 от поставщика из Telegram в папку IN, приложение смотрит эту папку.

  • Открываю браузер, где меня встречает лаконичная вебка моего приложения. Слева отображаются все файлы в папке для загрузки. Выбираю файл, указываю поставщика и склад получателя (если тот же поставщик, то выбирать уже не надо, вебка запоминает последнее действие). Отображается номер накладной, дата, и сумма. Если ТТН принята без расхождений нажимаю кнопку «Отправить в StoreHouse 5». Если с расхождениями, то в этом же веб документе, меняю количество, сумма сама пересчитывается.

    Сверка с базой

    Сверка с базой
  • Система мгновенно считывает строки и сверяет их с базой PostgreSQL.

  • Интерфейс подсвечивает статус визуальными анкорами:

    • 🟢 Зеленый маркер: Товар системе известен, связь подтверждена.

    • ❌ Красный маркер: Поставщик привез новинку, которой еще нет в базе сопоставлений.

  • Если есть «красные» позиции, я прямо в веб‑интерфейсе один раз вписываю внутренний код (RID) товара из SH5 и нажимаю «Сохранить связи». PostgreSQL запоминает эту связку навсегда. В следующий раз автоматика сделает всё сама.

  • Так в этом же окне веб интерфейса, можно пересчитать товары, которые поставщик привозит штучно, а в SH5 они весом, или на оборот, в поле «пересчет» проставляю «как есть», «умножить», «разделить».

  • Вот как выглядит ядро робота сопоставлений в PostgreSQL.

Ядро робота сопоставлений

Ядро робота сопоставлений

Каждая строка — связка: как называет товар поставщик > какой RID у него в SH5.

Вишня свежая > 3752.
Алыча свежая > 3703.
Кедровые орехи > 726.

Если завтра поставщик напишет Вишня св — робот найдёт связь и подставит тот же RID.

Робот ведёт журнал загрузок — кто, когда, куда, на какую сумму

Каждая отправка пишется в историю: номер накладной, дата, поставщик, склад, сумма, количество товаров, статус. Это основа будущей аналитики — после каждой выгрузки можно посмотреть динамику закупок или обороты по поставщикам.

Журнал загрузок

Журнал загрузок

А вот детализация — что именно было в накладной

Здесь каждая строка каждой накладной: товар, RID, количество, цена, сумма. Именно отсюда можно достать информацию: цена моркови вчера vs сегодня или среднюю цену на арбузы за лето.

Рисунок_3

Рисунок_3

8. Далее, нажимаю кнопку «Сохранить связи» и «Отправить в StoreHouse 5». Скрипт формирует JSON‑запрос, передает заголовки, склады, корреспондентов, рассчитывает коэффициенты и единицы измерения, после чего вызывает процедуру InsGDoc0 через Web API.

30 Секунд — и накладная на 100 строк со 100% точностью цен уже лежит в журнале приходных документов Store House, в неактивном виде. Без ручного ввода, без опечаток.

9. Но путь был нелёгким. Вот лишь несколько проблем, через которые мы прошли:

Ошибка 183 — «нет прав». Думали, что у пользователя нет доступа к складам. Оказалось — накладная ложилась на склад по умолчанию, потому что мы передавали ID склада не в то поле. Как только поменяли 1721 на 105#11 — всё заработало.

Ошибка 37 — «объект не найден». Оказалось, что поле 105#11 (склад‑получатель) на боевом сервере и на тестовом — разные ID. Мы хардкодили значение с тестового, а на боевом оно просто не существовало.

Ошибка 84 — «нарушение целостности». Цена и сумма — это разные поля. Мы передавали цену в поле, которое SH5 считает суммой. Итог: накладная создавалась, но цены в ней были пересчитаны неверно. Поменяли — и всё сошлось.

Ошибка 1007 — «бизнес‑логика». Товар передавался в единицах измерения, которые ему не назначены. Например, абрикос в SH5 измеряется в килограммах, а мы передали штуки. Пришлось сделать функцию get_good_unit_rid, которая сама подтягивает правильную единицу измерения для каждого товара.

Пример того, как мы вайбкодили: 3 месяца диалога с нейросетью

Я не программист и не знаю синтаксис Python, не умею писать циклы, функции и классы. Но умею ставить задачи и отсылать логи обратно (копипастить).

DeepSeek умеет писать код. Мы работали в цикле:

  • Я описываю проблему. «Накладная создаётся, но ложится не на тот склад. Вот лог ошибки».

  • DeepSeek пишет скрипт. Присылает json запрос в теле чата, что вставить в test.py, который проверяет разные варианты.

  • Я запускаю в консоле python test.py

  • Копирую вывод обратно. «Ошибка 37. Вот что вернулось».

  • DeepSeek анализирует. «Значит, поле 105#11 — это склад. Попробуй передать RID‑склада».

  • Я запускаю снова. «Получилось!»

  • Пример общения ниже. Это мы создавали две тестовые накладные, дата, номер, поставщик, склад приемщик, без позиций и цен.

import requests
import json

SH5_API_URL = "сервер:порт/api/sh5exec"
SH5_USER = "имя"
SH5_PASS = "пароль"

# Создаём тестовую накладную БЕЗ указания склада
print("=== Создаём TEST-DELETE-1 (без склада) ===")
payload = {
    "UserName": SH5_USER,
    "Password": SH5_PASS,
    "procName": "InsGDoc0",
    "Input": [
        {
            "head": "111",
            "original": ["33", "31", "100\1", "34", "35", "105\1", "105#1\1", "3"],
            "values": [
                [0], ["2026-10-06"], [0], [1], [1], [213], [RID-склада2], ["TEST-DELETE-1"]
            ]
        },
        {
            "head": "112",
            "original": ["210\1", "210\206\1", "212\9", "213\9", "31", "40", "41", "42", "32"],
            "values": [
                [3629], [5], [0], [0], [10], [1500], [0], [0], [0]
            ]
        }
    ]
}

r = requests.post(SH5_API_URL, json=payload, timeout=30)
data = r.json()
print(json.dumps(data, indent=2, ensure_ascii=False)[:500])

# Создаём тестовую накладную на Кухню ресторана
print("n=== Создаём TEST-DELETE-2 (Кухня ресторана) ===")
payload2 = {
    "UserName": SH5_USER,
    "Password": SH5_PASS,
    "procName": "InsGDoc0",
    "Input": [
        {
            "head": "111",
            "original": ["33", "31", "100\1", "34", "35", "105\1", "105#1\1", "3"],
            "values": [
                [0], ["2026-10-06"], [0], [1], [1], [213], [RID-склада], ["TEST-DELETE-2"]
            ]
        },
        {
            "head": "112",
            "original": ["210\1", "210\206\1", "212\9", "213\9", "31", "40", "41", "42", "32"],
            "values": [
                [3629], [5], [0], [0], [10], [1500], [0], [0], [0]
            ]
        }
    ]
}

r2 = requests.post(SH5_API_URL, json=payload2, timeout=30)
data2 = r2.json()
print(json.dumps(data2, indent=2, ensure_ascii=False)[:500])

Когда команда отработала, я выделил все сообщение и переслал обратно в чат с DeepSeek.

(venv) F:SH5_test>python test.py
=== Создаём TEST-DELETE-1 (без склада) ===
{
  "errorCode": 0,
  "errMessage": "OK",
  "Version": "1.12",
  "UserName": "Имя",
  "actionName": "InsGDoc0",
  "actionType": "Execute",
  "shTable": [
    {
      "head": "111",
      "recCount": 1,
      "original": [
        "4",
        "33",
        "31",
        "111\1",
        "100\1",
        "100\2",
        "34",
        "35",
        "38",
        "117\1",
        "117\5",
        "117\3",
        "117\31",
        "179\1",
        "179\3",
        "172\1",


=== Создаём TEST-DELETE-2 (Кухня ресторана) ===
{
  "errorCode": 0,
  "errMessage": "OK",
  "Version": "1.12",
  "UserName": "Имя",
  "actionName": "InsGDoc0",
  "actionType": "Execute",
  "shTable": [
    {
      "head": "111",
      "recCount": 1,
      "original": [
        "4",
        "33",
        "31",
        "111\1",
        "100\1",
        "100\2",
        "34",
        "35",
        "38",
        "117\1",
        "117\5",
        "117\3",
        "117\31",
        "179\1",
        "179\3",
        "172\1",


(venv) F:SH5_test>

В самом начале, когда только прочли статьи разработчиков, процесс шел медленнее. Мог 10 раз за вечер, методом перебора, присылать ему ответы консоли с ошибками, он думал где мы не правильно ставили риды, присылал новые запросы. Но каждый раз мы становились на шаг ближе.

Это и есть вайбкодинг. Не «ИИ написал программу», а «ИИ + эксперт = результат». Я знаю бизнес‑логику, ИИ знает синтаксис. Вместе — сделали то, что я один не потянул бы.

Вместо вывода: ИИ не заменит человека, он заменит рутину

Когда я только начинал, я сомневался, сможет ли — обычный бухгалтер — создать что‑то подобное. Но ИИ‑ассистенты помогли мне понять важную вещь: в современном мире не обязательно зубрить синтаксис языков программирования. Самое главное открытие: ИИ не требует от тебя быть программистом. Он требует быть экспертом в своей области. Я знаю, как работает бухгалтерия — ИИ знает, как работает Python. Вместе мы закрыли эту дыру.

Проект работает, развивается, и у меня уже есть планы на будущее: расширить внутреннюю аналитику цен в веб‑интерфейсе и полностью автоматизировать приход и списание.

Профессиональные разработчики наверняка найдут в моей архитектуре кучу костылей и дыр. Но для бизнеса важен результат: система работает, она обошлась в небольшую сумму за аренду облачного сервера StoreHouse 5.

Зато экономит вагон времени.

Коллеги‑рестораторы и бухгалтеры, сталкиваетесь ли вы с такой же проблемой «ручных» накладных от мелких поставщиков? Как решаете её у себя? Давайте обсудим в комментариях!

Автор: DarthMuaddib

Источник

Оставить комментарий