stable версия 1.0
FSP Pricing 1.0
Как оператор описывает свой прайс: тарифный план, предложения, правила цены, условия, модификаторы, ступени.
Зависит от:
core. Объявить этот модуль в манифесте без него нельзя (PROFILE-002).
Прайс описывается так, чтобы по нему можно было посчитать смету, а не только прочитать глазами.
Услуги, единицы начисления, материалы и габаритные профили берутся из core.
Уровни соответствия
Глубина модели цены разная у разных операторов, и требовать от всех максимума бессмысленно: прайс из Excel так и останется прайсом из Excel. Поэтому модуль определяет три уровня. Оператор объявляет достигнутый в манифесте (pricing: {versions: ["1.0"], level: "L2"}), а запрос может потребовать минимальный через quote_request.pricing_level_min.
| Уровень | Что добавляет | Типичный источник |
|---|---|---|
| L1 Плоский прайс | rate_cards, offers, offer_services, offer_materials, price_rules; методы flat, per_unit, per_metric, manual_quote |
Прайс-лист, где у строки есть услуга, единица и одна ставка |
| L2 Условия и модификаторы | price_rule_conditions, price_adjustments, price_adjustment_conditions; метод minimum_charge |
Ставка зависит от габаритов, схемы, маршрута или объёма; наценка за срочность; минимальный чек |
| L3 Ступенчатые тарифы | price_tiers, поля tier_mode/tier_variable/tier_scope; метод tiered |
Бесплатный период хранения, разная цена первой и последующих единиц, матрицы цены по количеству |
Уровни кумулятивны. Согласованным между сторонами считается наименьший общий (PROFILE-006): запрос уровня выше не выкидывает оператора из выдачи, а возвращает код pricing_level_insufficient — понятную задачу «дописать прайс».
Из трёх реальных прайс-листов, разобранных в термины стандарта, L3 требуют ровно две конструкции: бесплатный период хранения и матрица «цена за заказ по числу хрупких товаров». Всё остальное укладывается в L1 и L2.
Валидатор следит, чтобы каждая сущность модели цены была отнесена ровно к одному уровню, а метод со ступенями не оказался на уровне без ступеней.
Реестры
| Реестр | Что внутри |
|---|---|
price_model |
Сущности и поля: rate_cards → offers → price_rules → price_tiers / price_adjustments |
price_conditions |
Закрытый реестр 92 типизированных полей условий |
condition_operators |
Единая семантика операторов и поведение при null |
calculation_methods |
Закрытый реестр значений calculation_method |
conformance_levels |
Уровни соответствия L1/L2/L3 |
tier_variables, tier_scopes |
Шкалы и области группировки для ступенчатых тарифов |
rules |
PRICE-*, TIME-*, STORAGE-* |
Ступенчатые тарифы
Ступенчатое правило описывается calculation_method=tiered и тремя обязательными полями:
| Поле | Что задаёт |
|---|---|
tier_variable |
шкала — код из tier_variables (storage_age_days, unit_sequence, weight_kg, …) |
tier_scope |
группа, внутри которой шкала считается заново — код из tier_scopes (batch, order_sku, …) |
tier_mode |
progressive — каждая часть по своей ставке; slab — весь объём по ставке итогового диапазона |
Границы лежат в price_tiers: quantity_from включена, quantity_to исключена. Ставки не обязаны расти: бесплатный период это первый диапазон с явным amount=0 (PRICE-018).
Хранение по сроку. Шкала storage_age_days считается в сутках от приёмки партии, а не от начала расчётного месяца, область — batch. В progressive каждые сутки тарифицируются по ставке своего диапазона: 0–14 → 0 ₽, 14–∞ → 120 ₽ за м³·сутки. Если оператор считает иначе, весь срок по ставке итогового диапазона, это slab по storage_stay_days. При частичном списании возраст остатка определяет inventory_rotation_method; без него расчёт блокируется (STORAGE-003).
Первая и последующие единицы одного товара. Шкала unit_sequence с нумерацией от 1, область order_sku / supply_sku / batch, режим progressive: 1–2 → 25 ₽, 2–∞ → 10 ₽. Отдельная услуга под это не заводится (PRICE-020, CAT-011).
Границы модуля
Поля условий — это входы контекста расчёта, а не ссылки на объекты этого модуля. route_id, warehouse_id, marketplace приходят от вызывающей стороны, и их значения не проверяются на ссылочную целостность внутри Pricing. Сами маршруты и графики отправки описываются в FSP Capacity: цена может зависеть от маршрута, но маршрут не является частью прайса.
Пустая цена не равна нулю (PRICE-003). Индивидуальная цена — это manual_quote, который возвращает MANUAL_QUOTE_REQUIRED, а не сумму 0.
Нормативные правила
Обязательны для соответствия модулю. Префиксы объявлены в манифесте:
PRICE-*, TIME-*, STORAGE-*.
PRICE-001Цена может зависеть от класса размера и от сырых метрик: веса, сторон, объёма и объёмного веса.PRICE-002Условия одной группы объединяются AND, группы объединяются OR.PRICE-003Пустая цена не равна нулю.PRICE-004Индивидуальная цена возвращает MANUAL_QUOTE_REQUIRED.PRICE-005Модификаторы показываются отдельной строкой расчёта.PRICE-006price_rules.amount обязателен для flat, per_unit, per_metric и minimum_charge. Для tiered и manual_quote поле пустое.PRICE-007Каждая строка price_tiers обязана содержать собственный amount и ссылку price_rule_id.PRICE-008Каждый price_adjustments обязан содержать value и ссылку price_rule_id.PRICE-009Подходящие price_rules сортируются по priority по возрастанию. Одинаковый priority в одном offer запрещён и возвращает PRICE_RULE_PRIORITY_CONFLICT.PRICE-010Если применимого активного правила нет, возвращается PRICE_RULE_NO_MATCH; нулевая цена допустима только как явный amount=0.PRICE-011Модификаторы применяются по priority. Одинаковый priority в одном stacking_group запрещён; application_base определяет базу fixed/percent/multiplier.PRICE-012minimum_charge требует base_price_rule_id и рассчитывается как max(результат базового правила, amount). Циклические ссылки запрещены.PRICE-013Диапазоны price_tiers одного правила не пересекаются, не имеют дыр, сортируются по sequence; quantity_from включена, quantity_to исключена.PRICE-014До умножения amount количество округляется по rounding_mode и rounding_step после применения minimum_billable_quantity.PRICE-015Денежный результат округляется half_up до rate_cards.money_scale; НДС рассчитывается по vat_mode и vat_rate и показывается отдельно.TIME-001Для calculation_datetime выбирается ровно одна active-версия rate_card с effective_from <= t < effective_to; пересечение периодов запрещено.STORAGE-001Бесплатный период хранения задаётся условиями по storage_age_days; метод FIFO/FEFO/LIFO не заменяет ценовое условие.PRICE-016calculation_method=tiered требует tier_mode, tier_variable и tier_scope. Переменная берётся только из реестра «Переменные диапазонов», область — только из реестра «Области диапазонов»; иное значение возвращает TIER_VARIABLE_UNKNOWN или TIER_SCOPE_UNKNOWN.PRICE-017tier_mode=slab: вся тарифицируемая величина считается по ставке диапазона, в который попало итоговое значение переменной. tier_mode=progressive: величина делится по диапазонам, каждая часть считается по своей ставке, результат суммируется.PRICE-018Ставки диапазонов не обязаны меняться монотонно: цена может расти и убывать по шкале. Бесплатный диапазон задаётся явным amount=0 и не равен пустой цене.PRICE-019tier_scope задаёт группу, внутри которой переменная считается заново: расчёт выполняется по каждой группе отдельно, суммы групп складываются. Режим и область обязаны входить в списки, разрешённые переменной.PRICE-020Разная цена первой и последующих единиц одного товара задаётся tier_variable=unit_sequence с областью уровня товара (order_sku, supply_sku, batch) и tier_mode=progressive. Отдельная услуга для этого не создаётся.PRICE-021minimum_billable_quantity и округление по PRICE-014 применяются к тарифицируемому количеству до разбиения по диапазонам; к частям в режиме progressive повторно не применяются.STORAGE-002Зависимость цены хранения от срока задаётся диапазонами по storage_age_days в сутках от приёмки партии: в режиме progressive каждые сутки тарифицируются по ставке своего диапазона, в режиме slab по storage_stay_days весь срок считается по ставке итогового диапазона. Направление ставок любое.STORAGE-003При частичном списании возраст остатка определяется методом ротации inventory_rotation_method (FIFO/FEFO/LIFO). Без указанного метода ступени по сроку не рассчитываются и возвращается STORAGE_ROTATION_REQUIRED.
Схема
JSON Schema 2020-12: /1.0/pricing/schema.json.
Определения: calculation_method, tier_mode, tier_variable, tier_scope, condition_operator, condition, rate_card, offer, offer_service, offer_material, price_rule, price_tier, price_adjustment.
Манифест модуля
{
"module": "pricing",
"title": "FSP Pricing",
"version": "1.0",
"status": "stable",
"summary": "Как оператор описывает свой прайс: тарифный план, предложения, правила цены, условия, модификаторы, ступени.",
"depends": [
"core"
],
"registries": [
"price_model",
"price_conditions",
"condition_operators",
"calculation_methods",
"tier_variables",
"tier_scopes",
"conformance_levels",
"rules"
],
"rule_prefixes": [
"PRICE",
"TIME",
"STORAGE"
],
"schema": "schema.json",
"conformance": {
"levels_registry": "conformance_levels",
"methods_registry": "calculation_methods",
"declared_as": "level"
}
}