@fiado/type-kit 3.476.0 → 3.478.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/_test_/unit/retailCatalog/storeCatalogItemSaga.test.ts +18 -0
- package/_test_/unit/retailPromotions/constants.test.ts +15 -2
- package/_test_/unit/retailWizard/promotionM2mRequests.test.ts +37 -0
- package/bin/retailCatalog/dtos/responses/StoreCatalogItem.d.ts +4 -0
- package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.d.ts +2 -2
- package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +8 -1
- package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.d.ts +14 -1
- package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +13 -0
- package/bin/retailWizard/dtos/requests/SetAnchorGiftsRequest.d.ts +3 -0
- package/bin/retailWizard/dtos/requests/SetAnchorGiftsRequest.js +18 -0
- package/package.json +1 -1
- package/src/retailCatalog/dtos/responses/StoreCatalogItem.ts +4 -0
- package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +9 -2
- package/src/retailPromotions/enums/RetailPromotionsErrorCodeEnum.ts +13 -0
- package/src/retailWizard/dtos/requests/SetAnchorGiftsRequest.ts +18 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import * as RetailCatalog from '../../../src/retailCatalog';
|
|
3
|
+
|
|
4
|
+
type SagaFields = Pick<RetailCatalog.StoreCatalogItem, 'sku' | 'listPriceCents' | 'salePriority'>;
|
|
5
|
+
|
|
6
|
+
describe('StoreCatalogItem — campos de la venta saga', () => {
|
|
7
|
+
it('acepta listPriceCents y salePriority', () => {
|
|
8
|
+
const item: SagaFields = { sku: 'SKU-1', listPriceCents: 129900, salePriority: 80 };
|
|
9
|
+
expect(item.listPriceCents).toBe(129900);
|
|
10
|
+
expect(item.salePriority).toBe(80);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('sigue válido sin listPriceCents ni salePriority', () => {
|
|
14
|
+
const item: SagaFields = { sku: 'SKU-1' };
|
|
15
|
+
expect(item.listPriceCents).toBeUndefined();
|
|
16
|
+
expect(item.salePriority).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -38,12 +38,12 @@ describe('resolveSalePromotionTransition', () => {
|
|
|
38
38
|
describe('catálogo de errores', () => {
|
|
39
39
|
it('cada code tiene su status del catálogo cerrado', () => {
|
|
40
40
|
for (const code of Object.values(RetailPromotionsErrorCodeEnum)) {
|
|
41
|
-
expect([403, 404, 409, 422]).toContain(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[code]);
|
|
41
|
+
expect([400, 403, 404, 409, 422, 502]).toContain(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[code]);
|
|
42
42
|
}
|
|
43
43
|
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_FOUND).toBe(404);
|
|
44
44
|
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.DISCOUNT_AFTER_QUOTE).toBe(409);
|
|
45
45
|
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_OWNED).toBe(422);
|
|
46
|
-
expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(
|
|
46
|
+
expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(28);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it('promo de regalo y de venta mandadas en el campo equivocado son 422', () => {
|
|
@@ -58,6 +58,19 @@ describe('catálogo de errores', () => {
|
|
|
58
58
|
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.MISSING_STORE_SCOPE).toBe(403);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
it.each([
|
|
62
|
+
['SESSION_STILL_ACTIVE', 409],
|
|
63
|
+
['WIZARD_SESSION_NOT_FOUND', 404],
|
|
64
|
+
['UPSTREAM_UNAVAILABLE', 502],
|
|
65
|
+
['MISSING_TENANT_SCOPE', 403],
|
|
66
|
+
['PROMOTION_NOT_REVALIDATED', 409],
|
|
67
|
+
['SALE_PROMOTION_HOLD_CONFLICT', 409],
|
|
68
|
+
['INVALID_NEXT_TOKEN', 400],
|
|
69
|
+
] as const)('%s es %i', (code, status) => {
|
|
70
|
+
expect(RetailPromotionsErrorCodeEnum[code]).toBe(code);
|
|
71
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[code]).toBe(status);
|
|
72
|
+
});
|
|
73
|
+
|
|
61
74
|
it('promo y código con rangos, signo o palancas inválidos son 422', () => {
|
|
62
75
|
expect(RetailPromotionsErrorCodeEnum.INVALID_SALE_PROMOTION).toBe('INVALID_SALE_PROMOTION');
|
|
63
76
|
expect(RetailPromotionsErrorCodeEnum.INVALID_DISCOUNT_CODE).toBe('INVALID_DISCOUNT_CODE');
|
|
@@ -83,6 +83,43 @@ describe('SetAnchorGiftsRequest', () => {
|
|
|
83
83
|
expect(errorsOf(SetAnchorGiftsRequest, { ...ok, items: [{ sku: '' }] })).toContain('items.0.sku'));
|
|
84
84
|
it('exige items como arreglo', () =>
|
|
85
85
|
expect(errorsOf(SetAnchorGiftsRequest, { promotionId: null, promotionVersion: null })).toContain('items'));
|
|
86
|
+
|
|
87
|
+
describe('promo por item', () => {
|
|
88
|
+
const own = { sku: 'CASE-1', promotionId: 'promo-2', promotionVersion: 3 };
|
|
89
|
+
|
|
90
|
+
it('acepta un item sin promo propia (usa la de la raíz)', () => {
|
|
91
|
+
const req = plainToInstance(SetAnchorGiftsRequest, ok);
|
|
92
|
+
expect(validateSync(req)).toEqual([]);
|
|
93
|
+
expect(req.items[0].promotionId).toBeUndefined();
|
|
94
|
+
expect(req.items[0].promotionVersion).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
it('acepta un item con promotionId y promotionVersion', () =>
|
|
97
|
+
expect(errorsOf(SetAnchorGiftsRequest, { ...ok, items: [own] })).toEqual([]));
|
|
98
|
+
it('exige promotionVersion si el item trae solo promotionId', () =>
|
|
99
|
+
expect(errorsOf(SetAnchorGiftsRequest, { ...ok, items: [{ sku: 'CASE-1', promotionId: 'promo-2' }] })).toContain(
|
|
100
|
+
'items.0.promotionVersion',
|
|
101
|
+
));
|
|
102
|
+
it('exige promotionId si el item trae solo promotionVersion', () =>
|
|
103
|
+
expect(errorsOf(SetAnchorGiftsRequest, { ...ok, items: [{ sku: 'CASE-1', promotionVersion: 3 }] })).toContain(
|
|
104
|
+
'items.0.promotionId',
|
|
105
|
+
));
|
|
106
|
+
it('rechaza la versión 0 en el item', () =>
|
|
107
|
+
expect(errorsOf(SetAnchorGiftsRequest, { ...ok, items: [{ ...own, promotionVersion: 0 }] })).toContain(
|
|
108
|
+
'items.0.promotionVersion',
|
|
109
|
+
));
|
|
110
|
+
it('rechaza promotionId vacío en el item', () =>
|
|
111
|
+
expect(errorsOf(SetAnchorGiftsRequest, { ...ok, items: [{ ...own, promotionId: '' }] })).toContain(
|
|
112
|
+
'items.0.promotionId',
|
|
113
|
+
));
|
|
114
|
+
it('acepta dos items de la misma ancla con promos distintas', () => {
|
|
115
|
+
const req = plainToInstance(SetAnchorGiftsRequest, {
|
|
116
|
+
...ok,
|
|
117
|
+
items: [own, { sku: 'MICA-1', promotionId: 'promo-9', promotionVersion: 1 }],
|
|
118
|
+
});
|
|
119
|
+
expect(validateSync(req)).toEqual([]);
|
|
120
|
+
expect(req.items.map((i) => i.promotionId)).toEqual(['promo-2', 'promo-9']);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
86
123
|
});
|
|
87
124
|
|
|
88
125
|
describe('ApplySalePromotionRequest', () => {
|
|
@@ -36,6 +36,10 @@ export interface StoreCatalogItem {
|
|
|
36
36
|
itemType?: ItemTypeEnum;
|
|
37
37
|
/** Nombre visible para kits y servicios, que no tienen marca/modelo. */
|
|
38
38
|
description?: string;
|
|
39
|
+
/** Precio de lista de respaldo para un regalo: el precio vigente o, si no hay, product.basePriceCents. */
|
|
40
|
+
listPriceCents?: number;
|
|
41
|
+
/** Prioridad de venta de 0 a 100; «Recomendados» ordena por ella. */
|
|
42
|
+
salePriority?: number;
|
|
39
43
|
}
|
|
40
44
|
/** Response de privateGetStoreStockCatalog. */
|
|
41
45
|
export type GetStoreStockCatalogResponse = StoreCatalogItem[];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { RetailPromotionsErrorCodeEnum as C } from '../enums/RetailPromotionsErrorCodeEnum';
|
|
2
|
-
/** Status HTTP de cada code: 403 sesión sin alcance · 404 no existe · 409 conflicto
|
|
3
|
-
export declare const RETAIL_PROMOTIONS_ERROR_HTTP_STATUS: Readonly<Record<C, 403 | 404 | 409 | 422>>;
|
|
2
|
+
/** Status HTTP de cada code: 400 request inválido · 403 sesión sin alcance · 404 no existe · 409 conflicto · 422 regla de negocio · 502 externo. */
|
|
3
|
+
export declare const RETAIL_PROMOTIONS_ERROR_HTTP_STATUS: Readonly<Record<C, 400 | 403 | 404 | 409 | 422 | 502>>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RETAIL_PROMOTIONS_ERROR_HTTP_STATUS = void 0;
|
|
4
4
|
const RetailPromotionsErrorCodeEnum_1 = require("../enums/RetailPromotionsErrorCodeEnum");
|
|
5
|
-
/** Status HTTP de cada code: 403 sesión sin alcance · 404 no existe · 409 conflicto
|
|
5
|
+
/** Status HTTP de cada code: 400 request inválido · 403 sesión sin alcance · 404 no existe · 409 conflicto · 422 regla de negocio · 502 externo. */
|
|
6
6
|
exports.RETAIL_PROMOTIONS_ERROR_HTTP_STATUS = {
|
|
7
7
|
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.INVALID_PROMOTION_TRANSITION]: 409,
|
|
8
8
|
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.CODE_TEXT_TAKEN]: 409,
|
|
@@ -25,4 +25,11 @@ exports.RETAIL_PROMOTIONS_ERROR_HTTP_STATUS = {
|
|
|
25
25
|
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.PROMOTION_KIND_MISMATCH]: 422,
|
|
26
26
|
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.MISSING_RETAILER_SCOPE]: 403,
|
|
27
27
|
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.MISSING_STORE_SCOPE]: 403,
|
|
28
|
+
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.SESSION_STILL_ACTIVE]: 409,
|
|
29
|
+
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.WIZARD_SESSION_NOT_FOUND]: 404,
|
|
30
|
+
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.UPSTREAM_UNAVAILABLE]: 502,
|
|
31
|
+
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.MISSING_TENANT_SCOPE]: 403,
|
|
32
|
+
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.PROMOTION_NOT_REVALIDATED]: 409,
|
|
33
|
+
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.SALE_PROMOTION_HOLD_CONFLICT]: 409,
|
|
34
|
+
[RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.INVALID_NEXT_TOKEN]: 400,
|
|
28
35
|
};
|
|
@@ -27,5 +27,18 @@ export declare enum RetailPromotionsErrorCodeEnum {
|
|
|
27
27
|
/** La sesión no trae retailer: no se puede acotar la consulta. */
|
|
28
28
|
MISSING_RETAILER_SCOPE = "MISSING_RETAILER_SCOPE",
|
|
29
29
|
/** La sesión no trae tienda: no se puede acotar la consulta. */
|
|
30
|
-
MISSING_STORE_SCOPE = "MISSING_STORE_SCOPE"
|
|
30
|
+
MISSING_STORE_SCOPE = "MISSING_STORE_SCOPE",
|
|
31
|
+
/** La sesión del wizard sigue abierta y no admite la operación. */
|
|
32
|
+
SESSION_STILL_ACTIVE = "SESSION_STILL_ACTIVE",
|
|
33
|
+
WIZARD_SESSION_NOT_FOUND = "WIZARD_SESSION_NOT_FOUND",
|
|
34
|
+
/** Un servicio externo falló o no respondió. */
|
|
35
|
+
UPSTREAM_UNAVAILABLE = "UPSTREAM_UNAVAILABLE",
|
|
36
|
+
/** La sesión no trae tenant: no se puede acotar la consulta. */
|
|
37
|
+
MISSING_TENANT_SCOPE = "MISSING_TENANT_SCOPE",
|
|
38
|
+
/** La promo no se revalidó antes de cobrar. */
|
|
39
|
+
PROMOTION_NOT_REVALIDATED = "PROMOTION_NOT_REVALIDATED",
|
|
40
|
+
/** Otra venta ya retiene la promo. */
|
|
41
|
+
SALE_PROMOTION_HOLD_CONFLICT = "SALE_PROMOTION_HOLD_CONFLICT",
|
|
42
|
+
/** El token de paginación no es válido. */
|
|
43
|
+
INVALID_NEXT_TOKEN = "INVALID_NEXT_TOKEN"
|
|
31
44
|
}
|
|
@@ -32,4 +32,17 @@ var RetailPromotionsErrorCodeEnum;
|
|
|
32
32
|
RetailPromotionsErrorCodeEnum["MISSING_RETAILER_SCOPE"] = "MISSING_RETAILER_SCOPE";
|
|
33
33
|
/** La sesión no trae tienda: no se puede acotar la consulta. */
|
|
34
34
|
RetailPromotionsErrorCodeEnum["MISSING_STORE_SCOPE"] = "MISSING_STORE_SCOPE";
|
|
35
|
+
/** La sesión del wizard sigue abierta y no admite la operación. */
|
|
36
|
+
RetailPromotionsErrorCodeEnum["SESSION_STILL_ACTIVE"] = "SESSION_STILL_ACTIVE";
|
|
37
|
+
RetailPromotionsErrorCodeEnum["WIZARD_SESSION_NOT_FOUND"] = "WIZARD_SESSION_NOT_FOUND";
|
|
38
|
+
/** Un servicio externo falló o no respondió. */
|
|
39
|
+
RetailPromotionsErrorCodeEnum["UPSTREAM_UNAVAILABLE"] = "UPSTREAM_UNAVAILABLE";
|
|
40
|
+
/** La sesión no trae tenant: no se puede acotar la consulta. */
|
|
41
|
+
RetailPromotionsErrorCodeEnum["MISSING_TENANT_SCOPE"] = "MISSING_TENANT_SCOPE";
|
|
42
|
+
/** La promo no se revalidó antes de cobrar. */
|
|
43
|
+
RetailPromotionsErrorCodeEnum["PROMOTION_NOT_REVALIDATED"] = "PROMOTION_NOT_REVALIDATED";
|
|
44
|
+
/** Otra venta ya retiene la promo. */
|
|
45
|
+
RetailPromotionsErrorCodeEnum["SALE_PROMOTION_HOLD_CONFLICT"] = "SALE_PROMOTION_HOLD_CONFLICT";
|
|
46
|
+
/** El token de paginación no es válido. */
|
|
47
|
+
RetailPromotionsErrorCodeEnum["INVALID_NEXT_TOKEN"] = "INVALID_NEXT_TOKEN";
|
|
35
48
|
})(RetailPromotionsErrorCodeEnum || (exports.RetailPromotionsErrorCodeEnum = RetailPromotionsErrorCodeEnum = {}));
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** Un regalo del conjunto que queda ligado al ancla. */
|
|
2
2
|
export declare class AnchorGiftItem {
|
|
3
3
|
sku: string;
|
|
4
|
+
/** Promo propia de esta línea GIFT; va junto con `promotionVersion` y, si falta, se usa la de la raíz. */
|
|
5
|
+
promotionId?: string;
|
|
6
|
+
promotionVersion?: number;
|
|
4
7
|
}
|
|
5
8
|
/**
|
|
6
9
|
* PUT /private/wizard/sessions/{id}/lines/{anchorLineId}/gifts (M2M de Promociones).
|
|
@@ -12,6 +12,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.SetAnchorGiftsRequest = exports.AnchorGiftItem = void 0;
|
|
13
13
|
const class_transformer_1 = require("class-transformer");
|
|
14
14
|
const class_validator_1 = require("class-validator");
|
|
15
|
+
/** Hay que validar la promo del item si vino cualquiera de sus dos campos. */
|
|
16
|
+
function hasOwnPromotion(o) {
|
|
17
|
+
return o.promotionId !== undefined || o.promotionVersion !== undefined;
|
|
18
|
+
}
|
|
15
19
|
/** Un regalo del conjunto que queda ligado al ancla. */
|
|
16
20
|
class AnchorGiftItem {
|
|
17
21
|
}
|
|
@@ -22,6 +26,20 @@ __decorate([
|
|
|
22
26
|
(0, class_validator_1.IsNotEmpty)(),
|
|
23
27
|
__metadata("design:type", String)
|
|
24
28
|
], AnchorGiftItem.prototype, "sku", void 0);
|
|
29
|
+
__decorate([
|
|
30
|
+
(0, class_transformer_1.Expose)(),
|
|
31
|
+
(0, class_validator_1.ValidateIf)(hasOwnPromotion),
|
|
32
|
+
(0, class_validator_1.IsString)(),
|
|
33
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
34
|
+
__metadata("design:type", String)
|
|
35
|
+
], AnchorGiftItem.prototype, "promotionId", void 0);
|
|
36
|
+
__decorate([
|
|
37
|
+
(0, class_transformer_1.Expose)(),
|
|
38
|
+
(0, class_validator_1.ValidateIf)(hasOwnPromotion),
|
|
39
|
+
(0, class_validator_1.IsInt)(),
|
|
40
|
+
(0, class_validator_1.Min)(1),
|
|
41
|
+
__metadata("design:type", Number)
|
|
42
|
+
], AnchorGiftItem.prototype, "promotionVersion", void 0);
|
|
25
43
|
/** Hay que validar el campo si hay regalos o si vino con valor. */
|
|
26
44
|
function mustValidate(o, value) {
|
|
27
45
|
return (Array.isArray(o.items) && o.items.length > 0) || (value !== null && value !== undefined);
|
package/package.json
CHANGED
|
@@ -38,6 +38,10 @@ export interface StoreCatalogItem {
|
|
|
38
38
|
itemType?: ItemTypeEnum;
|
|
39
39
|
/** Nombre visible para kits y servicios, que no tienen marca/modelo. */
|
|
40
40
|
description?: string;
|
|
41
|
+
/** Precio de lista de respaldo para un regalo: el precio vigente o, si no hay, product.basePriceCents. */
|
|
42
|
+
listPriceCents?: number;
|
|
43
|
+
/** Prioridad de venta de 0 a 100; «Recomendados» ordena por ella. */
|
|
44
|
+
salePriority?: number;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
/** Response de privateGetStoreStockCatalog. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RetailPromotionsErrorCodeEnum as C } from '../enums/RetailPromotionsErrorCodeEnum';
|
|
2
2
|
|
|
3
|
-
/** Status HTTP de cada code: 403 sesión sin alcance · 404 no existe · 409 conflicto
|
|
4
|
-
export const RETAIL_PROMOTIONS_ERROR_HTTP_STATUS: Readonly<Record<C, 403 | 404 | 409 | 422>> = {
|
|
3
|
+
/** Status HTTP de cada code: 400 request inválido · 403 sesión sin alcance · 404 no existe · 409 conflicto · 422 regla de negocio · 502 externo. */
|
|
4
|
+
export const RETAIL_PROMOTIONS_ERROR_HTTP_STATUS: Readonly<Record<C, 400 | 403 | 404 | 409 | 422 | 502>> = {
|
|
5
5
|
[C.INVALID_PROMOTION_TRANSITION]: 409,
|
|
6
6
|
[C.CODE_TEXT_TAKEN]: 409,
|
|
7
7
|
[C.PROMOTION_NOT_ACTIVE]: 409,
|
|
@@ -23,4 +23,11 @@ export const RETAIL_PROMOTIONS_ERROR_HTTP_STATUS: Readonly<Record<C, 403 | 404 |
|
|
|
23
23
|
[C.PROMOTION_KIND_MISMATCH]: 422,
|
|
24
24
|
[C.MISSING_RETAILER_SCOPE]: 403,
|
|
25
25
|
[C.MISSING_STORE_SCOPE]: 403,
|
|
26
|
+
[C.SESSION_STILL_ACTIVE]: 409,
|
|
27
|
+
[C.WIZARD_SESSION_NOT_FOUND]: 404,
|
|
28
|
+
[C.UPSTREAM_UNAVAILABLE]: 502,
|
|
29
|
+
[C.MISSING_TENANT_SCOPE]: 403,
|
|
30
|
+
[C.PROMOTION_NOT_REVALIDATED]: 409,
|
|
31
|
+
[C.SALE_PROMOTION_HOLD_CONFLICT]: 409,
|
|
32
|
+
[C.INVALID_NEXT_TOKEN]: 400,
|
|
26
33
|
};
|
|
@@ -28,4 +28,17 @@ export enum RetailPromotionsErrorCodeEnum {
|
|
|
28
28
|
MISSING_RETAILER_SCOPE = 'MISSING_RETAILER_SCOPE',
|
|
29
29
|
/** La sesión no trae tienda: no se puede acotar la consulta. */
|
|
30
30
|
MISSING_STORE_SCOPE = 'MISSING_STORE_SCOPE',
|
|
31
|
+
/** La sesión del wizard sigue abierta y no admite la operación. */
|
|
32
|
+
SESSION_STILL_ACTIVE = 'SESSION_STILL_ACTIVE',
|
|
33
|
+
WIZARD_SESSION_NOT_FOUND = 'WIZARD_SESSION_NOT_FOUND',
|
|
34
|
+
/** Un servicio externo falló o no respondió. */
|
|
35
|
+
UPSTREAM_UNAVAILABLE = 'UPSTREAM_UNAVAILABLE',
|
|
36
|
+
/** La sesión no trae tenant: no se puede acotar la consulta. */
|
|
37
|
+
MISSING_TENANT_SCOPE = 'MISSING_TENANT_SCOPE',
|
|
38
|
+
/** La promo no se revalidó antes de cobrar. */
|
|
39
|
+
PROMOTION_NOT_REVALIDATED = 'PROMOTION_NOT_REVALIDATED',
|
|
40
|
+
/** Otra venta ya retiene la promo. */
|
|
41
|
+
SALE_PROMOTION_HOLD_CONFLICT = 'SALE_PROMOTION_HOLD_CONFLICT',
|
|
42
|
+
/** El token de paginación no es válido. */
|
|
43
|
+
INVALID_NEXT_TOKEN = 'INVALID_NEXT_TOKEN',
|
|
31
44
|
}
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import { Expose, Type } from 'class-transformer';
|
|
2
2
|
import { IsArray, IsInt, IsNotEmpty, IsString, Min, ValidateIf, ValidateNested } from 'class-validator';
|
|
3
3
|
|
|
4
|
+
/** Hay que validar la promo del item si vino cualquiera de sus dos campos. */
|
|
5
|
+
function hasOwnPromotion(o: AnchorGiftItem): boolean {
|
|
6
|
+
return o.promotionId !== undefined || o.promotionVersion !== undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
/** Un regalo del conjunto que queda ligado al ancla. */
|
|
5
10
|
export class AnchorGiftItem {
|
|
6
11
|
@Expose()
|
|
7
12
|
@IsString()
|
|
8
13
|
@IsNotEmpty()
|
|
9
14
|
sku!: string;
|
|
15
|
+
|
|
16
|
+
/** Promo propia de esta línea GIFT; va junto con `promotionVersion` y, si falta, se usa la de la raíz. */
|
|
17
|
+
@Expose()
|
|
18
|
+
@ValidateIf(hasOwnPromotion)
|
|
19
|
+
@IsString()
|
|
20
|
+
@IsNotEmpty()
|
|
21
|
+
promotionId?: string;
|
|
22
|
+
|
|
23
|
+
@Expose()
|
|
24
|
+
@ValidateIf(hasOwnPromotion)
|
|
25
|
+
@IsInt()
|
|
26
|
+
@Min(1)
|
|
27
|
+
promotionVersion?: number;
|
|
10
28
|
}
|
|
11
29
|
|
|
12
30
|
/** Hay que validar el campo si hay regalos o si vino con valor. */
|