frappe_docker/docs/superpowers/specs/2026-03-20-moysklad-orders-sync-design.md

6 KiB
Raw Blame History

МойСклад: синхронизация заказов покупателей

Дата: 2026-03-20 Приложение: picking_app

Цель

Расширить существующую интеграцию с МойСкладом: добавить синхронизацию заказов покупателей (customerorder) в очередь комплектации наравне с перемещениями (move).

Контекст

В picking_app уже реализована синхронизация перемещений (sync_picking_list()), хранение токена в Picking Settings, пагинация через nextHref. Заказы должны попасть в ту же очередь с минимальными изменениями в UI и логике комплектации.

Модель данных

Picking Settings — новое поле

Поле Тип Значение по умолчанию
ms_order_state Data "Подтверждён"

Поле помечается reqd: 1 и default: "Подтверждён" в DocType, чтобы пустое значение было исключено на уровне схемы. Дополнительно в коде: если значение пустое — использовать "Подтверждён" как runtime-fallback.

Picking List — новые поля

Поле Тип Значение
source_type Select "Move" / "Order"

Уникальность ms_id: текущий unique: 1 на поле ms_id снять. Вместо этого хранить ms_id с префиксом типа:

  • Перемещения: move:{uuid}
  • Заказы: order:{uuid}

Это исключает коллизии UUID между разными типами сущностей МС.

Логика синхронизации

sync_picking_list() — изменения

  • При создании новой записи проставлять source_type = "Move" и ms_id = "move:{uuid}"
  • При обновлении существующей записи — также проставлять source_type = "Move" если поле пустое (backfill)

sync_picking_orders()

  1. Читает ms_order_state из Picking Settings; если пусто — использует "Подтверждён"
  2. Тянет customerorder из МС:
    GET /entity/customerorder
    expand=state,store,positions.assortment,positions.assortment.uom
    limit=100
    order=moment,desc
    
    store обязательно включён в expand — иначе объект склада придёт как stub без name/id.
  3. Фильтрует на клиенте: order["state"]["name"] == ms_order_state
  4. to_warehouse берётся из объекта store через существующий _extract_store_id(); from_warehouse — пустой
  5. ms_id сохраняется с префиксом "order:{uuid}"
  6. При созданииsource_type = "Order". При обновленииsource_type всегда перезаписывается значением "Order" (поле не является иммутабельным).

Производительность: фильтрация по статусу происходит на клиенте после получения всех страниц (МС API не поддерживает фильтр по state.name напрямую без UUID состояния). Лимит: не более 1000 заказов за одну синхронизацию (10 страниц по 100) — это осознанный компромисс.

sync_all() — новая публичная функция (@frappe.whitelist)

Вызывает последовательно sync_picking_list() и sync_picking_orders(), возвращает суммарный результат:

{"status": "ok", "moves": {"created": N, "updated": N}, "orders": {"created": N, "updated": N}}

Исправление существующего бага

В add_items_from_picking_list() строка 462 содержит ссылку на неопределённую переменную added_rows вместо added_row_names. Исправить в рамках этой задачи, иначе функция упадёт с NameError при первом же вызове.

API: get_picking_list_items()

Добавить опциональный параметр source_type:

  • source_type=None — вернуть все записи (включая NULL из legacy)
  • source_type="Move" / "Order" — фильтровать по значению; NULL-записи при "Move" включаются (обратная совместимость)

Добавить source_type в список возвращаемых полей (fields), чтобы UI мог рендерить колонку "Тип".

UI

В попап-диалоге выбора позиций:

  • Добавить колонку Тип: отображает "Перемещение" (Move и NULL) / "Заказ" (Order)
  • Добавить фильтр по типу: табы или кнопки Все / Перемещения / Заказы
  • Логика добавления позиций в Picking Document — без изменений

Прочее

  • Поле ms_date в UI показывает "Дата перемещения" — для заказов это несточное название. Принимаем как допустимое упрощение: переименование метки выходит за рамки задачи.
  • Статусная машина Picking List (Draft → Partial → Added) — без изменений
  • Структура Picking List Item — без изменений