@escapenavigator/utils 2.0.48 → 2.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,13 @@
1
+ import { OrderComposition, TariffCategory } from '@escapenavigator/types/dist/tariff/tariff-category';
1
2
  import { ReducedPricingConfig } from './reduced-price';
2
3
  /**
3
- * Источник цены игры. Кроме прайс-карты и состава принимает снимок льготной
4
- * ставки и минимальной стоимости — у `OrderEntity` эти поля есть, поэтому
5
- * заказы получают правильную цену без правок на каждом вызове.
4
+ * Источник цены игры. Кроме прайс-карты и состава принимает снимок условий
5
+ * тарифа — у `OrderEntity` эти поля есть, поэтому заказы получают правильную
6
+ * цену без правок на каждом вызове.
7
+ *
8
+ * Состав задаётся либо категориями (`composition` + `categories`), либо
9
+ * полями старого формата. Второе оставлено ради не обновлённых клиентов:
10
+ * внутри всё равно считает один движок — `composition-price`.
6
11
  */
7
12
  export type GamePriceSource = ReducedPricingConfig & {
8
13
  price: Record<string, number>;
@@ -10,6 +15,13 @@ export type GamePriceSource = ReducedPricingConfig & {
10
15
  children: number;
11
16
  /** Сколько мест в составе идёт по льготной ставке (школьники и т.п.). */
12
17
  reducedPlayers?: number | null;
18
+ /** Категории участников тарифа (или их снимок на брони). */
19
+ categories?: TariffCategory[] | null;
20
+ /** Состав по категориям. Приоритетнее полей старого формата. */
21
+ composition?: OrderComposition | null;
22
+ reducedTitle?: string | null;
23
+ reducedPriceDescription?: string | null;
24
+ childPriceDescription?: string | null;
13
25
  };
14
26
  export declare const getPlayersPrice: (order: GamePriceSource) => number;
15
27
  /**
@@ -1,25 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getMaxGameDiscount = exports.getPlayersPrice = void 0;
4
+ const composition_price_1 = require("./composition-price");
4
5
  const reduced_price_1 = require("./reduced-price");
6
+ /** Приводит источник любого поколения к аргументам движка категорий. */
7
+ const toCompositionSource = (order) => {
8
+ const config = {
9
+ price: order.price,
10
+ categories: order.categories,
11
+ minGameAmount: order.minGameAmount,
12
+ legacy: {
13
+ reducedRateMode: order.reducedRateMode,
14
+ reducedRate: order.reducedRate,
15
+ reducedTitle: order.reducedTitle,
16
+ reducedPriceDescription: order.reducedPriceDescription,
17
+ childPriceDescription: order.childPriceDescription,
18
+ },
19
+ };
20
+ const categories = (0, composition_price_1.resolveCategories)(config);
21
+ const hasComposition = !!order.composition && Object.keys(order.composition).length > 0;
22
+ return {
23
+ ...config,
24
+ categories,
25
+ composition: hasComposition
26
+ ? order.composition
27
+ : (0, composition_price_1.compositionFromLegacy)(order, categories),
28
+ };
29
+ };
5
30
  const getPlayersPrice = (order) => {
6
31
  if (!order?.price)
7
32
  return 0;
8
- const groupAmount = Number(order.price[order.players]) || 0;
9
- const childAmount = !Number.isNaN(+order.price.child) && order.children
10
- ? order.price.child * order.children
11
- : 0;
12
- // Цены на такой состав нет (или она 0 — «состав недоступен»). Заказ по
13
- // нему не считается, и минимальная стоимость игры к нему не применяется:
14
- // иначе отключённое число игроков внезапно начало бы стоить пол выручки.
15
- if (groupAmount <= 0)
16
- return childAmount;
17
- // Льготные места вычитаются из групповой цены, а не считаются отдельной
18
- // суммой по головам: групповой итог остаётся ровно из прайса, а неровное
19
- // деление (108 / 3) не размазывается копейками.
20
- const seats = Math.min(Math.max(0, Math.floor(Number(order.reducedPlayers) || 0)), order.players);
21
- const reducedDiscount = Math.min(seats * (0, reduced_price_1.getReducedSeatDiscount)(order), groupAmount);
22
- return (0, reduced_price_1.applyMinGameAmount)(groupAmount - reducedDiscount + childAmount, order);
33
+ return (0, composition_price_1.getCompositionPrice)(toCompositionSource(order));
23
34
  };
24
35
  exports.getPlayersPrice = getPlayersPrice;
25
36
  /**
@@ -1,12 +1,17 @@
1
1
  import { PromocodeTypeEnum } from '@escapenavigator/types/dist/promocode/emun/promocode-type.enum';
2
+ import { OrderComposition, TariffCategory } from '@escapenavigator/types/dist/tariff/tariff-category';
2
3
  import { ReducedPricingConfig } from './reduced-price';
3
- export declare const getPromocodeDiscount: ({ slotId, slotDiscount, slotDiscountAmount, total, promocodeType, promocodeDiscount, price, players, children, reducedPlayers, reducedRateMode, reducedRate, minGameAmount, }: ReducedPricingConfig & {
4
+ export declare const getPromocodeDiscount: ({ slotId, slotDiscount, slotDiscountAmount, total, promocodeType, promocodeDiscount, price, players, children, reducedPlayers, composition, categories, reducedRateMode, reducedRate, minGameAmount, }: ReducedPricingConfig & {
4
5
  slotId: number;
5
6
  children?: number;
6
7
  total: number;
7
8
  players: number;
8
9
  /** Сколько мест идёт по льготной ставке (снимок с заказа). */
9
10
  reducedPlayers?: number | null;
11
+ /** Состав по категориям. Приоритетнее полей выше. */
12
+ composition?: OrderComposition | null;
13
+ /** Категории тарифа (или их снимок на брони). */
14
+ categories?: TariffCategory[] | null;
10
15
  /**
11
16
  * Легаси-параметр: часть вызовов передаёт сумму, часть — ставку в bps.
12
17
  * Используется только процентными типами (вычитание из total).
@@ -4,13 +4,15 @@ exports.getPromocodeDiscount = void 0;
4
4
  const promocode_type_enum_1 = require("@escapenavigator/types/dist/promocode/emun/promocode-type.enum");
5
5
  const get_players_price_1 = require("./get-players-price");
6
6
  const reduced_price_1 = require("./reduced-price");
7
- const getPromocodeDiscount = ({ slotId, slotDiscount, slotDiscountAmount = 0, total, promocodeType, promocodeDiscount, price, players, children = 0, reducedPlayers = 0, reducedRateMode = null, reducedRate = 0, minGameAmount = 0, }) => {
7
+ const getPromocodeDiscount = ({ slotId, slotDiscount, slotDiscountAmount = 0, total, promocodeType, promocodeDiscount, price, players, children = 0, reducedPlayers = 0, composition, categories, reducedRateMode = null, reducedRate = 0, minGameAmount = 0, }) => {
8
8
  const orderTotal = slotId ? total - slotDiscount : total;
9
9
  const game = {
10
10
  price,
11
11
  players,
12
12
  children,
13
13
  reducedPlayers,
14
+ composition,
15
+ categories,
14
16
  reducedRateMode,
15
17
  reducedRate,
16
18
  minGameAmount,
@@ -2,11 +2,10 @@ import { TariffReducedRateModeEnum } from '@escapenavigator/types/dist/tariff/en
2
2
  /**
3
3
  * Льготные места внутри одной брони (школьники, студенты) и пол выручки.
4
4
  *
5
- * Ключевая идея: сколько льготных мест влезает — не отдельная настройка, а
6
- * следствие минимальной стоимости игры. Венью задаёт «ниже 98 € комната не
7
- * продаётся», и правила вида «на двоих льготных мест нет, на троих — одно»
8
- * получаются сами. Поэтому здесь нет ни лимитов по составам, ни таблицы
9
- * льготных цен: только ставка и минимум.
5
+ * Льготное место — скидка от групповой цены состава, не отдельный билет.
6
+ * Выбрать можно любое число до размера группы; если скидка пробивает
7
+ * минимум, итог остаётся на полу, выбор не блокируется. Отдельной таблицы
8
+ * льготных цен и лимитов по составам нет: только ставка и минимум.
10
9
  */
11
10
  export type ReducedPricingConfig = {
12
11
  /** `null` — тариф без льготных мест. */
@@ -52,9 +51,8 @@ export declare const getPerPersonAmount: (level: PriceLevel) => number;
52
51
  */
53
52
  export declare const getReducedSeatDiscount: (args: ReducedArgs) => number;
54
53
  /**
55
- * Сколько мест можно отдать по льготной ставке, не пробив минимум. Ноль
56
- * значит «при этом составе льготных мест нет» — так получается прочерк на
57
- * минимальной группе.
54
+ * Сколько мест реально меняют сумму, не упираясь в пол. Это не лимит
55
+ * выбора — гость может отметить больше, цена просто перестанет падать.
58
56
  */
59
57
  export declare const getMaxReducedPlayers: (args: ReducedArgs) => number;
60
58
  /**
@@ -65,4 +63,11 @@ export declare const getMaxReducedPlayers: (args: ReducedArgs) => number;
65
63
  export declare const getGroupPriceWithReduced: (args: ReducedArgs & {
66
64
  reducedPlayers?: number | null;
67
65
  }) => number;
66
+ /**
67
+ * Сколько фактически сняли со состава после пола. Меньше номинала
68
+ * `места × ставка`, если итог уже на минимуме.
69
+ */
70
+ export declare const getAppliedReducedDiscount: (args: ReducedArgs & {
71
+ reducedPlayers?: number | null;
72
+ }) => number;
68
73
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getGroupPriceWithReduced = exports.getMaxReducedPlayers = exports.getReducedSeatDiscount = exports.getPerPersonAmount = exports.applyMinGameAmount = exports.getMinGameAmount = exports.tariffGameSnapshot = void 0;
3
+ exports.getAppliedReducedDiscount = exports.getGroupPriceWithReduced = exports.getMaxReducedPlayers = exports.getReducedSeatDiscount = exports.getPerPersonAmount = exports.applyMinGameAmount = exports.getMinGameAmount = exports.tariffGameSnapshot = void 0;
4
4
  const tariff_reduced_rate_mode_enum_1 = require("@escapenavigator/types/dist/tariff/enum/tariff-reduced-rate-mode.enum");
5
5
  /**
6
6
  * Достаёт из тарифа поля, влияющие на цену игры. Хранить их на заказе нужно по
@@ -58,9 +58,8 @@ const getReducedSeatDiscount = (args) => {
58
58
  };
59
59
  exports.getReducedSeatDiscount = getReducedSeatDiscount;
60
60
  /**
61
- * Сколько мест можно отдать по льготной ставке, не пробив минимум. Ноль
62
- * значит «при этом составе льготных мест нет» — так получается прочерк на
63
- * минимальной группе.
61
+ * Сколько мест реально меняют сумму, не упираясь в пол. Это не лимит
62
+ * выбора — гость может отметить больше, цена просто перестанет падать.
64
63
  */
65
64
  const getMaxReducedPlayers = (args) => {
66
65
  const seatDiscount = (0, exports.getReducedSeatDiscount)(args);
@@ -88,3 +87,14 @@ const getGroupPriceWithReduced = (args) => {
88
87
  return (0, exports.applyMinGameAmount)(amount - seats * (0, exports.getReducedSeatDiscount)(args), args);
89
88
  };
90
89
  exports.getGroupPriceWithReduced = getGroupPriceWithReduced;
90
+ /**
91
+ * Сколько фактически сняли со состава после пола. Меньше номинала
92
+ * `места × ставка`, если итог уже на минимуме.
93
+ */
94
+ const getAppliedReducedDiscount = (args) => {
95
+ const amount = groupAmount(args);
96
+ if (!amount)
97
+ return 0;
98
+ return Math.max(0, amount - (0, exports.getGroupPriceWithReduced)(args));
99
+ };
100
+ exports.getAppliedReducedDiscount = getAppliedReducedDiscount;
@@ -134,6 +134,20 @@ describe('края', () => {
134
134
  ...PARIS_REDUCED,
135
135
  })).toBe(10500);
136
136
  });
137
+ it('лишние льготные места можно выбрать — скидка упирается в пол, а не режет выбор', () => {
138
+ expect((0, reduced_price_1.getAppliedReducedDiscount)({
139
+ price: PARIS,
140
+ players: 2,
141
+ reducedPlayers: 2,
142
+ ...PARIS_REDUCED,
143
+ })).toBe(0);
144
+ expect((0, reduced_price_1.getGroupPriceWithReduced)({
145
+ price: PARIS,
146
+ players: 2,
147
+ reducedPlayers: 2,
148
+ ...PARIS_REDUCED,
149
+ })).toBe(9800);
150
+ });
137
151
  it('мусор в минимуме игнорируется', () => {
138
152
  expect((0, reduced_price_1.applyMinGameAmount)(5000, { minGameAmount: null })).toBe(5000);
139
153
  expect((0, reduced_price_1.applyMinGameAmount)(5000, { minGameAmount: -100 })).toBe(5000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escapenavigator/utils",
3
- "version": "2.0.48",
3
+ "version": "2.0.50",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -14,7 +14,7 @@
14
14
  "test": "jest"
15
15
  },
16
16
  "dependencies": {
17
- "@escapenavigator/types": "^2.0.46",
17
+ "@escapenavigator/types": "^2.0.48",
18
18
  "axios": "^0.21.4",
19
19
  "class-transformer": "^0.5.1",
20
20
  "class-validator": "^0.13.2",
@@ -29,5 +29,5 @@
29
29
  "ts-jest": "^29.1.1",
30
30
  "typescript": "^5.6"
31
31
  },
32
- "gitHead": "af1ce55d865cc7be91336093f79c50ab2ec88f34"
32
+ "gitHead": "480174116c4a107d2db7e0a4ae92fd0b4e0c63e5"
33
33
  }