@zauru-sdk/common 6.6.0 → 6.8.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 +22 -0
- package/dist/common.d.ts +17 -7
- package/dist/esm/common.js +55 -12
- package/dist/esm/rejectionPercentageAdjustment.js +75 -1
- package/dist/rejectionPercentageAdjustment.d.ts +18 -1
- package/package.json +3 -3
- package/src/common.ts +80 -18
- package/src/rejectionPercentageAdjustment.ts +107 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.8.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.7.0...@zauru-sdk/common@6.8.0) (2026-09-18)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* descuento sucesivo en porcentajes de rechazo ([c437bbd](https://github.com/intuitiva/zauru-typescript-sdk/commit/c437bbd6a1305fd417c4ac55f8e07eb244f2fe3d))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [6.7.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.6.0...@zauru-sdk/common@6.7.0) (2026-09-18)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* registrar reglas automáticas de % rechazo en el historial ([4784849](https://github.com/intuitiva/zauru-typescript-sdk/commit/4784849904f9ac6570e60543895a858dece8e039))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# [6.6.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.5.0...@zauru-sdk/common@6.6.0) (2026-09-14)
|
|
7
29
|
|
|
8
30
|
|
package/dist/common.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "moment-timezone";
|
|
2
|
-
import type { PayeeGraphQL, AxiosUtilsResponse, BasketSchema, SelectFieldOption, JsonMemoType, CalculatePurchaseOrderFinancialsInput, CalculatePurchaseOrderFinancialsResult } from "@zauru-sdk/types";
|
|
2
|
+
import type { PayeeGraphQL, AxiosUtilsResponse, BasketSchema, SelectFieldOption, JsonMemoType, CalculatePurchaseOrderFinancialsInput, CalculatePurchaseOrderFinancialsResult, RejectionPercentageLayers } from "@zauru-sdk/types";
|
|
3
3
|
export declare const DESTINOS_MUESTRA_OPTIONS: SelectFieldOption[];
|
|
4
4
|
export declare const parseJsonMemo: (memo?: string | object) => JsonMemoType;
|
|
5
5
|
export declare const stringifyJsonMemo: (memo: JsonMemoType) => string;
|
|
@@ -12,20 +12,30 @@ export declare const getRejectionPercentage: (source: string | {
|
|
|
12
12
|
memo?: string | object;
|
|
13
13
|
} | JsonMemoType | undefined) => number;
|
|
14
14
|
export declare const setRejectionPercentage: (memo: string | object | undefined, percentage: number) => string;
|
|
15
|
+
/**
|
|
16
|
+
* User rejection layers (before automatic rules). Legacy memos without
|
|
17
|
+
* `rejectionLayers` use the stored origin as additive and no successive rates.
|
|
18
|
+
*/
|
|
19
|
+
export declare const resolveRejectionPercentageLayers: (memo?: string | object | JsonMemoType, fallbackAdditive?: number) => RejectionPercentageLayers;
|
|
20
|
+
export declare const getRejectionPercentageLayers: (source: string | {
|
|
21
|
+
memo?: string | object;
|
|
22
|
+
} | JsonMemoType | undefined) => RejectionPercentageLayers;
|
|
23
|
+
export declare const hasSuccessiveRejectionPercentage: (source: string | {
|
|
24
|
+
memo?: string | object;
|
|
25
|
+
} | JsonMemoType | undefined) => boolean;
|
|
15
26
|
/**
|
|
16
27
|
* Rejection % of origin stored in the memo, ignoring the adjustment rules
|
|
17
28
|
* already applied on top of it.
|
|
18
29
|
*/
|
|
19
30
|
export declare const resolveRejectionPercentageBase: (memo?: string | object) => number;
|
|
20
31
|
/**
|
|
21
|
-
* Header money for a purchase order: subtotal = qty × unit_cost
|
|
22
|
-
*
|
|
32
|
+
* Header money for a purchase order: subtotal = qty × unit_cost.
|
|
33
|
+
* Additive rates are summed first; successive rates then apply to the remainder.
|
|
23
34
|
* Does not change unit cost. purchase_orders.discount is the monetary column.
|
|
24
35
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `rejectionPercentage` in the result is the % that was
|
|
28
|
-
* `rejectionCalculations` is the trace to store in the memo.
|
|
36
|
+
* User layers (before rules) are returned as `rejectionLayers` for the memo.
|
|
37
|
+
* Automatic rules add percentage points on the additive block only.
|
|
38
|
+
* `rejectionPercentage` in the result is the effective % that was charged.
|
|
29
39
|
*/
|
|
30
40
|
export declare const calculatePurchaseOrderFinancials: (input: CalculatePurchaseOrderFinancialsInput) => CalculatePurchaseOrderFinancialsResult;
|
|
31
41
|
/**
|
package/dist/esm/common.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.parsedObject = exports.sortByProperty = exports.labFormPatter = exports.labServicePattern = exports.getRandomNum = exports.CURRENCY_PREFIX = exports.truncateDecimals = exports.ZAURU_REGEX = exports.priceToText = exports.arrayToObject = exports.isNumeric = exports.toFixedIfNeeded = exports.formatTimeToTimePicker = exports.formatDateToDatePicker = exports.getFormattedDate = exports.parsedBaculoFormValue = exports.getPayeeInfoOptions = exports.getPayeeInfoIdOptions = exports.getPayeeFormated = exports.getDateAfterDays = exports.getTimePickerCurrentTime = exports.obtenerFechaActualConZonaHoraria = exports.getDatePickerCurrentDate = exports.stringDateToParsedUTCDate = exports.localDateToUSDate = exports.getStringFullDate = exports.getTodayMinutesDifference = exports.getTodayDaysDifference = exports.getStringDate = exports.getZauruDateByText = exports.getNewDateByFormat = exports.getFechaJuliana = exports.getBasketsSchema = exports.calculatePurchaseOrderFinancials = exports.resolveRejectionPercentageBase = exports.setRejectionPercentage = exports.getRejectionPercentage = exports.mergeJsonMemo = exports.stringifyJsonMemo = exports.parseJsonMemo = exports.DESTINOS_MUESTRA_OPTIONS = void 0;
|
|
6
|
+
exports.parsedObject = exports.sortByProperty = exports.labFormPatter = exports.labServicePattern = exports.getRandomNum = exports.CURRENCY_PREFIX = exports.truncateDecimals = exports.ZAURU_REGEX = exports.priceToText = exports.arrayToObject = exports.isNumeric = exports.toFixedIfNeeded = exports.formatTimeToTimePicker = exports.formatDateToDatePicker = exports.getFormattedDate = exports.parsedBaculoFormValue = exports.getPayeeInfoOptions = exports.getPayeeInfoIdOptions = exports.getPayeeFormated = exports.getDateAfterDays = exports.getTimePickerCurrentTime = exports.obtenerFechaActualConZonaHoraria = exports.getDatePickerCurrentDate = exports.stringDateToParsedUTCDate = exports.localDateToUSDate = exports.getStringFullDate = exports.getTodayMinutesDifference = exports.getTodayDaysDifference = exports.getStringDate = exports.getZauruDateByText = exports.getNewDateByFormat = exports.getFechaJuliana = exports.getBasketsSchema = exports.calculatePurchaseOrderFinancials = exports.resolveRejectionPercentageBase = exports.hasSuccessiveRejectionPercentage = exports.getRejectionPercentageLayers = exports.resolveRejectionPercentageLayers = exports.setRejectionPercentage = exports.getRejectionPercentage = exports.mergeJsonMemo = exports.stringifyJsonMemo = exports.parseJsonMemo = exports.DESTINOS_MUESTRA_OPTIONS = void 0;
|
|
7
7
|
exports.generateClientUUID = generateClientUUID;
|
|
8
8
|
exports.extractValueBetweenTags = extractValueBetweenTags;
|
|
9
9
|
exports.isJsonArray = isJsonArray;
|
|
@@ -81,11 +81,44 @@ const getRejectionPercentage = (source) => {
|
|
|
81
81
|
exports.getRejectionPercentage = getRejectionPercentage;
|
|
82
82
|
const setRejectionPercentage = (memo, percentage) => (0, exports.mergeJsonMemo)(memo, { rejectionPercentage: percentage });
|
|
83
83
|
exports.setRejectionPercentage = setRejectionPercentage;
|
|
84
|
+
const memoFromRejectionSource = (source) => {
|
|
85
|
+
if (source == null || source === "")
|
|
86
|
+
return {};
|
|
87
|
+
if (typeof source === "string")
|
|
88
|
+
return (0, exports.parseJsonMemo)(source);
|
|
89
|
+
if (typeof source !== "object")
|
|
90
|
+
return {};
|
|
91
|
+
const record = source;
|
|
92
|
+
if (record.memo != null && record.memo !== "") {
|
|
93
|
+
return (0, exports.parseJsonMemo)(record.memo);
|
|
94
|
+
}
|
|
95
|
+
return (0, exports.parseJsonMemo)(record);
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* User rejection layers (before automatic rules). Legacy memos without
|
|
99
|
+
* `rejectionLayers` use the stored origin as additive and no successive rates.
|
|
100
|
+
*/
|
|
101
|
+
const resolveRejectionPercentageLayers = (memo, fallbackAdditive) => {
|
|
102
|
+
const parsed = (0, exports.parseJsonMemo)(memo);
|
|
103
|
+
if (parsed.rejectionLayers) {
|
|
104
|
+
return (0, rejectionPercentageAdjustment_js_1.normalizeRejectionPercentageLayers)(parsed.rejectionLayers);
|
|
105
|
+
}
|
|
106
|
+
const origin = (0, rejectionPercentageAdjustment_js_1.resolveRejectionPercentageOrigin)(fallbackAdditive ?? (0, exports.getRejectionPercentage)(parsed), parsed.rejectionCalculations);
|
|
107
|
+
return (0, rejectionPercentageAdjustment_js_1.normalizeRejectionPercentageLayers)({
|
|
108
|
+
additive: origin,
|
|
109
|
+
successive: [],
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
exports.resolveRejectionPercentageLayers = resolveRejectionPercentageLayers;
|
|
113
|
+
const getRejectionPercentageLayers = (source) => (0, exports.resolveRejectionPercentageLayers)(memoFromRejectionSource(source));
|
|
114
|
+
exports.getRejectionPercentageLayers = getRejectionPercentageLayers;
|
|
115
|
+
const hasSuccessiveRejectionPercentage = (source) => (0, rejectionPercentageAdjustment_js_1.hasSuccessiveRejectionLayers)((0, exports.getRejectionPercentageLayers)(source));
|
|
116
|
+
exports.hasSuccessiveRejectionPercentage = hasSuccessiveRejectionPercentage;
|
|
84
117
|
/**
|
|
85
118
|
* Rejection % of origin stored in the memo, ignoring the adjustment rules
|
|
86
119
|
* already applied on top of it.
|
|
87
120
|
*/
|
|
88
|
-
const resolveRejectionPercentageBase = (memo) => (0,
|
|
121
|
+
const resolveRejectionPercentageBase = (memo) => (0, exports.resolveRejectionPercentageLayers)(memo).additive;
|
|
89
122
|
exports.resolveRejectionPercentageBase = resolveRejectionPercentageBase;
|
|
90
123
|
const roundMoney = (value) => Math.round((value + Number.EPSILON) * 100) / 100;
|
|
91
124
|
const lineQuantity = (detail) => {
|
|
@@ -99,14 +132,13 @@ const lineQuantity = (detail) => {
|
|
|
99
132
|
0);
|
|
100
133
|
};
|
|
101
134
|
/**
|
|
102
|
-
* Header money for a purchase order: subtotal = qty × unit_cost
|
|
103
|
-
*
|
|
135
|
+
* Header money for a purchase order: subtotal = qty × unit_cost.
|
|
136
|
+
* Additive rates are summed first; successive rates then apply to the remainder.
|
|
104
137
|
* Does not change unit cost. purchase_orders.discount is the monetary column.
|
|
105
138
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* `rejectionPercentage` in the result is the % that was
|
|
109
|
-
* `rejectionCalculations` is the trace to store in the memo.
|
|
139
|
+
* User layers (before rules) are returned as `rejectionLayers` for the memo.
|
|
140
|
+
* Automatic rules add percentage points on the additive block only.
|
|
141
|
+
* `rejectionPercentage` in the result is the effective % that was charged.
|
|
110
142
|
*/
|
|
111
143
|
const calculatePurchaseOrderFinancials = (input) => {
|
|
112
144
|
const details = input.details ?? [];
|
|
@@ -115,17 +147,28 @@ const calculatePurchaseOrderFinancials = (input) => {
|
|
|
115
147
|
return sum + lineQuantity(detail) * unitCost;
|
|
116
148
|
}, 0));
|
|
117
149
|
const safeSubtotal = Number.isFinite(subtotal) ? subtotal : 0;
|
|
118
|
-
const
|
|
150
|
+
const parsedMemo = (0, exports.parseJsonMemo)(input.memo);
|
|
151
|
+
let userLayers = input.rejectionLayers
|
|
152
|
+
? (0, rejectionPercentageAdjustment_js_1.normalizeRejectionPercentageLayers)(input.rejectionLayers)
|
|
153
|
+
: (0, exports.resolveRejectionPercentageLayers)(parsedMemo, toFiniteNumber(input.rejectionPercentage));
|
|
154
|
+
if (input.rejectionApplication) {
|
|
155
|
+
userLayers = (0, rejectionPercentageAdjustment_js_1.applyRejectionPercentageLayers)(userLayers, input.rejectionApplication);
|
|
156
|
+
}
|
|
119
157
|
const rejectionCalculations = input.rules
|
|
120
|
-
? (0, rejectionPercentageAdjustment_js_1.applyRejectionPercentageAdjustmentRules)(
|
|
158
|
+
? (0, rejectionPercentageAdjustment_js_1.applyRejectionPercentageAdjustmentRules)(userLayers.additive, input.rules, input.ctx ?? { itemIds: [] })
|
|
121
159
|
: undefined;
|
|
122
|
-
const
|
|
160
|
+
const chargedLayers = {
|
|
161
|
+
additive: rejectionCalculations?.finalPercentage ?? userLayers.additive,
|
|
162
|
+
successive: userLayers.successive,
|
|
163
|
+
};
|
|
164
|
+
const rejectionPercentage = (0, rejectionPercentageAdjustment_js_1.computeEffectiveRejectionPercentage)(chargedLayers);
|
|
123
165
|
return {
|
|
124
166
|
subtotal: safeSubtotal,
|
|
125
|
-
discount: rejectionPercentage
|
|
167
|
+
discount: rejectionPercentage !== 0 && safeSubtotal > 0
|
|
126
168
|
? roundMoney(safeSubtotal * (rejectionPercentage / 100))
|
|
127
169
|
: 0,
|
|
128
170
|
rejectionPercentage,
|
|
171
|
+
rejectionLayers: userLayers,
|
|
129
172
|
rejectionCalculations,
|
|
130
173
|
};
|
|
131
174
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.applyRejectionPercentageAdjustmentRules = exports.resolveRejectionPercentageOrigin = exports.formatRejectionPercentageAdjustmentDescription = exports.rejectionPercentageAdjustmentRuleMatches = exports.filterActiveRejectionPercentageAdjustmentRules = exports.REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR = void 0;
|
|
3
|
+
exports.applyRejectionPercentageAdjustmentRules = exports.isAutomaticRejectionRuleHistoryType = exports.formatAutomaticRejectionRuleHistoryDescription = exports.getNewlyAppliedRejectionRuleSteps = exports.rejectionRuleStepKey = exports.resolveRejectionPercentageOrigin = exports.formatRejectionPercentageAdjustmentDescription = exports.hasSuccessiveRejectionLayers = exports.computeEffectiveRejectionPercentage = exports.applyRejectionPercentageLayers = exports.normalizeRejectionPercentageLayers = exports.rejectionPercentageAdjustmentRuleMatches = exports.filterActiveRejectionPercentageAdjustmentRules = exports.REJECTION_PERCENTAGE_RULE_HISTORY_TYPE = exports.REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR = void 0;
|
|
4
4
|
exports.REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR = "rejection_percentage_adjustment_rules_web_app_table_id";
|
|
5
|
+
exports.REJECTION_PERCENTAGE_RULE_HISTORY_TYPE = "rechazo_regla_automatica";
|
|
5
6
|
const defaultFilters = () => ({
|
|
6
7
|
itemMode: "all",
|
|
7
8
|
itemIds: [],
|
|
@@ -56,6 +57,58 @@ const roundPercentage = (value, digits = 4) => {
|
|
|
56
57
|
return Math.round((value + Number.EPSILON) * factor) / factor;
|
|
57
58
|
};
|
|
58
59
|
const clampPercentage = (value) => Math.min(100, Math.max(0, value));
|
|
60
|
+
const toLayerNumber = (value) => {
|
|
61
|
+
const parsed = Number(value);
|
|
62
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
63
|
+
};
|
|
64
|
+
const normalizeRejectionPercentageLayers = (layers) => ({
|
|
65
|
+
additive: clampPercentage(roundPercentage(toLayerNumber(layers?.additive))),
|
|
66
|
+
successive: Array.isArray(layers?.successive)
|
|
67
|
+
? layers.successive.map((value) => toLayerNumber(value))
|
|
68
|
+
: [],
|
|
69
|
+
});
|
|
70
|
+
exports.normalizeRejectionPercentageLayers = normalizeRejectionPercentageLayers;
|
|
71
|
+
const applyRejectionPercentageLayers = (layers, application) => {
|
|
72
|
+
const current = (0, exports.normalizeRejectionPercentageLayers)(layers);
|
|
73
|
+
const value = toLayerNumber(application.value);
|
|
74
|
+
switch (application.mode) {
|
|
75
|
+
case "add":
|
|
76
|
+
return {
|
|
77
|
+
additive: clampPercentage(roundPercentage(current.additive + value)),
|
|
78
|
+
successive: [...current.successive],
|
|
79
|
+
};
|
|
80
|
+
case "replace":
|
|
81
|
+
return {
|
|
82
|
+
additive: clampPercentage(roundPercentage(value)),
|
|
83
|
+
successive: [],
|
|
84
|
+
};
|
|
85
|
+
case "successive":
|
|
86
|
+
return {
|
|
87
|
+
additive: current.additive,
|
|
88
|
+
successive: [...current.successive, value],
|
|
89
|
+
};
|
|
90
|
+
default: {
|
|
91
|
+
const _exhaustive = application.mode;
|
|
92
|
+
return _exhaustive;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
exports.applyRejectionPercentageLayers = applyRejectionPercentageLayers;
|
|
97
|
+
/**
|
|
98
|
+
* Effective rejection % after additive then each successive rate on the remainder.
|
|
99
|
+
* 10% then 10% successive → 19, not 20.
|
|
100
|
+
*/
|
|
101
|
+
const computeEffectiveRejectionPercentage = (layers, digits = 2) => {
|
|
102
|
+
const normalized = (0, exports.normalizeRejectionPercentageLayers)(layers);
|
|
103
|
+
let remainingFactor = 1 - normalized.additive / 100;
|
|
104
|
+
for (const rate of normalized.successive) {
|
|
105
|
+
remainingFactor *= 1 - rate / 100;
|
|
106
|
+
}
|
|
107
|
+
return roundPercentage((1 - remainingFactor) * 100, digits);
|
|
108
|
+
};
|
|
109
|
+
exports.computeEffectiveRejectionPercentage = computeEffectiveRejectionPercentage;
|
|
110
|
+
const hasSuccessiveRejectionLayers = (layers) => Array.isArray(layers?.successive) && layers.successive.length > 0;
|
|
111
|
+
exports.hasSuccessiveRejectionLayers = hasSuccessiveRejectionLayers;
|
|
59
112
|
const formatPercentage = (value) => Number.isInteger(value) ? String(value) : String(roundPercentage(value));
|
|
60
113
|
const formatFinalPercentage = (value) => roundPercentage(value, 2).toFixed(2);
|
|
61
114
|
const formatRuleEffect = (step) => {
|
|
@@ -91,6 +144,27 @@ const resolveRejectionPercentageOrigin = (passedPercentage, previousCalculations
|
|
|
91
144
|
return clampPercentage(safePreviousBase + (passed - previousFinal));
|
|
92
145
|
};
|
|
93
146
|
exports.resolveRejectionPercentageOrigin = resolveRejectionPercentageOrigin;
|
|
147
|
+
const rejectionRuleStepKey = (step) => `${step.ruleName}|${step.operation}|${step.value}`;
|
|
148
|
+
exports.rejectionRuleStepKey = rejectionRuleStepKey;
|
|
149
|
+
/**
|
|
150
|
+
* Rules already stored in the memo are skipped so a later edit does not
|
|
151
|
+
* create a second history line (or a second +2%) for the same rule.
|
|
152
|
+
*/
|
|
153
|
+
const getNewlyAppliedRejectionRuleSteps = (previous, next) => {
|
|
154
|
+
if (!next?.steps?.length) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
const previousKeys = new Set((previous?.steps ?? []).map(exports.rejectionRuleStepKey));
|
|
158
|
+
return next.steps.filter((step) => !previousKeys.has((0, exports.rejectionRuleStepKey)(step)));
|
|
159
|
+
};
|
|
160
|
+
exports.getNewlyAppliedRejectionRuleSteps = getNewlyAppliedRejectionRuleSteps;
|
|
161
|
+
const formatAutomaticRejectionRuleHistoryDescription = (step) => {
|
|
162
|
+
const sign = step.operation === "subtract" ? "-" : "+";
|
|
163
|
+
return `% Rechazo aplicado automáticamente por regla "${step.ruleName}": ${sign}${formatPercentage(step.value)}%.`;
|
|
164
|
+
};
|
|
165
|
+
exports.formatAutomaticRejectionRuleHistoryDescription = formatAutomaticRejectionRuleHistoryDescription;
|
|
166
|
+
const isAutomaticRejectionRuleHistoryType = (type) => type === exports.REJECTION_PERCENTAGE_RULE_HISTORY_TYPE;
|
|
167
|
+
exports.isAutomaticRejectionRuleHistoryType = isAutomaticRejectionRuleHistoryType;
|
|
94
168
|
const applyRejectionPercentageAdjustmentRules = (basePercentage, rules, ctx) => {
|
|
95
169
|
const start = Number(basePercentage);
|
|
96
170
|
const safeBase = Number.isFinite(start) ? clampPercentage(start) : 0;
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import type { RejectionCalculationMemo, RejectionPercentageAdjustmentContext, RejectionPercentageAdjustmentResult, RejectionPercentageAdjustmentRule, WebAppRowGraphQL } from "@zauru-sdk/types";
|
|
1
|
+
import type { RejectionCalculationMemo, RejectionPercentageAdjustmentContext, RejectionPercentageAdjustmentResult, RejectionPercentageAdjustmentRule, RejectionPercentageAdjustmentStep, RejectionPercentageApplication, RejectionPercentageLayers, WebAppRowGraphQL } from "@zauru-sdk/types";
|
|
2
2
|
export declare const REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR = "rejection_percentage_adjustment_rules_web_app_table_id";
|
|
3
|
+
export declare const REJECTION_PERCENTAGE_RULE_HISTORY_TYPE: "rechazo_regla_automatica";
|
|
3
4
|
export declare const filterActiveRejectionPercentageAdjustmentRules: (rules?: WebAppRowGraphQL<RejectionPercentageAdjustmentRule>[]) => WebAppRowGraphQL<RejectionPercentageAdjustmentRule>[];
|
|
4
5
|
export declare const rejectionPercentageAdjustmentRuleMatches: (rule: RejectionPercentageAdjustmentRule, ctx: RejectionPercentageAdjustmentContext) => boolean;
|
|
6
|
+
export declare const normalizeRejectionPercentageLayers: (layers?: Partial<RejectionPercentageLayers> | null) => RejectionPercentageLayers;
|
|
7
|
+
export declare const applyRejectionPercentageLayers: (layers: RejectionPercentageLayers, application: RejectionPercentageApplication) => RejectionPercentageLayers;
|
|
8
|
+
/**
|
|
9
|
+
* Effective rejection % after additive then each successive rate on the remainder.
|
|
10
|
+
* 10% then 10% successive → 19, not 20.
|
|
11
|
+
*/
|
|
12
|
+
export declare const computeEffectiveRejectionPercentage: (layers: RejectionPercentageLayers, digits?: number) => number;
|
|
13
|
+
export declare const hasSuccessiveRejectionLayers: (layers?: Partial<RejectionPercentageLayers> | null) => boolean;
|
|
5
14
|
export declare const formatRejectionPercentageAdjustmentDescription: (result: Pick<RejectionPercentageAdjustmentResult, "basePercentage" | "finalPercentage" | "steps">, baseLabel?: string) => string;
|
|
6
15
|
/**
|
|
7
16
|
* Callers may pass the origin % or the previously stored final %. With the
|
|
8
17
|
* previous calculations we go back to the origin so the rules never stack twice.
|
|
9
18
|
*/
|
|
10
19
|
export declare const resolveRejectionPercentageOrigin: (passedPercentage: number, previousCalculations?: RejectionCalculationMemo | null) => number;
|
|
20
|
+
export declare const rejectionRuleStepKey: (step: Pick<RejectionPercentageAdjustmentStep, "ruleName" | "operation" | "value">) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Rules already stored in the memo are skipped so a later edit does not
|
|
23
|
+
* create a second history line (or a second +2%) for the same rule.
|
|
24
|
+
*/
|
|
25
|
+
export declare const getNewlyAppliedRejectionRuleSteps: (previous?: RejectionCalculationMemo | null, next?: RejectionCalculationMemo | null) => RejectionPercentageAdjustmentStep[];
|
|
26
|
+
export declare const formatAutomaticRejectionRuleHistoryDescription: (step: RejectionPercentageAdjustmentStep) => string;
|
|
27
|
+
export declare const isAutomaticRejectionRuleHistoryType: (type: string | undefined) => boolean;
|
|
11
28
|
export declare const applyRejectionPercentageAdjustmentRules: (basePercentage: number, rules: Array<RejectionPercentageAdjustmentRule | WebAppRowGraphQL<RejectionPercentageAdjustmentRule>>, ctx: RejectionPercentageAdjustmentContext) => RejectionPercentageAdjustmentResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauru-sdk/common",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.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",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@remix-run/node": "^2.8.1",
|
|
24
|
-
"@zauru-sdk/types": "^6.
|
|
24
|
+
"@zauru-sdk/types": "^6.18.0",
|
|
25
25
|
"moment": "^2.30.1",
|
|
26
26
|
"moment-timezone": "^0.5.45"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "c5b785baa29fdd2ab7f42a9077ba5cbe5c7c9707"
|
|
29
29
|
}
|
package/src/common.ts
CHANGED
|
@@ -10,10 +10,15 @@ import type {
|
|
|
10
10
|
CalculatePurchaseOrderFinancialsInput,
|
|
11
11
|
CalculatePurchaseOrderFinancialsResult,
|
|
12
12
|
PurchaseOrderFinancialDetail,
|
|
13
|
+
RejectionPercentageLayers,
|
|
13
14
|
} from "@zauru-sdk/types";
|
|
14
15
|
import { MONTHS } from "@zauru-sdk/types";
|
|
15
16
|
import {
|
|
16
17
|
applyRejectionPercentageAdjustmentRules,
|
|
18
|
+
applyRejectionPercentageLayers,
|
|
19
|
+
computeEffectiveRejectionPercentage,
|
|
20
|
+
hasSuccessiveRejectionLayers,
|
|
21
|
+
normalizeRejectionPercentageLayers,
|
|
17
22
|
resolveRejectionPercentageOrigin,
|
|
18
23
|
} from "./rejectionPercentageAdjustment.js";
|
|
19
24
|
|
|
@@ -89,17 +94,58 @@ export const setRejectionPercentage = (
|
|
|
89
94
|
percentage: number,
|
|
90
95
|
): string => mergeJsonMemo(memo, { rejectionPercentage: percentage });
|
|
91
96
|
|
|
97
|
+
const memoFromRejectionSource = (
|
|
98
|
+
source: string | { memo?: string | object } | JsonMemoType | undefined,
|
|
99
|
+
): JsonMemoType => {
|
|
100
|
+
if (source == null || source === "") return {};
|
|
101
|
+
if (typeof source === "string") return parseJsonMemo(source);
|
|
102
|
+
if (typeof source !== "object") return {};
|
|
103
|
+
const record = source as { memo?: string | object } & JsonMemoType;
|
|
104
|
+
if (record.memo != null && record.memo !== "") {
|
|
105
|
+
return parseJsonMemo(record.memo);
|
|
106
|
+
}
|
|
107
|
+
return parseJsonMemo(record);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* User rejection layers (before automatic rules). Legacy memos without
|
|
112
|
+
* `rejectionLayers` use the stored origin as additive and no successive rates.
|
|
113
|
+
*/
|
|
114
|
+
export const resolveRejectionPercentageLayers = (
|
|
115
|
+
memo?: string | object | JsonMemoType,
|
|
116
|
+
fallbackAdditive?: number,
|
|
117
|
+
): RejectionPercentageLayers => {
|
|
118
|
+
const parsed = parseJsonMemo(memo);
|
|
119
|
+
if (parsed.rejectionLayers) {
|
|
120
|
+
return normalizeRejectionPercentageLayers(parsed.rejectionLayers);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const origin = resolveRejectionPercentageOrigin(
|
|
124
|
+
fallbackAdditive ?? getRejectionPercentage(parsed),
|
|
125
|
+
parsed.rejectionCalculations,
|
|
126
|
+
);
|
|
127
|
+
return normalizeRejectionPercentageLayers({
|
|
128
|
+
additive: origin,
|
|
129
|
+
successive: [],
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const getRejectionPercentageLayers = (
|
|
134
|
+
source: string | { memo?: string | object } | JsonMemoType | undefined,
|
|
135
|
+
): RejectionPercentageLayers =>
|
|
136
|
+
resolveRejectionPercentageLayers(memoFromRejectionSource(source));
|
|
137
|
+
|
|
138
|
+
export const hasSuccessiveRejectionPercentage = (
|
|
139
|
+
source: string | { memo?: string | object } | JsonMemoType | undefined,
|
|
140
|
+
): boolean => hasSuccessiveRejectionLayers(getRejectionPercentageLayers(source));
|
|
141
|
+
|
|
92
142
|
/**
|
|
93
143
|
* Rejection % of origin stored in the memo, ignoring the adjustment rules
|
|
94
144
|
* already applied on top of it.
|
|
95
145
|
*/
|
|
96
146
|
export const resolveRejectionPercentageBase = (
|
|
97
147
|
memo?: string | object,
|
|
98
|
-
): number =>
|
|
99
|
-
resolveRejectionPercentageOrigin(
|
|
100
|
-
getRejectionPercentage(memo),
|
|
101
|
-
parseJsonMemo(memo).rejectionCalculations,
|
|
102
|
-
);
|
|
148
|
+
): number => resolveRejectionPercentageLayers(memo).additive;
|
|
103
149
|
|
|
104
150
|
const roundMoney = (value: number): number =>
|
|
105
151
|
Math.round((value + Number.EPSILON) * 100) / 100;
|
|
@@ -119,14 +165,13 @@ const lineQuantity = (detail: PurchaseOrderFinancialDetail): number => {
|
|
|
119
165
|
};
|
|
120
166
|
|
|
121
167
|
/**
|
|
122
|
-
* Header money for a purchase order: subtotal = qty × unit_cost
|
|
123
|
-
*
|
|
168
|
+
* Header money for a purchase order: subtotal = qty × unit_cost.
|
|
169
|
+
* Additive rates are summed first; successive rates then apply to the remainder.
|
|
124
170
|
* Does not change unit cost. purchase_orders.discount is the monetary column.
|
|
125
171
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* `rejectionPercentage` in the result is the % that was
|
|
129
|
-
* `rejectionCalculations` is the trace to store in the memo.
|
|
172
|
+
* User layers (before rules) are returned as `rejectionLayers` for the memo.
|
|
173
|
+
* Automatic rules add percentage points on the additive block only.
|
|
174
|
+
* `rejectionPercentage` in the result is the effective % that was charged.
|
|
130
175
|
*/
|
|
131
176
|
export const calculatePurchaseOrderFinancials = (
|
|
132
177
|
input: CalculatePurchaseOrderFinancialsInput,
|
|
@@ -140,28 +185,45 @@ export const calculatePurchaseOrderFinancials = (
|
|
|
140
185
|
);
|
|
141
186
|
const safeSubtotal = Number.isFinite(subtotal) ? subtotal : 0;
|
|
142
187
|
|
|
143
|
-
const
|
|
188
|
+
const parsedMemo = parseJsonMemo(input.memo);
|
|
189
|
+
let userLayers = input.rejectionLayers
|
|
190
|
+
? normalizeRejectionPercentageLayers(input.rejectionLayers)
|
|
191
|
+
: resolveRejectionPercentageLayers(
|
|
192
|
+
parsedMemo,
|
|
193
|
+
toFiniteNumber(input.rejectionPercentage),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (input.rejectionApplication) {
|
|
197
|
+
userLayers = applyRejectionPercentageLayers(
|
|
198
|
+
userLayers,
|
|
199
|
+
input.rejectionApplication,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
144
203
|
const rejectionCalculations = input.rules
|
|
145
204
|
? applyRejectionPercentageAdjustmentRules(
|
|
146
|
-
|
|
147
|
-
passedPercentage,
|
|
148
|
-
parseJsonMemo(input.memo).rejectionCalculations,
|
|
149
|
-
),
|
|
205
|
+
userLayers.additive,
|
|
150
206
|
input.rules,
|
|
151
207
|
input.ctx ?? { itemIds: [] },
|
|
152
208
|
)
|
|
153
209
|
: undefined;
|
|
154
210
|
|
|
211
|
+
const chargedLayers = {
|
|
212
|
+
additive:
|
|
213
|
+
rejectionCalculations?.finalPercentage ?? userLayers.additive,
|
|
214
|
+
successive: userLayers.successive,
|
|
215
|
+
};
|
|
155
216
|
const rejectionPercentage =
|
|
156
|
-
|
|
217
|
+
computeEffectiveRejectionPercentage(chargedLayers);
|
|
157
218
|
|
|
158
219
|
return {
|
|
159
220
|
subtotal: safeSubtotal,
|
|
160
221
|
discount:
|
|
161
|
-
rejectionPercentage
|
|
222
|
+
rejectionPercentage !== 0 && safeSubtotal > 0
|
|
162
223
|
? roundMoney(safeSubtotal * (rejectionPercentage / 100))
|
|
163
224
|
: 0,
|
|
164
225
|
rejectionPercentage,
|
|
226
|
+
rejectionLayers: userLayers,
|
|
165
227
|
rejectionCalculations,
|
|
166
228
|
};
|
|
167
229
|
};
|
|
@@ -6,12 +6,17 @@ import type {
|
|
|
6
6
|
RejectionPercentageAdjustmentResult,
|
|
7
7
|
RejectionPercentageAdjustmentRule,
|
|
8
8
|
RejectionPercentageAdjustmentStep,
|
|
9
|
+
RejectionPercentageApplication,
|
|
10
|
+
RejectionPercentageLayers,
|
|
9
11
|
WebAppRowGraphQL,
|
|
10
12
|
} from "@zauru-sdk/types";
|
|
11
13
|
|
|
12
14
|
export const REJECTION_PERCENTAGE_ADJUSTMENT_RULES_TABLE_VAR =
|
|
13
15
|
"rejection_percentage_adjustment_rules_web_app_table_id";
|
|
14
16
|
|
|
17
|
+
export const REJECTION_PERCENTAGE_RULE_HISTORY_TYPE =
|
|
18
|
+
"rechazo_regla_automatica" as const;
|
|
19
|
+
|
|
15
20
|
const defaultFilters = (): PriceAdjustmentFilters => ({
|
|
16
21
|
itemMode: "all",
|
|
17
22
|
itemIds: [],
|
|
@@ -108,6 +113,71 @@ const roundPercentage = (value: number, digits = 4) => {
|
|
|
108
113
|
|
|
109
114
|
const clampPercentage = (value: number) => Math.min(100, Math.max(0, value));
|
|
110
115
|
|
|
116
|
+
const toLayerNumber = (value: unknown): number => {
|
|
117
|
+
const parsed = Number(value);
|
|
118
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const normalizeRejectionPercentageLayers = (
|
|
122
|
+
layers?: Partial<RejectionPercentageLayers> | null,
|
|
123
|
+
): RejectionPercentageLayers => ({
|
|
124
|
+
additive: clampPercentage(roundPercentage(toLayerNumber(layers?.additive))),
|
|
125
|
+
successive: Array.isArray(layers?.successive)
|
|
126
|
+
? layers.successive.map((value) => toLayerNumber(value))
|
|
127
|
+
: [],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export const applyRejectionPercentageLayers = (
|
|
131
|
+
layers: RejectionPercentageLayers,
|
|
132
|
+
application: RejectionPercentageApplication,
|
|
133
|
+
): RejectionPercentageLayers => {
|
|
134
|
+
const current = normalizeRejectionPercentageLayers(layers);
|
|
135
|
+
const value = toLayerNumber(application.value);
|
|
136
|
+
|
|
137
|
+
switch (application.mode) {
|
|
138
|
+
case "add":
|
|
139
|
+
return {
|
|
140
|
+
additive: clampPercentage(roundPercentage(current.additive + value)),
|
|
141
|
+
successive: [...current.successive],
|
|
142
|
+
};
|
|
143
|
+
case "replace":
|
|
144
|
+
return {
|
|
145
|
+
additive: clampPercentage(roundPercentage(value)),
|
|
146
|
+
successive: [],
|
|
147
|
+
};
|
|
148
|
+
case "successive":
|
|
149
|
+
return {
|
|
150
|
+
additive: current.additive,
|
|
151
|
+
successive: [...current.successive, value],
|
|
152
|
+
};
|
|
153
|
+
default: {
|
|
154
|
+
const _exhaustive: never = application.mode;
|
|
155
|
+
return _exhaustive;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Effective rejection % after additive then each successive rate on the remainder.
|
|
162
|
+
* 10% then 10% successive → 19, not 20.
|
|
163
|
+
*/
|
|
164
|
+
export const computeEffectiveRejectionPercentage = (
|
|
165
|
+
layers: RejectionPercentageLayers,
|
|
166
|
+
digits = 2,
|
|
167
|
+
): number => {
|
|
168
|
+
const normalized = normalizeRejectionPercentageLayers(layers);
|
|
169
|
+
let remainingFactor = 1 - normalized.additive / 100;
|
|
170
|
+
for (const rate of normalized.successive) {
|
|
171
|
+
remainingFactor *= 1 - rate / 100;
|
|
172
|
+
}
|
|
173
|
+
return roundPercentage((1 - remainingFactor) * 100, digits);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const hasSuccessiveRejectionLayers = (
|
|
177
|
+
layers?: Partial<RejectionPercentageLayers> | null,
|
|
178
|
+
): boolean =>
|
|
179
|
+
Array.isArray(layers?.successive) && layers.successive.length > 0;
|
|
180
|
+
|
|
111
181
|
const formatPercentage = (value: number) =>
|
|
112
182
|
Number.isInteger(value) ? String(value) : String(roundPercentage(value));
|
|
113
183
|
|
|
@@ -162,6 +232,43 @@ export const resolveRejectionPercentageOrigin = (
|
|
|
162
232
|
return clampPercentage(safePreviousBase + (passed - previousFinal));
|
|
163
233
|
};
|
|
164
234
|
|
|
235
|
+
export const rejectionRuleStepKey = (
|
|
236
|
+
step: Pick<
|
|
237
|
+
RejectionPercentageAdjustmentStep,
|
|
238
|
+
"ruleName" | "operation" | "value"
|
|
239
|
+
>,
|
|
240
|
+
): string => `${step.ruleName}|${step.operation}|${step.value}`;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Rules already stored in the memo are skipped so a later edit does not
|
|
244
|
+
* create a second history line (or a second +2%) for the same rule.
|
|
245
|
+
*/
|
|
246
|
+
export const getNewlyAppliedRejectionRuleSteps = (
|
|
247
|
+
previous?: RejectionCalculationMemo | null,
|
|
248
|
+
next?: RejectionCalculationMemo | null,
|
|
249
|
+
): RejectionPercentageAdjustmentStep[] => {
|
|
250
|
+
if (!next?.steps?.length) {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
const previousKeys = new Set(
|
|
254
|
+
(previous?.steps ?? []).map(rejectionRuleStepKey),
|
|
255
|
+
);
|
|
256
|
+
return next.steps.filter(
|
|
257
|
+
(step) => !previousKeys.has(rejectionRuleStepKey(step)),
|
|
258
|
+
);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export const formatAutomaticRejectionRuleHistoryDescription = (
|
|
262
|
+
step: RejectionPercentageAdjustmentStep,
|
|
263
|
+
): string => {
|
|
264
|
+
const sign = step.operation === "subtract" ? "-" : "+";
|
|
265
|
+
return `% Rechazo aplicado automáticamente por regla "${step.ruleName}": ${sign}${formatPercentage(step.value)}%.`;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const isAutomaticRejectionRuleHistoryType = (
|
|
269
|
+
type: string | undefined,
|
|
270
|
+
): boolean => type === REJECTION_PERCENTAGE_RULE_HISTORY_TYPE;
|
|
271
|
+
|
|
165
272
|
export const applyRejectionPercentageAdjustmentRules = (
|
|
166
273
|
basePercentage: number,
|
|
167
274
|
rules: Array<
|