@escapenavigator/utils 2.1.5 → 2.1.6

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.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Нормализация языка i18next в ключ локали для `Calendar` из `@alphakits/ui`.
3
+ *
4
+ * Календарь принимает не BCP-47 тег, а ключ своей внутренней таблицы
5
+ * (`ru`, `de`, `en`, ...), и мапит его в тег сам: `locales[locale]`. Если
6
+ * ключа в таблице нет, наружу уходит `undefined`, который дальше по дереву
7
+ * попадает в `days-table` и падает там на `locale.toLowerCase()` — рушится
8
+ * весь рендер страницы, а не один компонент.
9
+ *
10
+ * Поэтому `i18n.language` нельзя отдавать в `locale` напрямую: он приходит
11
+ * полным тегом (`en-US`, `de-DE`) и может быть языком, которого в таблице
12
+ * нет вовсе (`ro`, `uk`, `lv`). Всегда прогоняем через `toCalendarLocale`.
13
+ *
14
+ * Список зеркалит `locales` из `@alphakits/ui/dist/utils/format-date`.
15
+ * Прямой импорт оттуда невозможен: `@escapenavigator/utils` тянет и бэкенд
16
+ * (`packages/api`), а `@alphakits/ui` — браузерный пакет.
17
+ */
18
+ declare const CALENDAR_LOCALES: readonly ["ru", "de", "en", "pl", "et", "fr", "it", "pt", "es", "el", "nl", "cs"];
19
+ /** Ключ локали, который понимает `Calendar` из `@alphakits/ui`. */
20
+ export type CalendarLocale = (typeof CALENDAR_LOCALES)[number];
21
+ export declare const CALENDAR_FALLBACK_LOCALE: CalendarLocale;
22
+ /**
23
+ * Приводит любой язык i18next к поддерживаемому ключу календаря.
24
+ * Неизвестные и пустые значения дают `en`, а не падение рендера.
25
+ */
26
+ export declare function toCalendarLocale(lang?: string): CalendarLocale;
27
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CALENDAR_FALLBACK_LOCALE = void 0;
4
+ exports.toCalendarLocale = toCalendarLocale;
5
+ /**
6
+ * Нормализация языка i18next в ключ локали для `Calendar` из `@alphakits/ui`.
7
+ *
8
+ * Календарь принимает не BCP-47 тег, а ключ своей внутренней таблицы
9
+ * (`ru`, `de`, `en`, ...), и мапит его в тег сам: `locales[locale]`. Если
10
+ * ключа в таблице нет, наружу уходит `undefined`, который дальше по дереву
11
+ * попадает в `days-table` и падает там на `locale.toLowerCase()` — рушится
12
+ * весь рендер страницы, а не один компонент.
13
+ *
14
+ * Поэтому `i18n.language` нельзя отдавать в `locale` напрямую: он приходит
15
+ * полным тегом (`en-US`, `de-DE`) и может быть языком, которого в таблице
16
+ * нет вовсе (`ro`, `uk`, `lv`). Всегда прогоняем через `toCalendarLocale`.
17
+ *
18
+ * Список зеркалит `locales` из `@alphakits/ui/dist/utils/format-date`.
19
+ * Прямой импорт оттуда невозможен: `@escapenavigator/utils` тянет и бэкенд
20
+ * (`packages/api`), а `@alphakits/ui` — браузерный пакет.
21
+ */
22
+ const CALENDAR_LOCALES = [
23
+ 'ru',
24
+ 'de',
25
+ 'en',
26
+ 'pl',
27
+ 'et',
28
+ 'fr',
29
+ 'it',
30
+ 'pt',
31
+ 'es',
32
+ 'el',
33
+ 'nl',
34
+ 'cs',
35
+ ];
36
+ exports.CALENDAR_FALLBACK_LOCALE = 'en';
37
+ /**
38
+ * Приводит любой язык i18next к поддерживаемому ключу календаря.
39
+ * Неизвестные и пустые значения дают `en`, а не падение рендера.
40
+ */
41
+ function toCalendarLocale(lang) {
42
+ const key = lang?.split('-')[0]?.toLowerCase();
43
+ return CALENDAR_LOCALES.includes(key || '')
44
+ ? key
45
+ : exports.CALENDAR_FALLBACK_LOCALE;
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const calendar_locale_1 = require("./calendar-locale");
4
+ /**
5
+ * Регрессия ESCAPE-NAVIGATOR-APP-8Z: `locale={i18n.language}` уронил CRM
6
+ * целиком, потому что `Calendar` не нашёл полный тег в своей таблице и
7
+ * передал `undefined` в `days-table`. Фиксируем, что наружу всегда уходит
8
+ * ключ из таблицы.
9
+ */
10
+ describe('toCalendarLocale', () => {
11
+ it('срезает регион у полного BCP-47 тега', () => {
12
+ expect((0, calendar_locale_1.toCalendarLocale)('en-US')).toBe('en');
13
+ expect((0, calendar_locale_1.toCalendarLocale)('de-DE')).toBe('de');
14
+ expect((0, calendar_locale_1.toCalendarLocale)('pt-BR')).toBe('pt');
15
+ });
16
+ it('пропускает короткий поддерживаемый код как есть', () => {
17
+ expect((0, calendar_locale_1.toCalendarLocale)('ru')).toBe('ru');
18
+ expect((0, calendar_locale_1.toCalendarLocale)('cs')).toBe('cs');
19
+ });
20
+ it('нормализует регистр', () => {
21
+ expect((0, calendar_locale_1.toCalendarLocale)('ET')).toBe('et');
22
+ expect((0, calendar_locale_1.toCalendarLocale)('IT-it')).toBe('it');
23
+ });
24
+ it('неподдерживаемый язык → en, а не undefined', () => {
25
+ expect((0, calendar_locale_1.toCalendarLocale)('ro-RO')).toBe('en');
26
+ expect((0, calendar_locale_1.toCalendarLocale)('uk')).toBe('en');
27
+ expect((0, calendar_locale_1.toCalendarLocale)('lv-LV')).toBe('en');
28
+ });
29
+ it('пустое значение → en', () => {
30
+ expect((0, calendar_locale_1.toCalendarLocale)(undefined)).toBe('en');
31
+ expect((0, calendar_locale_1.toCalendarLocale)('')).toBe('en');
32
+ });
33
+ });
@@ -25,13 +25,16 @@ exports.isCancelationAllowed = isCancelationAllowed;
25
25
  function getOrderCancelationState({ cancelationReason, cancelationRule, cancelationAmount, transactions, date, returnCertificates, forceFine, certificates, minHoursForFreeCanceling, minHoursForFullFine, playersPrice, slotDiscount = 0, payed, minimalPrice, nonRefundableAmount = 0, }) {
26
26
  // Удерживаем стоимость невозвратных услуг из суммы к возврату (пол 0).
27
27
  const withholdNonRefundable = (amount) => Math.max(0, +(amount - nonRefundableAmount).toFixed(0));
28
- const halfFineCancelation = cancelationRule !== questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO &&
28
+ // `NO` = «штрафа нет», не «отмена запрещена». Окно часов при этом
29
+ // правиле скрыто в настройках, дедлайн = старт игры (minHours = 0).
30
+ const noPenaltyRule = cancelationRule === questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO;
31
+ const halfFineCancelation = !noPenaltyRule &&
29
32
  cancelationReason !== order_cancel_reson_enum_1.OrderCancelReasonEnum.QUESTROOM &&
30
33
  !(0, exports.isCancelationAllowed)({
31
34
  date,
32
35
  minHoursForFreeCanceling,
33
36
  });
34
- const foolFineCancelation = cancelationRule !== questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO &&
37
+ const foolFineCancelation = !noPenaltyRule &&
35
38
  cancelationReason !== order_cancel_reson_enum_1.OrderCancelReasonEnum.QUESTROOM &&
36
39
  !!minHoursForFullFine &&
37
40
  !(0, exports.isCancelationAllowed)({
@@ -39,9 +42,9 @@ function getOrderCancelationState({ cancelationReason, cancelationRule, cancelat
39
42
  minHoursForFreeCanceling: minHoursForFullFine,
40
43
  });
41
44
  const allowedCancelation = !forceFine &&
42
- cancelationRule !== questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO &&
43
- !halfFineCancelation &&
44
- !foolFineCancelation;
45
+ (noPenaltyRule
46
+ ? (0, exports.isCancelationAllowed)({ date, minHoursForFreeCanceling: 0 })
47
+ : !halfFineCancelation && !foolFineCancelation);
45
48
  const receivedTransactions = getReceivedTransactions(transactions);
46
49
  const receivedTransactionsAmount = receivedTransactions.reduce((acc, tr) => acc + tr.amount - (tr.includedIncrease || 0), 0);
47
50
  const autoReturtAvailable = receivedTransactions.length === 1 &&
@@ -55,14 +55,38 @@ describe('getOrderCancelationState function', () => {
55
55
  allowedCancelation: true,
56
56
  });
57
57
  });
58
- test('should return "no charge / no return" if cancelationType is NO', () => {
58
+ test('should allow free cancelation until start when cancelationType is NO', () => {
59
59
  const result = (0, _1.getOrderCancelationState)({
60
60
  cancelationReason: order_cancel_reson_enum_1.OrderCancelReasonEnum.CLIENT,
61
61
  cancelationRule: questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO,
62
62
  cancelationAmount: 0,
63
63
  transactions: [],
64
64
  slotDiscount: 0,
65
- date: new Date().toISOString(),
65
+ date: (0, date_1.addHours)(new Date(), 2).toISOString(),
66
+ returnCertificates: true,
67
+ certificates: [],
68
+ minHoursForFreeCanceling: 48,
69
+ minHoursForFullFine: 0,
70
+ playersPrice: 100,
71
+ payed: 0,
72
+ minimalPrice: 50,
73
+ });
74
+ expect(result).toEqual({
75
+ type: 'descriptionFineNotSetup',
76
+ fineAmount: 0,
77
+ returnAmount: 0,
78
+ chargeAmount: 0,
79
+ allowedCancelation: true,
80
+ });
81
+ });
82
+ test('should not allow cancelation after start when cancelationType is NO', () => {
83
+ const result = (0, _1.getOrderCancelationState)({
84
+ cancelationReason: order_cancel_reson_enum_1.OrderCancelReasonEnum.CLIENT,
85
+ cancelationRule: questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO,
86
+ cancelationAmount: 0,
87
+ transactions: [],
88
+ slotDiscount: 0,
89
+ date: (0, date_1.addHours)(new Date(), -2).toISOString(),
66
90
  returnCertificates: true,
67
91
  certificates: [],
68
92
  minHoursForFreeCanceling: 48,
@@ -108,7 +132,7 @@ describe('getOrderCancelationState function', () => {
108
132
  allowedCancelation: true,
109
133
  });
110
134
  });
111
- test('should return "no charge / hand return" if not alowed cancelation has inside transaction', () => {
135
+ test('should return "no charge / hand return" if NO-penalty cancelation has inside transaction', () => {
112
136
  const result = (0, _1.getOrderCancelationState)({
113
137
  cancelationReason: order_cancel_reson_enum_1.OrderCancelReasonEnum.CLIENT,
114
138
  slotDiscount: 0,
@@ -121,7 +145,7 @@ describe('getOrderCancelationState function', () => {
121
145
  type: transaction_type_enum_1.TransactionTypeEnum.INSIDE,
122
146
  },
123
147
  ],
124
- date: new Date().toISOString(),
148
+ date: (0, date_1.addHours)(new Date(), 2).toISOString(),
125
149
  returnCertificates: true,
126
150
  certificates: [],
127
151
  minHoursForFreeCanceling: 48,
@@ -134,7 +158,7 @@ describe('getOrderCancelationState function', () => {
134
158
  fineAmount: 0,
135
159
  returnAmount: 100,
136
160
  chargeAmount: 0,
137
- allowedCancelation: false,
161
+ allowedCancelation: true,
138
162
  });
139
163
  });
140
164
  test('should return "no charge / no return" if not alowed cancelation payed same price', () => {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './agreement-config';
2
2
  export * from './booking-lead-time';
3
3
  export * from './booking-page-url';
4
+ export * from './calendar-locale';
4
5
  export * from './classify-api-error';
5
6
  export * from './convert-hhmm-to-minutes';
6
7
  export * from './convert-minutes-to-hhmm';
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./agreement-config"), exports);
18
18
  __exportStar(require("./booking-lead-time"), exports);
19
19
  __exportStar(require("./booking-page-url"), exports);
20
+ __exportStar(require("./calendar-locale"), exports);
20
21
  __exportStar(require("./classify-api-error"), exports);
21
22
  __exportStar(require("./convert-hhmm-to-minutes"), exports);
22
23
  __exportStar(require("./convert-minutes-to-hhmm"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escapenavigator/utils",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
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.1.5",
17
+ "@escapenavigator/types": "^2.1.6",
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": "d8214323f0f1fc011d7c57a80e1d148ddad821bc"
32
+ "gitHead": "75e12fefcc518fe5882597477618d7181eb61084"
33
33
  }