@escapenavigator/utils 2.0.16 → 2.0.18
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/city-quest-content.js +1 -1
- package/dist/format-promocode-discount/index.js +4 -0
- package/dist/get-promocode-discount.d.ts +11 -1
- package/dist/get-promocode-discount.js +10 -1
- package/dist/get-promocode-discount.spec.d.ts +1 -0
- package/dist/get-promocode-discount.spec.js +92 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/validate-promocode.js +7 -1
- package/package.json +3 -3
|
@@ -4,7 +4,7 @@ exports.removeQuestRouteEvent = exports.addMessageRouteEvent = exports.patchQues
|
|
|
4
4
|
/** Patch one stop by id; returns new content reference for jsonb saves. */
|
|
5
5
|
const patchQuestStop = (content, stopId, patch) => ({
|
|
6
6
|
...content,
|
|
7
|
-
stops: (content.stops || []).map((stop) => stop.id === stopId ? { ...stop, ...patch } : stop),
|
|
7
|
+
stops: (content.stops || []).map((stop) => (stop.id === stopId ? { ...stop, ...patch } : stop)),
|
|
8
8
|
});
|
|
9
9
|
exports.patchQuestStop = patchQuestStop;
|
|
10
10
|
/** Patch one route event by id. */
|
|
@@ -15,6 +15,10 @@ const formatPromocodeDiscount = (type, discount, currency, clientDescription) =>
|
|
|
15
15
|
if (type === promocode_type_enum_1.PromocodeTypeEnum.FIXED) {
|
|
16
16
|
return (0, format_amount_1.formatAmount)(discount, currency);
|
|
17
17
|
}
|
|
18
|
+
// Флеш цена: discount — итоговая цена за игроков, показываем «= 99,99 €».
|
|
19
|
+
if (type === promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE) {
|
|
20
|
+
return `= ${(0, format_amount_1.formatAmount)(discount, currency)}`;
|
|
21
|
+
}
|
|
18
22
|
return `${discount / 100}%`;
|
|
19
23
|
};
|
|
20
24
|
exports.formatPromocodeDiscount = formatPromocodeDiscount;
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { PromocodeTypeEnum } from '@escapenavigator/types/dist/promocode/emun/promocode-type.enum';
|
|
2
|
-
export declare const getPromocodeDiscount: ({ slotId, slotDiscount, total, promocodeType, promocodeDiscount, price, players, children, }: {
|
|
2
|
+
export declare const getPromocodeDiscount: ({ slotId, slotDiscount, slotDiscountAmount, total, promocodeType, promocodeDiscount, price, players, children, }: {
|
|
3
3
|
slotId: number;
|
|
4
4
|
children?: number;
|
|
5
5
|
total: number;
|
|
6
6
|
players: number;
|
|
7
|
+
/**
|
|
8
|
+
* Легаси-параметр: часть вызовов передаёт сумму, часть — ставку в bps.
|
|
9
|
+
* Используется только процентными типами (вычитание из total).
|
|
10
|
+
*/
|
|
7
11
|
slotDiscount: number;
|
|
12
|
+
/**
|
|
13
|
+
* Скидка слота строго как СУММА в минорных единицах. Нужна только для
|
|
14
|
+
* FLASH_PRICE — флеш-цена поглощает слот-скидку, поэтому вычитаем её из
|
|
15
|
+
* базовой цены за игроков, чтобы итог за игроков был ровно равен флеш-цене.
|
|
16
|
+
*/
|
|
17
|
+
slotDiscountAmount?: number;
|
|
8
18
|
price: Record<any, any>;
|
|
9
19
|
promocodeDiscount: number;
|
|
10
20
|
promocodeType: PromocodeTypeEnum;
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
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
|
-
const getPromocodeDiscount = ({ slotId, slotDiscount, total, promocodeType, promocodeDiscount, price, players, children = 0, }) => {
|
|
6
|
+
const getPromocodeDiscount = ({ slotId, slotDiscount, slotDiscountAmount = 0, total, promocodeType, promocodeDiscount, price, players, children = 0, }) => {
|
|
7
7
|
const orderTotal = slotId ? total - slotDiscount : total;
|
|
8
8
|
let amount = 0;
|
|
9
9
|
if (promocodeType === promocode_type_enum_1.PromocodeTypeEnum.PROMOTIONAL)
|
|
@@ -14,6 +14,15 @@ const getPromocodeDiscount = ({ slotId, slotDiscount, total, promocodeType, prom
|
|
|
14
14
|
amount = Math.floor(((0, get_players_price_1.getPlayersPrice)({ price, children, players }) / 10000) * promocodeDiscount);
|
|
15
15
|
if (promocodeType === promocode_type_enum_1.PromocodeTypeEnum.PERCENT_ALL_SUM)
|
|
16
16
|
amount = Math.floor((orderTotal / 10000) * promocodeDiscount);
|
|
17
|
+
if (promocodeType === promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE) {
|
|
18
|
+
// «Флеш цена»: promocodeDiscount хранит итоговую цену за игроков.
|
|
19
|
+
// Скидка = базовая цена за игроков (за вычетом слот-скидки) − флеш-цена.
|
|
20
|
+
// Если базовая цена уже ниже флеш-цены — скидки нет (не удорожаем).
|
|
21
|
+
// Доп. услуги и наценка на оплату в формуле не участвуют — идут сверху.
|
|
22
|
+
const playersPrice = (0, get_players_price_1.getPlayersPrice)({ price, children, players });
|
|
23
|
+
const playersPriceAfterSlot = playersPrice - (slotId ? slotDiscountAmount : 0);
|
|
24
|
+
amount = Math.max(0, playersPriceAfterSlot - promocodeDiscount);
|
|
25
|
+
}
|
|
17
26
|
return amount;
|
|
18
27
|
};
|
|
19
28
|
exports.getPromocodeDiscount = getPromocodeDiscount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const promocode_type_enum_1 = require("@escapenavigator/types/dist/promocode/emun/promocode-type.enum");
|
|
4
|
+
const get_promocode_discount_1 = require("./get-promocode-discount");
|
|
5
|
+
// Тариф: 2 игрока — 100.00, 4 игрока — 180.00, ребёнок — 20.00 (minor units).
|
|
6
|
+
const price = { 2: 10000, 4: 18000, child: 2000 };
|
|
7
|
+
const base = {
|
|
8
|
+
slotId: 1,
|
|
9
|
+
slotDiscount: 0,
|
|
10
|
+
slotDiscountAmount: 0,
|
|
11
|
+
total: 10000,
|
|
12
|
+
players: 2,
|
|
13
|
+
children: 0,
|
|
14
|
+
price,
|
|
15
|
+
};
|
|
16
|
+
describe('getPromocodeDiscount / FLASH_PRICE', () => {
|
|
17
|
+
it('скидка = цена за игроков − флеш-цена', () => {
|
|
18
|
+
// 2 игрока = 100.00, флеш 79.99 → скидка 20.01
|
|
19
|
+
const amount = (0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
20
|
+
...base,
|
|
21
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE,
|
|
22
|
+
promocodeDiscount: 7999,
|
|
23
|
+
});
|
|
24
|
+
expect(amount).toBe(2001);
|
|
25
|
+
});
|
|
26
|
+
it('пересчитывается от кол-ва игроков (доп. услуги не участвуют)', () => {
|
|
27
|
+
// 4 игрока = 180.00; total включает допы, но не влияет на флеш
|
|
28
|
+
const amount = (0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
29
|
+
...base,
|
|
30
|
+
players: 4,
|
|
31
|
+
total: 25000,
|
|
32
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE,
|
|
33
|
+
promocodeDiscount: 7999,
|
|
34
|
+
});
|
|
35
|
+
expect(amount).toBe(18000 - 7999);
|
|
36
|
+
});
|
|
37
|
+
it('учитывает детей в базовой цене', () => {
|
|
38
|
+
// 2 взрослых + 1 ребёнок = 120.00, флеш 99.99 → скидка 20.01
|
|
39
|
+
const amount = (0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
40
|
+
...base,
|
|
41
|
+
children: 1,
|
|
42
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE,
|
|
43
|
+
promocodeDiscount: 9999,
|
|
44
|
+
});
|
|
45
|
+
expect(amount).toBe(2001);
|
|
46
|
+
});
|
|
47
|
+
it('поглощает слот-скидку: итог за игроков ровно = флеш-цене', () => {
|
|
48
|
+
// 100.00 − слот-скидка 10.00 = 90.00, флеш 79.99 → скидка 10.01
|
|
49
|
+
const amount = (0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
50
|
+
...base,
|
|
51
|
+
slotDiscountAmount: 1000,
|
|
52
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE,
|
|
53
|
+
promocodeDiscount: 7999,
|
|
54
|
+
});
|
|
55
|
+
expect(amount).toBe(1001);
|
|
56
|
+
});
|
|
57
|
+
it('не удорожает заказ: если базовая цена ниже флеш-цены — скидка 0', () => {
|
|
58
|
+
const amount = (0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
59
|
+
...base,
|
|
60
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE,
|
|
61
|
+
promocodeDiscount: 12000,
|
|
62
|
+
});
|
|
63
|
+
expect(amount).toBe(0);
|
|
64
|
+
});
|
|
65
|
+
it('без слота слот-скидка не вычитается', () => {
|
|
66
|
+
const amount = (0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
67
|
+
...base,
|
|
68
|
+
slotId: 0,
|
|
69
|
+
slotDiscountAmount: 1000,
|
|
70
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE,
|
|
71
|
+
promocodeDiscount: 7999,
|
|
72
|
+
});
|
|
73
|
+
expect(amount).toBe(2001);
|
|
74
|
+
});
|
|
75
|
+
it('не ломает существующие типы', () => {
|
|
76
|
+
expect((0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
77
|
+
...base,
|
|
78
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.FIXED,
|
|
79
|
+
promocodeDiscount: 500,
|
|
80
|
+
})).toBe(500);
|
|
81
|
+
expect((0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
82
|
+
...base,
|
|
83
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.PERCENT_BASE_SUM,
|
|
84
|
+
promocodeDiscount: 1500,
|
|
85
|
+
})).toBe(1500);
|
|
86
|
+
expect((0, get_promocode_discount_1.getPromocodeDiscount)({
|
|
87
|
+
...base,
|
|
88
|
+
promocodeType: promocode_type_enum_1.PromocodeTypeEnum.PROMOTIONAL,
|
|
89
|
+
promocodeDiscount: 1500,
|
|
90
|
+
})).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export * from './city-quest-content';
|
|
2
1
|
export * from './agreement-config';
|
|
3
2
|
export * from './booking-lead-time';
|
|
4
3
|
export * from './booking-page-url';
|
|
4
|
+
export * from './city-quest-content';
|
|
5
5
|
export * from './classify-api-error';
|
|
6
6
|
export * from './convert-hhmm-to-minutes';
|
|
7
7
|
export * from './convert-minutes-to-hhmm';
|
package/dist/index.js
CHANGED
|
@@ -14,10 +14,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./city-quest-content"), exports);
|
|
18
17
|
__exportStar(require("./agreement-config"), exports);
|
|
19
18
|
__exportStar(require("./booking-lead-time"), exports);
|
|
20
19
|
__exportStar(require("./booking-page-url"), exports);
|
|
20
|
+
__exportStar(require("./city-quest-content"), exports);
|
|
21
21
|
__exportStar(require("./classify-api-error"), exports);
|
|
22
22
|
__exportStar(require("./convert-hhmm-to-minutes"), exports);
|
|
23
23
|
__exportStar(require("./convert-minutes-to-hhmm"), exports);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validatePromocode = validatePromocode;
|
|
4
|
+
/* eslint-disable complexity */
|
|
5
|
+
const promocode_type_enum_1 = require("@escapenavigator/types/dist/promocode/emun/promocode-type.enum");
|
|
4
6
|
const date_1 = require("./date");
|
|
5
7
|
const promocode_error_codes_1 = require("./promocode-error-codes");
|
|
6
8
|
function getWeekdayNumber(date) {
|
|
@@ -35,7 +37,11 @@ function validatePromocode({ promocode, players, questroomId, timeZone, orderDat
|
|
|
35
37
|
else if (promocode.applyings > 0) {
|
|
36
38
|
errors.push({ code: promocode_error_codes_1.PromocodeErrorCode.SINGLE_USE });
|
|
37
39
|
}
|
|
38
|
-
|
|
40
|
+
// Флеш-цена поглощает слот-скидку (итог за игроков всегда = флеш-цене),
|
|
41
|
+
// поэтому скидка слота не считается конфликтом — блокируем только
|
|
42
|
+
// комбинирование с другими промокодами.
|
|
43
|
+
const slotDiscountConflict = promocode.type === promocode_type_enum_1.PromocodeTypeEnum.FLASH_PRICE ? 0 : slotDiscount;
|
|
44
|
+
if (!promocode.canUseWithOtherPromocodes && (otherPromocodes.length || slotDiscountConflict)) {
|
|
39
45
|
errors.push({ code: promocode_error_codes_1.PromocodeErrorCode.NOT_COMBINABLE_WITH_OTHER_DISCOUNTS });
|
|
40
46
|
}
|
|
41
47
|
if (!promocode.canUseWithOtherCertificates && hasCertificates) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/utils",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
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.
|
|
17
|
+
"@escapenavigator/types": "^2.0.17",
|
|
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": "38923f4b8d67b6f00321a16994251085cea3ed24"
|
|
33
33
|
}
|