> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yume.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Продажа товаров (заказы на продажу)

> Как Yume оформляет разовую продажу инвентаря: заказ-продажа и его строки, автоматический пересчёт итогов, статуса и оплаты, списание со склада и расчёт прибыли.

Модуль продажи товаров оформляет разовую продажу инвентаря клиенту — в отличие от аренды, товар уходит со склада насовсем.
Продажа состоит из двух уровней: **заказа-продажи** (шапка: клиент, агент-реферал, точка проката, тип оплаты, скидки, статус и
денежные итоги) и **строк продажи** (позиции: конкретная единица инвентаря либо групповая позиция с количеством, своим складом,
контрагентом, тарифом, ценой и построчной скидкой). Итоги заказа, его статус и оплаченная сумма **не задаются вручную** — они
целиком пересчитываются фоновой задачей при любом изменении строк, а прибыль по продаже считает отдельная задача пересчёта.
Строки создаются, обновляются и удаляются пачками, а списки, детали и фильтры доступны в разделе магазина.

Полный перечень моделей и полей — в разделе [«Глоссарий»](/ru/logic/order-sale/glossary).

<Info>
  Все денежные поля заказа (сумма без скидки, сумма к оплате, сумма скидки, оплаченная сумма) и статус доступны только для чтения
  через API. Их значения — результат пересчёта, а не ввода пользователя.
</Info>

## Логика

### Структура заказа-продажи

| Поле                        | Тип          | Назначение                                        |
| --------------------------- | ------------ | ------------------------------------------------- |
| Клиент                      | связь        | Покупатель (может быть пустым)                    |
| Агент (реферал)             | связь        | Реферальный агент по продаже                      |
| Точка проката               | связь        | Точка, к которой относится продажа                |
| Тип оплаты                  | связь        | Способ оплаты                                     |
| Кем создан                  | связь        | Сотрудник, оформивший продажу                     |
| Скидка                      | связь        | Процентная скидка на весь заказ                   |
| Дополнительная скидка       | число        | Ручная скидка суммой                              |
| Сумма без скидки            | число        | Сумма строк (цена × количество)                   |
| Сумма к оплате (со скидкой) | число        | Итог после всех скидок                            |
| Сумма скидки                | число        | Разница между суммой без скидки и суммой к оплате |
| Оплаченная сумма            | число        | Сумма успешных платежей                           |
| Статус продажи              | перечисление | Черновик / Продано / Отменено                     |

### Структура строки продажи

| Поле              | Тип    | Назначение                                                             |
| ----------------- | ------ | ---------------------------------------------------------------------- |
| Заказ-продажа     | связь  | Родительский заказ                                                     |
| Единица инвентаря | связь  | Конкретная единица (для поштучной продажи), может быть пустой          |
| Группа инвентаря  | связь  | Товарная группа позиции                                                |
| Количество        | число  | Число единиц в групповой позиции (по умолчанию 1)                      |
| Склад             | связь  | Склад списания товара                                                  |
| Контрагент        | связь  | Поставщик/владелец товара на складе                                    |
| Тариф             | связь  | Тариф инвентаря                                                        |
| Скидка на строку  | связь  | Процентная скидка на конкретную позицию                                |
| Цена продажи      | число  | Цена за единицу                                                        |
| Цена со скидкой   | число  | Итог по строке после всех скидок (пересчитывается)                     |
| Цены по единицам  | список | Массив закупочных цен по единицам, заполняется из склада при пересчёте |
| Доп. данные       | JSON   | Произвольные дополнительные поля позиции                               |

### Две модели скидок и правило взаимоисключения

В заказе есть два способа снизить цену, и они **взаимно исключают друг друга**:

* **Скидка** (связь) — процентная скидка на весь заказ.
* **Дополнительная скидка** — фиксированная сумма, вычитаемая из итога.

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

**Дополнительная скидка не может превышать стоимость строк заказа** (цена × количество по всем строкам). Попытка сохранить
такую скидку отклоняется с ошибкой. Исключение — заказ без строк: в него можно завести скидку заранее, а товары добавить
потом.

<Note>
  Построчные скидки (скидка на строку) работают независимо от этого правила и применяются всегда, вместе с любой из двух скидок
  заказа.
</Note>

### Как считаются итоги (пересчёт)

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

**Шаг 1. Цена со скидкой по каждой строке.** Для каждой строки считается:

> цена за единицу × количество × (1 − процент скидки строки) × (1 − процент скидки заказа)

То есть скидка строки и процентная скидка заказа применяются **последовательно, мультипликативно**. Результат записывается в
поле «цена со скидкой» строки.

**Шаг 2. Итоги заказа:**

* **Сумма без скидки** = сумма по всем строкам (цена × количество).
* **Сумма к оплате** = сумма всех построчных цен со скидкой **минус дополнительная скидка**, но **не ниже нуля**.
* **Оплаченная сумма** = сумма всех успешных платежей заказа, не связанных с банковской выпиской.
* **Сумма скидки** = сумма без скидки − сумма к оплате.

<Note>
  Нижняя граница нуля у суммы к оплате — последний рубеж на случай, когда строки удалили уже после того, как дополнительную
  скидку приняли: сама скидка при заведении проверяется на превышение стоимости строк. Итог никогда не уходит в минус, поэтому
  и сумма скидки не может превысить стоимость товаров.
</Note>

**Шаг 3. Статус.** Заказ получает статус **Продано**, если сумма к оплате меньше либо равна оплаченной сумме И оплаченная сумма
больше нуля. Во всех остальных случаях (в том числе при частичной оплате или нулевой оплате) статус — **Черновик**.

<Warning>
  Статус **Отменено** объявлен в системе, но нигде не выставляется автоматически. На практике заказ-продажа бывает только в
  статусе Черновик или Продано.
</Warning>

### Списание товара со склада

После пересчёта итогов система синхронизирует склад по каждой строке.

* **Признак полной оплаты** проверяется строгим равенством: оплаченная сумма **в точности равна** сумме к оплате. Только при
  точном совпадении и только для строк с конкретной единицей инвентаря эта единица помечается на складе как проданная.
* Для каждой строки система находит остаток товара на складе по сочетанию группа + склад + контрагент. Если остаток найден, из
  него берутся закупочные цены единиц (первые N по количеству; если единиц на складе меньше — недостающие дополняются последней
  ценой; если цен на складе нет вовсе, поле остаётся пустым) и записываются в строку как «цены по единицам». Затем остаток на
  складе пересохраняется, что пересчитывает доступное количество.
* Если остатка по такому сочетанию нет — строка просто пропускается.

<Warning>
  Признак полной оплаты (строгое равенство) и условие статуса «Продано» (оплачено ≥ к оплате) — это **разные правила**. Если
  клиент переплатил (оплачено больше суммы к оплате), заказ станет «Продано», но единицы инвентаря на складе НЕ будут помечены
  проданными, потому что точного равенства нет.
</Warning>

<Warning>
  Пометка единицы проданной обращается к складской карточке единицы без обработки её отсутствия. Если карточки нет, пересчёт
  заказа завершится ошибкой.
</Warning>

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

### Работа со строками пачками

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

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

<Warning>
  Из-за правила пропуска дублей по сочетанию группа + склад + контрагент + единица в одном заказе может существовать только
  **одна** групповая позиция (без конкретной единицы) на каждое такое сочетание. Попытка добавить вторую идентичную групповую
  строку молча игнорируется — вторая строка не создаётся.
</Warning>

### Прибыль

После каждого пересчёта итогов в фоне запускается отдельная задача расчёта прибыли по продаже. Она наполняет отчётные данные
(выручка, себестоимость, рентабельность), которые уже подробно описаны в отчётности.

Подробнее об отчёте и формулах рентабельности: [«Отчётность прикладных модулей»](/ru/logic/metrics/modules).

### Адреса и доступ

| Адрес                                           | Назначение                                                   |
| ----------------------------------------------- | ------------------------------------------------------------ |
| Список / создание продаж                        | Список заказов с фильтрами и поиском; создание нового заказа |
| Деталь продажи                                  | Просмотр и изменение шапки одного заказа                     |
| Список строк заказа                             | Все строки конкретного заказа (без постраничной разбивки)    |
| Список групп заказа                             | Товарные группы заказа с вложенными строками                 |
| Пакетные создание / обновление / удаление строк | Массовые операции со строками                                |

**Фильтрация списка** доступна по контрагенту (через наличие строк с этим контрагентом), диапазону дат создания, типу платежа,
сотруднику-создателю, клиенту и агенту. **Поиск** — по имени и телефону клиента, названию и уникальному номеру единицы
инвентаря.

<Info>
  Список продаж показывает только «содержательные» заказы: у которых сумма без скидки, либо сумма к оплате, либо оплаченная сумма
  больше нуля. Пустые заказы-черновики без строк в списке не отображаются, хотя физически существуют и доступны по прямой ссылке.
</Info>

**Доступ** ко всем адресам требует одновременно прав на модель заказа-продажи и включённой интеграции «магазин» у компании.

## Особенности поведения

Прочие особенности:

* Статус «Отменено» объявлен в системе, но нигде не устанавливается автоматически — при пересчёте вычисляется только «Черновик»
  или «Продано».
* При пакетном создании построчная цена со скидкой временно приравнивается к цене продажи без учёта каких-либо скидок и
  корректна только после последующего пересчёта.
* Если у складского остатка нет ни одной закупочной цены единицы, поле «цены по единицам» строки остаётся пустым (не
  дополняется), в отличие от случая, когда цен просто меньше нужного количества.
* Скидка заказа и дополнительная скидка взаимоисключаемы: при положительной дополнительной скидке связь со скидкой обнуляется
  при сохранении; построчные скидки при этом действуют всегда. Правило учитывает сохранённое значение дополнительной скидки,
  поэтому изменение одной только процентной скидки её не обнуляет.
* Дополнительная скидка не может превышать стоимость строк заказа, а сумма к оплате не может стать отрицательной — итог
  ограничен нулём снизу.
* Изменение скидок в шапке пересчитывает сумму к оплате сразу же: пересчёт выполняется после применения новых значений.
* Пакетное обновление строк принимает только строки текущего заказа — идентификаторы чужого заказа приводят к ошибке.
* Оплаченная сумма считается только по успешным платежам, не связанным с банковской выпиской; частично оплаченный заказ остаётся
  в статусе «Черновик».
* Список продаж скрывает заказы, у которых все денежные итоги равны нулю, хотя такие заказы существуют и доступны по прямой
  ссылке.
* Удаление строки продажи автоматически пересобирает складской остаток по её группе, складу и контрагенту; пакетное удаление
  дополнительно сбрасывает дату и цену продажи у затронутых единиц.
