23 авг. 2025 г.·5 мин. чтения

Создайте приложение для учёта запасов с ИИ, которое никогда не уходит в минус

Создайте приложение для учёта запасов с ИИ‑инструментами и предотвратите отрицательные остатки с помощью простых правил приёма, продажи и корректировок, которые можно протестировать до деплоя.

Создайте приложение для учёта запасов с ИИ, которое никогда не уходит в минус

Что мы строим и почему запасы идут не так

Когда люди говорят, что приложение для учёта запасов «неправильное», обычно имеют в виду одно: остатки уходят в минус. Как только это случается, любой отчёт вызывает сомнения. Непонятно, закончился ли товар, был ли перевыкуп или число получилось из‑за неверного обновления.

Цель — не полный ERP. Это небольшое, простое приложение для одного магазина или небольшого каталога, где счёт остаётся надёжным. Даже если вы используете инструменты на базе ИИ для генерации экранов и кода, правила всё равно идут первыми.

Оставьте систему на трёх действиях:

  • Receive: приходит товар (поставка, возврат на склад, первичное наполнение).
  • Sell: товар уходит (продажа, отгрузка или любое расходование).
  • Adjust: изменение запаса из‑за несоответствия системы и реальности (повреждение, потеря, пересчёт).

«Правильно» легко определить и протестировать:

  • Остаток никогда не опускается ниже нуля для любого товара.
  • Каждое изменение имеет причину и временную метку.
  • Итоги воспроизводимы: воспроизведите историю — получите то же число.
  • Любое число можно объяснить, указав транзакции, которые его создали.

Запасы обычно ломаются по двум причинам:

  1. Приложение прямо редактирует поле «текущий остаток», вместо записи транзакции.

  2. Транзакции записывают, но не проверяют наличие в момент продажи.

Простой пример: у вас 5 кружек. Два человека покупают по 4 кружки за несколько минут. Если приложение не блокирует операцию проверки‑и‑уменьшения, обе продажи пройдут проверку «в наличии» и в итоге вы получите −3.

Самая простая модель данных, которая работает

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

Для большинства небольших приложений минимум такой:

  • Product: то, что вы отслеживаете (название, SKU, флаг активности).
  • Transaction: каждая смена запаса (receive, sell, adjust) с количеством и временной меткой.
  • Location (опционально): где хранится товар (склад, магазин, фургон). Добавляйте только если действительно нужно.
  • User (опционально): кто создал транзакцию.

Ключевое правило: не храните «stock on hand» как свободно редактируемое поле. Вычисляйте его как сумму всех количеств транзакций для этого продукта (и для локации, если вы используете локации). Это делает число объяснимым, потому что у каждой единицы есть причина.

Одна локация сейчас, несколько позже

Если вы не уверены насчёт нескольких локаций, выберите простой безопасный путь:

  • Single location v1: без таблицы Location, итоги по продукту.
  • Future-ready v1: добавьте Location и требуйте location_id в каждой транзакции, но начните с одной локации по умолчанию.

Избегайте «опциональной локации» в транзакциях. Null‑значения локации почти всегда создают путаницу позже.

ID и временные метки, на которые вы будете опираться

Решите это заранее:

  • Используйте уникальные ID для Product и Transaction.
  • Храните created_at в каждой транзакции.
  • Держите отдельное effective_at только если нужны задним числом правки.
  • Не редактируйте количества на месте. Добавляйте новую корректирующую транзакцию.

Пример: если вы пересчитали вчера 10 единиц, но ввели 8, не перезаписывайте 8 на 10. Добавьте корректировку +2 с заметкой.

Правила, которые не дадут запасам уйти в минус

Правила важнее интерфейса. Большинство багов с отрицательным остатком происходят потому, что приложение «правит число», вместо того чтобы записывать, что произошло.

Думайте об инвентаре как о банковском счёте: вы меняете его только через записанные транзакции и никогда не допускаете отрицательного баланса.

Вот правила, которые дают наибольший эффект:

  • После применения любой транзакции запас никогда не должен быть ниже нуля.
  • Каждое изменение создаёт запись транзакции (никаких тихих правок).
  • Количества — положительные числа; тип транзакции решает, прибавлять или вычитать.
  • Любая корректировка требует причины и указания, кто её сделал.
  • Защитите систему от одновременных действий.

Число «on hand» — не источник истины. Источник — журнал транзакций.

Что на практике означает «ни в коем случае не отрицательный запас»

Перед сохранением транзакции, которая убирает товар, приложение проверяет текущий остаток по товару. Если удаление сделает его отрицательным, отклоните транзакцию (или пометьте как ожидающую, если вы поддерживаете такой поток).

Конкуренция — самый коварный случай. Два человека продают последнюю единицу одновременно. Оба экрана показывают «1 в наличии», оба нажимают Сохранить, и вы получаете −1, если база данных не применяет проверку. Безопасный паттерн — прочитать текущий остаток и записать транзакцию в одной операции базы данных, так только одна операция сможет пройти.

Приём товара: самый простой безопасный рабочий процесс

Приём — это место, где начинаются ваши числа. Если вы неверно запишите поставку, все последующие продажи будут казаться неправильными.

Держите форму приёма компактной, чтобы её действительно использовали. Надёжный минимум: товар, количество и временная метка, плюс опциональная заметка. Стоимость и поставщик могут подождать, если только вы заранее не знаете, что они нужны.

Валидация должна быть строгой и скучной: количество > 0, товар существует, временная метка записана. Если проверка провалилась — ничего не меняйте.

При сохранении создайте транзакцию RECEIVE. Если вы также поддерживаете кэшированный on-hand для производительности, обновляйте его в той же транзакции базы данных, чтобы не получить рассинхрон.

Краевой случай: дублирующий приём

Дубли случаются, когда кто‑то дважды нажал Сохранить или повторно ввёл уже залогированную квитанцию. Не удаляйте старую строку. Исправляйте историей.

Пример: вы должны были принять 10 единиц, но запись попала дважды. Добавьте отменяющую корректировку (или действие void, если поддерживаете), которая вычитает 10 и ссылается на ошибочную квитанцию в заметке.

Продажа: проверки, которые не допустят перепродажи

Быстро остановите отрицательный остаток
FixMyMess проверит ваш AI-сгенерированный код для инвентаря и укажет все риски, приводящие к отрицательным остаткам.

Продажа — это то место, где приложения для учёта запасов обычно ломаются.

Для каждой строки продажи фиксируйте товар (или SKU), количество и временную метку. Цена и покупатель опциональны, но короткая заметка поможет позже ответить на вопрос «почему упал запас?».

Две проверки имеют значение:

  • Количество должно быть больше 0.
  • Количество не может превышать доступный остаток в момент продажи.

Если кто‑то пытается продать 7, а доступно только 5 — блокируйте. Покажите понятное сообщение, например: «Только 5 в наличии. Уменьшите количество или примите товар сначала.» Избегайте расплывчатых ошибок.

Обновление остатка без потери истории

Не перезаписывайте поле «stock on hand» напрямую. Запишите строку транзакции для продажи (движение в минус), связав её с товаром и записью продажи. Остаток остаётся суммой всех движений.

Бэко́рдеры можно отложить до следующих версий. Самый безопасный ранний выбор — не поддерживать их. Если уж нужно — держите их как отдельный статус, который не меняет запас, пока товар реально не отгружен.

Корректировки: правим без ломки истории

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

Большинству маленьких приложений хватит двух стилей корректировок:

  • Set-to: «Сделать on-hand равным X» (лучше после физического пересчёта).
  • Delta: «Добавить или убрать N» (лучше для известного события вроде «-2 повреждены»).

Валидация корректировок как для любого движения:

  • Set-to значение должно быть ≥ 0.
  • Delta может быть положительным или отрицательным, но не должен опустить on-hand ниже 0.
  • Товар и локация (если используются) должны быть выбраны.
  • Время корректировки должно быть записано.

Делайте корректировки подотчётными. Требуйте причину и кто сделал изменение. Причины держите короткими и последовательными (recount, damage, theft, data cleanup) с опциональной заметкой.

Пошагово: как просить AI‑инструмент собрать это безопасно

Если вы используете AI‑инструменты для построения приложения, конкретно скажите, что инструмент должен выдать: схему базы, серверную логику и несколько базовых тестов. Если просить только экраны, часто правила останутся в UI — а именно так и просачивается отрицательный остаток.

Опишите модель данных простым языком. Назовите типы транзакций (receive, sell, adjust) и что каждая из них должна хранить: товар, количество, временная метка, заметка. Попросите о неизменяемой таблице транзакций и явном способе вычислить текущий остаток из реестра.

Правила должны жить на сервере

Попросите серверную валидацию и чётко скажите, что UI не вызывает доверие.

Полезный набор подсказок (адаптируйте к вашему стеку):

  • "Generate DB tables for items and inventory_transactions. Transactions are append-only."
  • "Create one server function applyTransaction(type, itemId, qty, note) that validates and writes in a DB transaction."
  • "Rules: receive adds stock, sell subtracts stock but must not allow stock < 0, adjust can add or subtract but also must not allow stock < 0."
  • "Add API endpoints that call applyTransaction. UI is just a client and cannot bypass rules."
  • "Generate 3 seed items and 8 sample transactions that demonstrate a blocked oversell."

Попросите ещё экран аудита: страницу со списком транзакций (сначала новые) и текущим остатком рядом с каждым товаром. Этот экран поможет быстро заметить странности.

Быстрые проверки перед тем, как доверять цифрам

Помощь с унаследованным AI-кодом
Если приложение создано с помощью Lovable, Bolt, v0, Cursor или Replit, мы можем его восстановить.

Прежде чем реальные пользователи начнут пользоваться системой, докажите работоспособность правил несколькими простыми тестами. Баги по инвентарю прячутся до тех пор, пока не произойдёт точная неправильная последовательность.

Напишите 5–10 тесткейсов простым языком. Например:

  • Receive 10, sell 3, подтвердить on-hand = 7.
  • Попытаться продать 8 при 7 в наличии — подтвердить, что блокируется с понятным сообщением.
  • Receive 1, sell 1, затем снова sell 1 — подтвердить, что вторая продажа блокируется.
  • Сделать recount (set-to) корректировку — подтвердить, что математика и история остаются понятными.

Затем протестируйте «две продажи одновременно» для одного товара. Откройте товар в двух вкладках (или на двух устройствах) и попытайтесь почти одновременно продать последнюю единицу. Один запрос должен пройти, другой — упасть.

Понятные сообщения важны. «Нельзя продать: в наличии только 2» полезно. «Error 500» — нет.

Наконец, решите политику отмены и придерживайтесь её:

  • Мягкое удаление (скрыть транзакцию, но сохранить для аудита), или
  • Отменяющие транзакции (добавить противоположную запись для отката), что часто безопаснее.

Частые ошибки, которые всё равно приводят к отрицательному остатку

Большинство проблем не в арифметике. Они появляются из‑за упрощений, которые выглядят нормальными в демо, но разваливаются, когда двумя людьми начинают пользоваться приложением.

Главная ловушка — считать «stock on hand» как одно редактируемое число. Это кажется простым, но отнимает у вас одно важное — понятную историю изменений.

Частые провокаторы:

  • Хранение остатка как редактируемого поля без журнала транзакций.
  • Валидация только на фронтенде.
  • Игнорирование конкурентности.
  • Разрешение «временных» отрицательных корректировок.
  • Отсутствие следа аудита, когда кто‑то спрашивает, почему товар показывает −3.

Небольшой сценарий провала: у вас 5 единиц. Два сотрудника одновременно продают по 3. Если приложение проверяет «stock >= 3» в браузере, оба экрана увидят 5 и обе проверки пройдут. Каждая запись обновит число, и вы получите −1. Исправляйте это, делая проверку на сервере и применяя запись атомарно.

Ещё одна легко пропускаемая ошибка — смешивание редактирования товара (название, SKU, цена) и правки стока в одной форме. Это приглашает к случайным изменениям остатков и трудно объяснимым итогам.

Пример сценария: от поставки до продажи и пересчёта

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

Представьте небольшой магазин с одной локацией и тремя товарами: Coffee Beans (1 lb bag), Paper Filters и Oat Milk. Проследим один товар — Coffee Beans.

Начало дня: on hand = 0.

Безопасное приложение хранит список транзакций и вычисляет текущее число из этой истории.

День с текущим балансом:

  • Receive +10: on hand 0 → 10.
  • Sell -3: on hand 10 → 7.
  • Adjust -2 (damage): on hand 7 → 5.
  • Попытка продать -6: приложение блокирует, потому что 5 − 6 = −1.

Когда кассир пытается совершить последнюю продажу, приложение должно остановить её до сохранения чего‑либо. Сообщение простое: «Недостаточно товара. У вас 5, вы пытаетесь продать 6.» Главное — проверка происходит в момент записи транзакции.

Теперь пересчёт: кто‑то посчитал 5 пакетов, что совпадает с балансом. Ничего править не нужно, потому что журнал уже объясняет число: одна приёмка, одна продажа, одна корректировка по повреждению.

Следующие шаги: выпустите v1, затем укрепляйте её для реального использования

Как только v1 умеет принимать, продавать и корректировать, не допуская отрицательных остатков, выпустите её небольшой группе и используйте для реальных заказов. Вы узнаете гораздо больше за неделю реальной эксплуатации, чем за ещё один раунд сгенерированных экранов.

Добавляйте только то, что реально просят пользователи. Частые следующие шаги: несколько локаций, простые роли, чтобы не все могли корректировать запасы, импорт CSV для начальных остатков, оповещения о низком остатке и несколько базовых отчётов.

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

Простая контрольная таблица:

  • Аутентификация, которая действительно блокирует доступ (не только скрытые кнопки).
  • Секреты вне браузера и вне репозитория.
  • Валидация вводов для количеств, SKU и ID.
  • Понятные ошибки и журналы аудита для каждого движения запаса.
  • Базовые проверки безопасности против инъекций и проблем с правами доступа.

Если у вас уже есть AI‑сгенерированный прототип с плохим поведением (отрицательные остатки, валидация только в UI или бизнес‑логика разбросана по страницам), FixMyMess (fixmymess.ai) может помочь: провести аудит кода и перенести правила в единый исполнимый поток транзакций. Практический старт — их бесплатный аудит кода, который картирует проблемы, прежде чем вы решите, что фиксить или перестраивать.

Часто задаваемые вопросы

Почему запас уходит в минус, хотя математика кажется правильной?

Отрицательный остаток обычно возникает, когда вы напрямую изменяете одно поле «текущий остаток» или когда фиксируете транзакции, но не проверяете доступность на момент продажи. Самый надёжный подход — относиться к учёту как к реестру: каждое изменение — это транзакция, и любая транзакция, которая опустит запас ниже нуля, отклоняется на сервере.

Какая самая маленькая схема базы данных, которая всё ещё работает для инвентаря?

Начните с таблицы products и добавьте только дозаписываемую таблицу inventory_transactions. Каждая транзакция должна содержать ID продукта, тип (receive, sell, adjust), количество, временную метку и поле с заметкой или причиной, чтобы позже можно было объяснить любое изменение.

Стоит ли хранить поле “stock_on_hand” в таблице продуктов?

Нет, по крайней мере не как источник правды. Вычисляйте «on hand» из истории транзакций, чтобы можно было воспроизвести реестр и всегда получать тот же результат, и чтобы каждая единица была прослеживаема до причины.

Как предотвратить ситуацию, когда два человека продают последний товар одновременно?

Выполняйте проверку и запись в одной транзакции базы данных, чтобы две продажи не могли одновременно пройти проверку «в наличии». Если вы проверяете в браузере, а запись делаете позже, два человека могут перепродать последний товар, даже если оба экрана показывали корректный остаток.

Какие валидации нужно применять при приёме товара?

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

Что делать, если кто‑то случайно дважды ввёл ту же поставку?

Не удаляйте исходную запись и не правьте её незаметно. Добавьте корректирующую транзакцию, которая отменяет ошибку, и укажите в заметке, что произошло, чтобы будущему вам было понятно по следу аудита.

Должен ли v1 разрешать бэко́рдеры или продажи в отрицательный остаток?

По умолчанию блокируйте это. Когда кто‑то пытается продать больше, чем есть в наличии, отклоняйте транзакцию и показывайте понятное сообщение вроде «Только 5 в наличии», чтобы пользователь мог уменьшить количество или сначала принять товар.

Когда использовать корректировку вместо редактирования или удаления транзакции?

Используйте корректировки для реальных причин: повреждение, кража, пересчёт или исправление данных после импорта. Предпочитайте «set-to» после физического пересчёта и «delta» для известных событий, всегда требуйте причину и пользователя, который сделал изменение.

Как подсказать AI-инструменту, чтобы он не построил ломающееся приложение для инвентаря?

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

Как понять, что мой AI‑сгенерированный прототип инвентаря небезопасен, и что с этим делать?

Ищите бизнес‑правила, реализованные только в UI, разрозненную логику по разным страницам, редактируемые поля остатков, отсутствие временных меток и отсутствие понятного экрана аудита транзакций. Если вы унаследовали AI‑прототип с такими проблемами, FixMyMess может провести бесплатный аудит кода и свести правила в один выполнимый поток транзакций, чтобы числа выдержали нагрузку в продакшене.