@escapenavigator/utils 2.1.4 → 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.
- package/dist/calendar-locale.d.ts +27 -0
- package/dist/calendar-locale.js +46 -0
- package/dist/calendar-locale.spec.d.ts +1 -0
- package/dist/calendar-locale.spec.js +33 -0
- package/dist/classify-api-error.spec.js +1 -5
- package/dist/get-order-cancelation-state/index.js +8 -5
- package/dist/get-order-cancelation-state/index.test.js +29 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +3 -3
|
@@ -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
|
+
});
|
|
@@ -123,11 +123,7 @@ describe('classifyApiError', () => {
|
|
|
123
123
|
expect(r.shouldReport).toBe(true);
|
|
124
124
|
});
|
|
125
125
|
it('все embed-домены схлопываются в один issue', () => {
|
|
126
|
-
expect((0, classify_api_error_1.classifyApiError)({ ...xhrBlocked, code: 'InvalidStateError' }).fingerprint).toEqual([
|
|
127
|
-
'api',
|
|
128
|
-
'client-env',
|
|
129
|
-
'xhr-blocked',
|
|
130
|
-
]);
|
|
126
|
+
expect((0, classify_api_error_1.classifyApiError)({ ...xhrBlocked, code: 'InvalidStateError' }).fingerprint).toEqual(['api', 'client-env', 'xhr-blocked']);
|
|
131
127
|
});
|
|
132
128
|
it('ответ сервера важнее: со status классификация обычная', () => {
|
|
133
129
|
const r = (0, classify_api_error_1.classifyApiError)({ ...xhrBlocked, status: 500 });
|
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
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": "
|
|
32
|
+
"gitHead": "75e12fefcc518fe5882597477618d7181eb61084"
|
|
33
33
|
}
|