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

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

Какие остатки сравниваются

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

  • фактический остаток — количество, подтвержденное пересчетом в конкретной ячейке;
  • учетный остаток — результат проведенных приходов, расходов, перемещений и корректировок;
  • доступный остаток — учетное количество за вычетом резервов и блокировок;
  • ожидаемый остаток — количество с учетом еще не завершенных поставок или отгрузок;
  • остаток на начало или конец периода — значение на выбранную границу времени;
  • остаток партии — количество в определенной партии, сроке годности или серийном номере;
  • остаток витрины — предварительно рассчитанное значение в отчете или BI-системе.

Например, на складе физически лежит 10 единиц, учетный остаток равен 12, две единицы зарезервированы. Отчет по доступному количеству тоже покажет 10, но это не означает отсутствия расхождения. Сравнивать нужно одинаковые показатели в одинаковых измерениях.

Зафиксируйте контрольный пример

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

  • внутренний ID товара и артикул;
  • организация, склад, зона и ячейка;
  • партия, серия, срок годности и единица измерения;
  • ID и номер документа инвентаризации;
  • фактическое количество и учетное количество в момент пересчета;
  • время начала, закрытия и проведения пересчета с часовым поясом;
  • статус документа: черновик, подтвержден, проведен, отменен;
  • ID корректировки и созданных складских движений;
  • время последней синхронизации и идентификатор пакета обмена;
  • параметры отчета, пользователь и набор его прав.

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

Балансовая проверка остатка

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

Остаток на конец = остаток на начало + приходы - расходы + положительные корректировки - отрицательные корректировки Расхождение = фактический остаток - учетный остаток

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

Почему расхождение не появляется в отчете

Документ не проведен

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

Корректировка создана в другом периоде

Дата документа, момент проведения, бизнес-дата склада и время попадания в отчет могут различаться. Операция, проведенная после полуночи или в другом часовом поясе, способна попасть в соседний день. Отдельно проверьте границы «включительно/исключительно»: запрос до 00:00 следующего дня и запрос до 23:59:59 дают разные результаты при точности до миллисекунд.

Фильтр исключает нужное измерение

Отчет может быть построен только по основному складу, активным партиям, доступному товару или определенной организации. Расхождение останется в карантинной зоне, транзитной ячейке, архивной партии или под другим владельцем запасов. Снимите фильтры по одному и сравните результат, а не сбрасывайте все настройки одновременно.

Потеряна партия, серия или единица измерения

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

Резерв смешан с физическим остатком

Некоторые отчеты вычитают резервы, задания на отбор и заблокированные партии. Инвентаризация при этом считает физическое наличие. Сравнение on_hand с available дает ложный вывод. Выведите on_hand, reserved, blocked и available отдельными колонками и проверьте формулу.

Отчет читает старый snapshot или реплику

Операционная система уже записала корректировку, но отчет может использовать кеш, read replica, ночной snapshot или отдельное хранилище. Тогда в основной базе движение есть, а витрина еще не обновлена. Нужно измерить фактическую задержку каждого этапа и проверить watermark последней успешной загрузки.

Инкрементальная загрузка пропустила запись

ETL часто выбирает строки по updated_at или возрастающему ID. Запись с временем ровно на границе окна, поздняя транзакция или сброс курсора способны выпасть. Опасна и обратная ситуация: перекрывающиеся окна без дедупликации загружают движение дважды. Watermark должен храниться после успешной фиксации пакета, а загрузка — быть идемпотентной.

JOIN скрывает строку

Движение существует, но inner join со справочником товара, партии, склада или пользователя исключает его, если справочная запись удалена или не синхронизирована. Для диагностики временно сравните количество строк до и после каждого соединения и найдите unmatched keys. В рабочем отчете осиротевшие записи нужно показывать как ошибку качества данных, а не молча отбрасывать.

Права пользователя меняют результат

Row-level security может скрывать склад, организацию или тип документа. Один и тот же отчет у администратора и кладовщика будет отличаться. Сравните параметры запроса и разрешенные измерения, но не отключайте контроль доступа в рабочей среде.

Округление маскирует отклонение

При учете веса, длины и упаковок расхождение может быть меньше шага отображения. В базе хранится 0,004, а интерфейс показывает 0,00. Другой источник ошибки — преждевременное округление при каждом движении вместо округления итоговой суммы. На время проверки выведите полную точность и коэффициенты единиц.

Пошаговая диагностика

1. Зафиксируйте момент среза

Определите, на какой момент сравниваются фактический и учетный остатки. Если склад продолжает отгрузки во время пересчета, создайте контрольную точку: заморозьте движения в выбранной зоне, используйте cycle count с блокировкой ячейки либо учтите операции, произошедшие между началом и закрытием пересчета.

2. Проверьте документ инвентаризации

  • документ относится к правильной организации и складу;
  • в строке указан нужный SKU, партия, серия и единица;
  • фактическое количество сохранено после последнего сканирования;
  • документ не отменен и не заменен более новой версией;
  • проведение завершилось без частичной ошибки;
  • для строки создана ожидаемая корректировка или движение;
  • повторное нажатие не создало второй документ с противоположным эффектом.

3. Восстановите движения по позиции

Получите журнал движений только для выбранного полного ключа остатка и ограниченного периода. Пример ниже показывает принцип; названия таблиц и статусов зависят от системы. Выполняйте запрос на копии или с правами только на чтение.

SELECT occurred_at, document_id, movement_type, quantity, unit, status FROM inventory_movements WHERE organization_id = :organization AND warehouse_id = :warehouse AND product_id = :product AND lot_id = :lot AND occurred_at >= :period_start AND occurred_at < :period_end ORDER BY occurred_at, id;

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

4. Сравните регистр и журнал

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

5. Проследите загрузку в отчет

  • когда источник пометил движение как обновленное;
  • какой пакет ETL должен был его забрать;
  • какой watermark был до и после пакета;
  • прошел ли пакет полностью или сохранил частичный результат;
  • есть ли строка в staging-таблице;
  • не потерялась ли она при преобразовании единиц и JOIN;
  • обновился ли snapshot или materialized view;
  • не отдает ли интерфейс старый кеш.

6. Сравните запрос отчета с интерфейсом

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

Как безопасно восстановить отчет

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

Если документ не проведен

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

Если потерян агрегированный остаток

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

Если запись пропустил ETL

Повторите ограниченное окно с небольшим перекрытием и дедупликацией по идентификатору движения. Не откатывайте курсор на начало всей истории без оценки объема: старые неидемпотентные преобразования могут задвоить данные. После загрузки обновите snapshot и очистите только относящийся к отчету кеш.

Если неверен ключ измерений

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

Если проблема в правах или фильтрах

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

Как сделать синхронизацию идемпотентной

Каждое складское движение должно иметь неизменяемый source_movement_id. Повторный импорт записи с тем же ключом обновляет техническое состояние или игнорируется, но не создает второе движение. Успешный watermark фиксируется только после сохранения всего пакета. Если загрузка падает посередине, повтор безопасно обрабатывает уже записанные строки.

  • уникальный индекс по источнику и source_movement_id;
  • отдельный идентификатор для отмены и ссылка на исходное движение;
  • перекрывающиеся окна загрузки с дедупликацией;
  • контроль количества и суммы до и после пакета;
  • статус пакета started, committed или failed;
  • карантин для строк с неизвестным товаром, складом или единицей;
  • повторная обработка карантина после исправления справочника;
  • журнал изменения watermark и версии преобразований.

Как проверить результат

  1. Повторно рассчитайте выбранный SKU из журнала движений.
  2. Сравните расчет с операционным регистром остатков.
  3. Убедитесь, что корректировка видна в staging и отчетной витрине.
  4. Постройте отчет на тот же момент времени и с теми же измерениями.
  5. Сравните результат под ролями администратора и обычного пользователя.
  6. Проверьте соседний день, другой часовой пояс и границу периода.
  7. Повторите загрузку того же пакета: остаток не должен измениться второй раз.
  8. Проведите тестовое перемещение и его отмену.
  9. Проверьте товар в упаковке, дробной единице, партии и серийном учете.
  10. После истечения кеша убедитесь, что цифра остается правильной.

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

Типичные ошибки при исправлении

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

Как предотвратить новые расхождения

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

Когда нужна помощь со складским учетом

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