@fiado/type-kit 3.472.0 → 3.473.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/catalogDebt.test.ts +123 -0
- package/_test_/unit/retailCatalog/catalogEnums.test.ts +2 -2
- package/_test_/unit/retailCatalog/ventaSaga.test.ts +152 -0
- package/_test_/unit/retailPromotions/computeBudgetUsedBps.test.ts +30 -0
- package/_test_/unit/retailPromotions/constants.test.ts +71 -0
- package/_test_/unit/retailPromotions/discountCodeAndWizardRequests.test.ts +84 -0
- package/_test_/unit/retailPromotions/salePromotionRequests.test.ts +151 -0
- package/_test_/unit/retailWizard/promotionM2mRequests.test.ts +189 -0
- package/_test_/unit/retailWizard/ventaSagaMessages.test.ts +163 -0
- package/_test_/unit/retailWizard/ventaSagaRequests.test.ts +59 -0
- package/_test_/unit/retailWizard/ventaSagaShapes.test.ts +99 -0
- package/bin/index.d.ts +1 -0
- package/bin/index.js +5 -2
- package/bin/retailCatalog/dtos/Product.d.ts +6 -0
- package/bin/retailCatalog/dtos/requests/BackofficeCreateProductRequest.d.ts +9 -0
- package/bin/retailCatalog/dtos/requests/BackofficeCreateProductRequest.js +30 -0
- package/bin/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.d.ts +2 -0
- package/bin/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.js +6 -0
- package/bin/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.d.ts +9 -0
- package/bin/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.js +30 -0
- package/bin/retailCatalog/dtos/requests/HoldStockRequest.d.ts +12 -0
- package/bin/retailCatalog/dtos/requests/HoldStockRequest.js +47 -0
- package/bin/retailCatalog/dtos/requests/ReserveStockRequest.d.ts +3 -0
- package/bin/retailCatalog/dtos/requests/ReserveStockRequest.js +6 -0
- package/bin/retailCatalog/dtos/requests/SuggestPiecesQuery.d.ts +2 -0
- package/bin/retailCatalog/dtos/requests/SuggestPiecesQuery.js +6 -0
- package/bin/retailCatalog/dtos/responses/GetInventorySkuResponse.d.ts +3 -0
- package/bin/retailCatalog/dtos/responses/HoldStockResponse.d.ts +8 -0
- package/bin/retailCatalog/dtos/responses/HoldStockResponse.js +2 -0
- package/bin/retailCatalog/dtos/responses/StoreCatalogItem.d.ts +5 -1
- package/bin/retailCatalog/enums/InventoryInboundMessageTypeEnum.d.ts +5 -0
- package/bin/retailCatalog/enums/InventoryInboundMessageTypeEnum.js +9 -0
- package/bin/retailCatalog/enums/InventoryItemStatusEnum.d.ts +4 -2
- package/bin/retailCatalog/enums/InventoryItemStatusEnum.js +3 -1
- package/bin/retailCatalog/enums/ReservePurposeEnum.d.ts +4 -0
- package/bin/retailCatalog/enums/ReservePurposeEnum.js +8 -0
- package/bin/retailCatalog/events/InventoryInboundMessage.d.ts +4 -0
- package/bin/retailCatalog/events/InventoryInboundMessage.js +2 -0
- package/bin/retailCatalog/events/InventoryReturnMessage.d.ts +12 -0
- package/bin/retailCatalog/events/InventoryReturnMessage.js +42 -0
- package/bin/retailCatalog/events/InventorySaleAppliedMessage.d.ts +12 -0
- package/bin/retailCatalog/events/InventorySaleAppliedMessage.js +42 -0
- package/bin/retailCatalog/index.d.ts +7 -0
- package/bin/retailCatalog/index.js +8 -0
- package/bin/retailPromotions/constants/bonusPreMoney.d.ts +7 -0
- package/bin/retailPromotions/constants/bonusPreMoney.js +21 -0
- package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.d.ts +3 -0
- package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +23 -0
- package/bin/retailPromotions/constants/salePromotionTransitions.d.ts +7 -0
- package/bin/retailPromotions/constants/salePromotionTransitions.js +13 -0
- package/bin/retailPromotions/dtos/DiscountCode.d.ts +35 -0
- package/bin/retailPromotions/dtos/DiscountCode.js +2 -0
- package/bin/retailPromotions/dtos/DiscountValue.d.ts +7 -0
- package/bin/retailPromotions/dtos/DiscountValue.js +2 -0
- package/bin/retailPromotions/dtos/LinePromotionDiscount.d.ts +10 -0
- package/bin/retailPromotions/dtos/LinePromotionDiscount.js +2 -0
- package/bin/retailPromotions/dtos/SalePromotion.d.ts +35 -0
- package/bin/retailPromotions/dtos/SalePromotion.js +2 -0
- package/bin/retailPromotions/dtos/SalePromotionLevers.d.ts +58 -0
- package/bin/retailPromotions/dtos/SalePromotionLevers.js +2 -0
- package/bin/retailPromotions/dtos/SalePromotionLogEntry.d.ts +18 -0
- package/bin/retailPromotions/dtos/SalePromotionLogEntry.js +2 -0
- package/bin/retailPromotions/dtos/requests/ChangeDiscountCodeStatusRequest.d.ts +6 -0
- package/bin/retailPromotions/dtos/requests/ChangeDiscountCodeStatusRequest.js +30 -0
- package/bin/retailPromotions/dtos/requests/ChangeSalePromotionStatusRequest.d.ts +6 -0
- package/bin/retailPromotions/dtos/requests/ChangeSalePromotionStatusRequest.js +30 -0
- package/bin/retailPromotions/dtos/requests/CreateDiscountCodeRequest.d.ts +17 -0
- package/bin/retailPromotions/dtos/requests/CreateDiscountCodeRequest.js +78 -0
- package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.d.ts +19 -0
- package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.js +83 -0
- package/bin/retailPromotions/dtos/requests/DiscountValueInput.d.ts +8 -0
- package/bin/retailPromotions/dtos/requests/DiscountValueInput.js +36 -0
- package/bin/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.d.ts +5 -0
- package/bin/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.js +31 -0
- package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.d.ts +11 -0
- package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.js +48 -0
- package/bin/retailPromotions/dtos/requests/ListDiscountCodesQuery.d.ts +10 -0
- package/bin/retailPromotions/dtos/requests/ListDiscountCodesQuery.js +49 -0
- package/bin/retailPromotions/dtos/requests/ListSalePromotionLogQuery.d.ts +8 -0
- package/bin/retailPromotions/dtos/requests/ListSalePromotionLogQuery.js +46 -0
- package/bin/retailPromotions/dtos/requests/ListSalePromotionsQuery.d.ts +7 -0
- package/bin/retailPromotions/dtos/requests/ListSalePromotionsQuery.js +40 -0
- package/bin/retailPromotions/dtos/requests/ListStoreSalePromotionsQuery.d.ts +4 -0
- package/bin/retailPromotions/dtos/requests/ListStoreSalePromotionsQuery.js +24 -0
- package/bin/retailPromotions/dtos/requests/SalePromotionLeversInput.d.ts +40 -0
- package/bin/retailPromotions/dtos/requests/SalePromotionLeversInput.js +166 -0
- package/bin/retailPromotions/dtos/requests/SalePromotionScopeInput.d.ts +14 -0
- package/bin/retailPromotions/dtos/requests/SalePromotionScopeInput.js +65 -0
- package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.d.ts +19 -0
- package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.js +85 -0
- package/bin/retailPromotions/dtos/responses/DiscountCodeResponses.d.ts +7 -0
- package/bin/retailPromotions/dtos/responses/DiscountCodeResponses.js +2 -0
- package/bin/retailPromotions/dtos/responses/SalePromotionCrudResponses.d.ts +30 -0
- package/bin/retailPromotions/dtos/responses/SalePromotionCrudResponses.js +2 -0
- package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.d.ts +102 -0
- package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.js +2 -0
- package/bin/retailPromotions/enums/CouponScopeEnum.d.ts +11 -0
- package/bin/retailPromotions/enums/CouponScopeEnum.js +15 -0
- package/bin/retailPromotions/enums/CouponStatusEnum.d.ts +11 -0
- package/bin/retailPromotions/enums/CouponStatusEnum.js +15 -0
- package/bin/retailPromotions/enums/DiscountCodeOriginEnum.d.ts +8 -0
- package/bin/retailPromotions/enums/DiscountCodeOriginEnum.js +12 -0
- package/bin/retailPromotions/enums/DiscountCodeStatusEnum.d.ts +11 -0
- package/bin/retailPromotions/enums/DiscountCodeStatusEnum.js +15 -0
- package/bin/retailPromotions/enums/DiscountCodeTransitionEnum.d.ts +7 -0
- package/bin/retailPromotions/enums/DiscountCodeTransitionEnum.js +11 -0
- package/bin/retailPromotions/enums/DiscountTypeEnum.d.ts +8 -0
- package/bin/retailPromotions/enums/DiscountTypeEnum.js +12 -0
- package/bin/retailPromotions/enums/EntitlementKindEnum.d.ts +9 -0
- package/bin/retailPromotions/enums/EntitlementKindEnum.js +13 -0
- package/bin/retailPromotions/enums/EntitlementStateEnum.d.ts +13 -0
- package/bin/retailPromotions/enums/EntitlementStateEnum.js +17 -0
- package/bin/retailPromotions/enums/GiftModeEnum.d.ts +8 -0
- package/bin/retailPromotions/enums/GiftModeEnum.js +12 -0
- package/bin/retailPromotions/enums/PromotionAppliesToEnum.d.ts +9 -0
- package/bin/retailPromotions/enums/PromotionAppliesToEnum.js +13 -0
- package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.d.ts +23 -0
- package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +27 -0
- package/bin/retailPromotions/enums/SaleCouponAvailabilityEnum.d.ts +10 -0
- package/bin/retailPromotions/enums/SaleCouponAvailabilityEnum.js +14 -0
- package/bin/retailPromotions/enums/SalePromotionLeverEnum.d.ts +13 -0
- package/bin/retailPromotions/enums/SalePromotionLeverEnum.js +17 -0
- package/bin/retailPromotions/enums/SalePromotionLogEventEnum.d.ts +14 -0
- package/bin/retailPromotions/enums/SalePromotionLogEventEnum.js +18 -0
- package/bin/retailPromotions/enums/SalePromotionLogFormatEnum.d.ts +5 -0
- package/bin/retailPromotions/enums/SalePromotionLogFormatEnum.js +9 -0
- package/bin/retailPromotions/enums/SalePromotionPhaseEnum.d.ts +11 -0
- package/bin/retailPromotions/enums/SalePromotionPhaseEnum.js +15 -0
- package/bin/retailPromotions/enums/SalePromotionStatusEnum.d.ts +12 -0
- package/bin/retailPromotions/enums/SalePromotionStatusEnum.js +16 -0
- package/bin/retailPromotions/enums/SalePromotionTransitionEnum.d.ts +13 -0
- package/bin/retailPromotions/enums/SalePromotionTransitionEnum.js +17 -0
- package/bin/retailPromotions/helpers/computeBudgetUsedBps.d.ts +3 -0
- package/bin/retailPromotions/helpers/computeBudgetUsedBps.js +15 -0
- package/bin/retailPromotions/helpers/resolveSalePromotionTransition.d.ts +4 -0
- package/bin/retailPromotions/helpers/resolveSalePromotionTransition.js +9 -0
- package/bin/retailPromotions/index.d.ts +46 -0
- package/bin/retailPromotions/index.js +67 -0
- package/bin/retailWizard/dtos/Sale.d.ts +8 -8
- package/bin/retailWizard/dtos/SaleLine.d.ts +4 -1
- package/bin/retailWizard/dtos/WizardFinancingPreview.d.ts +11 -0
- package/bin/retailWizard/dtos/WizardFinancingPreview.js +2 -0
- package/bin/retailWizard/dtos/WizardGiftLine.d.ts +8 -0
- package/bin/retailWizard/dtos/WizardGiftLine.js +2 -0
- package/bin/retailWizard/dtos/WizardLinePromotionFields.d.ts +15 -0
- package/bin/retailWizard/dtos/WizardLinePromotionFields.js +2 -0
- package/bin/retailWizard/dtos/WizardReservedLine.d.ts +4 -1
- package/bin/retailWizard/dtos/WizardSession.d.ts +10 -0
- package/bin/retailWizard/dtos/requests/AddProductRequest.d.ts +2 -0
- package/bin/retailWizard/dtos/requests/AddProductRequest.js +7 -0
- package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.d.ts +15 -0
- package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.js +58 -0
- package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.d.ts +9 -0
- package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.js +36 -0
- package/bin/retailWizard/dtos/requests/PinSessionPiecesRequest.d.ts +14 -0
- package/bin/retailWizard/dtos/requests/PinSessionPiecesRequest.js +60 -0
- package/bin/retailWizard/dtos/requests/QuoteRequest.d.ts +1 -1
- package/bin/retailWizard/dtos/requests/SetAnchorGiftsRequest.d.ts +14 -0
- package/bin/retailWizard/dtos/requests/SetAnchorGiftsRequest.js +56 -0
- package/bin/retailWizard/dtos/responses/PrivateWizardSessionResponse.d.ts +29 -0
- package/bin/retailWizard/dtos/responses/PrivateWizardSessionResponse.js +2 -0
- package/bin/retailWizard/dtos/responses/SetAnchorGiftsResponse.d.ts +5 -0
- package/bin/retailWizard/dtos/responses/SetAnchorGiftsResponse.js +2 -0
- package/bin/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.d.ts +5 -0
- package/bin/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.js +9 -0
- package/bin/retailWizard/enums/WizardLineRoleEnum.d.ts +4 -0
- package/bin/retailWizard/enums/WizardLineRoleEnum.js +8 -0
- package/bin/retailWizard/events/RetailCommissionInboundMessage.d.ts +19 -0
- package/bin/retailWizard/events/RetailCommissionInboundMessage.js +78 -0
- package/bin/retailWizard/events/RetailPromotionInboundMessage.d.ts +40 -0
- package/bin/retailWizard/events/RetailPromotionInboundMessage.js +160 -0
- package/bin/retailWizard/events/SaleCompletedV1.d.ts +2 -3
- package/bin/retailWizard/index.d.ts +13 -0
- package/bin/retailWizard/index.js +14 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/retailCatalog/dtos/Product.ts +6 -0
- package/src/retailCatalog/dtos/requests/BackofficeCreateProductRequest.ts +30 -1
- package/src/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.ts +6 -0
- package/src/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.ts +30 -1
- package/src/retailCatalog/dtos/requests/HoldStockRequest.ts +28 -0
- package/src/retailCatalog/dtos/requests/ReserveStockRequest.ts +7 -1
- package/src/retailCatalog/dtos/requests/SuggestPiecesQuery.ts +7 -1
- package/src/retailCatalog/dtos/responses/GetInventorySkuResponse.ts +3 -0
- package/src/retailCatalog/dtos/responses/HoldStockResponse.ts +9 -0
- package/src/retailCatalog/dtos/responses/StoreCatalogItem.ts +6 -1
- package/src/retailCatalog/enums/InventoryInboundMessageTypeEnum.ts +5 -0
- package/src/retailCatalog/enums/InventoryItemStatusEnum.ts +3 -1
- package/src/retailCatalog/enums/ReservePurposeEnum.ts +4 -0
- package/src/retailCatalog/events/InventoryInboundMessage.ts +5 -0
- package/src/retailCatalog/events/InventoryReturnMessage.ts +26 -0
- package/src/retailCatalog/events/InventorySaleAppliedMessage.ts +26 -0
- package/src/retailCatalog/index.ts +9 -0
- package/src/retailPromotions/constants/bonusPreMoney.ts +19 -0
- package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +21 -0
- package/src/retailPromotions/constants/salePromotionTransitions.ts +11 -0
- package/src/retailPromotions/dtos/DiscountCode.ts +36 -0
- package/src/retailPromotions/dtos/DiscountValue.ts +8 -0
- package/src/retailPromotions/dtos/LinePromotionDiscount.ts +10 -0
- package/src/retailPromotions/dtos/SalePromotion.ts +36 -0
- package/src/retailPromotions/dtos/SalePromotionLevers.ts +67 -0
- package/src/retailPromotions/dtos/SalePromotionLogEntry.ts +19 -0
- package/src/retailPromotions/dtos/requests/ChangeDiscountCodeStatusRequest.ts +15 -0
- package/src/retailPromotions/dtos/requests/ChangeSalePromotionStatusRequest.ts +15 -0
- package/src/retailPromotions/dtos/requests/CreateDiscountCodeRequest.ts +71 -0
- package/src/retailPromotions/dtos/requests/CreateSalePromotionRequest.ts +73 -0
- package/src/retailPromotions/dtos/requests/DiscountValueInput.ts +21 -0
- package/src/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.ts +16 -0
- package/src/retailPromotions/dtos/requests/HoldSalePromotionRequest.ts +32 -0
- package/src/retailPromotions/dtos/requests/ListDiscountCodesQuery.ts +33 -0
- package/src/retailPromotions/dtos/requests/ListSalePromotionLogQuery.ts +29 -0
- package/src/retailPromotions/dtos/requests/ListSalePromotionsQuery.ts +24 -0
- package/src/retailPromotions/dtos/requests/ListStoreSalePromotionsQuery.ts +10 -0
- package/src/retailPromotions/dtos/requests/SalePromotionLeversInput.ts +136 -0
- package/src/retailPromotions/dtos/requests/SalePromotionScopeInput.ts +45 -0
- package/src/retailPromotions/dtos/requests/UpdateSalePromotionRequest.ts +74 -0
- package/src/retailPromotions/dtos/responses/DiscountCodeResponses.ts +9 -0
- package/src/retailPromotions/dtos/responses/SalePromotionCrudResponses.ts +35 -0
- package/src/retailPromotions/dtos/responses/WizardSalePromotionResponses.ts +115 -0
- package/src/retailPromotions/enums/CouponScopeEnum.ts +11 -0
- package/src/retailPromotions/enums/CouponStatusEnum.ts +11 -0
- package/src/retailPromotions/enums/DiscountCodeOriginEnum.ts +8 -0
- package/src/retailPromotions/enums/DiscountCodeStatusEnum.ts +11 -0
- package/src/retailPromotions/enums/DiscountCodeTransitionEnum.ts +7 -0
- package/src/retailPromotions/enums/DiscountTypeEnum.ts +8 -0
- package/src/retailPromotions/enums/EntitlementKindEnum.ts +9 -0
- package/src/retailPromotions/enums/EntitlementStateEnum.ts +13 -0
- package/src/retailPromotions/enums/GiftModeEnum.ts +8 -0
- package/src/retailPromotions/enums/PromotionAppliesToEnum.ts +9 -0
- package/src/retailPromotions/enums/RetailPromotionsErrorCodeEnum.ts +23 -0
- package/src/retailPromotions/enums/SaleCouponAvailabilityEnum.ts +10 -0
- package/src/retailPromotions/enums/SalePromotionLeverEnum.ts +13 -0
- package/src/retailPromotions/enums/SalePromotionLogEventEnum.ts +14 -0
- package/src/retailPromotions/enums/SalePromotionLogFormatEnum.ts +5 -0
- package/src/retailPromotions/enums/SalePromotionPhaseEnum.ts +11 -0
- package/src/retailPromotions/enums/SalePromotionStatusEnum.ts +12 -0
- package/src/retailPromotions/enums/SalePromotionTransitionEnum.ts +13 -0
- package/src/retailPromotions/helpers/computeBudgetUsedBps.ts +15 -0
- package/src/retailPromotions/helpers/resolveSalePromotionTransition.ts +12 -0
- package/src/retailPromotions/index.ts +55 -0
- package/src/retailWizard/dtos/Sale.ts +8 -8
- package/src/retailWizard/dtos/SaleLine.ts +4 -1
- package/src/retailWizard/dtos/WizardFinancingPreview.ts +12 -0
- package/src/retailWizard/dtos/WizardGiftLine.ts +9 -0
- package/src/retailWizard/dtos/WizardLinePromotionFields.ts +16 -0
- package/src/retailWizard/dtos/WizardReservedLine.ts +4 -1
- package/src/retailWizard/dtos/WizardSession.ts +10 -0
- package/src/retailWizard/dtos/requests/AddProductRequest.ts +8 -1
- package/src/retailWizard/dtos/requests/ApplyLinePromotionRequest.ts +45 -0
- package/src/retailWizard/dtos/requests/ApplySalePromotionRequest.ts +24 -0
- package/src/retailWizard/dtos/requests/PinSessionPiecesRequest.ts +40 -0
- package/src/retailWizard/dtos/requests/QuoteRequest.ts +1 -1
- package/src/retailWizard/dtos/requests/SetAnchorGiftsRequest.ts +40 -0
- package/src/retailWizard/dtos/responses/PrivateWizardSessionResponse.ts +30 -0
- package/src/retailWizard/dtos/responses/SetAnchorGiftsResponse.ts +6 -0
- package/src/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.ts +5 -0
- package/src/retailWizard/enums/WizardLineRoleEnum.ts +4 -0
- package/src/retailWizard/events/RetailCommissionInboundMessage.ts +54 -0
- package/src/retailWizard/events/RetailPromotionInboundMessage.ts +138 -0
- package/src/retailWizard/events/SaleCompletedV1.ts +2 -3
- package/src/retailWizard/index.ts +15 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance, type ClassConstructor } from 'class-transformer';
|
|
3
|
+
import { validateSync, type ValidationError } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
BackofficeCreateProductRequest,
|
|
6
|
+
BackofficeRegisterAdjustmentRequest,
|
|
7
|
+
BackofficeUpdateProductRequest,
|
|
8
|
+
InventoryMovementReasonEnum,
|
|
9
|
+
InventoryMovementTypeEnum,
|
|
10
|
+
Product,
|
|
11
|
+
ProductCategoryEnum,
|
|
12
|
+
TrackingModeEnum,
|
|
13
|
+
type InventoryMovementEntry,
|
|
14
|
+
type ProductResponse,
|
|
15
|
+
} from '../../../src/retailCatalog';
|
|
16
|
+
|
|
17
|
+
function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
|
|
18
|
+
return validateSync(plainToInstance(cls, payload) as object, { whitelist: true, forbidNonWhitelisted: true }).map(
|
|
19
|
+
(e: ValidationError) => e.property,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const sheet = {
|
|
24
|
+
salePriority: 100,
|
|
25
|
+
highlights: ['Cámara de 50 MP', 'Batería de 5000 mAh'],
|
|
26
|
+
warrantyMonths: 12,
|
|
27
|
+
launchDate: '2026-03-01',
|
|
28
|
+
specs: { ram: '8GB', pantalla: '6.5"' },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const createOk = {
|
|
32
|
+
sku: 'PH-A15',
|
|
33
|
+
category: ProductCategoryEnum.PHONE,
|
|
34
|
+
trackingMode: TrackingModeEnum.IMEI,
|
|
35
|
+
sellable: true,
|
|
36
|
+
showInSalesFlow: true,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const productWrites: [string, ClassConstructor<object>, object][] = [
|
|
40
|
+
['BackofficeCreateProductRequest', BackofficeCreateProductRequest, createOk],
|
|
41
|
+
['BackofficeUpdateProductRequest', BackofficeUpdateProductRequest, {}],
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
describe.each(productWrites)('%s — prioridad de venta y ficha comercial', (_name, cls, base) => {
|
|
45
|
+
it('acepta la prioridad y la ficha completa', () => {
|
|
46
|
+
expect(errorsOf(cls, { ...base, ...sheet })).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
it('sigue aceptando el cuerpo sin los campos nuevos', () => {
|
|
49
|
+
expect(errorsOf(cls, base)).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
it.each([0, 100])('acepta salePriority %p (límite)', (salePriority) => {
|
|
52
|
+
expect(errorsOf(cls, { ...base, salePriority })).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
it.each([-1, 101, 50.5, '50'])('rechaza salePriority %p', (salePriority) => {
|
|
55
|
+
expect(errorsOf(cls, { ...base, salePriority })).toContain('salePriority');
|
|
56
|
+
});
|
|
57
|
+
it('rechaza highlights que no son lista de textos o traen uno en blanco', () => {
|
|
58
|
+
expect(errorsOf(cls, { ...base, highlights: 'Cámara' })).toContain('highlights');
|
|
59
|
+
expect(errorsOf(cls, { ...base, highlights: ['ok', ' '] })).toContain('highlights');
|
|
60
|
+
expect(errorsOf(cls, { ...base, highlights: [1] })).toContain('highlights');
|
|
61
|
+
});
|
|
62
|
+
it.each([-1, 1.5, '12'])('rechaza warrantyMonths %p', (warrantyMonths) => {
|
|
63
|
+
expect(errorsOf(cls, { ...base, warrantyMonths })).toContain('warrantyMonths');
|
|
64
|
+
});
|
|
65
|
+
it('acepta warrantyMonths 0 (sin garantía)', () => {
|
|
66
|
+
expect(errorsOf(cls, { ...base, warrantyMonths: 0 })).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
it.each(['marzo 2026', '2026-13-01', 20260301])('rechaza launchDate %p', (launchDate) => {
|
|
69
|
+
expect(errorsOf(cls, { ...base, launchDate })).toContain('launchDate');
|
|
70
|
+
});
|
|
71
|
+
it('rechaza specs que no son objeto', () => {
|
|
72
|
+
expect(errorsOf(cls, { ...base, specs: 'ram 8GB' })).toContain('specs');
|
|
73
|
+
expect(errorsOf(cls, { ...base, specs: [['ram', '8GB']] })).toContain('specs');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('Product — prioridad de venta y ficha comercial en la lectura', () => {
|
|
78
|
+
it('acepta los campos nuevos y sigue válido sin ellos', () => {
|
|
79
|
+
const product = new Product();
|
|
80
|
+
product.salePriority = 100;
|
|
81
|
+
product.highlights = ['Cámara de 50 MP'];
|
|
82
|
+
product.warrantyMonths = 12;
|
|
83
|
+
product.launchDate = '2026-03-01';
|
|
84
|
+
const response: ProductResponse = product;
|
|
85
|
+
expect(response.salePriority).toBe(100);
|
|
86
|
+
expect(response.highlights).toEqual(['Cámara de 50 MP']);
|
|
87
|
+
expect(response.warrantyMonths).toBe(12);
|
|
88
|
+
expect(response.launchDate).toBe('2026-03-01');
|
|
89
|
+
const bare = new Product();
|
|
90
|
+
expect(bare.salePriority).toBeUndefined();
|
|
91
|
+
expect(bare.highlights).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('BackofficeRegisterAdjustmentRequest — reversesMovementId', () => {
|
|
96
|
+
const ok = { storeId: 'ST-1', sku: 'PH-A15', qty: 1, reason: InventoryMovementReasonEnum.ADJUSTMENT };
|
|
97
|
+
it('acepta el ajuste que corrige un movimiento', () => {
|
|
98
|
+
expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reversesMovementId: 'mov-123' })).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
it('sigue aceptando el ajuste sin él', () => {
|
|
101
|
+
expect(errorsOf(BackofficeRegisterAdjustmentRequest, ok)).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
it.each(['', ' ', 'mov 1', 42])('rechaza reversesMovementId %p', (reversesMovementId) => {
|
|
104
|
+
expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reversesMovementId })).toContain('reversesMovementId');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('InventoryMovementEntry — reason', () => {
|
|
109
|
+
it('expone el motivo del movimiento y sigue válido sin él', () => {
|
|
110
|
+
const base: InventoryMovementEntry = {
|
|
111
|
+
storeId: 'ST-1',
|
|
112
|
+
sku: 'PH-A15',
|
|
113
|
+
type: InventoryMovementTypeEnum.ADJUSTMENT,
|
|
114
|
+
qty: 1,
|
|
115
|
+
identifier: null,
|
|
116
|
+
occurredAt: '2026-09-28T10:00:00.000Z',
|
|
117
|
+
createdBy: 'user-1',
|
|
118
|
+
};
|
|
119
|
+
const withReason: InventoryMovementEntry = { ...base, reason: InventoryMovementReasonEnum.SHRINKAGE };
|
|
120
|
+
expect(withReason.reason).toBe('SHRINKAGE');
|
|
121
|
+
expect(base.reason).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -39,9 +39,9 @@ describe('retailCatalog — enums de la operación independiente', () => {
|
|
|
39
39
|
expect(Object.values(RetailCatalog.ItemTypeEnum)).toEqual(['PRODUCT', 'SERVICE']);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it('agrega IN_TRANSIT al estado de pieza sin quitar los existentes', () => {
|
|
42
|
+
it('agrega IN_TRANSIT y HELD al estado de pieza sin quitar los existentes', () => {
|
|
43
43
|
expect(Object.values(RetailCatalog.InventoryItemStatusEnum)).toEqual([
|
|
44
|
-
'AVAILABLE', 'RESERVED', 'SOLD', 'RETURNED', 'DAMAGED', 'IN_TRANSIT',
|
|
44
|
+
'AVAILABLE', 'RESERVED', 'SOLD', 'RETURNED', 'DAMAGED', 'IN_TRANSIT', 'HELD',
|
|
45
45
|
]);
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance, type ClassConstructor } from 'class-transformer';
|
|
3
|
+
import { validateSync, type ValidationError } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
HoldStockRequest,
|
|
6
|
+
type HoldStockResponse,
|
|
7
|
+
InventoryInboundMessageTypeEnum,
|
|
8
|
+
InventoryItemStatusEnum,
|
|
9
|
+
InventoryReturnMessage,
|
|
10
|
+
InventorySaleAppliedMessage,
|
|
11
|
+
ProductCategoryEnum,
|
|
12
|
+
ReservePurposeEnum,
|
|
13
|
+
ReserveStockRequest,
|
|
14
|
+
type StoreCatalogItem,
|
|
15
|
+
SuggestPiecesQuery,
|
|
16
|
+
} from '../../../src/retailCatalog';
|
|
17
|
+
|
|
18
|
+
function paths(errors: ValidationError[], prefix = ''): string[] {
|
|
19
|
+
return errors.flatMap((e) => {
|
|
20
|
+
const p = prefix ? `${prefix}.${e.property}` : e.property;
|
|
21
|
+
return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
|
|
26
|
+
return paths(validateSync(plainToInstance(cls, payload) as object));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const saleLine = { lineId: 'L1', sku: 'KIT-1', qty: 1, components: [{ sku: 'SIM-1', qty: 1, identifiers: ['8952'] }] };
|
|
30
|
+
|
|
31
|
+
describe('InventoryItemStatusEnum — HELD', () => {
|
|
32
|
+
it('agrega HELD sin quitar los existentes', () => {
|
|
33
|
+
expect(InventoryItemStatusEnum.HELD).toBe('HELD');
|
|
34
|
+
expect(InventoryItemStatusEnum.AVAILABLE).toBe('AVAILABLE');
|
|
35
|
+
expect(InventoryItemStatusEnum.IN_TRANSIT).toBe('IN_TRANSIT');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('HoldStockRequest', () => {
|
|
40
|
+
const ok = {
|
|
41
|
+
lineId: 'L1',
|
|
42
|
+
reservationId: 'r',
|
|
43
|
+
sessionId: 's',
|
|
44
|
+
retailerId: 'ret',
|
|
45
|
+
components: [{ sku: 'PHONE-1', identifiers: ['356938035643809'] }],
|
|
46
|
+
};
|
|
47
|
+
it('acepta una línea con sus componentes', () => expect(errorsOf(HoldStockRequest, ok)).toEqual([]));
|
|
48
|
+
it('exige lineId y reservationId de la línea', () => {
|
|
49
|
+
const { lineId: _l, reservationId: _r, ...rest } = ok;
|
|
50
|
+
expect(errorsOf(HoldStockRequest, rest)).toEqual(expect.arrayContaining(['lineId', 'reservationId']));
|
|
51
|
+
});
|
|
52
|
+
it('exige sessionId y retailerId', () => {
|
|
53
|
+
const { sessionId: _s, retailerId: _r, ...rest } = ok;
|
|
54
|
+
expect(errorsOf(HoldStockRequest, rest)).toEqual(expect.arrayContaining(['sessionId', 'retailerId']));
|
|
55
|
+
});
|
|
56
|
+
it('rechaza sin componentes', () => expect(errorsOf(HoldStockRequest, { ...ok, components: [] })).toContain('components'));
|
|
57
|
+
it('valida cada componente anidado', () => {
|
|
58
|
+
expect(errorsOf(HoldStockRequest, { ...ok, components: [{ sku: 'PHONE-1', identifiers: [3] }] })).toContain(
|
|
59
|
+
'components.0.identifiers',
|
|
60
|
+
);
|
|
61
|
+
expect(errorsOf(HoldStockRequest, { ...ok, components: [{ identifiers: ['x'] }] })).toContain('components.0.sku');
|
|
62
|
+
});
|
|
63
|
+
it('la respuesta es el espejo de la línea', () => {
|
|
64
|
+
const res: HoldStockResponse = { lineId: 'L1', reservationId: 'r', sessionId: 's', components: ok.components };
|
|
65
|
+
expect(res.components[0].identifiers).toEqual(['356938035643809']);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('InventorySaleAppliedMessage', () => {
|
|
70
|
+
const ok = {
|
|
71
|
+
type: 'SALE_APPLIED',
|
|
72
|
+
tenantId: 't',
|
|
73
|
+
storeId: 'st',
|
|
74
|
+
saleId: 'sale',
|
|
75
|
+
soldAt: '2026-09-28T10:00:00.000Z',
|
|
76
|
+
sessionId: 's',
|
|
77
|
+
retailerId: 'ret',
|
|
78
|
+
lines: [saleLine],
|
|
79
|
+
};
|
|
80
|
+
it('acepta el aviso de venta completo', () => expect(errorsOf(InventorySaleAppliedMessage, ok)).toEqual([]));
|
|
81
|
+
it('fija el type en SALE_APPLIED', () => {
|
|
82
|
+
expect(InventoryInboundMessageTypeEnum.SALE_APPLIED).toBe('SALE_APPLIED');
|
|
83
|
+
expect(errorsOf(InventorySaleAppliedMessage, { ...ok, type: 'RETURN' })).toContain('type');
|
|
84
|
+
});
|
|
85
|
+
it('exige tenantId, storeId, saleId y soldAt ISO', () => {
|
|
86
|
+
const { tenantId: _t, storeId: _s, saleId: _sa, ...rest } = ok;
|
|
87
|
+
expect(errorsOf(InventorySaleAppliedMessage, { ...rest, soldAt: 'ayer' })).toEqual(
|
|
88
|
+
expect.arrayContaining(['tenantId', 'storeId', 'saleId', 'soldAt']),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
it('valida las líneas como SellStockRequest', () => {
|
|
92
|
+
expect(errorsOf(InventorySaleAppliedMessage, { ...ok, lines: [{ ...saleLine, qty: 0 }] })).toContain('lines.0.qty');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('InventoryReturnMessage', () => {
|
|
97
|
+
const ok = {
|
|
98
|
+
type: 'RETURN',
|
|
99
|
+
tenantId: 't',
|
|
100
|
+
storeId: 'st',
|
|
101
|
+
sessionId: 's',
|
|
102
|
+
returnedAt: '2026-09-29T10:00:00.000Z',
|
|
103
|
+
saleId: 'sale',
|
|
104
|
+
retailerId: 'ret',
|
|
105
|
+
soldAt: '2026-09-28T10:00:00.000Z',
|
|
106
|
+
lines: [saleLine],
|
|
107
|
+
};
|
|
108
|
+
it('acepta el aviso de devolución completo', () => expect(errorsOf(InventoryReturnMessage, ok)).toEqual([]));
|
|
109
|
+
it('fija el type en RETURN', () => {
|
|
110
|
+
expect(InventoryInboundMessageTypeEnum.RETURN).toBe('RETURN');
|
|
111
|
+
expect(errorsOf(InventoryReturnMessage, { ...ok, type: 'SALE_APPLIED' })).toContain('type');
|
|
112
|
+
});
|
|
113
|
+
it('exige tenantId, storeId, sessionId y returnedAt ISO', () => {
|
|
114
|
+
const { tenantId: _t, storeId: _s, sessionId: _se, ...rest } = ok;
|
|
115
|
+
expect(errorsOf(InventoryReturnMessage, { ...rest, returnedAt: 'hoy' })).toEqual(
|
|
116
|
+
expect.arrayContaining(['tenantId', 'storeId', 'sessionId', 'returnedAt']),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
it('valida las líneas como ReturnStockRequest', () => {
|
|
120
|
+
expect(errorsOf(InventoryReturnMessage, { ...ok, lines: [] })).toContain('lines');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('ReserveStockRequest.purpose', () => {
|
|
125
|
+
const ok = { sessionId: 's', lineId: 'L1', reservationId: 'r', retailerId: 'ret', sku: 'SKU-1', qty: 1 };
|
|
126
|
+
it('sigue válido sin purpose', () => expect(errorsOf(ReserveStockRequest, ok)).toEqual([]));
|
|
127
|
+
it('acepta purpose GIFT', () => {
|
|
128
|
+
expect(ReservePurposeEnum.GIFT).toBe('GIFT');
|
|
129
|
+
expect(errorsOf(ReserveStockRequest, { ...ok, purpose: 'GIFT' })).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
it('rechaza un purpose desconocido', () => expect(errorsOf(ReserveStockRequest, { ...ok, purpose: 'PROMO' })).toContain('purpose'));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('SuggestPiecesQuery.sessionId', () => {
|
|
135
|
+
const ok = { sku: 'SKU-1', prefix: '3569' };
|
|
136
|
+
it('sigue válido sin sessionId', () => expect(errorsOf(SuggestPiecesQuery, ok)).toEqual([]));
|
|
137
|
+
it('acepta sessionId', () => expect(errorsOf(SuggestPiecesQuery, { ...ok, sessionId: 's' })).toEqual([]));
|
|
138
|
+
it('rechaza sessionId vacío', () => expect(errorsOf(SuggestPiecesQuery, { ...ok, sessionId: '' })).toContain('sessionId'));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('StoreCatalogItem.components[].category', () => {
|
|
142
|
+
it('acepta la categoría por componente y sigue válido sin ella', () => {
|
|
143
|
+
const item: Pick<StoreCatalogItem, 'components'> = {
|
|
144
|
+
components: [
|
|
145
|
+
{ sku: 'PHONE-1', qty: 1, category: ProductCategoryEnum.PHONE },
|
|
146
|
+
{ sku: 'SIM-1', qty: 1 },
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
expect(item.components?.[0].category).toBe(ProductCategoryEnum.PHONE);
|
|
150
|
+
expect(item.components?.[1].category).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { computeBudgetUsedBps } from '../../../src/retailPromotions/index';
|
|
3
|
+
|
|
4
|
+
describe('computeBudgetUsedBps', () => {
|
|
5
|
+
it('devuelve null si ninguna promo tiene tope', () => {
|
|
6
|
+
expect(computeBudgetUsedBps([])).toBeNull();
|
|
7
|
+
expect(computeBudgetUsedBps([{ budgetMaxCents: null, budgetConsumedCents: 5000 }])).toBeNull();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('agrega consumido sobre tope solo de las promos con tope', () => {
|
|
11
|
+
const bps = computeBudgetUsedBps([
|
|
12
|
+
{ budgetMaxCents: 100000, budgetConsumedCents: 25000 },
|
|
13
|
+
{ budgetMaxCents: 300000, budgetConsumedCents: 75000 },
|
|
14
|
+
{ budgetMaxCents: null, budgetConsumedCents: 999999 },
|
|
15
|
+
]);
|
|
16
|
+
expect(bps).toBe(2500);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('redondea hacia abajo a punto base entero', () => {
|
|
20
|
+
expect(computeBudgetUsedBps([{ budgetMaxCents: 300, budgetConsumedCents: 100 }])).toBe(3333);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('refleja un consumo por arriba del tope sin recortarlo', () => {
|
|
24
|
+
expect(computeBudgetUsedBps([{ budgetMaxCents: 1000, budgetConsumedCents: 1100 }])).toBe(11000);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('con tope 0 lo trata como sin presupuesto disponible', () => {
|
|
28
|
+
expect(computeBudgetUsedBps([{ budgetMaxCents: 0, budgetConsumedCents: 0 }])).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import {
|
|
3
|
+
BONUS_PRE_MONEY_CODES,
|
|
4
|
+
PRE_HANDLER_HTTP_STATUSES,
|
|
5
|
+
RETAIL_PROMOTIONS_ERROR_HTTP_STATUS,
|
|
6
|
+
RetailPromotionsErrorCodeEnum,
|
|
7
|
+
SalePromotionStatusEnum as S,
|
|
8
|
+
SalePromotionTransitionEnum as T,
|
|
9
|
+
SalePromotionLeverEnum,
|
|
10
|
+
resolveSalePromotionTransition,
|
|
11
|
+
} from '../../../src/retailPromotions/index';
|
|
12
|
+
|
|
13
|
+
describe('resolveSalePromotionTransition', () => {
|
|
14
|
+
it.each([
|
|
15
|
+
[S.DRAFT, T.PUBLISH, S.ACTIVE],
|
|
16
|
+
[S.ACTIVE, T.PAUSE, S.PAUSED],
|
|
17
|
+
[S.PAUSED, T.RESUME, S.ACTIVE],
|
|
18
|
+
[S.ACTIVE, T.EXPIRE, S.EXPIRED],
|
|
19
|
+
[S.PAUSED, T.EXPIRE, S.EXPIRED],
|
|
20
|
+
[S.DRAFT, T.ARCHIVE, S.ARCHIVED],
|
|
21
|
+
[S.EXPIRED, T.ARCHIVE, S.ARCHIVED],
|
|
22
|
+
])('%s + %s → %s', (from, transition, to) => {
|
|
23
|
+
expect(resolveSalePromotionTransition(from, transition)).toBe(to);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it.each([
|
|
27
|
+
[S.DRAFT, T.PAUSE],
|
|
28
|
+
[S.DRAFT, T.EXPIRE],
|
|
29
|
+
[S.ACTIVE, T.PUBLISH],
|
|
30
|
+
[S.EXPIRED, T.RESUME],
|
|
31
|
+
[S.ARCHIVED, T.ARCHIVE],
|
|
32
|
+
[S.ARCHIVED, T.PUBLISH],
|
|
33
|
+
])('%s + %s no se permite', (from, transition) => {
|
|
34
|
+
expect(resolveSalePromotionTransition(from, transition)).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('catálogo de errores', () => {
|
|
39
|
+
it('cada code tiene su status del catálogo cerrado', () => {
|
|
40
|
+
for (const code of Object.values(RetailPromotionsErrorCodeEnum)) {
|
|
41
|
+
expect([404, 409, 422]).toContain(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[code]);
|
|
42
|
+
}
|
|
43
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_FOUND).toBe(404);
|
|
44
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.DISCOUNT_AFTER_QUOTE).toBe(409);
|
|
45
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_OWNED).toBe(422);
|
|
46
|
+
expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(16);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('promo y código con rangos, signo o palancas inválidos son 422', () => {
|
|
50
|
+
expect(RetailPromotionsErrorCodeEnum.INVALID_SALE_PROMOTION).toBe('INVALID_SALE_PROMOTION');
|
|
51
|
+
expect(RetailPromotionsErrorCodeEnum.INVALID_DISCOUNT_CODE).toBe('INVALID_DISCOUNT_CODE');
|
|
52
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.INVALID_SALE_PROMOTION).toBe(422);
|
|
53
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.INVALID_DISCOUNT_CODE).toBe(422);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('palancas', () => {
|
|
58
|
+
it('son siete', () => {
|
|
59
|
+
expect(Object.values(SalePromotionLeverEnum)).toHaveLength(7);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('lista pre-dinero del bono', () => {
|
|
64
|
+
it('es la whitelist del wizard: SYSTEM_ERROR queda fuera', () => {
|
|
65
|
+
expect(BONUS_PRE_MONEY_CODES.has('ACCOUNT_NOT_FOUND')).toBe(true);
|
|
66
|
+
expect(BONUS_PRE_MONEY_CODES.has('IDEMPOTENCY_KEY_REQUIRED')).toBe(true);
|
|
67
|
+
expect(BONUS_PRE_MONEY_CODES.has('SYSTEM_ERROR')).toBe(false);
|
|
68
|
+
expect(BONUS_PRE_MONEY_CODES.size).toBe(9);
|
|
69
|
+
expect([...PRE_HANDLER_HTTP_STATUSES].sort()).toEqual([401, 403, 404, 405]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
ChangeDiscountCodeStatusRequest,
|
|
6
|
+
CreateDiscountCodeRequest,
|
|
7
|
+
EvaluateSalePromotionsRequest,
|
|
8
|
+
HoldSalePromotionRequest,
|
|
9
|
+
ListDiscountCodesQuery,
|
|
10
|
+
ListStoreSalePromotionsQuery,
|
|
11
|
+
} from '../../../src/retailPromotions/index';
|
|
12
|
+
|
|
13
|
+
type Dto = new () => object;
|
|
14
|
+
|
|
15
|
+
const errorPaths = async (dtoClass: Dto, raw: Record<string, unknown>): Promise<string[]> => {
|
|
16
|
+
const errors = await validate(plainToInstance(dtoClass, raw) as object);
|
|
17
|
+
const walk = (list: typeof errors, prefix: string): string[] =>
|
|
18
|
+
list.flatMap((e) => {
|
|
19
|
+
const path = prefix ? `${prefix}.${e.property}` : e.property;
|
|
20
|
+
return e.children && e.children.length > 0 ? walk(e.children, path) : [path];
|
|
21
|
+
});
|
|
22
|
+
return walk(errors, '');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('CreateDiscountCodeRequest', () => {
|
|
26
|
+
const base = { codeText: 'BUENFIN', discount: { type: 'AMOUNT', amountCents: 20000 } };
|
|
27
|
+
|
|
28
|
+
it('valida el alta mínima', async () => {
|
|
29
|
+
expect(await errorPaths(CreateDiscountCodeRequest, base)).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('rechaza texto vacío o demasiado largo', async () => {
|
|
33
|
+
expect(await errorPaths(CreateDiscountCodeRequest, { ...base, codeText: '' })).toContain('codeText');
|
|
34
|
+
expect(await errorPaths(CreateDiscountCodeRequest, { codeText: 'X' })).toContain('discount');
|
|
35
|
+
expect(await errorPaths(CreateDiscountCodeRequest, { ...base, codeText: 'X'.repeat(33) })).toContain('codeText');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('valida el descuento anidado, los usos enteros y el appliesTo', async () => {
|
|
39
|
+
const paths = await errorPaths(CreateDiscountCodeRequest, {
|
|
40
|
+
...base,
|
|
41
|
+
discount: { type: 'PERCENTAGE' },
|
|
42
|
+
maxUses: 1.5,
|
|
43
|
+
appliesTo: 'OTRA',
|
|
44
|
+
});
|
|
45
|
+
expect(paths).toEqual(expect.arrayContaining(['discount.percentBps', 'maxUses', 'appliesTo']));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('ChangeDiscountCodeStatusRequest', () => {
|
|
50
|
+
it('solo acepta VOID', async () => {
|
|
51
|
+
expect(await errorPaths(ChangeDiscountCodeStatusRequest, { transition: 'VOID' })).toHaveLength(0);
|
|
52
|
+
expect(await errorPaths(ChangeDiscountCodeStatusRequest, { transition: 'ACTIVATE' })).toContain('transition');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('ListDiscountCodesQuery', () => {
|
|
57
|
+
it('acepta estados de código y de cupón, y rechaza otros', async () => {
|
|
58
|
+
expect(await errorPaths(ListDiscountCodesQuery, { origin: 'SALE_COUPON', status: 'USED' })).toHaveLength(0);
|
|
59
|
+
expect(await errorPaths(ListDiscountCodesQuery, { origin: 'ADMIN', status: 'EXHAUSTED' })).toHaveLength(0);
|
|
60
|
+
expect(await errorPaths(ListDiscountCodesQuery, { status: 'DRAFT' })).toContain('status');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('piezas del wizard', () => {
|
|
65
|
+
it('listar y evaluar exigen sessionId', async () => {
|
|
66
|
+
expect(await errorPaths(ListStoreSalePromotionsQuery, {})).toContain('sessionId');
|
|
67
|
+
expect(await errorPaths(EvaluateSalePromotionsRequest, { sessionId: '' })).toContain('sessionId');
|
|
68
|
+
expect(await errorPaths(EvaluateSalePromotionsRequest, { sessionId: 's1', discountCode: 'BUENFIN' })).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('apartar acepta null como «Ninguna» y valida versión entera y SKUs de regalo', async () => {
|
|
72
|
+
expect(await errorPaths(HoldSalePromotionRequest, { promotionId: null, discountCode: null })).toHaveLength(0);
|
|
73
|
+
expect(
|
|
74
|
+
await errorPaths(HoldSalePromotionRequest, { promotionId: 'p1', promotionVersion: 2, giftSkus: ['SKU-9'] }),
|
|
75
|
+
).toHaveLength(0);
|
|
76
|
+
expect(
|
|
77
|
+
await errorPaths(HoldSalePromotionRequest, { promotionId: 'p1', promotionVersion: '2', giftSkus: [1] }),
|
|
78
|
+
).toEqual(expect.arrayContaining(['promotionVersion', 'giftSkus']));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('apartar pide versión cuando trae promo', async () => {
|
|
82
|
+
expect(await errorPaths(HoldSalePromotionRequest, { promotionId: 'p1' })).toContain('promotionVersion');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
ChangeSalePromotionStatusRequest,
|
|
6
|
+
CreateSalePromotionRequest,
|
|
7
|
+
DiscountTypeEnum,
|
|
8
|
+
ListSalePromotionLogQuery,
|
|
9
|
+
ListSalePromotionsQuery,
|
|
10
|
+
PromotionAppliesToEnum,
|
|
11
|
+
SalePromotionTransitionEnum,
|
|
12
|
+
UpdateSalePromotionRequest,
|
|
13
|
+
} from '../../../src/retailPromotions/index';
|
|
14
|
+
|
|
15
|
+
type Dto = new () => object;
|
|
16
|
+
|
|
17
|
+
/** Rutas de propiedad con error, incluidas las anidadas (`levers.gift.skus`). */
|
|
18
|
+
const errorPaths = async (dtoClass: Dto, raw: Record<string, unknown>): Promise<string[]> => {
|
|
19
|
+
const instance = plainToInstance(dtoClass, raw);
|
|
20
|
+
const errors = await validate(instance as object);
|
|
21
|
+
const walk = (list: typeof errors, prefix: string): string[] =>
|
|
22
|
+
list.flatMap((e) => {
|
|
23
|
+
const path = prefix ? `${prefix}.${e.property}` : e.property;
|
|
24
|
+
return e.children && e.children.length > 0 ? walk(e.children, path) : [path];
|
|
25
|
+
});
|
|
26
|
+
return walk(errors, '');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const base = {
|
|
30
|
+
name: 'Regalo de audífonos',
|
|
31
|
+
customerText: 'Llévate unos audífonos gratis',
|
|
32
|
+
levers: { deviceDiscount: { type: 'AMOUNT', amountCents: 50000 } },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe('CreateSalePromotionRequest', () => {
|
|
36
|
+
it('valida el alta mínima y deja appliesTo ausente para que el lambda ponga el default', async () => {
|
|
37
|
+
const dto = plainToInstance(CreateSalePromotionRequest, base);
|
|
38
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
39
|
+
expect(dto.appliesTo).toBeUndefined();
|
|
40
|
+
expect(dto.levers.deviceDiscount?.type).toBe(DiscountTypeEnum.AMOUNT);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('exige nombre no vacío y palancas', async () => {
|
|
44
|
+
const paths = await errorPaths(CreateSalePromotionRequest, { customerText: 'x', name: '' });
|
|
45
|
+
expect(paths).toEqual(expect.arrayContaining(['name', 'levers']));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('rechaza porcentaje en puntos base con decimales', async () => {
|
|
49
|
+
const paths = await errorPaths(CreateSalePromotionRequest, {
|
|
50
|
+
...base,
|
|
51
|
+
levers: { deviceDiscount: { type: 'PERCENTAGE', percentBps: 12.5 } },
|
|
52
|
+
});
|
|
53
|
+
expect(paths).toContain('levers.deviceDiscount.percentBps');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('pide el campo que corresponde al tipo de descuento', async () => {
|
|
57
|
+
const amount = await errorPaths(CreateSalePromotionRequest, {
|
|
58
|
+
...base,
|
|
59
|
+
levers: { deviceDiscount: { type: 'AMOUNT', percentBps: 1000 } },
|
|
60
|
+
});
|
|
61
|
+
expect(amount).toContain('levers.deviceDiscount.amountCents');
|
|
62
|
+
const percent = await errorPaths(CreateSalePromotionRequest, {
|
|
63
|
+
...base,
|
|
64
|
+
levers: { deviceDiscount: { type: 'PERCENTAGE', percentBps: 1000 } },
|
|
65
|
+
});
|
|
66
|
+
expect(percent).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('regalo por lista exige SKUs; por gama exige categoría y precio máximo', async () => {
|
|
70
|
+
const list = await errorPaths(CreateSalePromotionRequest, {
|
|
71
|
+
...base,
|
|
72
|
+
levers: { gift: { mode: 'LIST', chooseCount: 1 } },
|
|
73
|
+
});
|
|
74
|
+
expect(list).toContain('levers.gift.skus');
|
|
75
|
+
const range = await errorPaths(CreateSalePromotionRequest, {
|
|
76
|
+
...base,
|
|
77
|
+
levers: { gift: { mode: 'RANGE', chooseCount: 1 } },
|
|
78
|
+
});
|
|
79
|
+
expect(range).toEqual(expect.arrayContaining(['levers.gift.category', 'levers.gift.maxPriceCents']));
|
|
80
|
+
const ok = await errorPaths(CreateSalePromotionRequest, {
|
|
81
|
+
...base,
|
|
82
|
+
levers: { gift: { mode: 'RANGE', category: 'AUDIO', maxPriceCents: 30000, chooseCount: 1 } },
|
|
83
|
+
});
|
|
84
|
+
expect(ok).toHaveLength(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('valida las 7 palancas juntas', async () => {
|
|
88
|
+
const paths = await errorPaths(CreateSalePromotionRequest, {
|
|
89
|
+
...base,
|
|
90
|
+
levers: {
|
|
91
|
+
deviceDiscount: { type: 'PERCENTAGE', percentBps: 1000 },
|
|
92
|
+
accessoryDiscount: { percentBps: 2000, maxAmountCents: null },
|
|
93
|
+
gift: { mode: 'LIST', skus: ['SKU-1'], chooseCount: 1 },
|
|
94
|
+
coupon: { type: 'AMOUNT', amountCents: 20000, validityDays: 30, scope: 'CHAIN' },
|
|
95
|
+
airtime: { rechargeCount: 3, rechargeAmountCents: 10000 },
|
|
96
|
+
extendedWarranty: { extraMonths: 6, costCents: 45000 },
|
|
97
|
+
walletBonus: { amountCents: 15000 },
|
|
98
|
+
},
|
|
99
|
+
appliesTo: 'ANY_FINANCIER',
|
|
100
|
+
scope: { tiers: ['HIGH'], productCategories: ['PHONE'], skus: null, storeIds: ['S1'] },
|
|
101
|
+
segmentation: { segmentIds: ['SEG_GOLD'], requiresActiveSurekeepCredit: true },
|
|
102
|
+
budgetMaxCents: null,
|
|
103
|
+
validFrom: '2026-10-01',
|
|
104
|
+
validUntil: null,
|
|
105
|
+
oncePerCustomer: true,
|
|
106
|
+
});
|
|
107
|
+
expect(paths).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('rechaza cupón sin alcance válido, appliesTo desconocido y fecha no ISO', async () => {
|
|
111
|
+
const paths = await errorPaths(CreateSalePromotionRequest, {
|
|
112
|
+
...base,
|
|
113
|
+
levers: { coupon: { type: 'AMOUNT', amountCents: 100, validityDays: 30, scope: 'WORLD' } },
|
|
114
|
+
appliesTo: 'ANY',
|
|
115
|
+
validUntil: 'el viernes',
|
|
116
|
+
scope: { tiers: ['GIGANTE'] },
|
|
117
|
+
});
|
|
118
|
+
expect(paths).toEqual(
|
|
119
|
+
expect.arrayContaining(['levers.coupon.scope', 'appliesTo', 'validUntil', 'scope.tiers']),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('UpdateSalePromotionRequest', () => {
|
|
125
|
+
it('el parche vacío es válido y un campo mal tipado no', async () => {
|
|
126
|
+
expect(await errorPaths(UpdateSalePromotionRequest, {})).toHaveLength(0);
|
|
127
|
+
expect(await errorPaths(UpdateSalePromotionRequest, { appliesTo: PromotionAppliesToEnum.SUREKEEP_ONLY })).toHaveLength(0);
|
|
128
|
+
expect(await errorPaths(UpdateSalePromotionRequest, { budgetMaxCents: 10.5 })).toContain('budgetMaxCents');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('ChangeSalePromotionStatusRequest', () => {
|
|
133
|
+
it('acepta una transición del enum y rechaza un estado', async () => {
|
|
134
|
+
expect(await errorPaths(ChangeSalePromotionStatusRequest, { transition: SalePromotionTransitionEnum.PAUSE, reason: 'fin de stock' })).toHaveLength(0);
|
|
135
|
+
expect(await errorPaths(ChangeSalePromotionStatusRequest, { transition: 'ACTIVE' })).toContain('transition');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('queries paginadas', () => {
|
|
140
|
+
it('convierte limit del query string y lo acota', async () => {
|
|
141
|
+
const dto = plainToInstance(ListSalePromotionsQuery, { limit: '25', status: 'ARCHIVED' });
|
|
142
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
143
|
+
expect(dto.limit).toBe(25);
|
|
144
|
+
expect(await errorPaths(ListSalePromotionsQuery, { limit: '500' })).toContain('limit');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('el log acepta formato CSV y rechaza otro', async () => {
|
|
148
|
+
expect(await errorPaths(ListSalePromotionLogQuery, { format: 'CSV', promotionId: 'p1' })).toHaveLength(0);
|
|
149
|
+
expect(await errorPaths(ListSalePromotionLogQuery, { format: 'XLSX' })).toContain('format');
|
|
150
|
+
});
|
|
151
|
+
});
|