hvp-shared 6.44.0 → 6.45.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.
@@ -5,8 +5,7 @@ export * from './mexican-states';
5
5
  export * from './sat-catalogs';
6
6
  export * from './collaborator.constants';
7
7
  export * from './catalog-item.constants';
8
- export * from './market-competition.constants';
9
- export * from './pricing-rule.constants';
8
+ export * from './pricing.constants';
10
9
  export * from './qvet-catalog';
11
10
  export * from './qvet-warehouses';
12
11
  export * from './qvet-inventory';
@@ -21,8 +21,7 @@ __exportStar(require("./mexican-states"), exports);
21
21
  __exportStar(require("./sat-catalogs"), exports);
22
22
  __exportStar(require("./collaborator.constants"), exports);
23
23
  __exportStar(require("./catalog-item.constants"), exports);
24
- __exportStar(require("./market-competition.constants"), exports);
25
- __exportStar(require("./pricing-rule.constants"), exports);
24
+ __exportStar(require("./pricing.constants"), exports);
26
25
  __exportStar(require("./qvet-catalog"), exports);
27
26
  __exportStar(require("./qvet-warehouses"), exports);
28
27
  __exportStar(require("./qvet-inventory"), exports);
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Pricing System
3
+ *
4
+ * Replaces the old 23-code PricingRuleCode + MarketCompetition system
5
+ * with two orthogonal fields: PricePolicy (how to price) and PricingTier (margin level).
6
+ *
7
+ * This enables formula-driven recommendedPrice calculation:
8
+ * recommendedPrice = unitCostBI * markupFactor * (1 + vatSale/100)
9
+ */
10
+ /**
11
+ * PricePolicy - HOW the item is priced
12
+ *
13
+ * Each policy describes the pricing strategy for a category of items.
14
+ */
15
+ export declare enum PricePolicy {
16
+ /** Pharmacy unit sales (original presentation) */
17
+ PHARM = "PHARM",
18
+ /** Pharmacy fractioned (sold in smaller quantities than purchased) */
19
+ PHARM_FRAC = "PHARM_FRAC",
20
+ /** Per-ml sales (liquid pharmacy items sold by ml) */
21
+ COST_ML = "COST_ML",
22
+ /** Medical supplies / other articles (cost-based) */
23
+ COST = "COST",
24
+ /** External services (commission-based) */
25
+ EXT_COST = "EXT_COST",
26
+ /** Internal services (priced by market study / competition) */
27
+ COMP_SVC = "COMP_SVC",
28
+ /** Diagnostic tests (TBD — no formula yet) */
29
+ TEST = "TEST",
30
+ /** Individual formula per item */
31
+ SPECIAL = "SPECIAL",
32
+ /** Fixed manual price (no formula) */
33
+ MANUAL = "MANUAL",
34
+ /** Not sold (internal use, equipment, etc.) */
35
+ NONE = "NONE"
36
+ }
37
+ /**
38
+ * PricingTier - Margin level within a policy
39
+ */
40
+ export declare enum PricingTier {
41
+ LOW_MARGIN = "LOW_MARGIN",
42
+ MID_MARGIN = "MID_MARGIN",
43
+ HIGH_MARGIN = "HIGH_MARGIN"
44
+ }
45
+ export declare const PRICE_POLICY_LABELS: Record<PricePolicy, string>;
46
+ export declare const PRICING_TIER_LABELS: Record<PricingTier, string>;
47
+ export declare const PRICE_POLICY_VALUES: PricePolicy[];
48
+ export declare const PRICING_TIER_VALUES: PricingTier[];
49
+ /**
50
+ * Policies that have a formula (cost * factor).
51
+ * Keys: PricePolicy, Values: { LOW_MARGIN, MID_MARGIN, HIGH_MARGIN } → factor
52
+ */
53
+ export declare const MARKUP_FACTORS: Partial<Record<PricePolicy, Record<PricingTier, number>>>;
54
+ /**
55
+ * For auto-tier policies, tier is determined from unitCostBI.
56
+ * Ranges are defined as [lowThreshold, highThreshold]:
57
+ * cost > highThreshold → LOW_MARGIN
58
+ * cost >= lowThreshold && cost <= highThreshold → MID_MARGIN
59
+ * cost < lowThreshold → HIGH_MARGIN
60
+ *
61
+ * Note: "low cost → high margin" because cheap items tolerate larger multipliers.
62
+ */
63
+ interface AutoTierRange {
64
+ /** Cost at or below this → HIGH_MARGIN */
65
+ highMarginMax: number;
66
+ /** Cost above highMarginMax and at or below this → MID_MARGIN; above this → LOW_MARGIN */
67
+ midMarginMax: number;
68
+ }
69
+ export declare const AUTO_TIER_RANGES: Partial<Record<PricePolicy, AutoTierRange>>;
70
+ /** Policies where tier is auto-calculated from cost */
71
+ export declare const AUTO_TIER_POLICIES: readonly PricePolicy[];
72
+ /** Policies that have no formula (recommendedPrice = null) */
73
+ export declare const NO_FORMULA_POLICIES: readonly PricePolicy[];
74
+ /** Sections where items are not sold (pricePolicy = NONE) */
75
+ export declare const NON_SELLABLE_SECTIONS: readonly string[];
76
+ /**
77
+ * Sections that use COMP_SVC policy (market-study pricing).
78
+ * No formula — prices set by competitive analysis.
79
+ */
80
+ export declare const COMP_SVC_SECTIONS: readonly string[];
81
+ /**
82
+ * Allowed policies per section (for validation in UI/API).
83
+ * Sections not listed here allow all policies.
84
+ */
85
+ export declare const ALLOWED_POLICIES_BY_SECTION: Partial<Record<string, readonly PricePolicy[]>>;
86
+ /**
87
+ * Get the auto-calculated tier based on unit cost.
88
+ *
89
+ * For auto-tier policies (COST_ML, COST, EXT_COST), the tier is determined
90
+ * by the unit cost. Cheaper items get higher margins because customers
91
+ * are less price-sensitive on low-cost items.
92
+ *
93
+ * @returns PricingTier, or null if the policy doesn't support auto-tier
94
+ */
95
+ export declare function getAutoTier(policy: PricePolicy, unitCostBI: number): PricingTier | null;
96
+ /**
97
+ * Get the markup factor for a policy + tier combination.
98
+ *
99
+ * @returns The factor to multiply unitCostBI by, or null if no formula exists
100
+ */
101
+ export declare function getMarkupFactor(policy: PricePolicy, tier: PricingTier): number | null;
102
+ /**
103
+ * Default price policy result
104
+ */
105
+ export interface DefaultPricePolicyResult {
106
+ policy: PricePolicy;
107
+ /** If set, this is the fixed tier. If null, tier should be auto-calculated. */
108
+ tier: PricingTier | null;
109
+ }
110
+ /**
111
+ * Determine the default PricePolicy (and optionally PricingTier) for an item
112
+ * based on its section, sale unit, and conversion factor.
113
+ *
114
+ * Used when:
115
+ * - Creating a new catalog item from QVET sync
116
+ * - Migration script for existing items
117
+ * - Items that don't have pricePolicy set yet
118
+ */
119
+ export declare function getDefaultPricePolicy(section: string, saleUnit: string, conversionFactor: number): DefaultPricePolicyResult;
120
+ /**
121
+ * Calculate the recommended price PVP (con IVA) for a catalog item.
122
+ *
123
+ * Formula: unitPurchasePriceBI * markupFactor * (1 + vatSalePercent / 100)
124
+ *
125
+ * @returns The recommended price, or null if the policy has no formula
126
+ */
127
+ export declare function calculateRecommendedPricePVP(params: {
128
+ pricePolicy: PricePolicy;
129
+ pricingTier: PricingTier;
130
+ unitPurchasePriceBI: number;
131
+ vatSalePercent: number;
132
+ }): number | null;
133
+ export {};
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ /**
3
+ * Pricing System
4
+ *
5
+ * Replaces the old 23-code PricingRuleCode + MarketCompetition system
6
+ * with two orthogonal fields: PricePolicy (how to price) and PricingTier (margin level).
7
+ *
8
+ * This enables formula-driven recommendedPrice calculation:
9
+ * recommendedPrice = unitCostBI * markupFactor * (1 + vatSale/100)
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ALLOWED_POLICIES_BY_SECTION = exports.COMP_SVC_SECTIONS = exports.NON_SELLABLE_SECTIONS = exports.NO_FORMULA_POLICIES = exports.AUTO_TIER_POLICIES = exports.AUTO_TIER_RANGES = exports.MARKUP_FACTORS = exports.PRICING_TIER_VALUES = exports.PRICE_POLICY_VALUES = exports.PRICING_TIER_LABELS = exports.PRICE_POLICY_LABELS = exports.PricingTier = exports.PricePolicy = void 0;
13
+ exports.getAutoTier = getAutoTier;
14
+ exports.getMarkupFactor = getMarkupFactor;
15
+ exports.getDefaultPricePolicy = getDefaultPricePolicy;
16
+ exports.calculateRecommendedPricePVP = calculateRecommendedPricePVP;
17
+ const qvet_catalog_1 = require("./qvet-catalog");
18
+ // =============================================================================
19
+ // ENUMS
20
+ // =============================================================================
21
+ /**
22
+ * PricePolicy - HOW the item is priced
23
+ *
24
+ * Each policy describes the pricing strategy for a category of items.
25
+ */
26
+ var PricePolicy;
27
+ (function (PricePolicy) {
28
+ /** Pharmacy unit sales (original presentation) */
29
+ PricePolicy["PHARM"] = "PHARM";
30
+ /** Pharmacy fractioned (sold in smaller quantities than purchased) */
31
+ PricePolicy["PHARM_FRAC"] = "PHARM_FRAC";
32
+ /** Per-ml sales (liquid pharmacy items sold by ml) */
33
+ PricePolicy["COST_ML"] = "COST_ML";
34
+ /** Medical supplies / other articles (cost-based) */
35
+ PricePolicy["COST"] = "COST";
36
+ /** External services (commission-based) */
37
+ PricePolicy["EXT_COST"] = "EXT_COST";
38
+ /** Internal services (priced by market study / competition) */
39
+ PricePolicy["COMP_SVC"] = "COMP_SVC";
40
+ /** Diagnostic tests (TBD — no formula yet) */
41
+ PricePolicy["TEST"] = "TEST";
42
+ /** Individual formula per item */
43
+ PricePolicy["SPECIAL"] = "SPECIAL";
44
+ /** Fixed manual price (no formula) */
45
+ PricePolicy["MANUAL"] = "MANUAL";
46
+ /** Not sold (internal use, equipment, etc.) */
47
+ PricePolicy["NONE"] = "NONE";
48
+ })(PricePolicy || (exports.PricePolicy = PricePolicy = {}));
49
+ /**
50
+ * PricingTier - Margin level within a policy
51
+ */
52
+ var PricingTier;
53
+ (function (PricingTier) {
54
+ PricingTier["LOW_MARGIN"] = "LOW_MARGIN";
55
+ PricingTier["MID_MARGIN"] = "MID_MARGIN";
56
+ PricingTier["HIGH_MARGIN"] = "HIGH_MARGIN";
57
+ })(PricingTier || (exports.PricingTier = PricingTier = {}));
58
+ // =============================================================================
59
+ // LABELS (Spanish, for UI)
60
+ // =============================================================================
61
+ exports.PRICE_POLICY_LABELS = {
62
+ [PricePolicy.PHARM]: 'Farmacia (presentación original)',
63
+ [PricePolicy.PHARM_FRAC]: 'Farmacia (fraccionado)',
64
+ [PricePolicy.COST_ML]: 'Farmacia (por ml)',
65
+ [PricePolicy.COST]: 'Insumos / Artículos (por costo)',
66
+ [PricePolicy.EXT_COST]: 'Servicios externos',
67
+ [PricePolicy.COMP_SVC]: 'Servicios internos (estudio de mercado)',
68
+ [PricePolicy.TEST]: 'Pruebas diagnósticas',
69
+ [PricePolicy.SPECIAL]: 'Fórmula individual',
70
+ [PricePolicy.MANUAL]: 'Precio manual',
71
+ [PricePolicy.NONE]: 'No se vende',
72
+ };
73
+ exports.PRICING_TIER_LABELS = {
74
+ [PricingTier.LOW_MARGIN]: 'Margen bajo',
75
+ [PricingTier.MID_MARGIN]: 'Margen medio',
76
+ [PricingTier.HIGH_MARGIN]: 'Margen alto',
77
+ };
78
+ exports.PRICE_POLICY_VALUES = Object.values(PricePolicy);
79
+ exports.PRICING_TIER_VALUES = Object.values(PricingTier);
80
+ // =============================================================================
81
+ // MARKUP TABLES
82
+ // =============================================================================
83
+ /**
84
+ * Policies that have a formula (cost * factor).
85
+ * Keys: PricePolicy, Values: { LOW_MARGIN, MID_MARGIN, HIGH_MARGIN } → factor
86
+ */
87
+ exports.MARKUP_FACTORS = {
88
+ [PricePolicy.PHARM]: { [PricingTier.LOW_MARGIN]: 1.33, [PricingTier.MID_MARGIN]: 1.67, [PricingTier.HIGH_MARGIN]: 2.00 },
89
+ [PricePolicy.PHARM_FRAC]: { [PricingTier.LOW_MARGIN]: 2.00, [PricingTier.MID_MARGIN]: 2.50, [PricingTier.HIGH_MARGIN]: 3.00 },
90
+ [PricePolicy.COST_ML]: { [PricingTier.LOW_MARGIN]: 3.00, [PricingTier.MID_MARGIN]: 5.00, [PricingTier.HIGH_MARGIN]: 10.00 },
91
+ [PricePolicy.COST]: { [PricingTier.LOW_MARGIN]: 2.00, [PricingTier.MID_MARGIN]: 3.00, [PricingTier.HIGH_MARGIN]: 5.00 },
92
+ [PricePolicy.EXT_COST]: { [PricingTier.LOW_MARGIN]: 1.40, [PricingTier.MID_MARGIN]: 1.70, [PricingTier.HIGH_MARGIN]: 2.00 },
93
+ };
94
+ exports.AUTO_TIER_RANGES = {
95
+ [PricePolicy.COST_ML]: { highMarginMax: 5, midMarginMax: 50 },
96
+ [PricePolicy.COST]: { highMarginMax: 30, midMarginMax: 500 },
97
+ [PricePolicy.EXT_COST]: { highMarginMax: 600, midMarginMax: 2000 },
98
+ };
99
+ /** Policies where tier is auto-calculated from cost */
100
+ exports.AUTO_TIER_POLICIES = [
101
+ PricePolicy.COST_ML,
102
+ PricePolicy.COST,
103
+ PricePolicy.EXT_COST,
104
+ ];
105
+ /** Policies that have no formula (recommendedPrice = null) */
106
+ exports.NO_FORMULA_POLICIES = [
107
+ PricePolicy.COMP_SVC,
108
+ PricePolicy.TEST,
109
+ PricePolicy.SPECIAL,
110
+ PricePolicy.MANUAL,
111
+ PricePolicy.NONE,
112
+ ];
113
+ // =============================================================================
114
+ // SECTION → DEFAULT POLICY MAPPING
115
+ // =============================================================================
116
+ /** Sections where items are not sold (pricePolicy = NONE) */
117
+ exports.NON_SELLABLE_SECTIONS = [
118
+ qvet_catalog_1.QVET_SECTIONS.CONSUMO_INTERNO,
119
+ qvet_catalog_1.QVET_SECTIONS.EQUIPAMIENTO,
120
+ qvet_catalog_1.QVET_SECTIONS.INSUMOS_ESCANDALLO,
121
+ ];
122
+ /**
123
+ * Sections that use COMP_SVC policy (market-study pricing).
124
+ * No formula — prices set by competitive analysis.
125
+ */
126
+ exports.COMP_SVC_SECTIONS = [
127
+ qvet_catalog_1.QVET_SECTIONS.SERVICIOS_MEDICOS,
128
+ qvet_catalog_1.QVET_SECTIONS.OTROS_SERVICIOS,
129
+ ];
130
+ /**
131
+ * Allowed policies per section (for validation in UI/API).
132
+ * Sections not listed here allow all policies.
133
+ */
134
+ exports.ALLOWED_POLICIES_BY_SECTION = {
135
+ [qvet_catalog_1.QVET_SECTIONS.CONSUMO_INTERNO]: [PricePolicy.NONE],
136
+ [qvet_catalog_1.QVET_SECTIONS.EQUIPAMIENTO]: [PricePolicy.NONE],
137
+ [qvet_catalog_1.QVET_SECTIONS.INSUMOS_ESCANDALLO]: [PricePolicy.NONE],
138
+ [qvet_catalog_1.QVET_SECTIONS.FARMACIA]: [PricePolicy.PHARM, PricePolicy.PHARM_FRAC, PricePolicy.COST_ML, PricePolicy.SPECIAL, PricePolicy.MANUAL],
139
+ [qvet_catalog_1.QVET_SECTIONS.FARMACIA_INTERNA]: [PricePolicy.PHARM, PricePolicy.PHARM_FRAC, PricePolicy.COST_ML, PricePolicy.SPECIAL, PricePolicy.MANUAL],
140
+ [qvet_catalog_1.QVET_SECTIONS.INSUMOS_MEDICOS]: [PricePolicy.COST, PricePolicy.SPECIAL, PricePolicy.MANUAL],
141
+ [qvet_catalog_1.QVET_SECTIONS.OTROS_ARTICULOS]: [PricePolicy.COST, PricePolicy.SPECIAL, PricePolicy.MANUAL],
142
+ [qvet_catalog_1.QVET_SECTIONS.SERVICIOS_MEDICOS]: [PricePolicy.COMP_SVC, PricePolicy.TEST, PricePolicy.SPECIAL, PricePolicy.MANUAL],
143
+ [qvet_catalog_1.QVET_SECTIONS.OTROS_SERVICIOS]: [PricePolicy.COMP_SVC, PricePolicy.SPECIAL, PricePolicy.MANUAL],
144
+ [qvet_catalog_1.QVET_SECTIONS.SERVICIOS_EXTERNOS]: [PricePolicy.EXT_COST, PricePolicy.SPECIAL, PricePolicy.MANUAL],
145
+ };
146
+ // =============================================================================
147
+ // HELPER FUNCTIONS
148
+ // =============================================================================
149
+ /**
150
+ * Get the auto-calculated tier based on unit cost.
151
+ *
152
+ * For auto-tier policies (COST_ML, COST, EXT_COST), the tier is determined
153
+ * by the unit cost. Cheaper items get higher margins because customers
154
+ * are less price-sensitive on low-cost items.
155
+ *
156
+ * @returns PricingTier, or null if the policy doesn't support auto-tier
157
+ */
158
+ function getAutoTier(policy, unitCostBI) {
159
+ const range = exports.AUTO_TIER_RANGES[policy];
160
+ if (!range)
161
+ return null;
162
+ if (unitCostBI <= range.highMarginMax)
163
+ return PricingTier.HIGH_MARGIN;
164
+ if (unitCostBI <= range.midMarginMax)
165
+ return PricingTier.MID_MARGIN;
166
+ return PricingTier.LOW_MARGIN;
167
+ }
168
+ /**
169
+ * Get the markup factor for a policy + tier combination.
170
+ *
171
+ * @returns The factor to multiply unitCostBI by, or null if no formula exists
172
+ */
173
+ function getMarkupFactor(policy, tier) {
174
+ const tierFactors = exports.MARKUP_FACTORS[policy];
175
+ if (!tierFactors)
176
+ return null;
177
+ return tierFactors[tier] ?? null;
178
+ }
179
+ /**
180
+ * Determine the default PricePolicy (and optionally PricingTier) for an item
181
+ * based on its section, sale unit, and conversion factor.
182
+ *
183
+ * Used when:
184
+ * - Creating a new catalog item from QVET sync
185
+ * - Migration script for existing items
186
+ * - Items that don't have pricePolicy set yet
187
+ */
188
+ function getDefaultPricePolicy(section, saleUnit, conversionFactor) {
189
+ // Non-sellable sections
190
+ if (exports.NON_SELLABLE_SECTIONS.includes(section)) {
191
+ return { policy: PricePolicy.NONE, tier: null };
192
+ }
193
+ // Services (market study pricing, no formula)
194
+ if (exports.COMP_SVC_SECTIONS.includes(section)) {
195
+ return { policy: PricePolicy.COMP_SVC, tier: null };
196
+ }
197
+ // External services (cost-based with auto-tier)
198
+ if (section === qvet_catalog_1.QVET_SECTIONS.SERVICIOS_EXTERNOS) {
199
+ return { policy: PricePolicy.EXT_COST, tier: null };
200
+ }
201
+ // Pharmacy sections
202
+ if (section === qvet_catalog_1.QVET_SECTIONS.FARMACIA || section === qvet_catalog_1.QVET_SECTIONS.FARMACIA_INTERNA) {
203
+ // Liquid items sold by ml
204
+ if (saleUnit === 'ML') {
205
+ return { policy: PricePolicy.COST_ML, tier: null }; // auto-tier
206
+ }
207
+ // Fractioned items (purchased in large packs, sold individually)
208
+ if (conversionFactor > 4) {
209
+ return { policy: PricePolicy.PHARM_FRAC, tier: PricingTier.HIGH_MARGIN };
210
+ }
211
+ // Original presentation
212
+ return { policy: PricePolicy.PHARM, tier: PricingTier.HIGH_MARGIN };
213
+ }
214
+ // Medical supplies & other articles (cost-based with auto-tier)
215
+ if (section === qvet_catalog_1.QVET_SECTIONS.INSUMOS_MEDICOS || section === qvet_catalog_1.QVET_SECTIONS.OTROS_ARTICULOS) {
216
+ return { policy: PricePolicy.COST, tier: null };
217
+ }
218
+ // Fallback: manual pricing
219
+ return { policy: PricePolicy.MANUAL, tier: null };
220
+ }
221
+ /**
222
+ * Calculate the recommended price PVP (con IVA) for a catalog item.
223
+ *
224
+ * Formula: unitPurchasePriceBI * markupFactor * (1 + vatSalePercent / 100)
225
+ *
226
+ * @returns The recommended price, or null if the policy has no formula
227
+ */
228
+ function calculateRecommendedPricePVP(params) {
229
+ const { pricePolicy, pricingTier, unitPurchasePriceBI, vatSalePercent } = params;
230
+ const factor = getMarkupFactor(pricePolicy, pricingTier);
231
+ if (factor === null)
232
+ return null;
233
+ if (unitPurchasePriceBI <= 0)
234
+ return null;
235
+ const priceBI = unitPurchasePriceBI * factor;
236
+ const pricePVP = priceBI * (1 + vatSalePercent / 100);
237
+ // Round to nearest multiple of 5
238
+ return Math.round(pricePVP / 5) * 5;
239
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,324 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const pricing_constants_1 = require("./pricing.constants");
4
+ // =============================================================================
5
+ // getAutoTier
6
+ // =============================================================================
7
+ describe('getAutoTier', () => {
8
+ describe('COST_ML (thresholds: ≤$5 HIGH, $5.01-$50 MID, >$50 LOW)', () => {
9
+ it('should return HIGH_MARGIN for cost ≤ $5', () => {
10
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 5)).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
11
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 1)).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
12
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 0.5)).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
13
+ });
14
+ it('should return MID_MARGIN for cost $5.01-$50', () => {
15
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 5.01)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
16
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 25)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
17
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 50)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
18
+ });
19
+ it('should return LOW_MARGIN for cost > $50', () => {
20
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 50.01)).toBe(pricing_constants_1.PricingTier.LOW_MARGIN);
21
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST_ML, 100)).toBe(pricing_constants_1.PricingTier.LOW_MARGIN);
22
+ });
23
+ });
24
+ describe('COST (thresholds: <$30 HIGH, $30-$500 MID, >$500 LOW)', () => {
25
+ it('should return HIGH_MARGIN for cost ≤ $30', () => {
26
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST, 29.99)).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
27
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST, 30)).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
28
+ });
29
+ it('should return MID_MARGIN for cost $30.01-$500', () => {
30
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST, 30.01)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
31
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST, 250)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
32
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST, 500)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
33
+ });
34
+ it('should return LOW_MARGIN for cost > $500', () => {
35
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST, 500.01)).toBe(pricing_constants_1.PricingTier.LOW_MARGIN);
36
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COST, 1000)).toBe(pricing_constants_1.PricingTier.LOW_MARGIN);
37
+ });
38
+ });
39
+ describe('EXT_COST (thresholds: ≤$600 HIGH, $600.01-$2000 MID, >$2000 LOW)', () => {
40
+ it('should return HIGH_MARGIN for cost ≤ $600', () => {
41
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.EXT_COST, 600)).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
42
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.EXT_COST, 100)).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
43
+ });
44
+ it('should return MID_MARGIN for cost $600.01-$2000', () => {
45
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.EXT_COST, 600.01)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
46
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.EXT_COST, 1500)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
47
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.EXT_COST, 2000)).toBe(pricing_constants_1.PricingTier.MID_MARGIN);
48
+ });
49
+ it('should return LOW_MARGIN for cost > $2000', () => {
50
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.EXT_COST, 2000.01)).toBe(pricing_constants_1.PricingTier.LOW_MARGIN);
51
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.EXT_COST, 5000)).toBe(pricing_constants_1.PricingTier.LOW_MARGIN);
52
+ });
53
+ });
54
+ describe('non-auto-tier policies', () => {
55
+ it('should return null for PHARM', () => {
56
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.PHARM, 100)).toBeNull();
57
+ });
58
+ it('should return null for PHARM_FRAC', () => {
59
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.PHARM_FRAC, 100)).toBeNull();
60
+ });
61
+ it('should return null for COMP_SVC', () => {
62
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.COMP_SVC, 100)).toBeNull();
63
+ });
64
+ it('should return null for NONE', () => {
65
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.NONE, 100)).toBeNull();
66
+ });
67
+ it('should return null for MANUAL', () => {
68
+ expect((0, pricing_constants_1.getAutoTier)(pricing_constants_1.PricePolicy.MANUAL, 100)).toBeNull();
69
+ });
70
+ });
71
+ });
72
+ // =============================================================================
73
+ // getMarkupFactor
74
+ // =============================================================================
75
+ describe('getMarkupFactor', () => {
76
+ it('should return correct factors for PHARM', () => {
77
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.PHARM, pricing_constants_1.PricingTier.LOW_MARGIN)).toBe(1.33);
78
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.PHARM, pricing_constants_1.PricingTier.MID_MARGIN)).toBe(1.67);
79
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.PHARM, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBe(2.00);
80
+ });
81
+ it('should return correct factors for PHARM_FRAC', () => {
82
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.PHARM_FRAC, pricing_constants_1.PricingTier.LOW_MARGIN)).toBe(2.00);
83
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.PHARM_FRAC, pricing_constants_1.PricingTier.MID_MARGIN)).toBe(2.50);
84
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.PHARM_FRAC, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBe(3.00);
85
+ });
86
+ it('should return correct factors for COST_ML', () => {
87
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.COST_ML, pricing_constants_1.PricingTier.LOW_MARGIN)).toBe(3.00);
88
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.COST_ML, pricing_constants_1.PricingTier.MID_MARGIN)).toBe(5.00);
89
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.COST_ML, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBe(10.00);
90
+ });
91
+ it('should return correct factors for COST', () => {
92
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.COST, pricing_constants_1.PricingTier.LOW_MARGIN)).toBe(2.00);
93
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.COST, pricing_constants_1.PricingTier.MID_MARGIN)).toBe(3.00);
94
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.COST, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBe(5.00);
95
+ });
96
+ it('should return correct factors for EXT_COST', () => {
97
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.EXT_COST, pricing_constants_1.PricingTier.LOW_MARGIN)).toBe(1.40);
98
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.EXT_COST, pricing_constants_1.PricingTier.MID_MARGIN)).toBe(1.70);
99
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.EXT_COST, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBe(2.00);
100
+ });
101
+ it('should return null for policies without a formula', () => {
102
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.COMP_SVC, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBeNull();
103
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.TEST, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBeNull();
104
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.SPECIAL, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBeNull();
105
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.MANUAL, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBeNull();
106
+ expect((0, pricing_constants_1.getMarkupFactor)(pricing_constants_1.PricePolicy.NONE, pricing_constants_1.PricingTier.HIGH_MARGIN)).toBeNull();
107
+ });
108
+ });
109
+ // =============================================================================
110
+ // getDefaultPricePolicy
111
+ // =============================================================================
112
+ describe('getDefaultPricePolicy', () => {
113
+ describe('non-sellable sections', () => {
114
+ it('should return NONE for CONSUMO INTERNO', () => {
115
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('CONSUMO INTERNO', 'UN', 1);
116
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.NONE);
117
+ });
118
+ it('should return NONE for EQUIPAMIENTO', () => {
119
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('EQUIPAMIENTO', 'UN', 1);
120
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.NONE);
121
+ });
122
+ it('should return NONE for INSUMOS ESCANDALLO', () => {
123
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('INSUMOS ESCANDALLO', 'UN', 1);
124
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.NONE);
125
+ });
126
+ });
127
+ describe('service sections', () => {
128
+ it('should return COMP_SVC for SERVICIOS MEDICOS', () => {
129
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('SERVICIOS MEDICOS', 'UN', 1);
130
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.COMP_SVC);
131
+ expect(result.tier).toBeNull();
132
+ });
133
+ it('should return COMP_SVC for OTROS SERVICIOS', () => {
134
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('OTROS SERVICIOS', 'UN', 1);
135
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.COMP_SVC);
136
+ });
137
+ it('should return EXT_COST for SERVICIOS EXTERNOS', () => {
138
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('SERVICIOS EXTERNOS', 'UN', 1);
139
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.EXT_COST);
140
+ expect(result.tier).toBeNull(); // auto-tier
141
+ });
142
+ });
143
+ describe('pharmacy sections', () => {
144
+ it('should return COST_ML when saleUnit is ML', () => {
145
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('FARMACIA', 'ML', 100);
146
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.COST_ML);
147
+ expect(result.tier).toBeNull(); // auto-tier
148
+ });
149
+ it('should return PHARM_FRAC when conversionFactor > 4', () => {
150
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('FARMACIA', 'UN', 10);
151
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.PHARM_FRAC);
152
+ expect(result.tier).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
153
+ });
154
+ it('should return PHARM for regular pharmacy items', () => {
155
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('FARMACIA', 'UN', 1);
156
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.PHARM);
157
+ expect(result.tier).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
158
+ });
159
+ it('should return PHARM when conversionFactor is exactly 4', () => {
160
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('FARMACIA', 'UN', 4);
161
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.PHARM);
162
+ expect(result.tier).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
163
+ });
164
+ it('should return PHARM_FRAC when conversionFactor is 5', () => {
165
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('FARMACIA', 'UN', 5);
166
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.PHARM_FRAC);
167
+ expect(result.tier).toBe(pricing_constants_1.PricingTier.HIGH_MARGIN);
168
+ });
169
+ it('should work for FARMACIA INTERNA with ML', () => {
170
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('FARMACIA INTERNA', 'ML', 50);
171
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.COST_ML);
172
+ });
173
+ it('should work for FARMACIA INTERNA with high conversionFactor', () => {
174
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('FARMACIA INTERNA', 'UN', 20);
175
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.PHARM_FRAC);
176
+ });
177
+ });
178
+ describe('cost-based sections', () => {
179
+ it('should return COST for INSUMOS MEDICOS', () => {
180
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('INSUMOS MEDICOS', 'UN', 1);
181
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.COST);
182
+ expect(result.tier).toBeNull(); // auto-tier
183
+ });
184
+ it('should return COST for OTROS ARTICULOS', () => {
185
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('OTROS ARTICULOS', 'UN', 1);
186
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.COST);
187
+ expect(result.tier).toBeNull();
188
+ });
189
+ });
190
+ describe('unknown section', () => {
191
+ it('should return MANUAL as fallback', () => {
192
+ const result = (0, pricing_constants_1.getDefaultPricePolicy)('UNKNOWN SECTION', 'UN', 1);
193
+ expect(result.policy).toBe(pricing_constants_1.PricePolicy.MANUAL);
194
+ });
195
+ });
196
+ });
197
+ // =============================================================================
198
+ // calculateRecommendedPricePVP
199
+ // =============================================================================
200
+ describe('calculateRecommendedPricePVP', () => {
201
+ it('should calculate price for PHARM HIGH_MARGIN (rounded to multiple of 5)', () => {
202
+ // $100 cost * 2.00 factor * 1.16 VAT = $232.00 → $230
203
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
204
+ pricePolicy: pricing_constants_1.PricePolicy.PHARM,
205
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
206
+ unitPurchasePriceBI: 100,
207
+ vatSalePercent: 16,
208
+ });
209
+ expect(result).toBe(230);
210
+ });
211
+ it('should calculate price for COST_ML LOW_MARGIN (rounded to multiple of 5)', () => {
212
+ // $60/ml cost * 3.00 factor * 1.16 VAT = $208.80 → $210
213
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
214
+ pricePolicy: pricing_constants_1.PricePolicy.COST_ML,
215
+ pricingTier: pricing_constants_1.PricingTier.LOW_MARGIN,
216
+ unitPurchasePriceBI: 60,
217
+ vatSalePercent: 16,
218
+ });
219
+ expect(result).toBe(210);
220
+ });
221
+ it('should calculate price for COST MID_MARGIN with 0% VAT', () => {
222
+ // $50 cost * 3.00 factor * 1.00 = $150.00 → $150
223
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
224
+ pricePolicy: pricing_constants_1.PricePolicy.COST,
225
+ pricingTier: pricing_constants_1.PricingTier.MID_MARGIN,
226
+ unitPurchasePriceBI: 50,
227
+ vatSalePercent: 0,
228
+ });
229
+ expect(result).toBe(150);
230
+ });
231
+ it('should calculate price for EXT_COST MID_MARGIN (rounded to multiple of 5)', () => {
232
+ // $1000 cost * 1.70 factor * 1.16 VAT = $1972.00 → $1970
233
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
234
+ pricePolicy: pricing_constants_1.PricePolicy.EXT_COST,
235
+ pricingTier: pricing_constants_1.PricingTier.MID_MARGIN,
236
+ unitPurchasePriceBI: 1000,
237
+ vatSalePercent: 16,
238
+ });
239
+ expect(result).toBe(1970);
240
+ });
241
+ it('should return null for COMP_SVC (no formula)', () => {
242
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
243
+ pricePolicy: pricing_constants_1.PricePolicy.COMP_SVC,
244
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
245
+ unitPurchasePriceBI: 100,
246
+ vatSalePercent: 16,
247
+ });
248
+ expect(result).toBeNull();
249
+ });
250
+ it('should return null for MANUAL', () => {
251
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
252
+ pricePolicy: pricing_constants_1.PricePolicy.MANUAL,
253
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
254
+ unitPurchasePriceBI: 100,
255
+ vatSalePercent: 16,
256
+ });
257
+ expect(result).toBeNull();
258
+ });
259
+ it('should return null for NONE', () => {
260
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
261
+ pricePolicy: pricing_constants_1.PricePolicy.NONE,
262
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
263
+ unitPurchasePriceBI: 100,
264
+ vatSalePercent: 16,
265
+ });
266
+ expect(result).toBeNull();
267
+ });
268
+ it('should return null for TEST', () => {
269
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
270
+ pricePolicy: pricing_constants_1.PricePolicy.TEST,
271
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
272
+ unitPurchasePriceBI: 100,
273
+ vatSalePercent: 16,
274
+ });
275
+ expect(result).toBeNull();
276
+ });
277
+ it('should return null for SPECIAL', () => {
278
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
279
+ pricePolicy: pricing_constants_1.PricePolicy.SPECIAL,
280
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
281
+ unitPurchasePriceBI: 100,
282
+ vatSalePercent: 16,
283
+ });
284
+ expect(result).toBeNull();
285
+ });
286
+ it('should return null when unitPurchasePriceBI is 0', () => {
287
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
288
+ pricePolicy: pricing_constants_1.PricePolicy.PHARM,
289
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
290
+ unitPurchasePriceBI: 0,
291
+ vatSalePercent: 16,
292
+ });
293
+ expect(result).toBeNull();
294
+ });
295
+ it('should return null when unitPurchasePriceBI is negative', () => {
296
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
297
+ pricePolicy: pricing_constants_1.PricePolicy.PHARM,
298
+ pricingTier: pricing_constants_1.PricingTier.HIGH_MARGIN,
299
+ unitPurchasePriceBI: -10,
300
+ vatSalePercent: 16,
301
+ });
302
+ expect(result).toBeNull();
303
+ });
304
+ it('should round to nearest multiple of 5', () => {
305
+ // $33.33 * 1.33 * 1.16 = $51.42... → $50
306
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
307
+ pricePolicy: pricing_constants_1.PricePolicy.PHARM,
308
+ pricingTier: pricing_constants_1.PricingTier.LOW_MARGIN,
309
+ unitPurchasePriceBI: 33.33,
310
+ vatSalePercent: 16,
311
+ });
312
+ expect(result).toBe(50);
313
+ });
314
+ it('should round up when closer to next multiple of 5', () => {
315
+ // $38 * 1.33 * 1.16 = $58.63... → $60
316
+ const result = (0, pricing_constants_1.calculateRecommendedPricePVP)({
317
+ pricePolicy: pricing_constants_1.PricePolicy.PHARM,
318
+ pricingTier: pricing_constants_1.PricingTier.LOW_MARGIN,
319
+ unitPurchasePriceBI: 38,
320
+ vatSalePercent: 16,
321
+ });
322
+ expect(result).toBe(60);
323
+ });
324
+ });
@@ -4,8 +4,7 @@
4
4
  * Request DTOs for catalog item endpoints.
5
5
  */
6
6
  import { UsageType, SyncStatus, StockPolicy } from '../../constants/catalog-item.constants';
7
- import { MarketCompetition } from '../../constants/market-competition.constants';
8
- import { PricingRuleCode } from '../../constants/pricing-rule.constants';
7
+ import { PricePolicy, PricingTier } from '../../constants/pricing.constants';
9
8
  /**
10
9
  * Query filters for listing catalog items
11
10
  *
@@ -98,11 +97,11 @@ export interface UpdateCatalogItemHvpDataRequest {
98
97
  notes?: string;
99
98
  /** Internal category */
100
99
  internalCategory?: string;
101
- /** Market competition level */
102
- marketCompetition?: MarketCompetition;
103
- /** Pricing rule code */
104
- pricingRuleCode?: PricingRuleCode;
105
- /** Recommended price (calculated from pricing rule) */
100
+ /** Pricing policy (how the item is priced) */
101
+ pricePolicy?: PricePolicy;
102
+ /** Pricing tier (margin level) */
103
+ pricingTier?: PricingTier;
104
+ /** Recommended price PVP (calculated from policy + tier + cost) */
106
105
  recommendedPrice?: number;
107
106
  /** How stock levels are managed */
108
107
  stockPolicy?: StockPolicy;
@@ -5,8 +5,7 @@
5
5
  */
6
6
  import { SyncField } from '../../types/sync-field.types';
7
7
  import { UsageType, SyncStatus, StockPolicy } from '../../constants/catalog-item.constants';
8
- import { MarketCompetition } from '../../constants/market-competition.constants';
9
- import { PricingRuleCode } from '../../constants/pricing-rule.constants';
8
+ import { PricePolicy, PricingTier } from '../../constants/pricing.constants';
10
9
  import { Warehouse } from '../../types/qvet.types';
11
10
  /**
12
11
  * Warehouse stock response
@@ -127,13 +126,13 @@ export interface CatalogItemDetailResponse {
127
126
  usageType: UsageType;
128
127
  notes?: string;
129
128
  internalCategory?: string;
130
- /** Nivel de competencia en el mercado */
131
- marketCompetition?: MarketCompetition;
132
- /** Código de regla de precio - determines target margin */
133
- pricingRuleCode?: PricingRuleCode;
129
+ /** Política de precio (cómo se calcula el precio) */
130
+ pricePolicy?: PricePolicy;
131
+ /** Nivel de margen */
132
+ pricingTier?: PricingTier;
134
133
  /**
135
- * Precio recomendado (PVP con IVA)
136
- * Calculated from: cost * (1 + targetMargin/100) * (1 + VAT/100)
134
+ * Precio recomendado PVP (con IVA)
135
+ * Calculated from: unitCostBI * markupFactor * (1 + VAT/100)
137
136
  */
138
137
  recommendedPrice?: number;
139
138
  /** How stock levels are managed */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hvp-shared",
3
- "version": "6.44.0",
3
+ "version": "6.45.1",
4
4
  "description": "Shared types and utilities for HVP backend and frontend",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,19 +0,0 @@
1
- /**
2
- * Market Competition Level
3
- *
4
- * Indicates the level of market competition for a product/service.
5
- * Used to determine pricing strategy and target margins.
6
- */
7
- export declare enum MarketCompetition {
8
- HIGH = "high",
9
- MEDIUM = "medium",
10
- LOW = "low"
11
- }
12
- /**
13
- * Spanish labels for MarketCompetition
14
- */
15
- export declare const MARKET_COMPETITION_LABELS: Record<MarketCompetition, string>;
16
- /**
17
- * All market competition values as array (for dropdowns)
18
- */
19
- export declare const MARKET_COMPETITION_VALUES: MarketCompetition[];
@@ -1,27 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MARKET_COMPETITION_VALUES = exports.MARKET_COMPETITION_LABELS = exports.MarketCompetition = void 0;
4
- /**
5
- * Market Competition Level
6
- *
7
- * Indicates the level of market competition for a product/service.
8
- * Used to determine pricing strategy and target margins.
9
- */
10
- var MarketCompetition;
11
- (function (MarketCompetition) {
12
- MarketCompetition["HIGH"] = "high";
13
- MarketCompetition["MEDIUM"] = "medium";
14
- MarketCompetition["LOW"] = "low";
15
- })(MarketCompetition || (exports.MarketCompetition = MarketCompetition = {}));
16
- /**
17
- * Spanish labels for MarketCompetition
18
- */
19
- exports.MARKET_COMPETITION_LABELS = {
20
- [MarketCompetition.HIGH]: 'Competencia Alta',
21
- [MarketCompetition.MEDIUM]: 'Competencia Media',
22
- [MarketCompetition.LOW]: 'Competencia Baja',
23
- };
24
- /**
25
- * All market competition values as array (for dropdowns)
26
- */
27
- exports.MARKET_COMPETITION_VALUES = Object.values(MarketCompetition);
@@ -1,67 +0,0 @@
1
- /**
2
- * Pricing Rule System
3
- *
4
- * Codes and target margins for pricing strategy.
5
- * Each product can be assigned a pricing rule that determines its target margin.
6
- */
7
- /**
8
- * Pricing Rule Codes (3-letter abbreviations)
9
- */
10
- export declare enum PricingRuleCode {
11
- ECA = "ECA",// External Commission Alto - 40%
12
- ECM = "ECM",// External Commission Medio - 70%
13
- ECB = "ECB",// External Commission Bajo - 100%
14
- ESA = "ESA",// External Sin-comision Alto - 50%
15
- ESM = "ESM",// External Sin-comision Medio - 75%
16
- ESB = "ESB",// External Sin-comision Bajo - 100%
17
- FRA = "FRA",// Fraccionada Alta - 100%
18
- FRM = "FRM",// Fraccionada Media - 150%
19
- FRB = "FRB",// Fraccionada Baja - 200%
20
- MGA = "MGA",// ML Grande Alta - 400%
21
- MGB = "MGB",// ML Grande Baja/Media - 800%
22
- MPA = "MPA",// ML Pequeño Alta - 150%
23
- MPB = "MPB",// ML Pequeño Baja/Media - 250%
24
- POA = "POA",// Presentación Original Alta - 33.33%
25
- POM = "POM",// Presentación Original Media - 66.67%
26
- POB = "POB",// Presentación Original Baja - 100%
27
- XPA = "XPA",// Productos caros - 100%
28
- XPM = "XPM",// Productos precio medio (<$500) - 200%
29
- XPB = "XPB",// Productos baratos (<$25) - 400%
30
- TUS = "TUS",// Tests de Un Solo uso - 150%
31
- RGL = "RGL",// Regla General - 100%
32
- PRE = "PRE",// Precio Específico (manual) - 0%
33
- NRA = "NRA"
34
- }
35
- /**
36
- * Pricing Rule Categories for grouping in UI
37
- */
38
- export type PricingRuleCategory = 'external_commission' | 'external_no_commission' | 'fractioned' | 'fractioned_ml_large' | 'fractioned_ml_small' | 'original' | 'price_range' | 'special' | 'control';
39
- /**
40
- * Category labels in Spanish
41
- */
42
- export declare const PRICING_RULE_CATEGORY_LABELS: Record<PricingRuleCategory, string>;
43
- /**
44
- * Pricing Rule Information
45
- */
46
- export interface PricingRuleInfo {
47
- code: PricingRuleCode;
48
- label: string;
49
- targetMargin: number;
50
- category: PricingRuleCategory;
51
- }
52
- /**
53
- * Complete pricing rules map
54
- */
55
- export declare const PRICING_RULES: Record<PricingRuleCode, PricingRuleInfo>;
56
- /**
57
- * Get pricing rules grouped by category (for UI dropdowns)
58
- */
59
- export declare function getPricingRulesByCategory(): Record<PricingRuleCategory, PricingRuleInfo[]>;
60
- /**
61
- * Get pricing rule info by code
62
- */
63
- export declare function getPricingRuleInfo(code: PricingRuleCode): PricingRuleInfo;
64
- /**
65
- * All pricing rule codes as array
66
- */
67
- export declare const PRICING_RULE_CODES: PricingRuleCode[];
@@ -1,245 +0,0 @@
1
- "use strict";
2
- /**
3
- * Pricing Rule System
4
- *
5
- * Codes and target margins for pricing strategy.
6
- * Each product can be assigned a pricing rule that determines its target margin.
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.PRICING_RULE_CODES = exports.PRICING_RULES = exports.PRICING_RULE_CATEGORY_LABELS = exports.PricingRuleCode = void 0;
10
- exports.getPricingRulesByCategory = getPricingRulesByCategory;
11
- exports.getPricingRuleInfo = getPricingRuleInfo;
12
- /**
13
- * Pricing Rule Codes (3-letter abbreviations)
14
- */
15
- var PricingRuleCode;
16
- (function (PricingRuleCode) {
17
- // Servicios externos CON comisión
18
- PricingRuleCode["ECA"] = "ECA";
19
- PricingRuleCode["ECM"] = "ECM";
20
- PricingRuleCode["ECB"] = "ECB";
21
- // Servicios externos SIN comisión
22
- PricingRuleCode["ESA"] = "ESA";
23
- PricingRuleCode["ESM"] = "ESM";
24
- PricingRuleCode["ESB"] = "ESB";
25
- // Venta fraccionada de productos
26
- PricingRuleCode["FRA"] = "FRA";
27
- PricingRuleCode["FRM"] = "FRM";
28
- PricingRuleCode["FRB"] = "FRB";
29
- // Venta fraccionada de ml de frascos GRANDES
30
- PricingRuleCode["MGA"] = "MGA";
31
- PricingRuleCode["MGB"] = "MGB";
32
- // Venta fraccionada de ml de frascos PEQUEÑOS
33
- PricingRuleCode["MPA"] = "MPA";
34
- PricingRuleCode["MPB"] = "MPB";
35
- // Venta en presentación original
36
- PricingRuleCode["POA"] = "POA";
37
- PricingRuleCode["POM"] = "POM";
38
- PricingRuleCode["POB"] = "POB";
39
- // Reglas por rango de precio
40
- PricingRuleCode["XPA"] = "XPA";
41
- PricingRuleCode["XPM"] = "XPM";
42
- PricingRuleCode["XPB"] = "XPB";
43
- // Especiales
44
- PricingRuleCode["TUS"] = "TUS";
45
- PricingRuleCode["RGL"] = "RGL";
46
- // Control
47
- PricingRuleCode["PRE"] = "PRE";
48
- PricingRuleCode["NRA"] = "NRA";
49
- })(PricingRuleCode || (exports.PricingRuleCode = PricingRuleCode = {}));
50
- /**
51
- * Category labels in Spanish
52
- */
53
- exports.PRICING_RULE_CATEGORY_LABELS = {
54
- external_commission: 'Servicios externos con comisión',
55
- external_no_commission: 'Servicios externos sin comisión',
56
- fractioned: 'Venta fraccionada de productos',
57
- fractioned_ml_large: 'Venta fraccionada ml frascos grandes',
58
- fractioned_ml_small: 'Venta fraccionada ml frascos pequeños',
59
- original: 'Venta en presentación original',
60
- price_range: 'Por rango de precio',
61
- special: 'Reglas especiales',
62
- control: 'Control',
63
- };
64
- /**
65
- * Complete pricing rules map
66
- */
67
- exports.PRICING_RULES = {
68
- // Servicios externos CON comisión
69
- [PricingRuleCode.ECA]: {
70
- code: PricingRuleCode.ECA,
71
- label: 'Servicios externos con comisión de precio alto',
72
- targetMargin: 40,
73
- category: 'external_commission',
74
- },
75
- [PricingRuleCode.ECM]: {
76
- code: PricingRuleCode.ECM,
77
- label: 'Servicios externos con comisión de precio medio',
78
- targetMargin: 70,
79
- category: 'external_commission',
80
- },
81
- [PricingRuleCode.ECB]: {
82
- code: PricingRuleCode.ECB,
83
- label: 'Servicios externos con comisión de precio bajo',
84
- targetMargin: 100,
85
- category: 'external_commission',
86
- },
87
- // Servicios externos SIN comisión
88
- [PricingRuleCode.ESA]: {
89
- code: PricingRuleCode.ESA,
90
- label: 'Servicios externos sin comisión de precio alto',
91
- targetMargin: 50,
92
- category: 'external_no_commission',
93
- },
94
- [PricingRuleCode.ESM]: {
95
- code: PricingRuleCode.ESM,
96
- label: 'Servicios externos sin comisión de precio medio',
97
- targetMargin: 75,
98
- category: 'external_no_commission',
99
- },
100
- [PricingRuleCode.ESB]: {
101
- code: PricingRuleCode.ESB,
102
- label: 'Servicios externos sin comisión de precio bajo',
103
- targetMargin: 100,
104
- category: 'external_no_commission',
105
- },
106
- // Venta fraccionada de productos
107
- [PricingRuleCode.FRA]: {
108
- code: PricingRuleCode.FRA,
109
- label: 'Venta fraccionada de productos competencia alta',
110
- targetMargin: 100,
111
- category: 'fractioned',
112
- },
113
- [PricingRuleCode.FRM]: {
114
- code: PricingRuleCode.FRM,
115
- label: 'Venta fraccionada de productos competencia media',
116
- targetMargin: 150,
117
- category: 'fractioned',
118
- },
119
- [PricingRuleCode.FRB]: {
120
- code: PricingRuleCode.FRB,
121
- label: 'Venta fraccionada de productos competencia baja',
122
- targetMargin: 200,
123
- category: 'fractioned',
124
- },
125
- // Venta fraccionada de ml de frascos GRANDES
126
- [PricingRuleCode.MGA]: {
127
- code: PricingRuleCode.MGA,
128
- label: 'Venta fraccionada de ml de frascos grandes competencia alta',
129
- targetMargin: 400,
130
- category: 'fractioned_ml_large',
131
- },
132
- [PricingRuleCode.MGB]: {
133
- code: PricingRuleCode.MGB,
134
- label: 'Venta fraccionada de ml de frascos grandes competencia baja o media',
135
- targetMargin: 800,
136
- category: 'fractioned_ml_large',
137
- },
138
- // Venta fraccionada de ml de frascos PEQUEÑOS
139
- [PricingRuleCode.MPA]: {
140
- code: PricingRuleCode.MPA,
141
- label: 'Venta fraccionada de ml de frascos pequeños competencia alta',
142
- targetMargin: 150,
143
- category: 'fractioned_ml_small',
144
- },
145
- [PricingRuleCode.MPB]: {
146
- code: PricingRuleCode.MPB,
147
- label: 'Venta fraccionada de ml de frascos pequeños competencia baja o media',
148
- targetMargin: 250,
149
- category: 'fractioned_ml_small',
150
- },
151
- // Venta en presentación original
152
- [PricingRuleCode.POA]: {
153
- code: PricingRuleCode.POA,
154
- label: 'Venta en presentación original productos competencia alta',
155
- targetMargin: 33.33,
156
- category: 'original',
157
- },
158
- [PricingRuleCode.POM]: {
159
- code: PricingRuleCode.POM,
160
- label: 'Venta en presentación original productos competencia media',
161
- targetMargin: 66.67,
162
- category: 'original',
163
- },
164
- [PricingRuleCode.POB]: {
165
- code: PricingRuleCode.POB,
166
- label: 'Venta en presentación original productos competencia baja',
167
- targetMargin: 100,
168
- category: 'original',
169
- },
170
- // Reglas por rango de precio
171
- [PricingRuleCode.XPA]: {
172
- code: PricingRuleCode.XPA,
173
- label: 'Venta de productos caros',
174
- targetMargin: 100,
175
- category: 'price_range',
176
- },
177
- [PricingRuleCode.XPM]: {
178
- code: PricingRuleCode.XPM,
179
- label: 'Venta de productos de precio medio (<$500)',
180
- targetMargin: 200,
181
- category: 'price_range',
182
- },
183
- [PricingRuleCode.XPB]: {
184
- code: PricingRuleCode.XPB,
185
- label: 'Venta de productos baratos (<$25)',
186
- targetMargin: 400,
187
- category: 'price_range',
188
- },
189
- // Especiales
190
- [PricingRuleCode.TUS]: {
191
- code: PricingRuleCode.TUS,
192
- label: 'Los precios de tests de un solo uso (ej. CANIV-4)',
193
- targetMargin: 150,
194
- category: 'special',
195
- },
196
- [PricingRuleCode.RGL]: {
197
- code: PricingRuleCode.RGL,
198
- label: 'Regla general',
199
- targetMargin: 100,
200
- category: 'special',
201
- },
202
- // Control
203
- [PricingRuleCode.PRE]: {
204
- code: PricingRuleCode.PRE,
205
- label: 'Precio específico',
206
- targetMargin: 0,
207
- category: 'control',
208
- },
209
- [PricingRuleCode.NRA]: {
210
- code: PricingRuleCode.NRA,
211
- label: 'Exclusión reglas',
212
- targetMargin: 0,
213
- category: 'control',
214
- },
215
- };
216
- /**
217
- * Get pricing rules grouped by category (for UI dropdowns)
218
- */
219
- function getPricingRulesByCategory() {
220
- const grouped = {
221
- external_commission: [],
222
- external_no_commission: [],
223
- fractioned: [],
224
- fractioned_ml_large: [],
225
- fractioned_ml_small: [],
226
- original: [],
227
- price_range: [],
228
- special: [],
229
- control: [],
230
- };
231
- Object.values(exports.PRICING_RULES).forEach((rule) => {
232
- grouped[rule.category].push(rule);
233
- });
234
- return grouped;
235
- }
236
- /**
237
- * Get pricing rule info by code
238
- */
239
- function getPricingRuleInfo(code) {
240
- return exports.PRICING_RULES[code];
241
- }
242
- /**
243
- * All pricing rule codes as array
244
- */
245
- exports.PRICING_RULE_CODES = Object.values(PricingRuleCode);