@zauru-sdk/common 6.14.3 → 6.15.1

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,25 @@
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.15.1](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.15.0...@zauru-sdk/common@6.15.1) (2026-11-03)
7
+
8
+ **Note:** Version bump only for package @zauru-sdk/common
9
+
10
+
11
+
12
+
13
+
14
+ # [6.15.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.14.3...@zauru-sdk/common@6.15.0) (2026-11-03)
15
+
16
+
17
+ ### Features
18
+
19
+ * agregar contrato de conversión de radicchio ([50d7efc](https://github.com/intuitiva/zauru-typescript-sdk/commit/50d7efca40cbf4b2b7789a5c4bf636fbd95c515c))
20
+
21
+
22
+
23
+
24
+
6
25
  ## [6.14.3](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/common@6.14.2...@zauru-sdk/common@6.14.3) (2026-11-02)
7
26
 
8
27
  **Note:** Version bump only for package @zauru-sdk/common
package/dist/esm/index.js CHANGED
@@ -20,5 +20,6 @@ __exportStar(require("./httpClientPolicy.js"), exports);
20
20
  __exportStar(require("./oauthErrors.js"), exports);
21
21
  __exportStar(require("./providerRestrictions.js"), exports);
22
22
  __exportStar(require("./providerReceptionMatrix.js"), exports);
23
+ __exportStar(require("./radicchioConversion.js"), exports);
23
24
  __exportStar(require("./rejectionPercentageAdjustment.js"), exports);
24
25
  __exportStar(require("./sessionReauth.js"), exports);
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRadicchioConversionMemo = exports.getRadicchioOriginalLines = exports.allocateRadicchioBoxesByLine = exports.validateRadicchioConversion = exports.calculateRadicchioConversion = exports.emptyRadicchioBoxes = void 0;
4
+ const boxSizes = [9, 12, 15];
5
+ const emptyRadicchioBoxes = () => ({
6
+ 9: 0,
7
+ 12: 0,
8
+ 15: 0,
9
+ });
10
+ exports.emptyRadicchioBoxes = emptyRadicchioBoxes;
11
+ const calculateRadicchioConversion = (receivedUnits, boxes) => {
12
+ const normalized = Object.fromEntries(boxSizes.map((size) => [size, Number(boxes[size])]));
13
+ const packedUnits = boxSizes.reduce((total, size) => total + size * normalized[size], 0);
14
+ return {
15
+ boxes: normalized,
16
+ packedUnits,
17
+ remainingUnits: Number(receivedUnits) - packedUnits,
18
+ totalBoxes: boxSizes.reduce((total, size) => total + normalized[size], 0),
19
+ };
20
+ };
21
+ exports.calculateRadicchioConversion = calculateRadicchioConversion;
22
+ const validateRadicchioConversion = (receivedUnits, boxes, remainderReason) => {
23
+ const errors = [];
24
+ if (!Number.isInteger(receivedUnits) || receivedUnits <= 0) {
25
+ errors.push("Las unidades recibidas deben ser un entero mayor que cero.");
26
+ }
27
+ for (const size of boxSizes) {
28
+ if (!Number.isInteger(Number(boxes[size])) || Number(boxes[size]) < 0) {
29
+ errors.push(`Las cajas de ${size} deben ser un entero mayor o igual a cero.`);
30
+ }
31
+ }
32
+ if (errors.length)
33
+ return errors;
34
+ const calculation = (0, exports.calculateRadicchioConversion)(receivedUnits, boxes);
35
+ if (calculation.totalBoxes <= 0) {
36
+ errors.push("Ingrese al menos una caja.");
37
+ }
38
+ if (calculation.remainingUnits < 0) {
39
+ errors.push("Las cajas ingresadas superan las unidades recibidas.");
40
+ }
41
+ if (calculation.remainingUnits > 0 &&
42
+ !String(remainderReason ?? "").trim()) {
43
+ errors.push("Indique el motivo de las unidades sin convertir.");
44
+ }
45
+ return errors;
46
+ };
47
+ exports.validateRadicchioConversion = validateRadicchioConversion;
48
+ /**
49
+ * Splits a converted box total across original weighing lines while preserving
50
+ * the exact total. Floors every proportional share, then assigns the remaining
51
+ * boxes to the largest fractional residues (stable by original line order).
52
+ */
53
+ const allocateRadicchioBoxesByLine = (totalBoxes, lineUnits) => {
54
+ if (!Number.isInteger(totalBoxes) || totalBoxes < 0) {
55
+ throw new Error("El total de cajas debe ser un entero mayor o igual a cero.");
56
+ }
57
+ if (!lineUnits.length)
58
+ return [];
59
+ const normalized = lineUnits.map((value) => Math.max(0, Number(value) || 0));
60
+ const unitTotal = normalized.reduce((sum, value) => sum + value, 0);
61
+ if (unitTotal <= 0) {
62
+ return normalized.map((_, index) => (index === 0 ? totalBoxes : 0));
63
+ }
64
+ const ideals = normalized.map((value) => (totalBoxes * value) / unitTotal);
65
+ const allocated = ideals.map(Math.floor);
66
+ let remaining = totalBoxes - allocated.reduce((sum, value) => sum + value, 0);
67
+ const order = ideals
68
+ .map((value, index) => ({ index, residue: value - Math.floor(value) }))
69
+ .sort((a, b) => b.residue - a.residue || a.index - b.index);
70
+ for (let index = 0; index < remaining; index += 1) {
71
+ allocated[order[index].index] += 1;
72
+ }
73
+ return allocated;
74
+ };
75
+ exports.allocateRadicchioBoxesByLine = allocateRadicchioBoxesByLine;
76
+ const getRadicchioOriginalLines = (memo) => {
77
+ const lines = memo.originalNetWeightLines ?? memo.originalNetWeighLines ?? [];
78
+ if (lines.length)
79
+ return lines.map((line) => Number(line.weight) || 0);
80
+ const total = Number(memo.radicchioConversion?.receivedUnits ?? memo.originalNetWeight ?? 0);
81
+ return total > 0 ? [total] : [];
82
+ };
83
+ exports.getRadicchioOriginalLines = getRadicchioOriginalLines;
84
+ const isRadicchioConversionMemo = (value) => {
85
+ if (!value || typeof value !== "object")
86
+ return false;
87
+ const memo = value;
88
+ return (memo.schemaVersion === 1 &&
89
+ (memo.status === "pending" || memo.status === "converted") &&
90
+ Number.isInteger(memo.sourceItemId) &&
91
+ Number.isInteger(memo.targetItemId) &&
92
+ Number.isFinite(memo.receivedUnits));
93
+ };
94
+ exports.isRadicchioConversionMemo = isRadicchioConversionMemo;
package/dist/index.d.ts CHANGED
@@ -4,5 +4,6 @@ export * from "./httpClientPolicy.js";
4
4
  export * from "./oauthErrors.js";
5
5
  export * from "./providerRestrictions.js";
6
6
  export * from "./providerReceptionMatrix.js";
7
+ export * from "./radicchioConversion.js";
7
8
  export * from "./rejectionPercentageAdjustment.js";
8
9
  export * from "./sessionReauth.js";
@@ -0,0 +1,18 @@
1
+ import type { JsonMemoType, RadicchioBoxes, RadicchioConversionMemo } from "@zauru-sdk/types";
2
+ export type RadicchioConversionCalculation = {
3
+ boxes: RadicchioBoxes;
4
+ packedUnits: number;
5
+ remainingUnits: number;
6
+ totalBoxes: number;
7
+ };
8
+ export declare const emptyRadicchioBoxes: () => RadicchioBoxes;
9
+ export declare const calculateRadicchioConversion: (receivedUnits: number, boxes: RadicchioBoxes) => RadicchioConversionCalculation;
10
+ export declare const validateRadicchioConversion: (receivedUnits: number, boxes: RadicchioBoxes, remainderReason?: string) => string[];
11
+ /**
12
+ * Splits a converted box total across original weighing lines while preserving
13
+ * the exact total. Floors every proportional share, then assigns the remaining
14
+ * boxes to the largest fractional residues (stable by original line order).
15
+ */
16
+ export declare const allocateRadicchioBoxesByLine: (totalBoxes: number, lineUnits: number[]) => number[];
17
+ export declare const getRadicchioOriginalLines: (memo: JsonMemoType) => number[];
18
+ export declare const isRadicchioConversionMemo: (value: unknown) => value is RadicchioConversionMemo;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/common",
3
- "version": "6.14.3",
3
+ "version": "6.15.1",
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",
@@ -12,7 +12,7 @@
12
12
  "scripts": {
13
13
  "build": "npm run build:esm",
14
14
  "build:esm": "tsc -p tsconfig.esm.json",
15
- "test": "tsx --test src/httpClientPolicy.test.ts src/providerRestrictions.test.ts src/providerReceptionMatrix.test.ts src/purchaseOrderFinancials.test.ts src/sessionReauth.test.ts"
15
+ "test": "tsx --test src/httpClientPolicy.test.ts src/providerRestrictions.test.ts src/providerReceptionMatrix.test.ts src/purchaseOrderFinancials.test.ts src/radicchioConversion.test.ts src/sessionReauth.test.ts"
16
16
  },
17
17
  "author": "Oscar Cuéllar",
18
18
  "license": "MIT",
@@ -22,9 +22,9 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@remix-run/node": "^2.8.1",
25
- "@zauru-sdk/types": "^6.31.0",
25
+ "@zauru-sdk/types": "^6.32.1",
26
26
  "moment": "^2.30.1",
27
27
  "moment-timezone": "^0.5.45"
28
28
  },
29
- "gitHead": "f5b9a14e062a1c9715757cefc08b82a2f3ffd1bf"
29
+ "gitHead": "ec249a3005f95dd2215592b4d31068a76e31eca5"
30
30
  }
package/src/index.ts CHANGED
@@ -4,5 +4,6 @@ export * from "./httpClientPolicy.js";
4
4
  export * from "./oauthErrors.js";
5
5
  export * from "./providerRestrictions.js";
6
6
  export * from "./providerReceptionMatrix.js";
7
+ export * from "./radicchioConversion.js";
7
8
  export * from "./rejectionPercentageAdjustment.js";
8
9
  export * from "./sessionReauth.js";
@@ -0,0 +1,68 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import {
4
+ allocateRadicchioBoxesByLine,
5
+ calculateRadicchioConversion,
6
+ getRadicchioOriginalLines,
7
+ validateRadicchioConversion,
8
+ } from "./radicchioConversion.js";
9
+
10
+ describe("radicchio conversion", () => {
11
+ it("calculates boxes, packed units and remainder", () => {
12
+ assert.deepEqual(
13
+ calculateRadicchioConversion(100, { 9: 2, 12: 3, 15: 2 }),
14
+ {
15
+ boxes: { 9: 2, 12: 3, 15: 2 },
16
+ packedUnits: 84,
17
+ remainingUnits: 16,
18
+ totalBoxes: 7,
19
+ },
20
+ );
21
+ });
22
+
23
+ it("requires a reason when units remain", () => {
24
+ assert.ok(
25
+ validateRadicchioConversion(100, { 9: 10, 12: 0, 15: 0 }).includes(
26
+ "Indique el motivo de las unidades sin convertir.",
27
+ ),
28
+ );
29
+ assert.deepEqual(
30
+ validateRadicchioConversion(
31
+ 100,
32
+ { 9: 10, 12: 0, 15: 0 },
33
+ "Unidades sueltas",
34
+ ),
35
+ [],
36
+ );
37
+ });
38
+
39
+ it("rejects over-allocation and non-integer boxes", () => {
40
+ assert.ok(
41
+ validateRadicchioConversion(10, { 9: 0, 12: 1, 15: 0 }).includes(
42
+ "Las cajas ingresadas superan las unidades recibidas.",
43
+ ),
44
+ );
45
+ assert.ok(
46
+ validateRadicchioConversion(100, { 9: 1.5, 12: 0, 15: 0 }).includes(
47
+ "Las cajas de 9 deben ser un entero mayor o igual a cero.",
48
+ ),
49
+ );
50
+ });
51
+
52
+ it("allocates boxes by largest residue and preserves the total", () => {
53
+ const result = allocateRadicchioBoxesByLine(10, [50, 30, 20]);
54
+ assert.deepEqual(result, [5, 3, 2]);
55
+ assert.equal(result.reduce((sum, value) => sum + value, 0), 10);
56
+ });
57
+
58
+ it("reads the corrected and legacy line keys", () => {
59
+ assert.deepEqual(
60
+ getRadicchioOriginalLines({ originalNetWeightLines: [{ weight: 12 }] }),
61
+ [12],
62
+ );
63
+ assert.deepEqual(
64
+ getRadicchioOriginalLines({ originalNetWeighLines: [{ weight: 15 }] }),
65
+ [15],
66
+ );
67
+ });
68
+ });
@@ -0,0 +1,128 @@
1
+ import type {
2
+ JsonMemoType,
3
+ RadicchioBoxes,
4
+ RadicchioConversionMemo,
5
+ } from "@zauru-sdk/types";
6
+
7
+ export type RadicchioConversionCalculation = {
8
+ boxes: RadicchioBoxes;
9
+ packedUnits: number;
10
+ remainingUnits: number;
11
+ totalBoxes: number;
12
+ };
13
+
14
+ const boxSizes = [9, 12, 15] as const;
15
+
16
+ export const emptyRadicchioBoxes = (): RadicchioBoxes => ({
17
+ 9: 0,
18
+ 12: 0,
19
+ 15: 0,
20
+ });
21
+
22
+ export const calculateRadicchioConversion = (
23
+ receivedUnits: number,
24
+ boxes: RadicchioBoxes,
25
+ ): RadicchioConversionCalculation => {
26
+ const normalized = Object.fromEntries(
27
+ boxSizes.map((size) => [size, Number(boxes[size])]),
28
+ ) as RadicchioBoxes;
29
+ const packedUnits = boxSizes.reduce(
30
+ (total, size) => total + size * normalized[size],
31
+ 0,
32
+ );
33
+ return {
34
+ boxes: normalized,
35
+ packedUnits,
36
+ remainingUnits: Number(receivedUnits) - packedUnits,
37
+ totalBoxes: boxSizes.reduce(
38
+ (total, size) => total + normalized[size],
39
+ 0,
40
+ ),
41
+ };
42
+ };
43
+
44
+ export const validateRadicchioConversion = (
45
+ receivedUnits: number,
46
+ boxes: RadicchioBoxes,
47
+ remainderReason?: string,
48
+ ): string[] => {
49
+ const errors: string[] = [];
50
+ if (!Number.isInteger(receivedUnits) || receivedUnits <= 0) {
51
+ errors.push("Las unidades recibidas deben ser un entero mayor que cero.");
52
+ }
53
+ for (const size of boxSizes) {
54
+ if (!Number.isInteger(Number(boxes[size])) || Number(boxes[size]) < 0) {
55
+ errors.push(`Las cajas de ${size} deben ser un entero mayor o igual a cero.`);
56
+ }
57
+ }
58
+ if (errors.length) return errors;
59
+
60
+ const calculation = calculateRadicchioConversion(receivedUnits, boxes);
61
+ if (calculation.totalBoxes <= 0) {
62
+ errors.push("Ingrese al menos una caja.");
63
+ }
64
+ if (calculation.remainingUnits < 0) {
65
+ errors.push("Las cajas ingresadas superan las unidades recibidas.");
66
+ }
67
+ if (
68
+ calculation.remainingUnits > 0 &&
69
+ !String(remainderReason ?? "").trim()
70
+ ) {
71
+ errors.push("Indique el motivo de las unidades sin convertir.");
72
+ }
73
+ return errors;
74
+ };
75
+
76
+ /**
77
+ * Splits a converted box total across original weighing lines while preserving
78
+ * the exact total. Floors every proportional share, then assigns the remaining
79
+ * boxes to the largest fractional residues (stable by original line order).
80
+ */
81
+ export const allocateRadicchioBoxesByLine = (
82
+ totalBoxes: number,
83
+ lineUnits: number[],
84
+ ): number[] => {
85
+ if (!Number.isInteger(totalBoxes) || totalBoxes < 0) {
86
+ throw new Error("El total de cajas debe ser un entero mayor o igual a cero.");
87
+ }
88
+ if (!lineUnits.length) return [];
89
+ const normalized = lineUnits.map((value) => Math.max(0, Number(value) || 0));
90
+ const unitTotal = normalized.reduce((sum, value) => sum + value, 0);
91
+ if (unitTotal <= 0) {
92
+ return normalized.map((_, index) => (index === 0 ? totalBoxes : 0));
93
+ }
94
+
95
+ const ideals = normalized.map((value) => (totalBoxes * value) / unitTotal);
96
+ const allocated = ideals.map(Math.floor);
97
+ let remaining = totalBoxes - allocated.reduce((sum, value) => sum + value, 0);
98
+ const order = ideals
99
+ .map((value, index) => ({ index, residue: value - Math.floor(value) }))
100
+ .sort((a, b) => b.residue - a.residue || a.index - b.index);
101
+ for (let index = 0; index < remaining; index += 1) {
102
+ allocated[order[index].index] += 1;
103
+ }
104
+ return allocated;
105
+ };
106
+
107
+ export const getRadicchioOriginalLines = (memo: JsonMemoType): number[] => {
108
+ const lines = memo.originalNetWeightLines ?? memo.originalNetWeighLines ?? [];
109
+ if (lines.length) return lines.map((line) => Number(line.weight) || 0);
110
+ const total = Number(
111
+ memo.radicchioConversion?.receivedUnits ?? memo.originalNetWeight ?? 0,
112
+ );
113
+ return total > 0 ? [total] : [];
114
+ };
115
+
116
+ export const isRadicchioConversionMemo = (
117
+ value: unknown,
118
+ ): value is RadicchioConversionMemo => {
119
+ if (!value || typeof value !== "object") return false;
120
+ const memo = value as Partial<RadicchioConversionMemo>;
121
+ return (
122
+ memo.schemaVersion === 1 &&
123
+ (memo.status === "pending" || memo.status === "converted") &&
124
+ Number.isInteger(memo.sourceItemId) &&
125
+ Number.isInteger(memo.targetItemId) &&
126
+ Number.isFinite(memo.receivedUnits)
127
+ );
128
+ };