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

Логика

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

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

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

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

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

Пересчёт запускается при любом создании, изменении или удалении строк, а также при изменении шапки заказа. При изменении шапки новые значения скидок сначала сохраняются, и только потом идёт пересчёт — поэтому сумма к оплате обновляется в том же ответе, без ожидания следующей операции со строками. Он выполняет следующее. Шаг 1. Цена со скидкой по каждой строке. Для каждой строки считается:
цена за единицу × количество × (1 − процент скидки строки) × (1 − процент скидки заказа)
То есть скидка строки и процентная скидка заказа применяются последовательно, мультипликативно. Результат записывается в поле «цена со скидкой» строки. Шаг 2. Итоги заказа:
  • Сумма без скидки = сумма по всем строкам (цена × количество).
  • Сумма к оплате = сумма всех построчных цен со скидкой минус дополнительная скидка, но не ниже нуля.
  • Оплаченная сумма = сумма всех успешных платежей заказа, не связанных с банковской выпиской.
  • Сумма скидки = сумма без скидки − сумма к оплате.
Нижняя граница нуля у суммы к оплате — последний рубеж на случай, когда строки удалили уже после того, как дополнительную скидку приняли: сама скидка при заведении проверяется на превышение стоимости строк. Итог никогда не уходит в минус, поэтому и сумма скидки не может превысить стоимость товаров.
Шаг 3. Статус. Заказ получает статус Продано, если сумма к оплате меньше либо равна оплаченной сумме И оплаченная сумма больше нуля. Во всех остальных случаях (в том числе при частичной оплате или нулевой оплате) статус — Черновик.
Статус Отменено объявлен в системе, но нигде не выставляется автоматически. На практике заказ-продажа бывает только в статусе Черновик или Продано.

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

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

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

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

Прибыль

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

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

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

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

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