# МойСклад: синхронизация заказов покупателей **Дата:** 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()`, возвращает суммарный результат: ```json {"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` — без изменений