@escapenavigator/utils 1.10.130 → 1.10.131
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/functional-popover-panel-style.d.ts +15 -0
- package/dist/functional-popover-panel-style.js +21 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/slot-representative-price.d.ts +10 -0
- package/dist/slot-representative-price.js +39 -0
- package/dist/slot-representative-price.spec.d.ts +1 -0
- package/dist/slot-representative-price.spec.js +27 -0
- package/dist/user-session-permissions.d.ts +37 -0
- package/dist/user-session-permissions.js +70 -0
- package/package.json +3 -3
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared chrome for interactive Popover panels (pickers, calendars, menus).
|
|
3
|
+
* Use on the inner content wrapper (`ref={popoverRef}`), not on Tooltip.
|
|
4
|
+
*/
|
|
5
|
+
export declare const functionalPopoverPanelStyle: {
|
|
6
|
+
readonly background: "var(--color-bg-primary)";
|
|
7
|
+
readonly border: "1px solid var(--color-border-primary)";
|
|
8
|
+
readonly borderRadius: "var(--radius-m)";
|
|
9
|
+
readonly boxShadow: "var(--shadow-m)";
|
|
10
|
+
readonly padding: 12;
|
|
11
|
+
readonly boxSizing: "border-box";
|
|
12
|
+
readonly color: "var(--color-text-primary)";
|
|
13
|
+
};
|
|
14
|
+
export type FunctionalPopoverPanelStyle = typeof functionalPopoverPanelStyle;
|
|
15
|
+
export declare const functionalPopoverPanel: (extra?: Record<string, string | number>) => Record<string, string | number>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.functionalPopoverPanel = exports.functionalPopoverPanelStyle = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Shared chrome for interactive Popover panels (pickers, calendars, menus).
|
|
6
|
+
* Use on the inner content wrapper (`ref={popoverRef}`), not on Tooltip.
|
|
7
|
+
*/
|
|
8
|
+
exports.functionalPopoverPanelStyle = {
|
|
9
|
+
background: 'var(--color-bg-primary)',
|
|
10
|
+
border: '1px solid var(--color-border-primary)',
|
|
11
|
+
borderRadius: 'var(--radius-m)',
|
|
12
|
+
boxShadow: 'var(--shadow-m)',
|
|
13
|
+
padding: 12,
|
|
14
|
+
boxSizing: 'border-box',
|
|
15
|
+
color: 'var(--color-text-primary)',
|
|
16
|
+
};
|
|
17
|
+
const functionalPopoverPanel = (extra) => ({
|
|
18
|
+
...exports.functionalPopoverPanelStyle,
|
|
19
|
+
...extra,
|
|
20
|
+
});
|
|
21
|
+
exports.functionalPopoverPanel = functionalPopoverPanel;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './convert-minutes-to-hhmm';
|
|
|
4
4
|
export * from './convert-to-options';
|
|
5
5
|
export * from './date';
|
|
6
6
|
export * from './enum-to-options';
|
|
7
|
+
export * from './functional-popover-panel-style';
|
|
7
8
|
export * from './get-documents-links';
|
|
8
9
|
export * from './get-full-name';
|
|
9
10
|
export * from './get-handled-error-message';
|
|
@@ -17,7 +18,9 @@ export * from './promocode-nominal-rules';
|
|
|
17
18
|
export * from './redirect';
|
|
18
19
|
export * from './serialize-record';
|
|
19
20
|
export * from './serialize-slot';
|
|
21
|
+
export * from './slot-representative-price';
|
|
20
22
|
export * from './tz-date';
|
|
23
|
+
export * from './user-session-permissions';
|
|
21
24
|
export * from './utm-touchpoints';
|
|
22
25
|
export * from './validate-by-dto';
|
|
23
26
|
export * from './validate-promocode';
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ __exportStar(require("./convert-minutes-to-hhmm"), exports);
|
|
|
20
20
|
__exportStar(require("./convert-to-options"), exports);
|
|
21
21
|
__exportStar(require("./date"), exports);
|
|
22
22
|
__exportStar(require("./enum-to-options"), exports);
|
|
23
|
+
__exportStar(require("./functional-popover-panel-style"), exports);
|
|
23
24
|
__exportStar(require("./get-documents-links"), exports);
|
|
24
25
|
__exportStar(require("./get-full-name"), exports);
|
|
25
26
|
__exportStar(require("./get-handled-error-message"), exports);
|
|
@@ -33,7 +34,9 @@ __exportStar(require("./promocode-nominal-rules"), exports);
|
|
|
33
34
|
__exportStar(require("./redirect"), exports);
|
|
34
35
|
__exportStar(require("./serialize-record"), exports);
|
|
35
36
|
__exportStar(require("./serialize-slot"), exports);
|
|
37
|
+
__exportStar(require("./slot-representative-price"), exports);
|
|
36
38
|
__exportStar(require("./tz-date"), exports);
|
|
39
|
+
__exportStar(require("./user-session-permissions"), exports);
|
|
37
40
|
__exportStar(require("./utm-touchpoints"), exports);
|
|
38
41
|
__exportStar(require("./validate-by-dto"), exports);
|
|
39
42
|
__exportStar(require("./validate-promocode"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type SlotPriceSource = {
|
|
2
|
+
tariff?: unknown;
|
|
3
|
+
basePrice?: number;
|
|
4
|
+
discount?: number;
|
|
5
|
+
};
|
|
6
|
+
/** Минимальная положительная цена из тарифа (без скидки слота). */
|
|
7
|
+
export declare const getTariffBasePrice: (tariff: unknown) => number | null;
|
|
8
|
+
/** Цена «от» для слота в расписании (с учётом скидки слота). */
|
|
9
|
+
export declare const getSlotRepresentativePrice: (slot: SlotPriceSource) => number | null;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSlotRepresentativePrice = exports.getTariffBasePrice = void 0;
|
|
4
|
+
const applySlotDiscount = (base, discount) => {
|
|
5
|
+
const value = discount || 0;
|
|
6
|
+
if (!value)
|
|
7
|
+
return base;
|
|
8
|
+
return base - Math.floor((base / 10000) * value);
|
|
9
|
+
};
|
|
10
|
+
/** Минимальная положительная цена из тарифа (без скидки слота). */
|
|
11
|
+
const getTariffBasePrice = (tariff) => {
|
|
12
|
+
if (!tariff || typeof tariff !== 'object')
|
|
13
|
+
return null;
|
|
14
|
+
const raw = tariff;
|
|
15
|
+
const priceMap = raw.price && typeof raw.price === 'object' && raw.price !== null
|
|
16
|
+
? raw.price
|
|
17
|
+
: raw;
|
|
18
|
+
const keys = Object.keys(priceMap)
|
|
19
|
+
.filter((k) => k !== 'child' && !Number.isNaN(Number(k)))
|
|
20
|
+
.map(Number)
|
|
21
|
+
.sort((a, b) => a - b);
|
|
22
|
+
for (const key of keys) {
|
|
23
|
+
const base = Number(priceMap[String(key)]);
|
|
24
|
+
if (Number.isFinite(base) && base > 0)
|
|
25
|
+
return base;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
exports.getTariffBasePrice = getTariffBasePrice;
|
|
30
|
+
/** Цена «от» для слота в расписании (с учётом скидки слота). */
|
|
31
|
+
const getSlotRepresentativePrice = (slot) => {
|
|
32
|
+
const base = typeof slot.basePrice === 'number' && slot.basePrice > 0
|
|
33
|
+
? slot.basePrice
|
|
34
|
+
: (0, exports.getTariffBasePrice)(slot.tariff);
|
|
35
|
+
if (base == null)
|
|
36
|
+
return null;
|
|
37
|
+
return applySlotDiscount(base, slot.discount);
|
|
38
|
+
};
|
|
39
|
+
exports.getSlotRepresentativePrice = getSlotRepresentativePrice;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const slot_representative_price_1 = require("./slot-representative-price");
|
|
4
|
+
describe('slot-representative-price', () => {
|
|
5
|
+
it('reads price from tariff.price map', () => {
|
|
6
|
+
expect((0, slot_representative_price_1.getTariffBasePrice)({
|
|
7
|
+
id: 1,
|
|
8
|
+
title: 'Default',
|
|
9
|
+
price: { 2: 10000, 3: 8000 },
|
|
10
|
+
})).toBe(10000);
|
|
11
|
+
});
|
|
12
|
+
it('reads price from plain price map', () => {
|
|
13
|
+
expect((0, slot_representative_price_1.getTariffBasePrice)({ 2: 5000, 4: 4000 })).toBe(5000);
|
|
14
|
+
});
|
|
15
|
+
it('skips zero min key and uses next positive price', () => {
|
|
16
|
+
expect((0, slot_representative_price_1.getTariffBasePrice)({ price: { 1: 0, 2: 7500 } })).toBe(7500);
|
|
17
|
+
});
|
|
18
|
+
it('uses basePrice and applies slot discount', () => {
|
|
19
|
+
expect((0, slot_representative_price_1.getSlotRepresentativePrice)({
|
|
20
|
+
basePrice: 10000,
|
|
21
|
+
discount: 1000,
|
|
22
|
+
})).toBe(9000);
|
|
23
|
+
});
|
|
24
|
+
it('coerces string amounts in tariff', () => {
|
|
25
|
+
expect((0, slot_representative_price_1.getTariffBasePrice)({ price: { 2: '9999' } })).toBe(9999);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RoleRO } from '@escapenavigator/types/dist/role/role.ro';
|
|
2
|
+
export declare const USER_SESSION_ERROR_KEYS: {
|
|
3
|
+
readonly shiftLocked: "shiftLocked";
|
|
4
|
+
readonly noPermission: "noPermission";
|
|
5
|
+
readonly notShiftAuthor: "notShiftAuthor";
|
|
6
|
+
readonly ownShiftPastDate: "ownShiftPastDate";
|
|
7
|
+
readonly cannotAssignOthers: "cannotAssignOthers";
|
|
8
|
+
readonly cannotCreateUnassigned: "cannotCreateUnassigned";
|
|
9
|
+
readonly sessionNotFound: "sessionNotFound";
|
|
10
|
+
};
|
|
11
|
+
export type UserSessionErrorKey = keyof typeof USER_SESSION_ERROR_KEYS;
|
|
12
|
+
export declare const USER_SESSION_ERRORS_EN: Record<UserSessionErrorKey, string>;
|
|
13
|
+
export type UserSessionRolePermissions = Pick<RoleRO, 'totalAccess' | 'canEditWorkedHours' | 'canEditOwnWorkedHours'>;
|
|
14
|
+
export type UserSessionPermissionTarget = {
|
|
15
|
+
hold?: boolean;
|
|
16
|
+
authorId?: number | null;
|
|
17
|
+
date: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const canEditOtherUserSessions: (role: UserSessionRolePermissions) => boolean;
|
|
20
|
+
export declare const isSessionDateTodayOrFuture: (date: string, today?: string) => boolean;
|
|
21
|
+
/** Returns an English denial message, or null when edit/delete is allowed. */
|
|
22
|
+
export declare const getUserSessionEditDenial: (params: {
|
|
23
|
+
session: UserSessionPermissionTarget;
|
|
24
|
+
role: UserSessionRolePermissions;
|
|
25
|
+
userId: number;
|
|
26
|
+
}) => string | null;
|
|
27
|
+
/** Returns an English denial message, or null when creating a shift is allowed. */
|
|
28
|
+
export declare const getUserSessionCreateDenial: (params: {
|
|
29
|
+
assigneeUserId: number | null | undefined;
|
|
30
|
+
role: UserSessionRolePermissions;
|
|
31
|
+
actorId: number;
|
|
32
|
+
}) => string | null;
|
|
33
|
+
export declare const canEditUserSession: (params: {
|
|
34
|
+
session: UserSessionPermissionTarget;
|
|
35
|
+
role: UserSessionRolePermissions;
|
|
36
|
+
userId: number;
|
|
37
|
+
}) => boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canEditUserSession = exports.getUserSessionCreateDenial = exports.getUserSessionEditDenial = exports.isSessionDateTodayOrFuture = exports.canEditOtherUserSessions = exports.USER_SESSION_ERRORS_EN = exports.USER_SESSION_ERROR_KEYS = void 0;
|
|
4
|
+
exports.USER_SESSION_ERROR_KEYS = {
|
|
5
|
+
shiftLocked: 'shiftLocked',
|
|
6
|
+
noPermission: 'noPermission',
|
|
7
|
+
notShiftAuthor: 'notShiftAuthor',
|
|
8
|
+
ownShiftPastDate: 'ownShiftPastDate',
|
|
9
|
+
cannotAssignOthers: 'cannotAssignOthers',
|
|
10
|
+
cannotCreateUnassigned: 'cannotCreateUnassigned',
|
|
11
|
+
sessionNotFound: 'sessionNotFound',
|
|
12
|
+
};
|
|
13
|
+
exports.USER_SESSION_ERRORS_EN = {
|
|
14
|
+
shiftLocked: 'This shift is locked. Unlock it before editing or deleting.',
|
|
15
|
+
noPermission: 'You do not have permission to manage work shifts.',
|
|
16
|
+
notShiftAuthor: 'You can only edit shifts that you created. Ask a manager with worked-hours access to change this shift.',
|
|
17
|
+
ownShiftPastDate: 'You can only edit or delete your own shifts on today or future dates.',
|
|
18
|
+
cannotAssignOthers: 'You cannot create or assign shifts for other employees. Enable "Can edit logged time" on your role.',
|
|
19
|
+
cannotCreateUnassigned: 'You cannot create unassigned shifts. Enable "Can edit logged time" on your role.',
|
|
20
|
+
sessionNotFound: 'Work shift not found.',
|
|
21
|
+
};
|
|
22
|
+
const canEditOtherUserSessions = (role) => !!role.totalAccess || !!role.canEditWorkedHours;
|
|
23
|
+
exports.canEditOtherUserSessions = canEditOtherUserSessions;
|
|
24
|
+
const isSessionDateTodayOrFuture = (date, today = formatTodayYmd()) => today <= date;
|
|
25
|
+
exports.isSessionDateTodayOrFuture = isSessionDateTodayOrFuture;
|
|
26
|
+
const formatTodayYmd = () => {
|
|
27
|
+
const d = new Date();
|
|
28
|
+
return `${d.getFullYear()}-${`${d.getMonth() + 1}`.padStart(2, '0')}-${`${d.getDate()}`.padStart(2, '0')}`;
|
|
29
|
+
};
|
|
30
|
+
/** Returns an English denial message, or null when edit/delete is allowed. */
|
|
31
|
+
const getUserSessionEditDenial = (params) => {
|
|
32
|
+
const { session, role, userId } = params;
|
|
33
|
+
if (session.hold) {
|
|
34
|
+
return exports.USER_SESSION_ERRORS_EN.shiftLocked;
|
|
35
|
+
}
|
|
36
|
+
if ((0, exports.canEditOtherUserSessions)(role)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (!role.canEditOwnWorkedHours) {
|
|
40
|
+
return exports.USER_SESSION_ERRORS_EN.noPermission;
|
|
41
|
+
}
|
|
42
|
+
if (session.authorId !== userId) {
|
|
43
|
+
return exports.USER_SESSION_ERRORS_EN.notShiftAuthor;
|
|
44
|
+
}
|
|
45
|
+
if (!(0, exports.isSessionDateTodayOrFuture)(session.date)) {
|
|
46
|
+
return exports.USER_SESSION_ERRORS_EN.ownShiftPastDate;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
exports.getUserSessionEditDenial = getUserSessionEditDenial;
|
|
51
|
+
/** Returns an English denial message, or null when creating a shift is allowed. */
|
|
52
|
+
const getUserSessionCreateDenial = (params) => {
|
|
53
|
+
const { assigneeUserId, role, actorId } = params;
|
|
54
|
+
if ((0, exports.canEditOtherUserSessions)(role)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (!role.canEditOwnWorkedHours) {
|
|
58
|
+
return exports.USER_SESSION_ERRORS_EN.noPermission;
|
|
59
|
+
}
|
|
60
|
+
if (assigneeUserId != null && assigneeUserId !== actorId) {
|
|
61
|
+
return exports.USER_SESSION_ERRORS_EN.cannotAssignOthers;
|
|
62
|
+
}
|
|
63
|
+
if (assigneeUserId == null) {
|
|
64
|
+
return exports.USER_SESSION_ERRORS_EN.cannotCreateUnassigned;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
exports.getUserSessionCreateDenial = getUserSessionCreateDenial;
|
|
69
|
+
const canEditUserSession = (params) => (0, exports.getUserSessionEditDenial)(params) === null;
|
|
70
|
+
exports.canEditUserSession = canEditUserSession;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/utils",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.131",
|
|
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": "^1.10.
|
|
17
|
+
"@escapenavigator/types": "^1.10.127",
|
|
18
18
|
"axios": "^0.21.4",
|
|
19
19
|
"class-transformer": "^0.5.1",
|
|
20
20
|
"class-validator": "^0.13.2",
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
"ts-jest": "^29.1.1",
|
|
27
27
|
"typescript": "^5.6"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "a592dd20f7b119d54930b606fd92effc45f7aec5"
|
|
30
30
|
}
|