@zauru-sdk/common 6.4.1 → 6.5.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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [6.5.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.4.1...@zauru-sdk/common@6.5.0) (2026-09-14)
7
+
8
+
9
+ ### Features
10
+
11
+ * apply rejection percentage rules when saving PO discounts ([2b44998](https://github.com/intuitiva/zauru-typescript-sdk/commit/2b44998241e3b25d2a0d98a1c7f83a21b21cc265))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [6.4.1](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.4.0...@zauru-sdk/common@6.4.1) (2026-09-14)
7
18
 
8
19
  **Note:** Version bump only for package @zauru-sdk/common
package/dist/esm/index.js CHANGED
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./common.js"), exports);
18
18
  __exportStar(require("./gtZones.js"), exports);
19
+ __exportStar(require("./rejectionPercentageAdjustment.js"), exports);
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyRejectionPercentageRulesToFinancials = exports.applyRejectionPercentageAdjustmentRules = exports.resolveRejectionPercentageBase = exports.resolveRejectionPercentageOrigin = exports.formatRejectionPercentageAdjustmentDescription = exports.rejectionPercentageAdjustmentRuleMatches = exports.filterActiveRejectionPercentageAdjustmentRules = exports.REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR = void 0;
4
+ const common_js_1 = require("./common.js");
5
+ exports.REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR = "rejection_percentage_adjustment_rules_web_app_table_id";
6
+ const defaultFilters = () => ({
7
+ itemMode: "all",
8
+ itemIds: [],
9
+ tipoMode: "all",
10
+ tipos: [],
11
+ programaMode: "all",
12
+ providerCategoryIds: [],
13
+ });
14
+ const normalizeComparableValue = (value) => String(value ?? "").replace(/\s+/g, " ").trim();
15
+ const matchesFilter = (mode, selected, value) => {
16
+ const normalizedMode = mode ?? "all";
17
+ const values = selected ?? [];
18
+ if (normalizedMode === "all") {
19
+ return true;
20
+ }
21
+ if (value === undefined || value === null) {
22
+ return false;
23
+ }
24
+ const asString = normalizeComparableValue(value);
25
+ if (!asString) {
26
+ return false;
27
+ }
28
+ const included = values.some((item) => normalizeComparableValue(item) === asString);
29
+ if (normalizedMode === "include") {
30
+ return values.length > 0 && included;
31
+ }
32
+ return !included;
33
+ };
34
+ const filterActiveRejectionPercentageAdjustmentRules = (rules) => (rules ?? [])
35
+ .filter((rule) => !rule.data?.fechaEliminacion && rule.data?.activa !== false)
36
+ .sort((a, b) => (a.data?.prioridad ?? 0) - (b.data?.prioridad ?? 0) || a.id - b.id);
37
+ exports.filterActiveRejectionPercentageAdjustmentRules = filterActiveRejectionPercentageAdjustmentRules;
38
+ const rejectionPercentageAdjustmentRuleMatches = (rule, ctx) => {
39
+ const filtros = rule.filtros ?? defaultFilters();
40
+ const itemIds = ctx.itemIds ?? [];
41
+ const tipoAndPrograma = matchesFilter(filtros.tipoMode, filtros.tipos, ctx.tipo) &&
42
+ matchesFilter(filtros.programaMode, filtros.providerCategoryIds, ctx.providerCategoryId);
43
+ if (!tipoAndPrograma) {
44
+ return false;
45
+ }
46
+ if (filtros.itemMode === "all") {
47
+ return true;
48
+ }
49
+ if (itemIds.length === 0) {
50
+ return false;
51
+ }
52
+ return itemIds.some((itemId) => matchesFilter(filtros.itemMode, filtros.itemIds, itemId));
53
+ };
54
+ exports.rejectionPercentageAdjustmentRuleMatches = rejectionPercentageAdjustmentRuleMatches;
55
+ const roundPercentage = (value, digits = 4) => {
56
+ const factor = 10 ** digits;
57
+ return Math.round((value + Number.EPSILON) * factor) / factor;
58
+ };
59
+ const clampPercentage = (value) => Math.min(100, Math.max(0, value));
60
+ const formatPercentage = (value) => Number.isInteger(value) ? String(value) : String(roundPercentage(value));
61
+ const formatFinalPercentage = (value) => roundPercentage(value, 2).toFixed(2);
62
+ const formatRuleEffect = (step) => {
63
+ const sign = step.operation === "subtract" ? "-" : "+";
64
+ return `${sign} ${formatPercentage(step.value)}% (${step.ruleName})`;
65
+ };
66
+ const formatRejectionPercentageAdjustmentDescription = (result, baseLabel = "origen") => {
67
+ const base = `${formatPercentage(result.basePercentage)} (${baseLabel})`;
68
+ if (result.steps.length === 0) {
69
+ return base;
70
+ }
71
+ return `${base} ${result.steps.map(formatRuleEffect).join(" ")} = ${formatFinalPercentage(result.finalPercentage)}`;
72
+ };
73
+ exports.formatRejectionPercentageAdjustmentDescription = formatRejectionPercentageAdjustmentDescription;
74
+ const resolveRejectionPercentageOrigin = (passedPercentage, memo) => {
75
+ const parsed = (0, common_js_1.parseJsonMemo)(memo);
76
+ const passed = clampPercentage(Number.isFinite(Number(passedPercentage)) ? Number(passedPercentage) : 0);
77
+ const calc = parsed.rejectionCalculations;
78
+ if (!calc || !Number.isFinite(Number(calc.finalPercentage))) {
79
+ return passed;
80
+ }
81
+ const previousFinal = roundPercentage(Number(calc.finalPercentage), 2);
82
+ const previousBase = Number(calc.basePercentage);
83
+ const safePreviousBase = Number.isFinite(previousBase)
84
+ ? clampPercentage(previousBase)
85
+ : passed;
86
+ if (roundPercentage(passed, 2) === previousFinal) {
87
+ return safePreviousBase;
88
+ }
89
+ return clampPercentage(safePreviousBase + (passed - previousFinal));
90
+ };
91
+ exports.resolveRejectionPercentageOrigin = resolveRejectionPercentageOrigin;
92
+ const resolveRejectionPercentageBase = (memo) => (0, exports.resolveRejectionPercentageOrigin)((0, common_js_1.getRejectionPercentage)(memo), memo);
93
+ exports.resolveRejectionPercentageBase = resolveRejectionPercentageBase;
94
+ const applyRejectionPercentageAdjustmentRules = (basePercentage, rules, ctx) => {
95
+ const start = Number(basePercentage);
96
+ const safeBase = Number.isFinite(start) ? clampPercentage(start) : 0;
97
+ let runningTotal = safeBase;
98
+ const steps = [];
99
+ const normalized = rules
100
+ .map((rule) => ("data" in rule ? rule.data : rule))
101
+ .filter((rule) => Boolean(rule))
102
+ .filter((rule) => !rule.fechaEliminacion && rule.activa !== false)
103
+ .sort((a, b) => (a.prioridad ?? 0) - (b.prioridad ?? 0));
104
+ for (const rule of normalized) {
105
+ if (!(0, exports.rejectionPercentageAdjustmentRuleMatches)(rule, ctx)) {
106
+ continue;
107
+ }
108
+ const value = Number(rule.valor) || 0;
109
+ const previous = runningTotal;
110
+ runningTotal =
111
+ rule.operacion === "subtract" ? previous - value : previous + value;
112
+ runningTotal = clampPercentage(roundPercentage(runningTotal));
113
+ steps.push({
114
+ ruleName: rule.nombre,
115
+ operation: rule.operacion,
116
+ value,
117
+ appliedAmount: roundPercentage(runningTotal - previous),
118
+ runningTotal,
119
+ });
120
+ }
121
+ const result = {
122
+ basePercentage: roundPercentage(safeBase),
123
+ finalPercentage: roundPercentage(runningTotal, 2),
124
+ description: "",
125
+ steps,
126
+ };
127
+ result.description = (0, exports.formatRejectionPercentageAdjustmentDescription)(result);
128
+ return result;
129
+ };
130
+ exports.applyRejectionPercentageAdjustmentRules = applyRejectionPercentageAdjustmentRules;
131
+ const applyRejectionPercentageRulesToFinancials = (input) => {
132
+ const origin = (0, exports.resolveRejectionPercentageOrigin)(input.originPercentage, input.memo);
133
+ const result = (0, exports.applyRejectionPercentageAdjustmentRules)(origin, input.rules, input.ctx);
134
+ const { discount } = (0, common_js_1.calculatePurchaseOrderFinancials)({
135
+ details: input.details,
136
+ rejectionPercentage: result.finalPercentage,
137
+ });
138
+ return {
139
+ memo: (0, common_js_1.mergeJsonMemo)(input.memo, {
140
+ rejectionPercentage: result.finalPercentage,
141
+ rejectionCalculations: {
142
+ basePercentage: result.basePercentage,
143
+ finalPercentage: result.finalPercentage,
144
+ description: result.description,
145
+ steps: result.steps,
146
+ },
147
+ }),
148
+ discount,
149
+ finalPercentage: result.finalPercentage,
150
+ };
151
+ };
152
+ exports.applyRejectionPercentageRulesToFinancials = applyRejectionPercentageRulesToFinancials;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./common.js";
2
2
  export * from "./gtZones.js";
3
+ export * from "./rejectionPercentageAdjustment.js";
@@ -0,0 +1,19 @@
1
+ import type { PurchaseOrderFinancialDetail, RejectionPercentageAdjustmentContext, RejectionPercentageAdjustmentResult, RejectionPercentageAdjustmentRule, WebAppRowGraphQL } from "@zauru-sdk/types";
2
+ export declare const REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR = "rejection_percentage_adjustment_rules_web_app_table_id";
3
+ export declare const filterActiveRejectionPercentageAdjustmentRules: (rules?: WebAppRowGraphQL<RejectionPercentageAdjustmentRule>[]) => WebAppRowGraphQL<RejectionPercentageAdjustmentRule>[];
4
+ export declare const rejectionPercentageAdjustmentRuleMatches: (rule: RejectionPercentageAdjustmentRule, ctx: RejectionPercentageAdjustmentContext) => boolean;
5
+ export declare const formatRejectionPercentageAdjustmentDescription: (result: Pick<RejectionPercentageAdjustmentResult, "basePercentage" | "finalPercentage" | "steps">, baseLabel?: string) => string;
6
+ export declare const resolveRejectionPercentageOrigin: (passedPercentage: number, memo?: string | object) => number;
7
+ export declare const resolveRejectionPercentageBase: (memo?: string | object) => number;
8
+ export declare const applyRejectionPercentageAdjustmentRules: (basePercentage: number, rules: Array<RejectionPercentageAdjustmentRule | WebAppRowGraphQL<RejectionPercentageAdjustmentRule>>, ctx: RejectionPercentageAdjustmentContext) => RejectionPercentageAdjustmentResult;
9
+ export declare const applyRejectionPercentageRulesToFinancials: (input: {
10
+ memo?: string | object;
11
+ originPercentage: number;
12
+ details: PurchaseOrderFinancialDetail[];
13
+ rules: Array<RejectionPercentageAdjustmentRule | WebAppRowGraphQL<RejectionPercentageAdjustmentRule>>;
14
+ ctx: RejectionPercentageAdjustmentContext;
15
+ }) => {
16
+ memo: string;
17
+ discount: number;
18
+ finalPercentage: number;
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/common",
3
- "version": "6.4.1",
3
+ "version": "6.5.0",
4
4
  "description": "Funciones de uso común en el desarrollo de webapps en Zauru",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -25,5 +25,5 @@
25
25
  "moment": "^2.30.1",
26
26
  "moment-timezone": "^0.5.45"
27
27
  },
28
- "gitHead": "ab1ff7197a4688c0e887942b4e7a1f6b729a510e"
28
+ "gitHead": "2dceb781cae3f4c45ceecb915977c6d142bca6b9"
29
29
  }
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./common.js";
2
2
  export * from "./gtZones.js";
3
+ export * from "./rejectionPercentageAdjustment.js";
@@ -0,0 +1,258 @@
1
+ import type {
2
+ PriceAdjustmentFilterMode,
3
+ PriceAdjustmentFilters,
4
+ PurchaseOrderFinancialDetail,
5
+ RejectionPercentageAdjustmentContext,
6
+ RejectionPercentageAdjustmentResult,
7
+ RejectionPercentageAdjustmentRule,
8
+ RejectionPercentageAdjustmentStep,
9
+ WebAppRowGraphQL,
10
+ } from "@zauru-sdk/types";
11
+ import {
12
+ calculatePurchaseOrderFinancials,
13
+ getRejectionPercentage,
14
+ mergeJsonMemo,
15
+ parseJsonMemo,
16
+ } from "./common.js";
17
+
18
+ export const REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR =
19
+ "rejection_percentage_adjustment_rules_web_app_table_id";
20
+
21
+ const defaultFilters = (): PriceAdjustmentFilters => ({
22
+ itemMode: "all",
23
+ itemIds: [],
24
+ tipoMode: "all",
25
+ tipos: [],
26
+ programaMode: "all",
27
+ providerCategoryIds: [],
28
+ });
29
+
30
+ const normalizeComparableValue = (
31
+ value: string | number | undefined | null,
32
+ ): string => String(value ?? "").replace(/\s+/g, " ").trim();
33
+
34
+ const matchesFilter = (
35
+ mode: PriceAdjustmentFilterMode | undefined,
36
+ selected: Array<string | number> | undefined,
37
+ value: string | number | undefined,
38
+ ): boolean => {
39
+ const normalizedMode = mode ?? "all";
40
+ const values = selected ?? [];
41
+
42
+ if (normalizedMode === "all") {
43
+ return true;
44
+ }
45
+
46
+ if (value === undefined || value === null) {
47
+ return false;
48
+ }
49
+
50
+ const asString = normalizeComparableValue(value);
51
+ if (!asString) {
52
+ return false;
53
+ }
54
+
55
+ const included = values.some(
56
+ (item) => normalizeComparableValue(item) === asString,
57
+ );
58
+
59
+ if (normalizedMode === "include") {
60
+ return values.length > 0 && included;
61
+ }
62
+
63
+ return !included;
64
+ };
65
+
66
+ export const filterActiveRejectionPercentageAdjustmentRules = (
67
+ rules?: WebAppRowGraphQL<RejectionPercentageAdjustmentRule>[],
68
+ ) =>
69
+ (rules ?? [])
70
+ .filter(
71
+ (rule) => !rule.data?.fechaEliminacion && rule.data?.activa !== false,
72
+ )
73
+ .sort(
74
+ (a, b) =>
75
+ (a.data?.prioridad ?? 0) - (b.data?.prioridad ?? 0) || a.id - b.id,
76
+ );
77
+
78
+ export const rejectionPercentageAdjustmentRuleMatches = (
79
+ rule: RejectionPercentageAdjustmentRule,
80
+ ctx: RejectionPercentageAdjustmentContext,
81
+ ): boolean => {
82
+ const filtros = rule.filtros ?? defaultFilters();
83
+ const itemIds = ctx.itemIds ?? [];
84
+
85
+ const tipoAndPrograma =
86
+ matchesFilter(filtros.tipoMode, filtros.tipos, ctx.tipo) &&
87
+ matchesFilter(
88
+ filtros.programaMode,
89
+ filtros.providerCategoryIds,
90
+ ctx.providerCategoryId,
91
+ );
92
+
93
+ if (!tipoAndPrograma) {
94
+ return false;
95
+ }
96
+
97
+ if (filtros.itemMode === "all") {
98
+ return true;
99
+ }
100
+
101
+ if (itemIds.length === 0) {
102
+ return false;
103
+ }
104
+
105
+ return itemIds.some((itemId) =>
106
+ matchesFilter(filtros.itemMode, filtros.itemIds, itemId),
107
+ );
108
+ };
109
+
110
+ const roundPercentage = (value: number, digits = 4) => {
111
+ const factor = 10 ** digits;
112
+ return Math.round((value + Number.EPSILON) * factor) / factor;
113
+ };
114
+
115
+ const clampPercentage = (value: number) => Math.min(100, Math.max(0, value));
116
+
117
+ const formatPercentage = (value: number) =>
118
+ Number.isInteger(value) ? String(value) : String(roundPercentage(value));
119
+
120
+ const formatFinalPercentage = (value: number) =>
121
+ roundPercentage(value, 2).toFixed(2);
122
+
123
+ const formatRuleEffect = (step: RejectionPercentageAdjustmentStep) => {
124
+ const sign = step.operation === "subtract" ? "-" : "+";
125
+ return `${sign} ${formatPercentage(step.value)}% (${step.ruleName})`;
126
+ };
127
+
128
+ export const formatRejectionPercentageAdjustmentDescription = (
129
+ result: Pick<
130
+ RejectionPercentageAdjustmentResult,
131
+ "basePercentage" | "finalPercentage" | "steps"
132
+ >,
133
+ baseLabel = "origen",
134
+ ): string => {
135
+ const base = `${formatPercentage(result.basePercentage)} (${baseLabel})`;
136
+ if (result.steps.length === 0) {
137
+ return base;
138
+ }
139
+ return `${base} ${result.steps.map(formatRuleEffect).join(" ")} = ${formatFinalPercentage(result.finalPercentage)}`;
140
+ };
141
+
142
+ export const resolveRejectionPercentageOrigin = (
143
+ passedPercentage: number,
144
+ memo?: string | object,
145
+ ): number => {
146
+ const parsed = parseJsonMemo(memo);
147
+ const passed = clampPercentage(
148
+ Number.isFinite(Number(passedPercentage)) ? Number(passedPercentage) : 0,
149
+ );
150
+ const calc = parsed.rejectionCalculations;
151
+ if (!calc || !Number.isFinite(Number(calc.finalPercentage))) {
152
+ return passed;
153
+ }
154
+
155
+ const previousFinal = roundPercentage(Number(calc.finalPercentage), 2);
156
+ const previousBase = Number(calc.basePercentage);
157
+ const safePreviousBase = Number.isFinite(previousBase)
158
+ ? clampPercentage(previousBase)
159
+ : passed;
160
+
161
+ if (roundPercentage(passed, 2) === previousFinal) {
162
+ return safePreviousBase;
163
+ }
164
+
165
+ return clampPercentage(safePreviousBase + (passed - previousFinal));
166
+ };
167
+
168
+ export const resolveRejectionPercentageBase = (
169
+ memo?: string | object,
170
+ ): number =>
171
+ resolveRejectionPercentageOrigin(getRejectionPercentage(memo), memo);
172
+
173
+ export const applyRejectionPercentageAdjustmentRules = (
174
+ basePercentage: number,
175
+ rules: Array<
176
+ | RejectionPercentageAdjustmentRule
177
+ | WebAppRowGraphQL<RejectionPercentageAdjustmentRule>
178
+ >,
179
+ ctx: RejectionPercentageAdjustmentContext,
180
+ ): RejectionPercentageAdjustmentResult => {
181
+ const start = Number(basePercentage);
182
+ const safeBase = Number.isFinite(start) ? clampPercentage(start) : 0;
183
+ let runningTotal = safeBase;
184
+ const steps: RejectionPercentageAdjustmentStep[] = [];
185
+
186
+ const normalized = rules
187
+ .map((rule) => ("data" in rule ? rule.data : rule))
188
+ .filter((rule): rule is RejectionPercentageAdjustmentRule => Boolean(rule))
189
+ .filter((rule) => !rule.fechaEliminacion && rule.activa !== false)
190
+ .sort((a, b) => (a.prioridad ?? 0) - (b.prioridad ?? 0));
191
+
192
+ for (const rule of normalized) {
193
+ if (!rejectionPercentageAdjustmentRuleMatches(rule, ctx)) {
194
+ continue;
195
+ }
196
+
197
+ const value = Number(rule.valor) || 0;
198
+ const previous = runningTotal;
199
+ runningTotal =
200
+ rule.operacion === "subtract" ? previous - value : previous + value;
201
+ runningTotal = clampPercentage(roundPercentage(runningTotal));
202
+ steps.push({
203
+ ruleName: rule.nombre,
204
+ operation: rule.operacion,
205
+ value,
206
+ appliedAmount: roundPercentage(runningTotal - previous),
207
+ runningTotal,
208
+ });
209
+ }
210
+
211
+ const result: RejectionPercentageAdjustmentResult = {
212
+ basePercentage: roundPercentage(safeBase),
213
+ finalPercentage: roundPercentage(runningTotal, 2),
214
+ description: "",
215
+ steps,
216
+ };
217
+ result.description = formatRejectionPercentageAdjustmentDescription(result);
218
+ return result;
219
+ };
220
+
221
+ export const applyRejectionPercentageRulesToFinancials = (input: {
222
+ memo?: string | object;
223
+ originPercentage: number;
224
+ details: PurchaseOrderFinancialDetail[];
225
+ rules: Array<
226
+ | RejectionPercentageAdjustmentRule
227
+ | WebAppRowGraphQL<RejectionPercentageAdjustmentRule>
228
+ >;
229
+ ctx: RejectionPercentageAdjustmentContext;
230
+ }): { memo: string; discount: number; finalPercentage: number } => {
231
+ const origin = resolveRejectionPercentageOrigin(
232
+ input.originPercentage,
233
+ input.memo,
234
+ );
235
+ const result = applyRejectionPercentageAdjustmentRules(
236
+ origin,
237
+ input.rules,
238
+ input.ctx,
239
+ );
240
+ const { discount } = calculatePurchaseOrderFinancials({
241
+ details: input.details,
242
+ rejectionPercentage: result.finalPercentage,
243
+ });
244
+
245
+ return {
246
+ memo: mergeJsonMemo(input.memo, {
247
+ rejectionPercentage: result.finalPercentage,
248
+ rejectionCalculations: {
249
+ basePercentage: result.basePercentage,
250
+ finalPercentage: result.finalPercentage,
251
+ description: result.description,
252
+ steps: result.steps,
253
+ },
254
+ }),
255
+ discount,
256
+ finalPercentage: result.finalPercentage,
257
+ };
258
+ };