Ты — архитектор-разработчик ERP-системы с опытом проектирования складского и операционного учёта. Задача: Перепроектировать логику учёта запчастей, резервирования и списания из рекламаций так, чтобы система была: консистентной, масштабируемой, безопасной с точки зрения остатков, без отрицательных физических остатков. Упрощённые, временные и компромиссные решения не допускаются. Проектировать необходимо «как правильно». 1. Архитектурные ограничения (обязательные) Каталог запчастей — справочник, а не источник остатков. В каталоге запрещено хранить: фактические остатки, зарезервированные остатки, отрицательные значения. Физические остатки не могут быть отрицательными ни при каких сценариях. Любое изменение количества должно быть выражено через операции (движения). Нехватка запчастей учитывается отдельной сущностью, а не минусами. 2. Доменные сущности (обязательны к проектированию) 2.1. Part (Каталог запчастей) Назначение: справочник. Поля: part_id / sku цены и тарифы нормативные коды min_stock метаданные ❌ Не хранит количества. 2.2. PartOrder (Заказ / партия) Назначение: физическое поступление. Поля: order_id part_id ordered_qty available_qty with_documents (bool) status (ordered / in_stock / issued) Инвариант: available_qty >= 0 2.3. InventoryMovement (Движение) Назначение: единственный механизм изменения остатков. Поля: movement_id part_id qty movement_type (receipt, reserve, issue, reserve_cancel) source_type (order / reclamation) source_id with_documents timestamp Остатки рассчитываются агрегированием движений. 2.4. Reservation (логическая модель) Реализуется через движения типа reserve. Назначение: уменьшение свободного остатка, предотвращение двойного использования, отделение резерва от физического списания. 2.5. Shortage (Дефицит) Назначение: фиксация неудовлетворённой потребности. Поля: shortage_id part_id with_documents required_qty reserved_qty missing_qty reclamation_id status (open / closed) ❗ Не влияет напрямую на физический остаток. 3. Алгоритм: добавление детали в рекламацию Пользователь выбирает part_id, количество и with_documents. Система рассчитывает: свободный остаток = физический остаток − активные резервы. Выполняется: резервирование доступного количества (reserve movement), создание Shortage, если required_qty > free_qty. Физический остаток не уменьшается. 4. Алгоритм: списание (отгрузка) Списание возможно только при наличии резерва. При списании: создаётся движение issue, резерв закрывается или уменьшается. Остаток партии уменьшается, но не уходит в минус. 5. Алгоритм: закрытие дефицита При создании PartOrder: система ищет открытые Shortage по part_id + with_documents. Поступившее количество: резервируется под дефициты (FIFO / по дате), уменьшает missing_qty. При missing_qty = 0: дефицит закрывается (status = closed). 6. Контроль наличия (read-only представление) Формируется автоматически, без ручного редактирования. 6.1. Критический уровень Показывать все Shortage со статусом open. 6.2. Минимальный остаток Показывать Part, для которых: free_stock < min_stock 7. Явные запреты (должны быть обеспечены кодом) Запрещено: хранить остатки в каталоге, использовать отрицательные значения, списывать детали напрямую из рекламации, корректировать остатки без InventoryMovement. 8. Ожидаемый результат от тебя Нужно предоставить: Схему сущностей и связей (ER / логическую модель). Описание ключевых инвариантов и бизнес-ограничений. Псевдокод или пошаговые алгоритмы: резервирования, списания, закрытия дефицита. Перечень существующих механизмов, которые должны быть удалены или переписаны. Фокус на корректности модели и безопасности данных, а не на UI.