@fiado/type-kit 3.577.0 → 3.579.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/platformRbac/dtos/levelEntityAdminDtos.test.ts +14 -0
- package/_test_/unit/retailCatalog/catalogEnums.test.ts +2 -2
- package/_test_/unit/retailCatalog/centralSaleAccepted.test.ts +54 -0
- package/_test_/unit/retailCatalog/sellInventoryRequest.test.ts +25 -0
- package/_test_/unit/retailCatalog/stockRequests.test.ts +14 -4
- package/_test_/unit/retailCatalog/ventaSaga.test.ts +7 -0
- package/_test_/unit/retailIntegration/reconciliationContracts.test.ts +121 -0
- package/_test_/unit/retailIntegration/retailIntegrationErrorCodes.test.ts +37 -0
- package/_test_/unit/retailIntegration/retailOutboundRequests.test.ts +46 -15
- package/_test_/unit/retailIntegration/retailSaleContracts.test.ts +160 -0
- package/_test_/unit/retailIntegration/retailSystemEnums.test.ts +1 -1
- package/_test_/unit/retailOrg/dtos/retailerIntegration.test.ts +92 -0
- package/_test_/unit/retailWizard/centralOutboundAndLiveCheck.test.ts +63 -0
- package/_test_/unit/retailWizard/centralSaleStatus.test.ts +49 -0
- package/_test_/unit/retailWizard/customerTableShapes.test.ts +22 -0
- package/_test_/unit/retailWizard/multiItemShapes.test.ts +2 -2
- package/bin/platformRbac/dtos/UpdateLevelEntityRequest.d.ts +3 -1
- package/bin/platformRbac/dtos/UpdateLevelEntityRequest.js +8 -1
- package/bin/retailCatalog/dtos/requests/ReturnStockRequest.d.ts +2 -0
- package/bin/retailCatalog/dtos/requests/ReturnStockRequest.js +5 -0
- package/bin/retailCatalog/dtos/requests/SellInventoryRequest.d.ts +9 -0
- package/bin/retailCatalog/dtos/requests/SellInventoryRequest.js +32 -0
- package/bin/retailCatalog/dtos/requests/SellStockRequest.d.ts +2 -2
- package/bin/retailCatalog/dtos/requests/SellStockRequest.js +1 -1
- package/bin/retailCatalog/enums/InventoryInboundMessageTypeEnum.d.ts +2 -1
- package/bin/retailCatalog/enums/InventoryInboundMessageTypeEnum.js +1 -0
- package/bin/retailCatalog/enums/InventoryItemStatusEnum.d.ts +3 -1
- package/bin/retailCatalog/enums/InventoryItemStatusEnum.js +2 -0
- package/bin/retailCatalog/events/InventoryCentralSaleAcceptedMessage.d.ts +13 -0
- package/bin/retailCatalog/events/InventoryCentralSaleAcceptedMessage.js +55 -0
- package/bin/retailCatalog/events/InventoryInboundMessage.d.ts +2 -1
- package/bin/retailCatalog/events/InventorySaleAppliedMessage.d.ts +0 -2
- package/bin/retailCatalog/events/InventorySaleAppliedMessage.js +0 -6
- package/bin/retailCatalog/index.d.ts +2 -0
- package/bin/retailCatalog/index.js +2 -0
- package/bin/retailCustomer/dtos/requests/UpsertCustomerFromKycRequest.d.ts +2 -0
- package/bin/retailCustomer/dtos/requests/UpsertCustomerFromKycRequest.js +6 -0
- package/bin/retailIntegration/constants/retailIntegrationErrorHttpStatus.d.ts +3 -0
- package/bin/retailIntegration/constants/retailIntegrationErrorHttpStatus.js +19 -0
- package/bin/retailIntegration/constants/retailIntegrationLimits.d.ts +2 -0
- package/bin/retailIntegration/constants/retailIntegrationLimits.js +3 -1
- package/bin/retailIntegration/dtos/CancelRetailSaleRequest.d.ts +6 -0
- package/bin/retailIntegration/dtos/CancelRetailSaleRequest.js +32 -0
- package/bin/retailIntegration/dtos/CheckRetailInventoryRequest.d.ts +7 -0
- package/bin/retailIntegration/dtos/CheckRetailInventoryRequest.js +43 -0
- package/bin/retailIntegration/dtos/GetReconciliationResponses.d.ts +35 -0
- package/bin/retailIntegration/dtos/GetReconciliationResponses.js +2 -0
- package/bin/retailIntegration/dtos/GetRetailInventoryRequest.d.ts +2 -2
- package/bin/retailIntegration/dtos/GetRetailInventoryRequest.js +2 -2
- package/bin/retailIntegration/dtos/ReconciliationPageMessage.d.ts +13 -0
- package/bin/retailIntegration/dtos/ReconciliationPageMessage.js +48 -0
- package/bin/retailIntegration/dtos/ReconciliationRunCounts.d.ts +9 -0
- package/bin/retailIntegration/dtos/ReconciliationRunCounts.js +2 -0
- package/bin/retailIntegration/dtos/RetailListQuery.d.ts +5 -0
- package/bin/retailIntegration/dtos/RetailListQuery.js +24 -0
- package/bin/retailIntegration/dtos/RetailOutboundResponses.d.ts +9 -0
- package/bin/retailIntegration/dtos/RetailPagedQuery.d.ts +2 -2
- package/bin/retailIntegration/dtos/RetailPagedQuery.js +2 -2
- package/bin/retailIntegration/dtos/RetailTenantQuery.d.ts +2 -1
- package/bin/retailIntegration/dtos/RetailTenantQuery.js +6 -1
- package/bin/retailIntegration/dtos/StartCatalogReconciliationRequest.d.ts +11 -0
- package/bin/retailIntegration/dtos/StartCatalogReconciliationRequest.js +28 -0
- package/bin/retailIntegration/dtos/StartOrgReconciliationRequest.d.ts +9 -0
- package/bin/retailIntegration/dtos/StartOrgReconciliationRequest.js +22 -0
- package/bin/retailIntegration/enums/InactiveReasonEnum.d.ts +5 -0
- package/bin/retailIntegration/enums/InactiveReasonEnum.js +9 -0
- package/bin/retailIntegration/enums/ReconciliationEntityEnum.d.ts +7 -0
- package/bin/retailIntegration/enums/ReconciliationEntityEnum.js +11 -0
- package/bin/retailIntegration/enums/ReconciliationRunStatusEnum.d.ts +7 -0
- package/bin/retailIntegration/enums/ReconciliationRunStatusEnum.js +11 -0
- package/bin/retailIntegration/enums/ReconciliationTriggerEnum.d.ts +6 -0
- package/bin/retailIntegration/enums/ReconciliationTriggerEnum.js +10 -0
- package/bin/retailIntegration/enums/RetailEventRejectCodeEnum.d.ts +3 -1
- package/bin/retailIntegration/enums/RetailEventRejectCodeEnum.js +2 -0
- package/bin/retailIntegration/enums/RetailIntegrationErrorCodeEnum.d.ts +23 -0
- package/bin/retailIntegration/enums/RetailIntegrationErrorCodeEnum.js +27 -0
- package/bin/retailIntegration/enums/RetailSaleLineRoleEnum.d.ts +4 -0
- package/bin/retailIntegration/enums/RetailSaleLineRoleEnum.js +8 -0
- package/bin/retailIntegration/enums/index.d.ts +6 -0
- package/bin/retailIntegration/enums/index.js +6 -0
- package/bin/retailIntegration/index.d.ts +9 -0
- package/bin/retailIntegration/index.js +9 -0
- package/bin/retailIntegration/models/RetailSystemSale.d.ts +8 -7
- package/bin/retailIntegration/models/RetailSystemSale.js +186 -0
- package/bin/retailOrg/dtos/Retailer.d.ts +5 -0
- package/bin/retailOrg/dtos/Retailer.js +20 -0
- package/bin/retailOrg/dtos/requests/ActivateRetailerIntegrationRequest.d.ts +5 -0
- package/bin/retailOrg/dtos/requests/ActivateRetailerIntegrationRequest.js +24 -0
- package/bin/retailOrg/dtos/responses/ActivateRetailerIntegrationResponse.d.ts +3 -0
- package/bin/retailOrg/dtos/responses/ActivateRetailerIntegrationResponse.js +2 -0
- package/bin/retailOrg/dtos/validation/RetailerValidationDto.d.ts +4 -0
- package/bin/retailOrg/enums/RetailIntegrationSystemEnum.d.ts +4 -0
- package/bin/retailOrg/enums/RetailIntegrationSystemEnum.js +8 -0
- package/bin/retailOrg/index.d.ts +3 -0
- package/bin/retailOrg/index.js +3 -0
- package/bin/retailWizard/dtos/Sale.d.ts +25 -0
- package/bin/retailWizard/dtos/WizardReservedLine.d.ts +3 -0
- package/bin/retailWizard/dtos/WizardSession.d.ts +3 -0
- package/bin/retailWizard/dtos/responses/BackofficeCustomerTableResponse.d.ts +18 -5
- package/bin/retailWizard/enums/CentralCancelStatusEnum.d.ts +18 -0
- package/bin/retailWizard/enums/CentralCancelStatusEnum.js +22 -0
- package/bin/retailWizard/enums/CentralLiveCheckEnum.d.ts +10 -0
- package/bin/retailWizard/enums/CentralLiveCheckEnum.js +14 -0
- package/bin/retailWizard/enums/CentralSyncStatusEnum.d.ts +8 -2
- package/bin/retailWizard/enums/CentralSyncStatusEnum.js +7 -1
- package/bin/retailWizard/enums/RetailCentralOutboundMessageTypeEnum.d.ts +5 -0
- package/bin/retailWizard/enums/RetailCentralOutboundMessageTypeEnum.js +9 -0
- package/bin/retailWizard/enums/WizardLineStatusEnum.d.ts +3 -1
- package/bin/retailWizard/enums/WizardLineStatusEnum.js +2 -0
- package/bin/retailWizard/events/RetailCentralOutboundMessage.d.ts +9 -0
- package/bin/retailWizard/events/RetailCentralOutboundMessage.js +42 -0
- package/bin/retailWizard/index.d.ts +4 -0
- package/bin/retailWizard/index.js +4 -0
- package/package.json +1 -1
- package/src/platformRbac/dtos/UpdateLevelEntityRequest.ts +3 -1
- package/src/retailCatalog/dtos/requests/ReturnStockRequest.ts +6 -1
- package/src/retailCatalog/dtos/requests/SellInventoryRequest.ts +19 -0
- package/src/retailCatalog/dtos/requests/SellStockRequest.ts +4 -4
- package/src/retailCatalog/enums/InventoryInboundMessageTypeEnum.ts +1 -0
- package/src/retailCatalog/enums/InventoryItemStatusEnum.ts +2 -0
- package/src/retailCatalog/events/InventoryCentralSaleAcceptedMessage.ts +36 -0
- package/src/retailCatalog/events/InventoryInboundMessage.ts +5 -1
- package/src/retailCatalog/events/InventorySaleAppliedMessage.ts +1 -7
- package/src/retailCatalog/index.ts +2 -0
- package/src/retailCustomer/dtos/requests/UpsertCustomerFromKycRequest.ts +6 -0
- package/src/retailIntegration/constants/retailIntegrationErrorHttpStatus.ts +17 -0
- package/src/retailIntegration/constants/retailIntegrationLimits.ts +3 -0
- package/src/retailIntegration/dtos/CancelRetailSaleRequest.ts +9 -0
- package/src/retailIntegration/dtos/CheckRetailInventoryRequest.ts +20 -0
- package/src/retailIntegration/dtos/GetReconciliationResponses.ts +37 -0
- package/src/retailIntegration/dtos/GetRetailInventoryRequest.ts +2 -2
- package/src/retailIntegration/dtos/ReconciliationPageMessage.ts +32 -0
- package/src/retailIntegration/dtos/ReconciliationRunCounts.ts +9 -0
- package/src/retailIntegration/dtos/RetailListQuery.ts +10 -0
- package/src/retailIntegration/dtos/RetailOutboundResponses.ts +11 -0
- package/src/retailIntegration/dtos/RetailPagedQuery.ts +2 -2
- package/src/retailIntegration/dtos/RetailTenantQuery.ts +5 -1
- package/src/retailIntegration/dtos/StartCatalogReconciliationRequest.ts +19 -0
- package/src/retailIntegration/dtos/StartOrgReconciliationRequest.ts +13 -0
- package/src/retailIntegration/enums/InactiveReasonEnum.ts +5 -0
- package/src/retailIntegration/enums/ReconciliationEntityEnum.ts +7 -0
- package/src/retailIntegration/enums/ReconciliationRunStatusEnum.ts +7 -0
- package/src/retailIntegration/enums/ReconciliationTriggerEnum.ts +6 -0
- package/src/retailIntegration/enums/RetailEventRejectCodeEnum.ts +2 -0
- package/src/retailIntegration/enums/RetailIntegrationErrorCodeEnum.ts +23 -0
- package/src/retailIntegration/enums/RetailSaleLineRoleEnum.ts +4 -0
- package/src/retailIntegration/enums/index.ts +6 -0
- package/src/retailIntegration/index.ts +9 -0
- package/src/retailIntegration/models/RetailSystemSale.ts +45 -37
- package/src/retailOrg/dtos/Retailer.ts +19 -1
- package/src/retailOrg/dtos/requests/ActivateRetailerIntegrationRequest.ts +10 -0
- package/src/retailOrg/dtos/responses/ActivateRetailerIntegrationResponse.ts +5 -0
- package/src/retailOrg/dtos/validation/RetailerValidationDto.ts +4 -0
- package/src/retailOrg/enums/RetailIntegrationSystemEnum.ts +4 -0
- package/src/retailOrg/index.ts +3 -0
- package/src/retailWizard/dtos/Sale.ts +25 -0
- package/src/retailWizard/dtos/WizardReservedLine.ts +3 -0
- package/src/retailWizard/dtos/WizardSession.ts +3 -0
- package/src/retailWizard/dtos/responses/BackofficeCustomerTableResponse.ts +19 -5
- package/src/retailWizard/enums/CentralCancelStatusEnum.ts +18 -0
- package/src/retailWizard/enums/CentralLiveCheckEnum.ts +10 -0
- package/src/retailWizard/enums/CentralSyncStatusEnum.ts +7 -1
- package/src/retailWizard/enums/RetailCentralOutboundMessageTypeEnum.ts +5 -0
- package/src/retailWizard/enums/WizardLineStatusEnum.ts +2 -0
- package/src/retailWizard/events/RetailCentralOutboundMessage.ts +24 -0
- package/src/retailWizard/index.ts +4 -0
|
@@ -58,3 +58,17 @@ describe('DeleteLevelEntityRequest', () => {
|
|
|
58
58
|
expect(errors.some(e => e.property === 'actedBy')).toBe(true);
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
|
+
|
|
62
|
+
describe('UpdateLevelEntityRequest con parentId', () => {
|
|
63
|
+
it('acepta parentId para mover el grupo de padre', async () => {
|
|
64
|
+
const dto = plainToInstance(UpdateLevelEntityRequest, { parentId: 'zona-2', actedBy: 'admin-sub-1' });
|
|
65
|
+
expect(await validate(dto)).toEqual([]);
|
|
66
|
+
expect(dto.parentId).toBe('zona-2');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rechaza parentId vacío', async () => {
|
|
70
|
+
const dto = plainToInstance(UpdateLevelEntityRequest, { parentId: '', actedBy: 'admin-sub-1' });
|
|
71
|
+
const errors = await validate(dto);
|
|
72
|
+
expect(errors.some(e => e.property === 'parentId')).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -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
|
|
42
|
+
it('agrega IN_TRANSIT, HELD e INACTIVE al estado de pieza sin quitar los existentes', () => {
|
|
43
43
|
expect(Object.values(RetailCatalog.InventoryItemStatusEnum)).toEqual([
|
|
44
|
-
'AVAILABLE', 'RESERVED', 'SOLD', 'RETURNED', 'DAMAGED', 'IN_TRANSIT', 'HELD',
|
|
44
|
+
'AVAILABLE', 'RESERVED', 'SOLD', 'RETURNED', 'DAMAGED', 'IN_TRANSIT', 'HELD', 'INACTIVE',
|
|
45
45
|
]);
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validateSync } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
InventoryCentralSaleAcceptedMessage,
|
|
6
|
+
type InventoryInboundMessage,
|
|
7
|
+
InventoryInboundMessageTypeEnum,
|
|
8
|
+
} from '../../../src/retailCatalog';
|
|
9
|
+
|
|
10
|
+
const line = { lineId: 'L1', sku: 'ACC-1', qty: 1, components: [{ sku: 'ACC-1', qty: 1, identifiers: [] }] };
|
|
11
|
+
const valid = {
|
|
12
|
+
type: 'CENTRAL_SALE_ACCEPTED',
|
|
13
|
+
tenantId: 'tenant-1',
|
|
14
|
+
retailerId: 'ret-1',
|
|
15
|
+
storeId: 'store-1',
|
|
16
|
+
saleId: 'sale-1',
|
|
17
|
+
lines: [line],
|
|
18
|
+
acceptedAt: '2026-10-25T10:00:00.000Z',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function propsWithErrors(payload: object): string[] {
|
|
22
|
+
return validateSync(plainToInstance(InventoryCentralSaleAcceptedMessage, payload)).map((e) => e.property);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('InventoryCentralSaleAcceptedMessage', () => {
|
|
26
|
+
it('el enum trae CENTRAL_SALE_ACCEPTED sin perder los tipos previos', () => {
|
|
27
|
+
expect(InventoryInboundMessageTypeEnum.CENTRAL_SALE_ACCEPTED).toBe('CENTRAL_SALE_ACCEPTED');
|
|
28
|
+
expect(InventoryInboundMessageTypeEnum.SALE_APPLIED).toBe('SALE_APPLIED');
|
|
29
|
+
expect(InventoryInboundMessageTypeEnum.RETURN).toBe('RETURN');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('acepta el mensaje completo', () => {
|
|
33
|
+
expect(propsWithErrors(valid)).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it.each(['tenantId', 'retailerId', 'storeId', 'saleId', 'lines', 'acceptedAt'])('rechaza sin %s', (field) => {
|
|
37
|
+
const payload: Record<string, unknown> = { ...valid };
|
|
38
|
+
delete payload[field];
|
|
39
|
+
expect(propsWithErrors(payload)).toContain(field);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('rechaza otro type', () => {
|
|
43
|
+
expect(propsWithErrors({ ...valid, type: 'RETURN' })).toContain('type');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('rechaza una línea mal formada', () => {
|
|
47
|
+
expect(propsWithErrors({ ...valid, lines: [{ lineId: 'L1' }] })).toContain('lines');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('forma parte de la unión de la cola', () => {
|
|
51
|
+
const message: InventoryInboundMessage = plainToInstance(InventoryCentralSaleAcceptedMessage, valid);
|
|
52
|
+
expect(message.type).toBe(InventoryInboundMessageTypeEnum.CENTRAL_SALE_ACCEPTED);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validateSync } from 'class-validator';
|
|
4
|
+
import { SellInventoryRequest } from '../../../src/retailCatalog';
|
|
5
|
+
import { RetailCatalog } from '../../../src';
|
|
6
|
+
|
|
7
|
+
function errorsOf(payload: object): string[] {
|
|
8
|
+
return validateSync(plainToInstance(SellInventoryRequest, payload)).map((e) => e.property);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('SellInventoryRequest (PUT /private/inventory/{imei}/sell)', () => {
|
|
12
|
+
const ok = { sessionId: 's-1', customerId: 'c-1', saleId: 'sale-1' };
|
|
13
|
+
|
|
14
|
+
it('acepta sesión, comprador y venta', () => expect(errorsOf(ok)).toEqual([]));
|
|
15
|
+
|
|
16
|
+
it.each([undefined, '', 123])('exige saleId no vacío (%p)', (saleId) => {
|
|
17
|
+
expect(errorsOf({ ...ok, saleId })).toEqual(['saleId']);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('exige sessionId y customerId', () => {
|
|
21
|
+
expect(errorsOf({ saleId: 'sale-1' }).sort()).toEqual(['customerId', 'sessionId']);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('se expone en la raíz', () => expect(RetailCatalog.SellInventoryRequest).toBe(SellInventoryRequest));
|
|
25
|
+
});
|
|
@@ -62,15 +62,16 @@ describe('SuggestPiecesQuery', () => {
|
|
|
62
62
|
|
|
63
63
|
describe('SellStockRequest y ReturnStockRequest', () => {
|
|
64
64
|
it('acepta una venta con componentes e identificadores', () => {
|
|
65
|
-
expect(errorsOf(SellStockRequest, { sessionId: 's', retailerId: 'ret', lines: [saleLine] })).toEqual([]);
|
|
65
|
+
expect(errorsOf(SellStockRequest, { sessionId: 's', saleId: 'sale-1', retailerId: 'ret', lines: [saleLine] })).toEqual([]);
|
|
66
66
|
});
|
|
67
67
|
it('valida los identificadores del componente', () => {
|
|
68
68
|
const bad = { ...saleLine, components: [{ sku: 'SIM-1', qty: 1, identifiers: [123] }] };
|
|
69
|
-
expect(errorsOf(SellStockRequest, { sessionId: 's', retailerId: 'ret', lines: [bad] })).toContain('lines.0.components.0.identifiers');
|
|
69
|
+
expect(errorsOf(SellStockRequest, { sessionId: 's', saleId: 'sale-1', retailerId: 'ret', lines: [bad] })).toContain('lines.0.components.0.identifiers');
|
|
70
70
|
});
|
|
71
|
-
it('
|
|
71
|
+
it('exige saleId no vacío en la venta', () => {
|
|
72
72
|
const base = { sessionId: 's', retailerId: 'ret', lines: [saleLine] };
|
|
73
|
-
expect(errorsOf(SellStockRequest,
|
|
73
|
+
expect(errorsOf(SellStockRequest, base)).toContain('saleId');
|
|
74
|
+
expect(errorsOf(SellStockRequest, { ...base, saleId: '' })).toContain('saleId');
|
|
74
75
|
expect(errorsOf(SellStockRequest, { ...base, saleId: 123 })).toContain('saleId');
|
|
75
76
|
});
|
|
76
77
|
it('devolución exige saleId y soldAt ISO', () => {
|
|
@@ -78,6 +79,15 @@ describe('SellStockRequest y ReturnStockRequest', () => {
|
|
|
78
79
|
expect(errorsOf(ReturnStockRequest, ok)).toEqual([]);
|
|
79
80
|
expect(errorsOf(ReturnStockRequest, { ...ok, soldAt: 'ayer' })).toContain('soldAt');
|
|
80
81
|
});
|
|
82
|
+
it('devolución acepta centralCancelFirstAttemptAt ISO opcional', () => {
|
|
83
|
+
const ok = { saleId: 'sale', retailerId: 'ret', soldAt: '2026-09-23T10:00:00.000Z', lines: [saleLine] };
|
|
84
|
+
expect(errorsOf(ReturnStockRequest, { ...ok, centralCancelFirstAttemptAt: '2026-09-24T10:00:00.000Z' })).toEqual([]);
|
|
85
|
+
expect(errorsOf(ReturnStockRequest, { ...ok, centralCancelFirstAttemptAt: 'ayer' })).toContain('centralCancelFirstAttemptAt');
|
|
86
|
+
});
|
|
87
|
+
it('devolución con centralCancelFirstAttemptAt no ISO falla solo en ese campo', () => {
|
|
88
|
+
const ok = { saleId: 'sale', retailerId: 'ret', soldAt: '2026-09-23T10:00:00.000Z', lines: [saleLine] };
|
|
89
|
+
expect(errorsOf(ReturnStockRequest, { ...ok, centralCancelFirstAttemptAt: 'ayer' })).toEqual(['centralCancelFirstAttemptAt']);
|
|
90
|
+
});
|
|
81
91
|
});
|
|
82
92
|
|
|
83
93
|
describe('SyncInventoryRequest', () => {
|
|
@@ -123,6 +123,13 @@ describe('InventoryReturnMessage', () => {
|
|
|
123
123
|
it('valida las líneas como ReturnStockRequest', () => {
|
|
124
124
|
expect(errorsOf(InventoryReturnMessage, { ...ok, lines: [] })).toContain('lines');
|
|
125
125
|
});
|
|
126
|
+
it('acepta el RETURN de hoy sin centralCancelFirstAttemptAt y con él en ISO', () => {
|
|
127
|
+
expect(errorsOf(InventoryReturnMessage, ok)).toEqual([]);
|
|
128
|
+
expect(errorsOf(InventoryReturnMessage, { ...ok, centralCancelFirstAttemptAt: '2026-09-29T09:00:00.000Z' })).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
it('rechaza centralCancelFirstAttemptAt que no es ISO', () => {
|
|
131
|
+
expect(errorsOf(InventoryReturnMessage, { ...ok, centralCancelFirstAttemptAt: 'hoy' })).toContain('centralCancelFirstAttemptAt');
|
|
132
|
+
});
|
|
126
133
|
});
|
|
127
134
|
|
|
128
135
|
describe('ReserveStockRequest.purpose', () => {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance, type ClassConstructor } from 'class-transformer';
|
|
3
|
+
import { validateSync } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
type GetCatalogReconciliationResponse,
|
|
6
|
+
type GetOrgReconciliationResponse,
|
|
7
|
+
InactiveReasonEnum,
|
|
8
|
+
ReconciliationEntityEnum,
|
|
9
|
+
ReconciliationPageMessage,
|
|
10
|
+
ReconciliationRunStatusEnum,
|
|
11
|
+
ReconciliationTriggerEnum,
|
|
12
|
+
StartCatalogReconciliationRequest,
|
|
13
|
+
type StartCatalogReconciliationResponse,
|
|
14
|
+
StartOrgReconciliationRequest,
|
|
15
|
+
type StartOrgReconciliationResponse,
|
|
16
|
+
} from '../../../src/retailIntegration';
|
|
17
|
+
|
|
18
|
+
function propsWithErrors<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
|
|
19
|
+
return validateSync(plainToInstance(cls, payload)).map((e) => e.property);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('enums de la revisión', () => {
|
|
23
|
+
it('ReconciliationEntityEnum trae las cuatro entidades', () => {
|
|
24
|
+
expect(Object.values(ReconciliationEntityEnum)).toEqual(['STORES', 'USERS', 'CATALOG', 'INVENTORY']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('ReconciliationRunStatusEnum trae los cuatro estados de la corrida', () => {
|
|
28
|
+
expect(Object.values(ReconciliationRunStatusEnum)).toEqual(['RUNNING', 'COMPLETE', 'ABORTED', 'BRAKED']);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('ReconciliationTriggerEnum distingue el reloj del botón', () => {
|
|
32
|
+
expect(Object.values(ReconciliationTriggerEnum)).toEqual(['SCHEDULED', 'MANUAL', 'ACTIVATION']);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('InactiveReasonEnum distingue la baja de la revisión de la del evento', () => {
|
|
36
|
+
expect(Object.values(InactiveReasonEnum)).toEqual(['NOT_IN_ZENTRAL', 'ZENTRAL_EVENT']);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('StartOrgReconciliationRequest', () => {
|
|
41
|
+
it.each(['STORES', 'USERS'])('acepta %s', (entity) => {
|
|
42
|
+
expect(propsWithErrors(StartOrgReconciliationRequest, { entity })).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it.each(['CATALOG', 'INVENTORY', 'OTRA', undefined])('rechaza %s', (entity) => {
|
|
46
|
+
expect(propsWithErrors(StartOrgReconciliationRequest, { entity })).toContain('entity');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('StartCatalogReconciliationRequest', () => {
|
|
51
|
+
it('acepta CATALOG sin tienda', () => {
|
|
52
|
+
expect(propsWithErrors(StartCatalogReconciliationRequest, { entity: 'CATALOG' })).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('acepta INVENTORY con y sin tienda', () => {
|
|
56
|
+
expect(propsWithErrors(StartCatalogReconciliationRequest, { entity: 'INVENTORY' })).toEqual([]);
|
|
57
|
+
expect(propsWithErrors(StartCatalogReconciliationRequest, { entity: 'INVENTORY', storeId: 's-1' })).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it.each(['STORES', 'USERS', undefined])('rechaza %s', (entity) => {
|
|
61
|
+
expect(propsWithErrors(StartCatalogReconciliationRequest, { entity })).toContain('entity');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('rechaza storeId vacío', () => {
|
|
65
|
+
expect(propsWithErrors(StartCatalogReconciliationRequest, { entity: 'INVENTORY', storeId: '' })).toContain('storeId');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('ReconciliationPageMessage', () => {
|
|
70
|
+
const valid = { tenantId: 't-1', retailerId: 'r-1', entity: 'STORES', runId: 'run-1', pageIndex: 0 };
|
|
71
|
+
|
|
72
|
+
it('acepta la página 1 y la página de una tienda de inventario', () => {
|
|
73
|
+
expect(propsWithErrors(ReconciliationPageMessage, valid)).toEqual([]);
|
|
74
|
+
expect(propsWithErrors(ReconciliationPageMessage, { ...valid, entity: 'INVENTORY', storeId: 's-1' })).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it.each(['tenantId', 'retailerId', 'entity', 'runId', 'pageIndex'])('rechaza sin %s', (field) => {
|
|
78
|
+
const payload: Record<string, unknown> = { ...valid };
|
|
79
|
+
delete payload[field];
|
|
80
|
+
expect(propsWithErrors(ReconciliationPageMessage, payload)).toContain(field);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('rechaza un índice negativo o fraccionario', () => {
|
|
84
|
+
expect(propsWithErrors(ReconciliationPageMessage, { ...valid, pageIndex: -1 })).toContain('pageIndex');
|
|
85
|
+
expect(propsWithErrors(ReconciliationPageMessage, { ...valid, pageIndex: 1.5 })).toContain('pageIndex');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('respuestas de la revisión', () => {
|
|
90
|
+
it('el 202 lleva el runId y el GET el avance, los conteos y el estado', () => {
|
|
91
|
+
const startOrg: StartOrgReconciliationResponse = { runId: 'run-1' };
|
|
92
|
+
const startCatalog: StartCatalogReconciliationResponse = { runId: 'run-2' };
|
|
93
|
+
const counts = { seen: 10, created: 1, updated: 2, deactivated: 0, reactivated: 1, skipped: 0 };
|
|
94
|
+
const org: GetOrgReconciliationResponse = {
|
|
95
|
+
runId: startOrg.runId,
|
|
96
|
+
retailerId: 'r-1',
|
|
97
|
+
entity: ReconciliationEntityEnum.USERS,
|
|
98
|
+
status: ReconciliationRunStatusEnum.RUNNING,
|
|
99
|
+
trigger: ReconciliationTriggerEnum.MANUAL,
|
|
100
|
+
triggeredBy: 'sub-1',
|
|
101
|
+
startedAt: '2026-10-25T10:00:00.000Z',
|
|
102
|
+
pagesProcessed: 2,
|
|
103
|
+
counts,
|
|
104
|
+
};
|
|
105
|
+
const catalog: GetCatalogReconciliationResponse = {
|
|
106
|
+
runId: startCatalog.runId,
|
|
107
|
+
retailerId: 'r-1',
|
|
108
|
+
entity: ReconciliationEntityEnum.INVENTORY,
|
|
109
|
+
status: ReconciliationRunStatusEnum.COMPLETE,
|
|
110
|
+
trigger: ReconciliationTriggerEnum.SCHEDULED,
|
|
111
|
+
startedAt: '2026-10-25T10:00:00.000Z',
|
|
112
|
+
finishedAt: '2026-10-25T10:05:00.000Z',
|
|
113
|
+
pagesProcessed: 4,
|
|
114
|
+
storeId: 's-1',
|
|
115
|
+
storesPending: 0,
|
|
116
|
+
counts,
|
|
117
|
+
};
|
|
118
|
+
expect(org.counts.seen).toBe(10);
|
|
119
|
+
expect(catalog.storesPending).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import {
|
|
3
|
+
RETAIL_INTEGRATION_ERROR_HTTP_STATUS,
|
|
4
|
+
RetailEventRejectCodeEnum,
|
|
5
|
+
RetailIntegrationErrorCodeEnum as C,
|
|
6
|
+
} from '../../../src/retailIntegration/index';
|
|
7
|
+
|
|
8
|
+
describe('RetailEventRejectCodeEnum', () => {
|
|
9
|
+
it('incluye EVENT_IN_FUTURE y no RETAILER_NOT_INTEGRATED', () => {
|
|
10
|
+
expect(RetailEventRejectCodeEnum.EVENT_IN_FUTURE).toBe('EVENT_IN_FUTURE');
|
|
11
|
+
expect(Object.values(RetailEventRejectCodeEnum)).not.toContain('RETAILER_NOT_INTEGRATED');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('RetailIntegrationErrorCodeEnum', () => {
|
|
16
|
+
it.each([
|
|
17
|
+
[C.ENTITY_MANAGED_BY_INTEGRATION, 422],
|
|
18
|
+
[C.RECONCILIATION_ALREADY_RUNNING, 409],
|
|
19
|
+
[C.RECONCILIATION_NOT_FOUND, 404],
|
|
20
|
+
[C.RESERVATION_LOST, 409],
|
|
21
|
+
[C.PRODUCT_NO_LONGER_SELLABLE, 409],
|
|
22
|
+
[C.STORE_NOT_OPERATIONAL, 422],
|
|
23
|
+
[C.CENTRAL_SYNC_NOT_RESENDABLE, 409],
|
|
24
|
+
[C.SALE_NOT_FOUND, 404],
|
|
25
|
+
[C.RETAILER_ALREADY_INTEGRATED, 409],
|
|
26
|
+
[C.RETAILER_NOT_INTEGRATED, 422],
|
|
27
|
+
[C.CREDIT_REQUIRES_MDM, 422],
|
|
28
|
+
[C.RETAILER_INTEGRATION_MISMATCH, 422],
|
|
29
|
+
])('%s → %i', (code, status) => {
|
|
30
|
+
expect(RETAIL_INTEGRATION_ERROR_HTTP_STATUS[code]).toBe(status);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('cada valor es igual a su llave y son exactamente 12', () => {
|
|
34
|
+
for (const [key, value] of Object.entries(C)) expect(value).toBe(key);
|
|
35
|
+
expect(Object.keys(C)).toHaveLength(12);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -6,10 +6,13 @@ import {
|
|
|
6
6
|
ListRetailCatalogRequest,
|
|
7
7
|
ListRetailStoresRequest,
|
|
8
8
|
ListRetailUsersRequest,
|
|
9
|
+
CheckRetailInventoryRequest,
|
|
9
10
|
RegisterRetailSaleRequest,
|
|
10
11
|
} from '../../../src/retailIntegration';
|
|
11
12
|
import { RetailIntegration } from '../../../src';
|
|
12
13
|
|
|
14
|
+
const tenant = { tenantId: 'surekeep', retailerId: 'ret-1' };
|
|
15
|
+
|
|
13
16
|
async function propertiesWithErrors(cls: new () => object, plain: Record<string, unknown>): Promise<string[]> {
|
|
14
17
|
const errors = await validate(plainToInstance(cls, plain));
|
|
15
18
|
return errors.map((error) => error.property);
|
|
@@ -17,23 +20,23 @@ async function propertiesWithErrors(cls: new () => object, plain: Record<string,
|
|
|
17
20
|
|
|
18
21
|
describe('DTOs de salida hacia el retailer', () => {
|
|
19
22
|
describe.each([ListRetailStoresRequest, ListRetailCatalogRequest, ListRetailUsersRequest])('%p (paginada)', (cls) => {
|
|
20
|
-
it('acepta solo tenantId', async () => {
|
|
21
|
-
expect(await propertiesWithErrors(cls, {
|
|
23
|
+
it('acepta solo tenantId y retailerId', async () => {
|
|
24
|
+
expect(await propertiesWithErrors(cls, { ...tenant })).toEqual([]);
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
it('convierte limit de string a número', async () => {
|
|
25
|
-
const dto = plainToInstance(cls, {
|
|
28
|
+
const dto = plainToInstance(cls, { ...tenant, limit: '50' }) as { limit?: number };
|
|
26
29
|
expect(dto.limit).toBe(50);
|
|
27
30
|
expect(await validate(dto)).toEqual([]);
|
|
28
31
|
});
|
|
29
32
|
|
|
30
33
|
it.each(['0', '201', 'abc', '1.5', ''])('rechaza limit=%p', async (limit) => {
|
|
31
|
-
expect(await propertiesWithErrors(cls, {
|
|
34
|
+
expect(await propertiesWithErrors(cls, { ...tenant, limit })).toEqual(['limit']);
|
|
32
35
|
});
|
|
33
36
|
|
|
34
37
|
it('acepta nextToken opaco y rechaza nextToken vacío', async () => {
|
|
35
|
-
expect(await propertiesWithErrors(cls, {
|
|
36
|
-
expect(await propertiesWithErrors(cls, {
|
|
38
|
+
expect(await propertiesWithErrors(cls, { ...tenant, nextToken: 'eyJpZCI6MTQwMH0' })).toEqual([]);
|
|
39
|
+
expect(await propertiesWithErrors(cls, { ...tenant, nextToken: '' })).toEqual(['nextToken']);
|
|
37
40
|
});
|
|
38
41
|
});
|
|
39
42
|
|
|
@@ -42,23 +45,51 @@ describe('DTOs de salida hacia el retailer', () => {
|
|
|
42
45
|
['con diagonal', 'sure/keep'],
|
|
43
46
|
['vacío', ''],
|
|
44
47
|
])('rechaza tenantId %s', async (_label, tenantId) => {
|
|
45
|
-
expect(await propertiesWithErrors(ListRetailStoresRequest, { tenantId })).toEqual(['tenantId']);
|
|
48
|
+
expect(await propertiesWithErrors(ListRetailStoresRequest, { retailerId: 'ret-1', tenantId })).toEqual(['tenantId']);
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
it('inventario exige storeId', async () => {
|
|
49
|
-
expect(await propertiesWithErrors(GetRetailInventoryRequest, {
|
|
50
|
-
expect(await propertiesWithErrors(GetRetailInventoryRequest, {
|
|
51
|
-
expect(await propertiesWithErrors(GetRetailInventoryRequest, {
|
|
52
|
+
expect(await propertiesWithErrors(GetRetailInventoryRequest, { ...tenant, storeId: '12' })).toEqual([]);
|
|
53
|
+
expect(await propertiesWithErrors(GetRetailInventoryRequest, { ...tenant })).toEqual(['storeId']);
|
|
54
|
+
expect(await propertiesWithErrors(GetRetailInventoryRequest, { ...tenant, storeId: ' ' })).toEqual(['storeId']);
|
|
52
55
|
});
|
|
53
56
|
|
|
54
57
|
it('usuarios acepta storeId opcional', async () => {
|
|
55
|
-
expect(await propertiesWithErrors(ListRetailUsersRequest, {
|
|
56
|
-
expect(await propertiesWithErrors(ListRetailUsersRequest, {
|
|
58
|
+
expect(await propertiesWithErrors(ListRetailUsersRequest, { ...tenant, storeId: '12' })).toEqual([]);
|
|
59
|
+
expect(await propertiesWithErrors(ListRetailUsersRequest, { ...tenant, storeId: '' })).toEqual(['storeId']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it.each([
|
|
63
|
+
['ausente', undefined],
|
|
64
|
+
['vacío', ''],
|
|
65
|
+
['en blanco', ' '],
|
|
66
|
+
['no string', 12],
|
|
67
|
+
])('toda ruta privada rechaza retailerId %s', async (_label, retailerId) => {
|
|
68
|
+
const live = { storeId: '12', identifiers: 'A1', sessionId: 's-1' };
|
|
69
|
+
for (const [cls, extra] of [
|
|
70
|
+
[ListRetailStoresRequest, {}],
|
|
71
|
+
[GetRetailInventoryRequest, { storeId: '12' }],
|
|
72
|
+
[RegisterRetailSaleRequest, {}],
|
|
73
|
+
[CheckRetailInventoryRequest, live],
|
|
74
|
+
] as const) {
|
|
75
|
+
expect(await propertiesWithErrors(cls, { tenantId: 'surekeep', retailerId, ...extra })).toEqual(['retailerId']);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe.each([ListRetailStoresRequest, ListRetailCatalogRequest, ListRetailUsersRequest, GetRetailInventoryRequest])('%p (listado)', (cls) => {
|
|
80
|
+
const base = { ...tenant, storeId: '12' };
|
|
81
|
+
it('acepta runId opcional', async () => {
|
|
82
|
+
expect(await propertiesWithErrors(cls, base)).toEqual([]);
|
|
83
|
+
expect(await propertiesWithErrors(cls, { ...base, runId: 'run-1' })).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
it.each(['', ' ', 7])('rechaza runId=%p', async (runId) => {
|
|
86
|
+
expect(await propertiesWithErrors(cls, { ...base, runId })).toEqual(['runId']);
|
|
87
|
+
});
|
|
57
88
|
});
|
|
58
89
|
|
|
59
|
-
it('venta solo valida tenantId', async () => {
|
|
60
|
-
expect(await propertiesWithErrors(RegisterRetailSaleRequest, {
|
|
61
|
-
expect(await propertiesWithErrors(RegisterRetailSaleRequest, {})).toEqual(['tenantId']);
|
|
90
|
+
it('venta solo valida tenantId y retailerId', async () => {
|
|
91
|
+
expect(await propertiesWithErrors(RegisterRetailSaleRequest, { ...tenant })).toEqual([]);
|
|
92
|
+
expect(await propertiesWithErrors(RegisterRetailSaleRequest, {})).toEqual(['tenantId', 'retailerId']);
|
|
62
93
|
});
|
|
63
94
|
|
|
64
95
|
it('el módulo se expone en la raíz como RetailIntegration', () => {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate, ValidationError } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
CancelRetailSaleRequest,
|
|
6
|
+
CancelRetailSaleResponse,
|
|
7
|
+
CheckRetailInventoryRequest,
|
|
8
|
+
CheckRetailInventoryResponse,
|
|
9
|
+
RETAIL_LIVE_CHECK_IDENTIFIERS_MAX,
|
|
10
|
+
RetailSaleLineRoleEnum,
|
|
11
|
+
RetailSystemSale,
|
|
12
|
+
RetailSystemSaleLine,
|
|
13
|
+
} from '../../../src/retailIntegration';
|
|
14
|
+
|
|
15
|
+
function paths(errors: ValidationError[], prefix = ''): string[] {
|
|
16
|
+
return errors.flatMap((e) => {
|
|
17
|
+
const p = prefix ? `${prefix}.${e.property}` : e.property;
|
|
18
|
+
return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function errorsOf(cls: new () => object, plain: object): Promise<string[]> {
|
|
23
|
+
return paths(await validate(plainToInstance(cls, plain)));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Literal tipado: prueba en compilación que la clase sigue aceptando objetos planos.
|
|
27
|
+
const sale: RetailSystemSale = {
|
|
28
|
+
saleId: '7f0c2a52-1b7e-4f0e-9d7a-2a1d3c4b5e6f',
|
|
29
|
+
folio: 'SK-000123',
|
|
30
|
+
storeId: '12',
|
|
31
|
+
sellerRetailUserId: '431',
|
|
32
|
+
soldAt: '2026-10-25T18:30:00Z',
|
|
33
|
+
saleType: 'CREDIT',
|
|
34
|
+
financier: 'SUREKEEP',
|
|
35
|
+
creditId: 'CR-991',
|
|
36
|
+
currencyId: 'MXN',
|
|
37
|
+
customer: { customerId: 'C-1', names: 'Ana', paternalLastName: 'López', maternalLastName: null, phone: '7221234567' },
|
|
38
|
+
equipmentPrice: '5999.00',
|
|
39
|
+
amountPaid: '600.00',
|
|
40
|
+
paymentMethod: 'CASH',
|
|
41
|
+
lines: [
|
|
42
|
+
{
|
|
43
|
+
lineId: 'L1', sku: 'IPH-15', qty: 1, basePrice: '5999.00', discount: '0.00', paymentType: 'CREDIT',
|
|
44
|
+
financedAmount: '5399.00', role: null, components: [{ sku: 'IPH-15', qty: 1, identifiers: ['356789012345678'] }],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
lineId: 'L2', sku: 'FUNDA-1', qty: 1, basePrice: '0.00', discount: '0.00', paymentType: 'CASH',
|
|
48
|
+
financedAmount: '0.00', role: 'GIFT', components: [],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe('RetailSystemSale (body de privateRegisterRetailSale)', () => {
|
|
54
|
+
it('acepta una venta completa', async () => {
|
|
55
|
+
expect(await errorsOf(RetailSystemSale, sale)).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('acepta los nullables en null', async () => {
|
|
59
|
+
expect(await errorsOf(RetailSystemSale, { ...sale, creditId: null, paymentMethod: null })).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it.each([
|
|
63
|
+
['saleId', { saleId: '' }],
|
|
64
|
+
['storeId', { storeId: 'a/b' }],
|
|
65
|
+
['soldAt', { soldAt: '2026-10-25T18:30:00' }],
|
|
66
|
+
['saleType', { saleType: 'BARTER' }],
|
|
67
|
+
['financier', { financier: 'OTRO' }],
|
|
68
|
+
['currencyId', { currencyId: 'EUR' }],
|
|
69
|
+
['equipmentPrice', { equipmentPrice: '5999' }],
|
|
70
|
+
['amountPaid', { amountPaid: '-1.00' }],
|
|
71
|
+
['paymentMethod', { paymentMethod: 'BITCOIN' }],
|
|
72
|
+
['creditId', { creditId: undefined }],
|
|
73
|
+
['lines', { lines: [] }],
|
|
74
|
+
])('rechaza %s inválido', async (field, patch) => {
|
|
75
|
+
expect(await errorsOf(RetailSystemSale, { ...sale, ...patch })).toEqual([field]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('valida el cliente anidado', async () => {
|
|
79
|
+
expect(await errorsOf(RetailSystemSale, { ...sale, customer: { ...sale.customer, names: '' } })).toEqual(['customer.names']);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('valida las líneas y sus componentes', async () => {
|
|
83
|
+
const [line] = sale.lines;
|
|
84
|
+
const lines = [{ ...line, qty: 0, role: 'PROMO', components: [{ sku: 'IPH-15', qty: 1, identifiers: [''] }] }];
|
|
85
|
+
expect(await errorsOf(RetailSystemSale, { ...sale, lines })).toEqual(['lines.0.qty', 'lines.0.role', 'lines.0.components.0.identifiers']);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('rechaza lineId repetido', async () => {
|
|
89
|
+
expect(await errorsOf(RetailSystemSale, { ...sale, lines: [sale.lines[0], sale.lines[0]] })).toEqual(['lines']);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('CancelRetailSaleRequest', () => {
|
|
94
|
+
const cancel = { saleId: sale.saleId, cancelledAt: '2026-10-25T19:00:00-06:00', reason: 'Cliente desistió' };
|
|
95
|
+
|
|
96
|
+
it('acepta la cancelación', async () => {
|
|
97
|
+
expect(await errorsOf(CancelRetailSaleRequest, cancel)).toEqual([]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it.each([
|
|
101
|
+
['saleId', { saleId: ' ' }],
|
|
102
|
+
['cancelledAt', { cancelledAt: '2026-02-31T10:00:00Z' }],
|
|
103
|
+
['reason', { reason: '' }],
|
|
104
|
+
['reason', { reason: undefined }],
|
|
105
|
+
])('rechaza %s inválido', async (field, patch) => {
|
|
106
|
+
expect(await errorsOf(CancelRetailSaleRequest, { ...cancel, ...patch })).toEqual([field]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('la respuesta trae el saleId', () => {
|
|
110
|
+
const response: CancelRetailSaleResponse = { saleId: cancel.saleId };
|
|
111
|
+
expect(response.saleId).toBe(cancel.saleId);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('RetailSaleLineRoleEnum', () => {
|
|
116
|
+
it('serializa GIFT y el rol acepta el literal o el miembro del enum', () => {
|
|
117
|
+
const fromLiteral: RetailSystemSaleLine['role'] = 'GIFT';
|
|
118
|
+
const fromEnum: RetailSystemSaleLine['role'] = RetailSaleLineRoleEnum.GIFT;
|
|
119
|
+
expect(Object.values(RetailSaleLineRoleEnum)).toEqual(['GIFT']);
|
|
120
|
+
expect(fromLiteral).toBe(fromEnum);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('CheckRetailInventoryRequest (query de privateCheckRetailInventory)', () => {
|
|
125
|
+
const query = { tenantId: 'surekeep', retailerId: 'ret-1', storeId: '12', identifiers: '356789012345678, SN-ABC', sessionId: 'sess-1' };
|
|
126
|
+
|
|
127
|
+
it('acepta hasta el tope de identificadores y rechaza uno más', async () => {
|
|
128
|
+
const many = (n: number) => Array.from({ length: n }, (_, i) => `SN-${i}`);
|
|
129
|
+
expect(RETAIL_LIVE_CHECK_IDENTIFIERS_MAX).toBe(50);
|
|
130
|
+
expect(await errorsOf(CheckRetailInventoryRequest, { ...query, identifiers: many(RETAIL_LIVE_CHECK_IDENTIFIERS_MAX) })).toEqual([]);
|
|
131
|
+
expect(await errorsOf(CheckRetailInventoryRequest, { ...query, identifiers: many(RETAIL_LIVE_CHECK_IDENTIFIERS_MAX + 1).join(',') })).toEqual(['identifiers']);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('convierte identifiers de csv a arreglo sin espacios', async () => {
|
|
135
|
+
const dto = plainToInstance(CheckRetailInventoryRequest, query);
|
|
136
|
+
expect(dto.identifiers).toEqual(['356789012345678', 'SN-ABC']);
|
|
137
|
+
expect(await validate(dto)).toEqual([]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('acepta identifiers ya como arreglo', async () => {
|
|
141
|
+
expect(await errorsOf(CheckRetailInventoryRequest, { ...query, identifiers: ['356789012345678'] })).toEqual([]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it.each([
|
|
145
|
+
['identifiers', { identifiers: '' }],
|
|
146
|
+
['identifiers', { identifiers: 'A,,B' }],
|
|
147
|
+
['identifiers', { identifiers: undefined }],
|
|
148
|
+
['storeId', { storeId: undefined }],
|
|
149
|
+
['sessionId', { sessionId: '' }],
|
|
150
|
+
['tenantId', { tenantId: 'sure/keep' }],
|
|
151
|
+
['retailerId', { retailerId: '' }],
|
|
152
|
+
])('rechaza %s inválido', async (field, patch) => {
|
|
153
|
+
expect(await errorsOf(CheckRetailInventoryRequest, { ...query, ...patch })).toEqual([field]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('la respuesta lista identificadores y la fecha de Zentral', () => {
|
|
157
|
+
const response: CheckRetailInventoryResponse = { listed: ['356789012345678'], generatedAt: '2026-10-25T18:30:00Z' };
|
|
158
|
+
expect(response.listed).toHaveLength(1);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -46,7 +46,7 @@ describe('codes y movimientos de los eventos del retailer', () => {
|
|
|
46
46
|
it('un solo enum de REJECTED para los 4 endpoints', () => {
|
|
47
47
|
expect(Object.values(RetailEventRejectCodeEnum).sort()).toEqual(
|
|
48
48
|
[
|
|
49
|
-
'EMAIL_ALREADY_IN_USE', 'FUTURE_PRICE_NOT_SUPPORTED', 'IDENTIFIER_CONFLICT', 'INVALID_IMEI',
|
|
49
|
+
'EMAIL_ALREADY_IN_USE', 'EVENT_IN_FUTURE', 'FUTURE_PRICE_NOT_SUPPORTED', 'IDENTIFIER_CONFLICT', 'INVALID_IMEI',
|
|
50
50
|
'INVALID_PAYLOAD', 'RETAIL_USER_RETAILER_MISMATCH', 'SKU_NOT_STOCKABLE', 'SKU_OWNED_BY_BACKOFFICE',
|
|
51
51
|
'STORE_CODE_TAKEN', 'STORE_NOT_IN_RETAILER', 'STORE_RETAILER_MISMATCH', 'TRACKING_MODE_MISMATCH',
|
|
52
52
|
'UNSUPPORTED_CURRENCY',
|