@zauru-sdk/utils 6.4.3 → 6.6.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 CHANGED
@@ -18,6 +18,8 @@ export * from "./zauru/providers.utils.js";
18
18
  export * from "./zauru/purchase-orders.utils.js";
19
19
  export * from "./zauru/shipments.utils.js";
20
20
  export * from "./zauru/specialItem.utils.js";
21
+ export * from "./zauru/priceAdjustmentRules.utils.js";
22
+ export * from "./zauru/weeklyItemPrice.utils.js";
21
23
  export * from "./zauru/templates.utils.js";
22
24
  export * from "./zauru/tiposMuestra.utils.js";
23
25
  export * from "./zauru/webapp-tables.utils.js";
@@ -0,0 +1,132 @@
1
+ import { handlePossibleAxiosErrors } from "@zauru-sdk/common";
2
+ import { createWebAppTableRegister, getVariablesByName, getWebAppTableRegisters, updateWebAppTableRegister, } from "@zauru-sdk/services";
3
+ const TABLE_VAR = "price_adjustment_rules_web_app_table_id";
4
+ const DEFAULT_FILTERS = {
5
+ itemMode: "all",
6
+ itemIds: [],
7
+ tipoMode: "all",
8
+ tipos: [],
9
+ programaMode: "all",
10
+ providerCategoryIds: [],
11
+ };
12
+ export const defaultPriceAdjustmentFilters = () => ({
13
+ ...DEFAULT_FILTERS,
14
+ itemIds: [],
15
+ tipos: [],
16
+ providerCategoryIds: [],
17
+ });
18
+ export const filterActivePriceAdjustmentRules = (rules) => (rules ?? [])
19
+ .filter((rule) => !rule.data?.fechaEliminacion && rule.data?.activa !== false)
20
+ .sort((a, b) => (a.data?.prioridad ?? 0) - (b.data?.prioridad ?? 0) || a.id - b.id);
21
+ const getTableId = async (headers, session) => {
22
+ const vars = await getVariablesByName(headers, session, [TABLE_VAR]);
23
+ return vars[TABLE_VAR];
24
+ };
25
+ export const getPriceAdjustmentRules = (headers, session) => {
26
+ return handlePossibleAxiosErrors(async () => {
27
+ const tableId = await getTableId(headers, session);
28
+ const response = await getWebAppTableRegisters(session, tableId);
29
+ if (response.error) {
30
+ throw new Error(`Ocurrió un error al consultar las reglas de ajuste de precio: ${response.userMsg}`);
31
+ }
32
+ return response.data ?? [];
33
+ });
34
+ };
35
+ export const createPriceAdjustmentRule = (headers, session, body) => {
36
+ return handlePossibleAxiosErrors(async () => {
37
+ const tableId = await getTableId(headers, session);
38
+ return createWebAppTableRegister(headers, tableId, body);
39
+ });
40
+ };
41
+ export const updatePriceAdjustmentRule = (headers, session, id, body) => {
42
+ return handlePossibleAxiosErrors(async () => {
43
+ const tableId = await getTableId(headers, session);
44
+ return updateWebAppTableRegister(headers, tableId, Number(id), body);
45
+ });
46
+ };
47
+ const matchesFilter = (mode, selected, value) => {
48
+ const normalizedMode = mode ?? "all";
49
+ const values = selected ?? [];
50
+ if (normalizedMode === "all") {
51
+ return true;
52
+ }
53
+ if (value === undefined || value === null || value === "") {
54
+ return false;
55
+ }
56
+ const asString = String(value);
57
+ const included = values.some((item) => String(item) === asString);
58
+ if (normalizedMode === "include") {
59
+ return values.length > 0 && included;
60
+ }
61
+ return !included;
62
+ };
63
+ export const priceAdjustmentRuleMatches = (rule, ctx) => {
64
+ const filtros = rule.filtros ?? defaultPriceAdjustmentFilters();
65
+ return (matchesFilter(filtros.itemMode, filtros.itemIds, ctx.itemId) &&
66
+ matchesFilter(filtros.tipoMode, filtros.tipos, ctx.tipo) &&
67
+ matchesFilter(filtros.programaMode, filtros.providerCategoryIds, ctx.providerCategoryId));
68
+ };
69
+ const roundMoney = (value) => Math.round((value + Number.EPSILON) * 10000) / 10000;
70
+ const formatMoney = (value) => Number.isInteger(value) ? String(value) : String(roundMoney(value));
71
+ const formatSignedAmount = (value) => {
72
+ const abs = formatMoney(Math.abs(value));
73
+ return value < 0 ? `- ${abs}` : `+ ${abs}`;
74
+ };
75
+ const formatRuleEffect = (step) => {
76
+ if (step.valueType === "percentage") {
77
+ const sign = step.operation === "subtract" ? "-" : "+";
78
+ return `${sign} ${formatMoney(step.value)}% (${step.ruleName})`;
79
+ }
80
+ return `${formatSignedAmount(step.appliedAmount)} (${step.ruleName})`;
81
+ };
82
+ export const formatPriceAdjustmentDescription = (result, baseLabel = "base") => {
83
+ const base = `${formatMoney(result.basePrice)} (${baseLabel})`;
84
+ if (result.steps.length === 0) {
85
+ return base;
86
+ }
87
+ return `${base} ${result.steps.map(formatRuleEffect).join(" ")} = ${formatMoney(result.finalPrice)}`;
88
+ };
89
+ export const applyPriceAdjustmentRules = (basePrice, rules, ctx) => {
90
+ const start = Number(basePrice);
91
+ const safeBase = Number.isFinite(start) ? start : 0;
92
+ let runningTotal = safeBase;
93
+ const steps = [];
94
+ const normalized = rules
95
+ .map((rule) => ("data" in rule ? rule.data : rule))
96
+ .filter((rule) => Boolean(rule))
97
+ .filter((rule) => !rule.fechaEliminacion && rule.activa !== false)
98
+ .sort((a, b) => (a.prioridad ?? 0) - (b.prioridad ?? 0));
99
+ for (const rule of normalized) {
100
+ if (!priceAdjustmentRuleMatches(rule, ctx)) {
101
+ continue;
102
+ }
103
+ const value = Number(rule.valor) || 0;
104
+ const previous = runningTotal;
105
+ if (rule.tipoValor === "percentage") {
106
+ const delta = previous * (value / 100);
107
+ runningTotal =
108
+ rule.operacion === "subtract" ? previous - delta : previous + delta;
109
+ }
110
+ else {
111
+ runningTotal =
112
+ rule.operacion === "subtract" ? previous - value : previous + value;
113
+ }
114
+ runningTotal = roundMoney(runningTotal);
115
+ steps.push({
116
+ ruleName: rule.nombre,
117
+ operation: rule.operacion,
118
+ valueType: rule.tipoValor,
119
+ value,
120
+ appliedAmount: roundMoney(runningTotal - previous),
121
+ runningTotal,
122
+ });
123
+ }
124
+ const result = {
125
+ basePrice: roundMoney(safeBase),
126
+ finalPrice: roundMoney(runningTotal),
127
+ description: "",
128
+ steps,
129
+ };
130
+ result.description = formatPriceAdjustmentDescription(result);
131
+ return result;
132
+ };
@@ -162,6 +162,7 @@ export const updatePurchaseItemPrice = async (headers, data, purchase_id) => {
162
162
  const body = {
163
163
  purchase_order: {
164
164
  purchase_order_details_attributes: data.purchase_order_details_attributes,
165
+ ...(data.memo !== undefined ? { memo: data.memo } : {}),
165
166
  },
166
167
  };
167
168
  const responseUpdate = await updateReceivedPurchaseOrder(headers, body, purchase_id);
@@ -0,0 +1,213 @@
1
+ import { handlePossibleAxiosErrors } from "@zauru-sdk/common";
2
+ import { getPayee, getPayees } from "@zauru-sdk/services";
3
+ import { getCostosBitacora } from "./costos-items.utils.js";
4
+ import { applyPriceAdjustmentRules, filterActivePriceAdjustmentRules, formatPriceAdjustmentDescription, getPriceAdjustmentRules, } from "./priceAdjustmentRules.utils.js";
5
+ import { getSpecialItems } from "./specialItem.utils.js";
6
+ export const MASSIVE_VEGETABLE_COST_ACCION = "Costos masivamente";
7
+ const FALLBACK_UNIT_COST = 1;
8
+ const isSpecialItemRow = (item) => typeof item.id === "number" &&
9
+ Boolean(item.data);
10
+ const isWeekday = (value) => value === 0 ||
11
+ value === 1 ||
12
+ value === 2 ||
13
+ value === 3 ||
14
+ value === 4 ||
15
+ value === 5 ||
16
+ value === 6;
17
+ export const parseWeeklyCostDate = (value) => {
18
+ const trimmed = String(value ?? "").trim();
19
+ if (!trimmed) {
20
+ return null;
21
+ }
22
+ const ymd = /^(\d{4})[-/](\d{2})[-/](\d{2})/.exec(trimmed);
23
+ if (ymd) {
24
+ return new Date(Number(ymd[1]), Number(ymd[2]) - 1, Number(ymd[3]));
25
+ }
26
+ const dmy = /^(\d{2})[-/](\d{2})[-/](\d{4})/.exec(trimmed);
27
+ if (dmy) {
28
+ return new Date(Number(dmy[3]), Number(dmy[2]) - 1, Number(dmy[1]));
29
+ }
30
+ const parsed = new Date(trimmed);
31
+ if (Number.isNaN(parsed.getTime())) {
32
+ return null;
33
+ }
34
+ return new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
35
+ };
36
+ const formatDateYYYYSlashMMSlashDD = (date) => {
37
+ const year = date.getFullYear();
38
+ const month = String(date.getMonth() + 1).padStart(2, "0");
39
+ const day = String(date.getDate()).padStart(2, "0");
40
+ return `${year}/${month}/${day}`;
41
+ };
42
+ export const getMondayOfWeeklyCostDate = (date) => {
43
+ const parsed = parseWeeklyCostDate(date);
44
+ if (!parsed) {
45
+ return null;
46
+ }
47
+ const weekday = parsed.getDay();
48
+ const daysFromMonday = weekday === 0 ? 6 : weekday - 1;
49
+ const monday = new Date(parsed);
50
+ monday.setDate(parsed.getDate() - daysFromMonday);
51
+ return formatDateYYYYSlashMMSlashDD(monday);
52
+ };
53
+ export const getWeeklyCostPeriod = (date) => {
54
+ const parsed = parseWeeklyCostDate(date);
55
+ const weekday = parsed?.getDay();
56
+ if (!parsed || weekday === undefined || !isWeekday(weekday)) {
57
+ return "lunMar";
58
+ }
59
+ switch (weekday) {
60
+ case 1:
61
+ case 2:
62
+ return "lunMar";
63
+ case 3:
64
+ case 4:
65
+ return "mieJue";
66
+ case 5:
67
+ case 6:
68
+ case 0:
69
+ return "vieSabDom";
70
+ default: {
71
+ const _exhaustive = weekday;
72
+ return _exhaustive;
73
+ }
74
+ }
75
+ };
76
+ export const getWeeklyCostPeriodPrice = (costo, period) => {
77
+ if (!costo) {
78
+ return null;
79
+ }
80
+ const value = Number(costo[period]);
81
+ if (!Number.isFinite(value) || value <= 0) {
82
+ return null;
83
+ }
84
+ return value;
85
+ };
86
+ export const getPriceByDate = (date, _fechas, costo) => getWeeklyCostPeriodPrice(costo, getWeeklyCostPeriod(date)) ?? FALLBACK_UNIT_COST;
87
+ export const filterActiveSpecialItems = (specialItems) => (specialItems ?? [])
88
+ .filter(isSpecialItemRow)
89
+ .filter((row) => !row.data?.fechaEliminacion)
90
+ .sort((a, b) => a.id - b.id);
91
+ export const matchSpecialItemForWeeklyPrice = (specialItems, params) => filterActiveSpecialItems(specialItems).find((specialItem) => specialItem.data.item === params.itemId &&
92
+ specialItem.data.region === params.region &&
93
+ specialItem.data.tipo === params.tipo);
94
+ export const pickLatestMassiveCostBitacora = (rows, date) => {
95
+ const masivas = (rows ?? [])
96
+ .filter((row) => row.data?.accion === MASSIVE_VEGETABLE_COST_ACCION)
97
+ .sort((a, b) => b.id - a.id);
98
+ const monday = date ? getMondayOfWeeklyCostDate(date) : null;
99
+ if (monday) {
100
+ const matchingWeek = masivas.find((row) => row.data?.fechas?.lunes === monday);
101
+ if (matchingWeek) {
102
+ return matchingWeek;
103
+ }
104
+ }
105
+ return masivas[0];
106
+ };
107
+ const sourceLabel = (source) => {
108
+ if (source === "especial") {
109
+ return "especial";
110
+ }
111
+ if (source === "fallback") {
112
+ return "sin matriz";
113
+ }
114
+ return "base";
115
+ };
116
+ export const calculateItemPriceFromWeeklyMatrix = (input) => {
117
+ const period = getWeeklyCostPeriod(input.date);
118
+ const matrix = input.matrix;
119
+ const matchedSpecial = matchSpecialItemForWeeklyPrice(input.specialItems, {
120
+ itemId: input.itemId,
121
+ region: input.region,
122
+ tipo: input.tipo,
123
+ });
124
+ let source = "fallback";
125
+ let basePrice = FALLBACK_UNIT_COST;
126
+ let specialItemId;
127
+ if (matchedSpecial) {
128
+ const specialPrice = getWeeklyCostPeriodPrice(matrix?.costosEspeciales?.find((costo) => costo.specialItemId === matchedSpecial.id), period);
129
+ if (specialPrice != null) {
130
+ source = "especial";
131
+ basePrice = specialPrice;
132
+ specialItemId = matchedSpecial.id;
133
+ }
134
+ }
135
+ if (source === "fallback") {
136
+ const generalPrice = getWeeklyCostPeriodPrice(matrix?.costos?.find((costo) => costo.item === input.itemId), period);
137
+ if (generalPrice != null) {
138
+ source = "general";
139
+ basePrice = generalPrice;
140
+ }
141
+ }
142
+ const adjusted = applyPriceAdjustmentRules(basePrice, input.rules ?? [], {
143
+ itemId: input.itemId,
144
+ tipo: input.tipo,
145
+ providerCategoryId: input.providerCategoryId,
146
+ });
147
+ return {
148
+ ...adjusted,
149
+ description: formatPriceAdjustmentDescription(adjusted, sourceLabel(source)),
150
+ source,
151
+ period,
152
+ specialItemId,
153
+ };
154
+ };
155
+ export const loadLatestWeeklyCostMatrix = (headers, session, date) => {
156
+ return handlePossibleAxiosErrors(async () => {
157
+ const bitacoraResponse = await getCostosBitacora(headers, session);
158
+ if (bitacoraResponse.error) {
159
+ throw new Error(`Error al intentar obtener la bitácora de costos de verduras: ${bitacoraResponse.userMsg ?? ""}`);
160
+ }
161
+ const bitacoraRow = pickLatestMassiveCostBitacora(bitacoraResponse.data ?? [], date);
162
+ if (!bitacoraRow?.data) {
163
+ return null;
164
+ }
165
+ return {
166
+ costos: bitacoraRow.data.costos ?? [],
167
+ costosEspeciales: bitacoraRow.data.costosEspeciales ?? [],
168
+ fechas: bitacoraRow.data.fechas,
169
+ };
170
+ });
171
+ };
172
+ export const resolveItemPriceFromLatestWeeklyMatrix = (headers, session, params) => {
173
+ return handlePossibleAxiosErrors(async () => {
174
+ const [matrixResponse, specialItemsResponse, rulesResponse] = await Promise.all([
175
+ loadLatestWeeklyCostMatrix(headers, session, params.date),
176
+ getSpecialItems(headers, session),
177
+ getPriceAdjustmentRules(headers, session),
178
+ ]);
179
+ if (matrixResponse.error) {
180
+ throw new Error(`Error al cargar la última matriz de costos: ${matrixResponse.userMsg ?? ""}`);
181
+ }
182
+ if (specialItemsResponse.error) {
183
+ throw new Error(`Error al intentar obtener los precios especiales de verduras: ${specialItemsResponse.userMsg ?? ""}`);
184
+ }
185
+ if (rulesResponse.error) {
186
+ throw new Error(`Error al obtener las reglas de ajuste de precio: ${rulesResponse.userMsg ?? ""}`);
187
+ }
188
+ let providerCategoryId;
189
+ if (params.payeeId != null) {
190
+ const payeeResponse = await getPayee(session, params.payeeId);
191
+ if (!payeeResponse.error && payeeResponse.data) {
192
+ providerCategoryId = payeeResponse.data.payee_category_id;
193
+ }
194
+ else {
195
+ const payeesResponse = await getPayees(session);
196
+ if (payeesResponse.error || !payeesResponse.data) {
197
+ throw new Error(`Error al obtener el proveedor para aplicar las reglas de ajuste: ${payeeResponse.userMsg ?? payeesResponse.userMsg ?? ""}`);
198
+ }
199
+ providerCategoryId = payeesResponse.data.find((payee) => payee.id === params.payeeId)?.payee_category_id;
200
+ }
201
+ }
202
+ return calculateItemPriceFromWeeklyMatrix({
203
+ itemId: params.itemId,
204
+ date: params.date,
205
+ region: params.region,
206
+ tipo: params.tipo,
207
+ providerCategoryId,
208
+ matrix: matrixResponse.data,
209
+ specialItems: specialItemsResponse.data ?? [],
210
+ rules: filterActivePriceAdjustmentRules(rulesResponse.data),
211
+ });
212
+ });
213
+ };
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ export * from "./zauru/providers.utils.js";
18
18
  export * from "./zauru/purchase-orders.utils.js";
19
19
  export * from "./zauru/shipments.utils.js";
20
20
  export * from "./zauru/specialItem.utils.js";
21
+ export * from "./zauru/priceAdjustmentRules.utils.js";
22
+ export * from "./zauru/weeklyItemPrice.utils.js";
21
23
  export * from "./zauru/templates.utils.js";
22
24
  export * from "./zauru/tiposMuestra.utils.js";
23
25
  export * from "./zauru/webapp-tables.utils.js";
@@ -0,0 +1,10 @@
1
+ import type { Session } from "@remix-run/node";
2
+ import { AxiosUtilsResponse, PriceAdjustmentContext, PriceAdjustmentFilters, PriceAdjustmentResult, PriceAdjustmentRule, WebAppRowGraphQL, WebAppTableUpdateResponse } from "@zauru-sdk/types";
3
+ export declare const defaultPriceAdjustmentFilters: () => PriceAdjustmentFilters;
4
+ export declare const filterActivePriceAdjustmentRules: (rules?: WebAppRowGraphQL<PriceAdjustmentRule>[]) => WebAppRowGraphQL<PriceAdjustmentRule>[];
5
+ export declare const getPriceAdjustmentRules: (headers: any, session: Session) => Promise<AxiosUtilsResponse<WebAppRowGraphQL<PriceAdjustmentRule>[]>>;
6
+ export declare const createPriceAdjustmentRule: (headers: any, session: Session, body: PriceAdjustmentRule) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
7
+ export declare const updatePriceAdjustmentRule: (headers: any, session: Session, id: string, body: Partial<PriceAdjustmentRule>) => Promise<AxiosUtilsResponse<WebAppTableUpdateResponse>>;
8
+ export declare const priceAdjustmentRuleMatches: (rule: PriceAdjustmentRule, ctx: PriceAdjustmentContext) => boolean;
9
+ export declare const formatPriceAdjustmentDescription: (result: Pick<PriceAdjustmentResult, "basePrice" | "finalPrice" | "steps">, baseLabel?: string) => string;
10
+ export declare const applyPriceAdjustmentRules: (basePrice: number, rules: Array<PriceAdjustmentRule | WebAppRowGraphQL<PriceAdjustmentRule>>, ctx: PriceAdjustmentContext) => PriceAdjustmentResult;
@@ -33,6 +33,7 @@ export declare const updatePurchaseItemPrice: (headers: any, data: {
33
33
  item_id: number;
34
34
  id: number;
35
35
  }>;
36
+ memo?: string;
36
37
  }, purchase_id: number) => Promise<AxiosUtilsResponse<boolean>>;
37
38
  /**
38
39
  * updateOchAndDis
@@ -0,0 +1,24 @@
1
+ import type { Session } from "@remix-run/node";
2
+ import { AxiosUtilsResponse, BitacoraCostosItems, CalculateItemPriceFromWeeklyMatrixInput, CalculateItemPriceFromWeeklyMatrixResult, CostoSemanal, CostoSemanalSpecialItem, SpecialItem, WebAppRowGraphQL, WeeklyCostMatrix, WeeklyCostPeriod } from "@zauru-sdk/types";
3
+ export declare const MASSIVE_VEGETABLE_COST_ACCION = "Costos masivamente";
4
+ export declare const parseWeeklyCostDate: (value: string) => Date | null;
5
+ export declare const getMondayOfWeeklyCostDate: (date: string) => string | null;
6
+ export declare const getWeeklyCostPeriod: (date: string) => WeeklyCostPeriod;
7
+ export declare const getWeeklyCostPeriodPrice: (costo: Pick<CostoSemanal, WeeklyCostPeriod> | undefined, period: WeeklyCostPeriod) => number | null;
8
+ export declare const getPriceByDate: (date: string, _fechas: WeeklyCostMatrix["fechas"] | undefined, costo?: CostoSemanal | CostoSemanalSpecialItem) => number;
9
+ export declare const filterActiveSpecialItems: (specialItems?: Array<SpecialItem | WebAppRowGraphQL<SpecialItem>>) => WebAppRowGraphQL<SpecialItem>[];
10
+ export declare const matchSpecialItemForWeeklyPrice: (specialItems: Array<SpecialItem | WebAppRowGraphQL<SpecialItem>> | undefined, params: {
11
+ itemId: number;
12
+ region?: string;
13
+ tipo?: string;
14
+ }) => WebAppRowGraphQL<SpecialItem> | undefined;
15
+ export declare const pickLatestMassiveCostBitacora: (rows: WebAppRowGraphQL<BitacoraCostosItems>[] | undefined, date?: string) => WebAppRowGraphQL<BitacoraCostosItems> | undefined;
16
+ export declare const calculateItemPriceFromWeeklyMatrix: (input: CalculateItemPriceFromWeeklyMatrixInput) => CalculateItemPriceFromWeeklyMatrixResult;
17
+ export declare const loadLatestWeeklyCostMatrix: (headers: any, session: Session, date?: string) => Promise<AxiosUtilsResponse<WeeklyCostMatrix | null>>;
18
+ export declare const resolveItemPriceFromLatestWeeklyMatrix: (headers: any, session: Session, params: {
19
+ itemId: number;
20
+ date: string;
21
+ region?: string;
22
+ tipo?: string;
23
+ payeeId?: number;
24
+ }) => Promise<AxiosUtilsResponse<CalculateItemPriceFromWeeklyMatrixResult>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/utils",
3
- "version": "6.4.3",
3
+ "version": "6.6.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",
@@ -25,12 +25,12 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@remix-run/node": "^2.8.1",
28
- "@zauru-sdk/common": "^6.3.2",
28
+ "@zauru-sdk/common": "^6.3.4",
29
29
  "@zauru-sdk/config": "^6.0.0",
30
30
  "@zauru-sdk/graphql": "^6.5.0",
31
- "@zauru-sdk/services": "^6.7.0",
32
- "@zauru-sdk/types": "^6.10.0",
31
+ "@zauru-sdk/services": "^6.7.2",
32
+ "@zauru-sdk/types": "^6.12.0",
33
33
  "moment": "^2.30.1"
34
34
  },
35
- "gitHead": "4df5f4a0b8160018e6047ed2afdd284c2ea0c8a6"
35
+ "gitHead": "7cec8a73384a6d1da9d612201b3ca2d233d6ce6e"
36
36
  }