@zauru-sdk/utils 6.19.1 → 6.21.0
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/esm/index.js +1 -0
- package/dist/esm/zauru/auth.loaders.js +5 -5
- package/dist/esm/zauru/priceAdjustmentRules.utils.js +109 -5
- package/dist/esm/zauru/rbac/access.js +36 -0
- package/dist/esm/zauru/rbac/catalog.js +67 -0
- package/dist/esm/zauru/rbac/createWebappRbac.js +252 -0
- package/dist/esm/zauru/rbac/index.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/zauru/auth.loaders.d.ts +1 -1
- package/dist/zauru/priceAdjustmentRules.utils.d.ts +18 -1
- package/dist/zauru/rbac/access.d.ts +8 -0
- package/dist/zauru/rbac/catalog.d.ts +15 -0
- package/dist/zauru/rbac/createWebappRbac.d.ts +39 -0
- package/dist/zauru/rbac/index.d.ts +3 -0
- package/package.json +5 -5
package/dist/esm/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export * from "./zauru/rejectionPercentageAdjustmentRules.utils.js";
|
|
|
26
26
|
export * from "./zauru/weeklyItemPrice.utils.js";
|
|
27
27
|
export * from "./zauru/templates.utils.js";
|
|
28
28
|
export * from "./zauru/tiposMuestra.utils.js";
|
|
29
|
+
export * from "./zauru/rbac/index.js";
|
|
29
30
|
export * from "./zauru/webapp-tables.utils.js";
|
|
30
31
|
export * from "./zauru/4pinos-po-discount-history.utils.js";
|
|
31
32
|
export * from "./zauru/programaciones.utils.js";
|
|
@@ -41,7 +41,7 @@ export function createLoginAction(extras) {
|
|
|
41
41
|
return redirect(authorizeUrl(request));
|
|
42
42
|
}
|
|
43
43
|
if (session.has("code")) {
|
|
44
|
-
if (isDenied(extras?.requireAccess?.(session))) {
|
|
44
|
+
if (isDenied(await extras?.requireAccess?.(session))) {
|
|
45
45
|
return redirect("/");
|
|
46
46
|
}
|
|
47
47
|
return redirect("/home");
|
|
@@ -50,7 +50,7 @@ export function createLoginAction(extras) {
|
|
|
50
50
|
if (loginResponse.error || !loginResponse.data) {
|
|
51
51
|
return Response.json(loginErrorPayload(loginResponse.userMsg?.toString() ?? ""));
|
|
52
52
|
}
|
|
53
|
-
if (isDenied(extras?.requireAccess?.(session))) {
|
|
53
|
+
if (isDenied(await extras?.requireAccess?.(session))) {
|
|
54
54
|
return Response.json(loginErrorPayload("No tienes permiso para acceder a esta aplicación. Si crees que esto es un error, contacta al administrador.", "Acceso denegado"));
|
|
55
55
|
}
|
|
56
56
|
if (extras?.afterLogin) {
|
|
@@ -78,7 +78,7 @@ export function createSessionGuardLoader(extras) {
|
|
|
78
78
|
if (!session.has("username")) {
|
|
79
79
|
return redirect("/");
|
|
80
80
|
}
|
|
81
|
-
if (isDenied(extras?.requireAccess?.(session))) {
|
|
81
|
+
if (isDenied(await extras?.requireAccess?.(session))) {
|
|
82
82
|
return redirect("/");
|
|
83
83
|
}
|
|
84
84
|
return Response.json({});
|
|
@@ -104,7 +104,7 @@ export function createIndexLoader(extras) {
|
|
|
104
104
|
return async ({ request }) => {
|
|
105
105
|
const session = await getSession(request.headers.get("Cookie"));
|
|
106
106
|
if (session.has("username")) {
|
|
107
|
-
if (isDenied(extras?.requireAccess?.(session))) {
|
|
107
|
+
if (isDenied(await extras?.requireAccess?.(session))) {
|
|
108
108
|
return Response.json({});
|
|
109
109
|
}
|
|
110
110
|
return redirect("/home");
|
|
@@ -121,7 +121,7 @@ export function createReloadCatalogsLoader(extras) {
|
|
|
121
121
|
if (!session.has("username")) {
|
|
122
122
|
return redirect("/");
|
|
123
123
|
}
|
|
124
|
-
if (isDenied(extras?.requireAccess?.(session))) {
|
|
124
|
+
if (isDenied(await extras?.requireAccess?.(session))) {
|
|
125
125
|
return redirect("/");
|
|
126
126
|
}
|
|
127
127
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { handlePossibleAxiosErrors } from "@zauru-sdk/common";
|
|
2
2
|
import { createWebAppTableRegister, getVariablesByName, getWebAppTableRegisters, updateWebAppTableRegister, } from "@zauru-sdk/services";
|
|
3
3
|
const TABLE_VAR = "price_adjustment_rules_web_app_table_id";
|
|
4
|
+
export const PRICE_ADJUSTMENT_REVERSAL_RULES_TABLE_VAR = "price_adjustment_reversal_rules_web_app_table_id";
|
|
4
5
|
const DEFAULT_FILTERS = {
|
|
5
6
|
itemMode: "all",
|
|
6
7
|
itemIds: [],
|
|
@@ -44,6 +45,29 @@ export const updatePriceAdjustmentRule = (headers, session, id, body) => {
|
|
|
44
45
|
return updateWebAppTableRegister(headers, tableId, Number(id), body);
|
|
45
46
|
});
|
|
46
47
|
};
|
|
48
|
+
const getReversalTableId = async (headers, session) => {
|
|
49
|
+
const vars = await getVariablesByName(headers, session, [
|
|
50
|
+
PRICE_ADJUSTMENT_REVERSAL_RULES_TABLE_VAR,
|
|
51
|
+
]);
|
|
52
|
+
return vars[PRICE_ADJUSTMENT_REVERSAL_RULES_TABLE_VAR];
|
|
53
|
+
};
|
|
54
|
+
export const filterActivePriceAdjustmentReversalRules = (rules) => (rules ?? []).filter((rule) => !rule.data?.fechaEliminacion && rule.data?.activa !== false);
|
|
55
|
+
export const getPriceAdjustmentReversalRules = (headers, session) => handlePossibleAxiosErrors(async () => {
|
|
56
|
+
const tableId = await getReversalTableId(headers, session);
|
|
57
|
+
const response = await getWebAppTableRegisters(session, tableId);
|
|
58
|
+
if (response.error) {
|
|
59
|
+
throw new Error(`Ocurrió un error al consultar las reglas de reversión de precio: ${response.userMsg}`);
|
|
60
|
+
}
|
|
61
|
+
return response.data ?? [];
|
|
62
|
+
});
|
|
63
|
+
export const createPriceAdjustmentReversalRule = (headers, session, body) => handlePossibleAxiosErrors(async () => {
|
|
64
|
+
const tableId = await getReversalTableId(headers, session);
|
|
65
|
+
return createWebAppTableRegister(headers, tableId, body);
|
|
66
|
+
});
|
|
67
|
+
export const updatePriceAdjustmentReversalRule = (headers, session, id, body) => handlePossibleAxiosErrors(async () => {
|
|
68
|
+
const tableId = await getReversalTableId(headers, session);
|
|
69
|
+
return updateWebAppTableRegister(headers, tableId, Number(id), body);
|
|
70
|
+
});
|
|
47
71
|
export const normalizeComparableValue = (value) => String(value ?? "").replace(/\s+/g, " ").trim();
|
|
48
72
|
export const formatReceptionTypeValue = (type) => [type?.Nombre, type?.Codigo]
|
|
49
73
|
.map((part) => String(part ?? "").trim())
|
|
@@ -104,11 +128,15 @@ export const applyPriceAdjustmentRules = (basePrice, rules, ctx) => {
|
|
|
104
128
|
let runningTotal = safeBase;
|
|
105
129
|
const steps = [];
|
|
106
130
|
const normalized = rules
|
|
107
|
-
.map((rule) => (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
.map((rule) => ({
|
|
132
|
+
rule: "data" in rule ? rule.data : rule,
|
|
133
|
+
ruleId: "data" in rule ? rule.id : undefined,
|
|
134
|
+
}))
|
|
135
|
+
.filter((entry) => Boolean(entry.rule));
|
|
136
|
+
const activeRules = normalized
|
|
137
|
+
.filter(({ rule }) => !rule.fechaEliminacion && rule.activa !== false)
|
|
138
|
+
.sort((a, b) => (a.rule.prioridad ?? 0) - (b.rule.prioridad ?? 0));
|
|
139
|
+
for (const { rule, ruleId } of activeRules) {
|
|
112
140
|
if (!priceAdjustmentRuleMatches(rule, ctx)) {
|
|
113
141
|
continue;
|
|
114
142
|
}
|
|
@@ -125,6 +153,7 @@ export const applyPriceAdjustmentRules = (basePrice, rules, ctx) => {
|
|
|
125
153
|
}
|
|
126
154
|
runningTotal = roundMoney(runningTotal);
|
|
127
155
|
steps.push({
|
|
156
|
+
...(ruleId !== undefined ? { ruleId } : {}),
|
|
128
157
|
ruleName: rule.nombre,
|
|
129
158
|
operation: rule.operacion,
|
|
130
159
|
valueType: rule.tipoValor,
|
|
@@ -142,3 +171,78 @@ export const applyPriceAdjustmentRules = (basePrice, rules, ctx) => {
|
|
|
142
171
|
result.description = formatPriceAdjustmentDescription(result);
|
|
143
172
|
return result;
|
|
144
173
|
};
|
|
174
|
+
const sameMoney = (left, right) => Math.abs(roundMoney(left, 2) - roundMoney(right, 2)) < 0.000001;
|
|
175
|
+
export const buildPriceAdjustmentReversalPlan = ({ calculation, currentPrice, reversalRules, history, }) => {
|
|
176
|
+
const base = {
|
|
177
|
+
calculationId: calculation.calculationId,
|
|
178
|
+
detailId: calculation.detailId,
|
|
179
|
+
itemId: calculation.itemId,
|
|
180
|
+
previousPrice: roundMoney(currentPrice, 2),
|
|
181
|
+
finalPrice: roundMoney(currentPrice, 2),
|
|
182
|
+
penaltyAmount: 0,
|
|
183
|
+
matches: [],
|
|
184
|
+
};
|
|
185
|
+
if (!calculation.calculationId || calculation.detailId == null) {
|
|
186
|
+
return {
|
|
187
|
+
...base,
|
|
188
|
+
status: "ineligible",
|
|
189
|
+
reason: "El cálculo no contiene calculationId y detailId.",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const activeRules = filterActivePriceAdjustmentReversalRules(reversalRules);
|
|
193
|
+
const reversalBySource = new Map(activeRules.map((entry) => [entry.data.sourceRuleId, entry]));
|
|
194
|
+
const applicable = calculation.steps.flatMap((step) => {
|
|
195
|
+
if (step.ruleId == null ||
|
|
196
|
+
step.operation !== "add" ||
|
|
197
|
+
step.appliedAmount <= 0) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const reversal = reversalBySource.get(step.ruleId);
|
|
201
|
+
if (!reversal)
|
|
202
|
+
return [];
|
|
203
|
+
return [
|
|
204
|
+
{
|
|
205
|
+
sourceRuleId: step.ruleId,
|
|
206
|
+
sourceRuleName: step.ruleName,
|
|
207
|
+
reversalRuleId: reversal.id,
|
|
208
|
+
reversalRuleName: reversal.data.nombre,
|
|
209
|
+
appliedAmount: roundMoney(step.appliedAmount),
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
});
|
|
213
|
+
if (applicable.length === 0) {
|
|
214
|
+
return {
|
|
215
|
+
...base,
|
|
216
|
+
status: "ineligible",
|
|
217
|
+
reason: "Ningún paso del cálculo tiene una regla de reversión activa.",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const calculationHistory = (history ?? []).filter((entry) => entry.calculationId === calculation.calculationId &&
|
|
221
|
+
entry.detailId === calculation.detailId);
|
|
222
|
+
const alreadyAppliedSourceIds = new Set(calculationHistory.map((entry) => entry.sourceRuleId));
|
|
223
|
+
const pending = applicable.filter((match) => !alreadyAppliedSourceIds.has(match.sourceRuleId));
|
|
224
|
+
if (pending.length === 0) {
|
|
225
|
+
return {
|
|
226
|
+
...base,
|
|
227
|
+
status: "already_applied",
|
|
228
|
+
reason: "Todas las reglas vinculadas ya fueron penalizadas.",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const expectedCurrentPrice = roundMoney(Number(calculation.finalPrice) +
|
|
232
|
+
calculationHistory.reduce((sum, entry) => sum + entry.amount, 0), 2);
|
|
233
|
+
if (!sameMoney(currentPrice, expectedCurrentPrice)) {
|
|
234
|
+
return {
|
|
235
|
+
...base,
|
|
236
|
+
status: "price_changed",
|
|
237
|
+
reason: `El precio actual ${roundMoney(currentPrice, 2)} no coincide con el precio esperado ${expectedCurrentPrice}.`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const penaltyAmount = roundMoney(-pending.reduce((sum, match) => sum + match.appliedAmount, 0));
|
|
241
|
+
return {
|
|
242
|
+
...base,
|
|
243
|
+
status: "eligible",
|
|
244
|
+
finalPrice: roundMoney(currentPrice + penaltyAmount, 2),
|
|
245
|
+
penaltyAmount,
|
|
246
|
+
matches: pending,
|
|
247
|
+
};
|
|
248
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES } from "./catalog.js";
|
|
2
|
+
export function isSuperAdminEmail(email, suffixes = DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES) {
|
|
3
|
+
if (!email) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const normalized = email.toLowerCase().trim();
|
|
7
|
+
return suffixes.some((suffix) => normalized.endsWith(suffix.toLowerCase()));
|
|
8
|
+
}
|
|
9
|
+
export function hasPermissionKey(superAdmin, permissionKeys, key) {
|
|
10
|
+
if (superAdmin) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return (permissionKeys ?? []).includes(key);
|
|
14
|
+
}
|
|
15
|
+
export function hasPermission(context, key) {
|
|
16
|
+
if (!context) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return hasPermissionKey(context.superAdmin, context.permissionKeys, key);
|
|
20
|
+
}
|
|
21
|
+
export function isActiveWebappRow(row) {
|
|
22
|
+
return Boolean(row?.data) && !row?.data?.fechaEliminacion;
|
|
23
|
+
}
|
|
24
|
+
export function emptyAccessContext(employeeId, email) {
|
|
25
|
+
return {
|
|
26
|
+
employeeId,
|
|
27
|
+
email,
|
|
28
|
+
superAdmin: false,
|
|
29
|
+
roleId: null,
|
|
30
|
+
roleName: null,
|
|
31
|
+
assignmentId: null,
|
|
32
|
+
permissionKeys: [],
|
|
33
|
+
allowedItemIds: [],
|
|
34
|
+
allowedPayeeIds: [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES = [
|
|
2
|
+
"@zauru.com",
|
|
3
|
+
"@intuitiva.biz",
|
|
4
|
+
];
|
|
5
|
+
export function flattenPermissionRows(groups) {
|
|
6
|
+
return groups.flatMap((group) => group.actions.map((action) => ({
|
|
7
|
+
groupId: group.id,
|
|
8
|
+
groupLabel: group.label,
|
|
9
|
+
key: action.key,
|
|
10
|
+
label: action.label,
|
|
11
|
+
})));
|
|
12
|
+
}
|
|
13
|
+
export function allPermissionKeysFromGroups(groups) {
|
|
14
|
+
return flattenPermissionRows(groups).map((row) => row.key);
|
|
15
|
+
}
|
|
16
|
+
export function isValidPermissionKey(key, validKeys) {
|
|
17
|
+
if (Array.isArray(validKeys)) {
|
|
18
|
+
return validKeys.includes(key);
|
|
19
|
+
}
|
|
20
|
+
return validKeys.has(key);
|
|
21
|
+
}
|
|
22
|
+
export function normalizePermissionKeys(value) {
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
return value.filter((key) => typeof key === "string");
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
const trimmed = value.trim();
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(trimmed);
|
|
33
|
+
if (Array.isArray(parsed)) {
|
|
34
|
+
return parsed.filter((key) => typeof key === "string");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return [trimmed];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
export function normalizeIdList(value) {
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return value
|
|
48
|
+
.map((item) => Number(item))
|
|
49
|
+
.filter((item) => Number.isFinite(item) && item > 0);
|
|
50
|
+
}
|
|
51
|
+
export function togglePermissionKey(keys, key, granted) {
|
|
52
|
+
const next = new Set(keys);
|
|
53
|
+
if (granted) {
|
|
54
|
+
next.add(key);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
next.delete(key);
|
|
58
|
+
}
|
|
59
|
+
return [...next];
|
|
60
|
+
}
|
|
61
|
+
export function addPermissionKeys(keys, add) {
|
|
62
|
+
return [...new Set([...keys, ...add])];
|
|
63
|
+
}
|
|
64
|
+
export function removePermissionKeys(keys, remove) {
|
|
65
|
+
const drop = new Set(remove);
|
|
66
|
+
return keys.filter((key) => !drop.has(key));
|
|
67
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { redirect } from "@remix-run/node";
|
|
2
|
+
import { handlePossibleAxiosErrors } from "@zauru-sdk/common";
|
|
3
|
+
import { createWebAppTableRegister, getVariablesByName, getWebAppRow, getWebAppTableRegisters, updateWebAppTableRegister, } from "@zauru-sdk/services";
|
|
4
|
+
import { hasPermission as hasPermissionInContext, isActiveWebappRow, isSuperAdminEmail, } from "./access.js";
|
|
5
|
+
import { DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES, isValidPermissionKey, normalizeIdList, normalizePermissionKeys, } from "./catalog.js";
|
|
6
|
+
const deletedAt = () => new Date().toISOString();
|
|
7
|
+
const toNumber = (value) => {
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
10
|
+
};
|
|
11
|
+
export function createWebappRbac(config) {
|
|
12
|
+
const suffixes = config.superAdminEmailSuffixes ?? DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES;
|
|
13
|
+
const validKeys = new Set(config.allPermissionKeys);
|
|
14
|
+
const getTableIds = async (headers, session) => {
|
|
15
|
+
const vars = await getVariablesByName(headers, session, [
|
|
16
|
+
config.rolesTableVar,
|
|
17
|
+
config.employeeRolesTableVar,
|
|
18
|
+
]);
|
|
19
|
+
return {
|
|
20
|
+
rolesTableId: vars[config.rolesTableVar],
|
|
21
|
+
employeeRolesTableId: vars[config.employeeRolesTableVar],
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
const sanitizeKeys = (keys) => normalizePermissionKeys(keys).filter((key) => isValidPermissionKey(key, validKeys));
|
|
25
|
+
const listRoles = (headers, session) => handlePossibleAxiosErrors(async () => {
|
|
26
|
+
const { rolesTableId } = await getTableIds(headers, session);
|
|
27
|
+
const response = await getWebAppTableRegisters(session, rolesTableId);
|
|
28
|
+
if (response.error) {
|
|
29
|
+
throw new Error(`Ocurrió un error al consultar los roles: ${response.userMsg}`);
|
|
30
|
+
}
|
|
31
|
+
return (response.data ?? [])
|
|
32
|
+
.filter(isActiveWebappRow)
|
|
33
|
+
.map((row) => ({
|
|
34
|
+
...row,
|
|
35
|
+
data: {
|
|
36
|
+
...row.data,
|
|
37
|
+
permission_keys: sanitizeKeys(row.data.permission_keys),
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
});
|
|
41
|
+
const getRoleById = (session, roleId) => handlePossibleAxiosErrors(async () => {
|
|
42
|
+
const response = await getWebAppRow(session, roleId);
|
|
43
|
+
if (response.error) {
|
|
44
|
+
throw new Error(`Ocurrió un error al consultar el rol: ${response.userMsg}`);
|
|
45
|
+
}
|
|
46
|
+
if (!response.data || response.data.fechaEliminacion) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
...response.data,
|
|
51
|
+
permission_keys: sanitizeKeys(response.data.permission_keys),
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
const listEmployeeRoles = (headers, session, employeeId) => handlePossibleAxiosErrors(async () => {
|
|
55
|
+
const { employeeRolesTableId } = await getTableIds(headers, session);
|
|
56
|
+
const response = await getWebAppTableRegisters(session, employeeRolesTableId, employeeId
|
|
57
|
+
? { data: { employee_id: employeeId } }
|
|
58
|
+
: undefined);
|
|
59
|
+
if (response.error) {
|
|
60
|
+
throw new Error(`Ocurrió un error al consultar las asignaciones de rol: ${response.userMsg}`);
|
|
61
|
+
}
|
|
62
|
+
const rows = (response.data ?? []).filter(isActiveWebappRow);
|
|
63
|
+
if (employeeId == null) {
|
|
64
|
+
return rows;
|
|
65
|
+
}
|
|
66
|
+
return rows.filter((row) => Number(row.data.employee_id) === Number(employeeId));
|
|
67
|
+
});
|
|
68
|
+
const createRole = (headers, session, body) => handlePossibleAxiosErrors(async () => {
|
|
69
|
+
const name = body.name.trim();
|
|
70
|
+
if (!name) {
|
|
71
|
+
throw new Error("El nombre del rol es requerido.");
|
|
72
|
+
}
|
|
73
|
+
const { rolesTableId } = await getTableIds(headers, session);
|
|
74
|
+
return createWebAppTableRegister(headers, rolesTableId, {
|
|
75
|
+
name,
|
|
76
|
+
description: body.description?.trim() ?? "",
|
|
77
|
+
permission_keys: sanitizeKeys(body.permission_keys),
|
|
78
|
+
fechaEliminacion: "",
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
const updateRole = (headers, session, roleId, body) => handlePossibleAxiosErrors(async () => {
|
|
82
|
+
const { rolesTableId } = await getTableIds(headers, session);
|
|
83
|
+
const current = await getWebAppRow(session, roleId);
|
|
84
|
+
if (current.error || !current.data || current.data.fechaEliminacion) {
|
|
85
|
+
throw new Error("El rol no existe o fue eliminado.");
|
|
86
|
+
}
|
|
87
|
+
const next = {
|
|
88
|
+
name: body.name?.trim() || current.data.name,
|
|
89
|
+
description: body.description !== undefined
|
|
90
|
+
? body.description.trim()
|
|
91
|
+
: (current.data.description ?? ""),
|
|
92
|
+
permission_keys: body.permission_keys !== undefined
|
|
93
|
+
? sanitizeKeys(body.permission_keys)
|
|
94
|
+
: sanitizeKeys(current.data.permission_keys),
|
|
95
|
+
fechaEliminacion: current.data.fechaEliminacion ?? "",
|
|
96
|
+
};
|
|
97
|
+
return updateWebAppTableRegister(headers, rolesTableId, roleId, next);
|
|
98
|
+
});
|
|
99
|
+
const deleteRole = (headers, session, roleId) => handlePossibleAxiosErrors(async () => {
|
|
100
|
+
const assignments = await listEmployeeRoles(headers, session);
|
|
101
|
+
if (assignments.error) {
|
|
102
|
+
throw new Error(assignments.userMsg ?? "No se pudieron consultar las asignaciones.");
|
|
103
|
+
}
|
|
104
|
+
const inUse = (assignments.data ?? []).some((row) => Number(row.data.role_id) === Number(roleId));
|
|
105
|
+
if (inUse) {
|
|
106
|
+
throw new Error("No se puede eliminar el rol: hay empleados asignados. Reasígnalos primero.");
|
|
107
|
+
}
|
|
108
|
+
const { rolesTableId } = await getTableIds(headers, session);
|
|
109
|
+
const current = await getWebAppRow(session, roleId);
|
|
110
|
+
if (current.error || !current.data) {
|
|
111
|
+
throw new Error("El rol no existe.");
|
|
112
|
+
}
|
|
113
|
+
return updateWebAppTableRegister(headers, rolesTableId, roleId, {
|
|
114
|
+
...current.data,
|
|
115
|
+
permission_keys: sanitizeKeys(current.data.permission_keys),
|
|
116
|
+
fechaEliminacion: deletedAt(),
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
const syncRolePermissionKeys = (headers, session, roleId, keys) => updateRole(headers, session, roleId, { permission_keys: keys });
|
|
120
|
+
const assignEmployeeRole = (headers, session, body) => handlePossibleAxiosErrors(async () => {
|
|
121
|
+
const employeeId = toNumber(body.employee_id);
|
|
122
|
+
if (!employeeId) {
|
|
123
|
+
throw new Error("El empleado es requerido.");
|
|
124
|
+
}
|
|
125
|
+
const existing = await listEmployeeRoles(headers, session, employeeId);
|
|
126
|
+
if (existing.error) {
|
|
127
|
+
throw new Error(existing.userMsg ?? "No se pudieron consultar las asignaciones.");
|
|
128
|
+
}
|
|
129
|
+
const current = existing.data?.[0];
|
|
130
|
+
const { employeeRolesTableId } = await getTableIds(headers, session);
|
|
131
|
+
if (body.role_id == null) {
|
|
132
|
+
if (!current) {
|
|
133
|
+
return {
|
|
134
|
+
id: 0,
|
|
135
|
+
webapp_table_id: Number(employeeRolesTableId),
|
|
136
|
+
data: { Nombre: "" },
|
|
137
|
+
creator_id: 0,
|
|
138
|
+
updater_id: null,
|
|
139
|
+
created_at: "",
|
|
140
|
+
updated_at: "",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return updateWebAppTableRegister(headers, employeeRolesTableId, current.id, {
|
|
144
|
+
...current.data,
|
|
145
|
+
allowed_item_ids: normalizeIdList(body.allowed_item_ids ?? current.data.allowed_item_ids),
|
|
146
|
+
allowed_payee_ids: normalizeIdList(body.allowed_payee_ids ?? current.data.allowed_payee_ids),
|
|
147
|
+
fechaEliminacion: deletedAt(),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const payload = {
|
|
151
|
+
employee_id: employeeId,
|
|
152
|
+
role_id: toNumber(body.role_id),
|
|
153
|
+
allowed_item_ids: normalizeIdList(body.allowed_item_ids),
|
|
154
|
+
allowed_payee_ids: normalizeIdList(body.allowed_payee_ids),
|
|
155
|
+
fechaEliminacion: "",
|
|
156
|
+
};
|
|
157
|
+
if (current) {
|
|
158
|
+
return updateWebAppTableRegister(headers, employeeRolesTableId, current.id, payload);
|
|
159
|
+
}
|
|
160
|
+
return createWebAppTableRegister(headers, employeeRolesTableId, payload);
|
|
161
|
+
});
|
|
162
|
+
const resolveAccess = async (headers, session) => {
|
|
163
|
+
const email = String(session.get("email") ?? "");
|
|
164
|
+
const employeeId = toNumber(session.get("employee_id"));
|
|
165
|
+
const superAdmin = isSuperAdminEmail(email, suffixes);
|
|
166
|
+
if (superAdmin) {
|
|
167
|
+
return {
|
|
168
|
+
employeeId,
|
|
169
|
+
email,
|
|
170
|
+
superAdmin: true,
|
|
171
|
+
roleId: null,
|
|
172
|
+
roleName: null,
|
|
173
|
+
assignmentId: null,
|
|
174
|
+
permissionKeys: [...config.allPermissionKeys],
|
|
175
|
+
allowedItemIds: [],
|
|
176
|
+
allowedPayeeIds: [],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (!employeeId) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const assignments = await listEmployeeRoles(headers, session, employeeId);
|
|
183
|
+
if (assignments.error) {
|
|
184
|
+
throw new Error(assignments.userMsg ?? "No se pudieron consultar las asignaciones.");
|
|
185
|
+
}
|
|
186
|
+
const assignment = assignments.data?.[0];
|
|
187
|
+
if (!assignment) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const roleId = Number(assignment.data.role_id);
|
|
191
|
+
const role = await getRoleById(session, roleId);
|
|
192
|
+
if (role.error) {
|
|
193
|
+
throw new Error(role.userMsg ?? "No se pudo consultar el rol.");
|
|
194
|
+
}
|
|
195
|
+
if (!role.data) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
employeeId,
|
|
200
|
+
email,
|
|
201
|
+
superAdmin: false,
|
|
202
|
+
roleId,
|
|
203
|
+
roleName: role.data.name,
|
|
204
|
+
assignmentId: assignment.id,
|
|
205
|
+
permissionKeys: sanitizeKeys(role.data.permission_keys),
|
|
206
|
+
allowedItemIds: normalizeIdList(assignment.data.allowed_item_ids),
|
|
207
|
+
allowedPayeeIds: normalizeIdList(assignment.data.allowed_payee_ids),
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
const hasAppAccess = async (headers, session) => {
|
|
211
|
+
if (isSuperAdminEmail(session.get("email"), suffixes)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
const context = await resolveAccess(headers, session);
|
|
215
|
+
return context != null;
|
|
216
|
+
};
|
|
217
|
+
const requireAppAccess = async (headers, session) => {
|
|
218
|
+
if (!session.has("username")) {
|
|
219
|
+
throw redirect("/");
|
|
220
|
+
}
|
|
221
|
+
const context = await resolveAccess(headers, session);
|
|
222
|
+
if (!context) {
|
|
223
|
+
throw redirect("/");
|
|
224
|
+
}
|
|
225
|
+
return context;
|
|
226
|
+
};
|
|
227
|
+
const requirePermission = async (headers, session, key) => {
|
|
228
|
+
const context = await requireAppAccess(headers, session);
|
|
229
|
+
if (!hasPermissionInContext(context, key)) {
|
|
230
|
+
throw redirect("/");
|
|
231
|
+
}
|
|
232
|
+
return context;
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
config,
|
|
236
|
+
getTableIds,
|
|
237
|
+
listRoles,
|
|
238
|
+
getRoleById,
|
|
239
|
+
createRole,
|
|
240
|
+
updateRole,
|
|
241
|
+
deleteRole,
|
|
242
|
+
syncRolePermissionKeys,
|
|
243
|
+
listEmployeeRoles,
|
|
244
|
+
assignEmployeeRole,
|
|
245
|
+
resolveAccess,
|
|
246
|
+
hasAppAccess,
|
|
247
|
+
requireAppAccess,
|
|
248
|
+
requirePermission,
|
|
249
|
+
hasPermission: hasPermissionInContext,
|
|
250
|
+
isSuperAdminEmail: (email) => isSuperAdminEmail(email, suffixes),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { addPermissionKeys, allPermissionKeysFromGroups, DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES, flattenPermissionRows, isValidPermissionKey, normalizeIdList, normalizePermissionKeys, removePermissionKeys, togglePermissionKey, } from "./catalog.js";
|
|
2
|
+
export { emptyAccessContext, hasPermission, hasPermissionKey, isActiveWebappRow, isSuperAdminEmail, } from "./access.js";
|
|
3
|
+
export { createWebappRbac, } from "./createWebappRbac.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export * from "./zauru/rejectionPercentageAdjustmentRules.utils.js";
|
|
|
26
26
|
export * from "./zauru/weeklyItemPrice.utils.js";
|
|
27
27
|
export * from "./zauru/templates.utils.js";
|
|
28
28
|
export * from "./zauru/tiposMuestra.utils.js";
|
|
29
|
+
export * from "./zauru/rbac/index.js";
|
|
29
30
|
export * from "./zauru/webapp-tables.utils.js";
|
|
30
31
|
export * from "./zauru/4pinos-po-discount-history.utils.js";
|
|
31
32
|
export * from "./zauru/programaciones.utils.js";
|
|
@@ -7,7 +7,7 @@ export type AuthAccessExtras = {
|
|
|
7
7
|
* Return `false` to deny. Throw `redirect(...)` to send the user elsewhere.
|
|
8
8
|
* Any other return (including `void`) allows access.
|
|
9
9
|
*/
|
|
10
|
-
requireAccess?: (session: AuthSession) => boolean | void
|
|
10
|
+
requireAccess?: (session: AuthSession) => boolean | void | Promise<boolean | void>;
|
|
11
11
|
};
|
|
12
12
|
export type LoginActionExtras = AuthAccessExtras & {
|
|
13
13
|
afterLogin?: (ctx: {
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { Session } from "@remix-run/node";
|
|
2
|
-
import { AxiosUtilsResponse, PriceAdjustmentContext, PriceAdjustmentFilters, PriceAdjustmentResult, PriceAdjustmentRule, WebAppRowGraphQL, WebAppTableUpdateResponse } from "@zauru-sdk/types";
|
|
2
|
+
import { AxiosUtilsResponse, PriceAdjustmentContext, PriceAdjustmentFilters, PriceAdjustmentResult, PriceAdjustmentRule, PriceAdjustmentReversalPlan, PriceAdjustmentReversalRule, PriceAdjustmentStep, CertificationPenaltyHistoryEntry, WebAppRowGraphQL, WebAppTableUpdateResponse } from "@zauru-sdk/types";
|
|
3
|
+
export declare const PRICE_ADJUSTMENT_REVERSAL_RULES_TABLE_VAR = "price_adjustment_reversal_rules_web_app_table_id";
|
|
3
4
|
export declare const defaultPriceAdjustmentFilters: () => PriceAdjustmentFilters;
|
|
4
5
|
export declare const filterActivePriceAdjustmentRules: (rules?: WebAppRowGraphQL<PriceAdjustmentRule>[]) => WebAppRowGraphQL<PriceAdjustmentRule>[];
|
|
5
6
|
export declare const getPriceAdjustmentRules: (headers: any, session: Session) => Promise<AxiosUtilsResponse<WebAppRowGraphQL<PriceAdjustmentRule>[]>>;
|
|
6
7
|
export declare const createPriceAdjustmentRule: (headers: any, session: Session, body: PriceAdjustmentRule) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
7
8
|
export declare const updatePriceAdjustmentRule: (headers: any, session: Session, id: string, body: Partial<PriceAdjustmentRule>) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
9
|
+
export declare const filterActivePriceAdjustmentReversalRules: (rules?: WebAppRowGraphQL<PriceAdjustmentReversalRule>[]) => WebAppRowGraphQL<PriceAdjustmentReversalRule>[];
|
|
10
|
+
export declare const getPriceAdjustmentReversalRules: (headers: any, session: Session) => Promise<AxiosUtilsResponse<WebAppRowGraphQL<PriceAdjustmentReversalRule>[]>>;
|
|
11
|
+
export declare const createPriceAdjustmentReversalRule: (headers: any, session: Session, body: PriceAdjustmentReversalRule) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
12
|
+
export declare const updatePriceAdjustmentReversalRule: (headers: any, session: Session, id: string | number, body: Partial<PriceAdjustmentReversalRule>) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
8
13
|
export declare const normalizeComparableValue: (value: string | number | undefined | null) => string;
|
|
9
14
|
export declare const formatReceptionTypeValue: (type?: {
|
|
10
15
|
Nombre?: string;
|
|
@@ -13,3 +18,15 @@ export declare const formatReceptionTypeValue: (type?: {
|
|
|
13
18
|
export declare const priceAdjustmentRuleMatches: (rule: PriceAdjustmentRule, ctx: PriceAdjustmentContext) => boolean;
|
|
14
19
|
export declare const formatPriceAdjustmentDescription: (result: Pick<PriceAdjustmentResult, "basePrice" | "finalPrice" | "steps">, baseLabel?: string) => string;
|
|
15
20
|
export declare const applyPriceAdjustmentRules: (basePrice: number, rules: Array<PriceAdjustmentRule | WebAppRowGraphQL<PriceAdjustmentRule>>, ctx: PriceAdjustmentContext) => PriceAdjustmentResult;
|
|
21
|
+
export declare const buildPriceAdjustmentReversalPlan: ({ calculation, currentPrice, reversalRules, history, }: {
|
|
22
|
+
calculation: {
|
|
23
|
+
calculationId?: string;
|
|
24
|
+
detailId?: number;
|
|
25
|
+
itemId: number;
|
|
26
|
+
finalPrice: number;
|
|
27
|
+
steps: PriceAdjustmentStep[];
|
|
28
|
+
};
|
|
29
|
+
currentPrice: number;
|
|
30
|
+
reversalRules: WebAppRowGraphQL<PriceAdjustmentReversalRule>[];
|
|
31
|
+
history?: CertificationPenaltyHistoryEntry[];
|
|
32
|
+
}) => PriceAdjustmentReversalPlan;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WebappAccessContext, WebAppRowGraphQL } from "@zauru-sdk/types";
|
|
2
|
+
export declare function isSuperAdminEmail(email: string | null | undefined, suffixes?: readonly string[]): boolean;
|
|
3
|
+
export declare function hasPermissionKey(superAdmin: boolean | undefined, permissionKeys: readonly string[] | undefined, key: string): boolean;
|
|
4
|
+
export declare function hasPermission(context: WebappAccessContext | null | undefined, key: string): boolean;
|
|
5
|
+
export declare function isActiveWebappRow<T extends {
|
|
6
|
+
fechaEliminacion?: string;
|
|
7
|
+
}>(row: WebAppRowGraphQL<T> | null | undefined): row is WebAppRowGraphQL<T>;
|
|
8
|
+
export declare function emptyAccessContext(employeeId: number, email: string): WebappAccessContext;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PermissionGroup } from "@zauru-sdk/types";
|
|
2
|
+
export declare const DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES: readonly ["@zauru.com", "@intuitiva.biz"];
|
|
3
|
+
export declare function flattenPermissionRows(groups: PermissionGroup[]): Array<{
|
|
4
|
+
groupId: string;
|
|
5
|
+
groupLabel: string;
|
|
6
|
+
key: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function allPermissionKeysFromGroups(groups: PermissionGroup[]): string[];
|
|
10
|
+
export declare function isValidPermissionKey(key: string, validKeys: ReadonlySet<string> | readonly string[]): boolean;
|
|
11
|
+
export declare function normalizePermissionKeys(value: unknown): string[];
|
|
12
|
+
export declare function normalizeIdList(value: unknown): number[];
|
|
13
|
+
export declare function togglePermissionKey(keys: readonly string[], key: string, granted: boolean): string[];
|
|
14
|
+
export declare function addPermissionKeys(keys: readonly string[], add: readonly string[]): string[];
|
|
15
|
+
export declare function removePermissionKeys(keys: readonly string[], remove: readonly string[]): string[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Session } from "@remix-run/node";
|
|
2
|
+
import type { AxiosUtilsResponse, WebappAccessContext, WebappEmployeeRole, WebappRbacConfig, WebappRole, WebAppRowGraphQL, WebAppTableUpdateResponse } from "@zauru-sdk/types";
|
|
3
|
+
import { hasPermission as hasPermissionInContext } from "./access.js";
|
|
4
|
+
type TableIds = {
|
|
5
|
+
rolesTableId: string;
|
|
6
|
+
employeeRolesTableId: string;
|
|
7
|
+
};
|
|
8
|
+
export type CreateRoleInput = {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
permission_keys?: string[];
|
|
12
|
+
};
|
|
13
|
+
export type UpdateRoleInput = Partial<Pick<WebappRole, "name" | "description" | "permission_keys">>;
|
|
14
|
+
export type AssignEmployeeRoleInput = {
|
|
15
|
+
employee_id: number;
|
|
16
|
+
role_id: number | null;
|
|
17
|
+
allowed_item_ids?: number[];
|
|
18
|
+
allowed_payee_ids?: number[];
|
|
19
|
+
};
|
|
20
|
+
export declare function createWebappRbac(config: WebappRbacConfig): {
|
|
21
|
+
config: WebappRbacConfig;
|
|
22
|
+
getTableIds: (headers: any, session: Session) => Promise<TableIds>;
|
|
23
|
+
listRoles: (headers: any, session: Session) => Promise<AxiosUtilsResponse<WebAppRowGraphQL<WebappRole>[]>>;
|
|
24
|
+
getRoleById: (session: Session, roleId: number) => Promise<AxiosUtilsResponse<WebappRole | null>>;
|
|
25
|
+
createRole: (headers: any, session: Session, body: CreateRoleInput) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
26
|
+
updateRole: (headers: any, session: Session, roleId: number, body: UpdateRoleInput) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
27
|
+
deleteRole: (headers: any, session: Session, roleId: number) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
28
|
+
syncRolePermissionKeys: (headers: any, session: Session, roleId: number, keys: string[]) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
29
|
+
listEmployeeRoles: (headers: any, session: Session, employeeId?: number) => Promise<AxiosUtilsResponse<WebAppRowGraphQL<WebappEmployeeRole>[]>>;
|
|
30
|
+
assignEmployeeRole: (headers: any, session: Session, body: AssignEmployeeRoleInput) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
|
|
31
|
+
resolveAccess: (headers: any, session: Session) => Promise<WebappAccessContext | null>;
|
|
32
|
+
hasAppAccess: (headers: any, session: Session) => Promise<boolean>;
|
|
33
|
+
requireAppAccess: (headers: any, session: Session) => Promise<WebappAccessContext>;
|
|
34
|
+
requirePermission: (headers: any, session: Session, key: string) => Promise<WebappAccessContext>;
|
|
35
|
+
hasPermission: typeof hasPermissionInContext;
|
|
36
|
+
isSuperAdminEmail: (email: string | null | undefined) => boolean;
|
|
37
|
+
};
|
|
38
|
+
export type WebappRbac = ReturnType<typeof createWebappRbac>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { addPermissionKeys, allPermissionKeysFromGroups, DEFAULT_SUPER_ADMIN_EMAIL_SUFFIXES, flattenPermissionRows, isValidPermissionKey, normalizeIdList, normalizePermissionKeys, removePermissionKeys, togglePermissionKey, } from "./catalog.js";
|
|
2
|
+
export { emptyAccessContext, hasPermission, hasPermissionKey, isActiveWebappRow, isSuperAdminEmail, } from "./access.js";
|
|
3
|
+
export { createWebappRbac, type AssignEmployeeRoleInput, type CreateRoleInput, type UpdateRoleInput, type WebappRbac, } from "./createWebappRbac.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauru-sdk/utils",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.21.0",
|
|
4
4
|
"description": "Utilidades, parseo de request y demás para desarrollo de webapps de Zauru",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -71,12 +71,12 @@
|
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@aws-sdk/client-cloudwatch-logs": "^3.1123.0",
|
|
73
73
|
"@remix-run/node": "^2.8.1",
|
|
74
|
-
"@zauru-sdk/common": "^6.8.
|
|
74
|
+
"@zauru-sdk/common": "^6.8.4",
|
|
75
75
|
"@zauru-sdk/config": "^6.0.0",
|
|
76
76
|
"@zauru-sdk/graphql": "^6.8.0",
|
|
77
|
-
"@zauru-sdk/services": "^6.11.
|
|
78
|
-
"@zauru-sdk/types": "^6.
|
|
77
|
+
"@zauru-sdk/services": "^6.11.7",
|
|
78
|
+
"@zauru-sdk/types": "^6.22.0",
|
|
79
79
|
"moment": "^2.30.1"
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "800600a8cdb22f1c49293fbc3a48d26aa03771c9"
|
|
82
82
|
}
|