@fiado/type-kit 3.583.0 → 3.585.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/UpdateTenantUserEmailRequest.test.ts +24 -0
- package/_test_/unit/platformRbac/enums/__snapshots__/permissionBits.test.ts.snap +2 -1
- package/_test_/unit/platformRbac/enums/apiUserPermission.test.ts +3 -2
- package/_test_/unit/platformRbac/enums/retailIntegrationPermission.test.ts +13 -0
- package/_test_/unit/retailIntegration/eventPayloadBounds.test.ts +147 -0
- package/_test_/unit/retailIntegration/eventPayloads.test.ts +8 -0
- package/_test_/unit/retailIntegration/reconciliationContracts.test.ts +3 -3
- package/_test_/unit/retailIntegration/retailMovementsContracts.test.ts +109 -0
- package/_test_/unit/retailIntegration/retailSystemResponses.test.ts +120 -0
- package/bin/platformRbac/dtos/CreateTenantUserRequest.d.ts +2 -1
- package/bin/platformRbac/dtos/CreateTenantUserRequest.js +2 -1
- package/bin/platformRbac/dtos/UpdateTenantUserEmailRequest.d.ts +5 -0
- package/bin/platformRbac/dtos/UpdateTenantUserEmailRequest.js +30 -0
- package/bin/platformRbac/enums/Permission.d.ts +2 -1
- package/bin/platformRbac/enums/Permission.js +3 -0
- package/bin/platformRbac/index.d.ts +1 -0
- package/bin/platformRbac/index.js +1 -0
- package/bin/retailIntegration/constants/retailIntegrationLimits.d.ts +25 -0
- package/bin/retailIntegration/constants/retailIntegrationLimits.js +26 -1
- package/bin/retailIntegration/dtos/CheckRetailInventoryRequest.d.ts +4 -1
- package/bin/retailIntegration/dtos/CheckRetailInventoryRequest.js +4 -1
- package/bin/retailIntegration/dtos/GetRetailInventoryRequest.d.ts +3 -3
- package/bin/retailIntegration/dtos/GetRetailInventoryRequest.js +3 -3
- package/bin/retailIntegration/dtos/InventoryEventPayload.js +3 -0
- package/bin/retailIntegration/dtos/ListRetailInventoryMovementsRequest.d.ts +6 -0
- package/bin/retailIntegration/dtos/ListRetailInventoryMovementsRequest.js +29 -0
- package/bin/retailIntegration/dtos/ProductEventPayload.js +10 -2
- package/bin/retailIntegration/dtos/ReconciliationPageMessage.d.ts +6 -0
- package/bin/retailIntegration/dtos/ReconciliationPageMessage.js +20 -0
- package/bin/retailIntegration/dtos/RetailOutboundResponses.d.ts +10 -2
- package/bin/retailIntegration/dtos/RetailSystemResponses.d.ts +41 -0
- package/bin/retailIntegration/dtos/RetailSystemResponses.js +136 -0
- package/bin/retailIntegration/dtos/RetailUserEventPayload.js +3 -0
- package/bin/retailIntegration/dtos/StoreEventPayload.js +13 -0
- package/bin/retailIntegration/enums/ReconciliationEntityEnum.d.ts +2 -1
- package/bin/retailIntegration/enums/ReconciliationEntityEnum.js +1 -0
- package/bin/retailIntegration/enums/ReconciliationTriggerEnum.d.ts +4 -2
- package/bin/retailIntegration/enums/ReconciliationTriggerEnum.js +3 -1
- package/bin/retailIntegration/index.d.ts +3 -0
- package/bin/retailIntegration/index.js +3 -0
- package/bin/retailIntegration/models/RetailPageMeta.d.ts +3 -1
- package/bin/retailIntegration/models/RetailSystemInventory.d.ts +9 -1
- package/bin/retailIntegration/validators/IsMovementsRangeValid.d.ts +6 -0
- package/bin/retailIntegration/validators/IsMovementsRangeValid.js +28 -0
- package/package.json +1 -1
- package/src/platformRbac/dtos/CreateTenantUserRequest.ts +2 -1
- package/src/platformRbac/dtos/UpdateTenantUserEmailRequest.ts +12 -0
- package/src/platformRbac/enums/Permission.ts +3 -0
- package/src/platformRbac/index.ts +1 -0
- package/src/retailIntegration/constants/retailIntegrationLimits.ts +37 -0
- package/src/retailIntegration/dtos/CheckRetailInventoryRequest.ts +4 -1
- package/src/retailIntegration/dtos/GetRetailInventoryRequest.ts +3 -3
- package/src/retailIntegration/dtos/InventoryEventPayload.ts +11 -5
- package/src/retailIntegration/dtos/ListRetailInventoryMovementsRequest.ts +14 -0
- package/src/retailIntegration/dtos/ProductEventPayload.ts +19 -12
- package/src/retailIntegration/dtos/ReconciliationPageMessage.ts +21 -1
- package/src/retailIntegration/dtos/RetailOutboundResponses.ts +11 -2
- package/src/retailIntegration/dtos/RetailSystemResponses.ts +75 -0
- package/src/retailIntegration/dtos/RetailUserEventPayload.ts +5 -5
- package/src/retailIntegration/dtos/StoreEventPayload.ts +16 -14
- package/src/retailIntegration/enums/ReconciliationEntityEnum.ts +1 -0
- package/src/retailIntegration/enums/ReconciliationTriggerEnum.ts +3 -1
- package/src/retailIntegration/index.ts +3 -0
- package/src/retailIntegration/models/RetailPageMeta.ts +3 -1
- package/src/retailIntegration/models/RetailSystemInventory.ts +10 -1
- package/src/retailIntegration/validators/IsMovementsRangeValid.ts +17 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validateSync } from 'class-validator';
|
|
4
|
+
import { UpdateTenantUserEmailRequest } from '../../../../src/platformRbac';
|
|
5
|
+
|
|
6
|
+
function propsWithErrors(payload: object): string[] {
|
|
7
|
+
return validateSync(plainToInstance(UpdateTenantUserEmailRequest, payload)).map((e) => e.property);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('UpdateTenantUserEmailRequest', () => {
|
|
11
|
+
it('normaliza el correo a minúsculas sin espacios', () => {
|
|
12
|
+
const dto = plainToInstance(UpdateTenantUserEmailRequest, { email: ' Ana.Perez@Example.COM ', actedBy: 'retail-org-business' });
|
|
13
|
+
expect(dto.email).toBe('ana.perez@example.com');
|
|
14
|
+
expect(validateSync(dto)).toEqual([]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it.each([undefined, '', 'no-es-correo', 42])('rechaza email=%p', (email) => {
|
|
18
|
+
expect(propsWithErrors({ email, actedBy: 'retail-org-business' })).toEqual(['email']);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it.each([undefined, '', 7])('rechaza actedBy=%p', (actedBy) => {
|
|
22
|
+
expect(propsWithErrors({ email: 'ana@example.com', actedBy })).toEqual(['actedBy']);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
2
|
|
|
3
|
-
exports[`PERMISSION_BIT_ORDER PERMS_VERSION es número estable 1`] = `
|
|
3
|
+
exports[`PERMISSION_BIT_ORDER PERMS_VERSION es número estable 1`] = `1071810597`;
|
|
4
4
|
|
|
5
5
|
exports[`PERMISSION_BIT_ORDER append-only: snapshot del ORDEN COMPLETO (rompe ante cualquier reorden/inserción) 1`] = `
|
|
6
6
|
[
|
|
@@ -96,5 +96,6 @@ exports[`PERMISSION_BIT_ORDER append-only: snapshot del ORDEN COMPLETO (rompe an
|
|
|
96
96
|
"platform.integration.key.revoke",
|
|
97
97
|
"tenant.integration.diagnostic",
|
|
98
98
|
"tenant.api_user.manage",
|
|
99
|
+
"platform.retail_integration.activate",
|
|
99
100
|
]
|
|
100
101
|
`;
|
|
@@ -5,7 +5,8 @@ describe('Permiso de administración de usuarios de API', () => {
|
|
|
5
5
|
expect(Permission.TENANT_API_USER_MANAGE).toBe('tenant.api_user.manage');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
-
it('
|
|
9
|
-
|
|
8
|
+
it('conserva su bit después de las integraciones M2M (append-only)', () => {
|
|
9
|
+
const index = PERMISSION_BIT_ORDER.indexOf(Permission.TENANT_API_USER_MANAGE);
|
|
10
|
+
expect(PERMISSION_BIT_ORDER[index - 1]).toBe(Permission.TENANT_INTEGRATION_DIAGNOSTIC);
|
|
10
11
|
});
|
|
11
12
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Permission, PERMISSION_BIT_ORDER } from '../../../../src/platformRbac/enums/Permission';
|
|
2
|
+
|
|
3
|
+
describe('Permiso de activación de la integración retail', () => {
|
|
4
|
+
it('existe con la llave platform.retail_integration.activate (scope PLATFORM)', () => {
|
|
5
|
+
expect(Permission.PLATFORM_RETAIL_INTEGRATION_ACTIVATE).toBe('platform.retail_integration.activate');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('va al FINAL del orden de bits, justo después de tenant.api_user.manage', () => {
|
|
9
|
+
const last = PERMISSION_BIT_ORDER.length - 1;
|
|
10
|
+
expect(PERMISSION_BIT_ORDER[last]).toBe(Permission.PLATFORM_RETAIL_INTEGRATION_ACTIVATE);
|
|
11
|
+
expect(PERMISSION_BIT_ORDER[last - 1]).toBe(Permission.TENANT_API_USER_MANAGE);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate, ValidationError } from 'class-validator';
|
|
4
|
+
import { StoreEventPayload } from '../../../src/retailIntegration/dtos/StoreEventPayload';
|
|
5
|
+
import { RetailUserEventPayload } from '../../../src/retailIntegration/dtos/RetailUserEventPayload';
|
|
6
|
+
import { ProductEventPayload } from '../../../src/retailIntegration/dtos/ProductEventPayload';
|
|
7
|
+
import { InventoryEventPayload } from '../../../src/retailIntegration/dtos/InventoryEventPayload';
|
|
8
|
+
import {
|
|
9
|
+
RETAIL_ELIGIBLE_FINANCIERS_MAX,
|
|
10
|
+
RETAIL_EVENT_STOCK_MAX,
|
|
11
|
+
RETAIL_ID_TEXT_MAX_LENGTH,
|
|
12
|
+
RETAIL_LONG_TEXT_MAX_LENGTH,
|
|
13
|
+
RETAIL_SHORT_TEXT_MAX_LENGTH,
|
|
14
|
+
} from '../../../src/retailIntegration/constants/retailIntegrationLimits';
|
|
15
|
+
|
|
16
|
+
function paths(errors: ValidationError[], prefix = ''): string[] {
|
|
17
|
+
return errors.flatMap((e) => {
|
|
18
|
+
const p = prefix ? `${prefix}.${e.property}` : e.property;
|
|
19
|
+
return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function errorsOf(cls: new () => object, plain: Record<string, unknown>): Promise<string[]> {
|
|
24
|
+
return paths(await validate(plainToInstance(cls, plain)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const text = (n: number): string => 'a'.repeat(n);
|
|
28
|
+
const SHORT = RETAIL_SHORT_TEXT_MAX_LENGTH;
|
|
29
|
+
const LONG = RETAIL_LONG_TEXT_MAX_LENGTH;
|
|
30
|
+
const ID = RETAIL_ID_TEXT_MAX_LENGTH;
|
|
31
|
+
|
|
32
|
+
const address = { address1: 'Av. Juárez', externalNumber: '120', internalNumber: null, neighborhood: 'Centro', city: 'Toluca', state: 'Estado de México', zipCode: '50000', country: 'MX', reference: null };
|
|
33
|
+
const store = {
|
|
34
|
+
storeId: '12', code: 'T12', name: 'Sucursal Centro', zoneId: 'SUR', address,
|
|
35
|
+
location: { lat: 19.2826, lng: -99.6557 }, phone: '7221234567', schedule: 'Lun a Dom 10:00 a 20:00', status: 'ACTIVE',
|
|
36
|
+
};
|
|
37
|
+
const user = {
|
|
38
|
+
retailUserId: '431', displayName: 'Grecia Cruz Mendoza', email: 'gcruz@ejemplo.mx', phone: null,
|
|
39
|
+
homeStoreId: '12', zoneId: null, role: 'SELLER', status: 'ACTIVE',
|
|
40
|
+
};
|
|
41
|
+
const price = { basePrice: '4299.00', listPrice: null, effectiveFrom: '2026-08-17T20:32:05Z' };
|
|
42
|
+
const product = {
|
|
43
|
+
sku: 'SAM-A17-128-NEG', externalId: '9001', brand: 'Samsung', model: 'A17', color: 'Negro', capacity: '128 GB', description: null,
|
|
44
|
+
category: 'PHONE', trackingMode: 'IMEI', tier: null, currencyId: 'MXN', price,
|
|
45
|
+
eligibleFinanciers: [], mdmProvider: null, status: 'ACTIVE',
|
|
46
|
+
};
|
|
47
|
+
const piece = { sku: 'SAM-A17-128-NEG', identifier: '356938035643809', storeId: '12', status: 'SOLD', reason: null };
|
|
48
|
+
const movement = { movementType: 'SALE', storeId: '12', targetStoreId: null, pieces: [piece], stock: [] };
|
|
49
|
+
|
|
50
|
+
describe('StoreEventPayload: topes', () => {
|
|
51
|
+
it('acepta textos justo en su tope', async () => {
|
|
52
|
+
const atMax = {
|
|
53
|
+
...store, name: text(SHORT), phone: text(SHORT), schedule: text(SHORT),
|
|
54
|
+
address: { ...address, address1: text(SHORT), externalNumber: text(SHORT), internalNumber: text(SHORT), neighborhood: text(SHORT), city: text(SHORT), state: text(SHORT), country: text(SHORT), reference: text(LONG) },
|
|
55
|
+
};
|
|
56
|
+
expect(await errorsOf(StoreEventPayload, atMax)).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it.each([
|
|
60
|
+
['name', { name: text(SHORT + 1) }],
|
|
61
|
+
['phone', { phone: text(SHORT + 1) }],
|
|
62
|
+
['schedule', { schedule: text(SHORT + 1) }],
|
|
63
|
+
['address.address1', { address: { ...address, address1: text(SHORT + 1) } }],
|
|
64
|
+
['address.externalNumber', { address: { ...address, externalNumber: text(SHORT + 1) } }],
|
|
65
|
+
['address.internalNumber', { address: { ...address, internalNumber: text(SHORT + 1) } }],
|
|
66
|
+
['address.neighborhood', { address: { ...address, neighborhood: text(SHORT + 1) } }],
|
|
67
|
+
['address.city', { address: { ...address, city: text(SHORT + 1) } }],
|
|
68
|
+
['address.state', { address: { ...address, state: text(SHORT + 1) } }],
|
|
69
|
+
['address.country', { address: { ...address, country: text(SHORT + 1) } }],
|
|
70
|
+
['address.reference', { address: { ...address, reference: text(LONG + 1) } }],
|
|
71
|
+
])('rechaza %s sobre su tope', async (path, over) => {
|
|
72
|
+
expect(await errorsOf(StoreEventPayload, { ...store, ...over })).toContain(path);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it.each([
|
|
76
|
+
['address', 'Av. Juárez 120'],
|
|
77
|
+
['address', [address]],
|
|
78
|
+
['location', 'lat,lng'],
|
|
79
|
+
['location', [19.2, -99.6]],
|
|
80
|
+
])('rechaza %s que no es objeto', async (path, value) => {
|
|
81
|
+
const errors = await validate(plainToInstance(StoreEventPayload, { ...store, [path]: value }));
|
|
82
|
+
expect(errors.find((e) => e.property === path)?.constraints).toHaveProperty('isObject');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('RetailUserEventPayload: topes', () => {
|
|
87
|
+
it('acepta textos justo en su tope', async () => {
|
|
88
|
+
// IsEmail ya topa en 254, uno abajo de RETAIL_SHORT_TEXT_MAX_LENGTH.
|
|
89
|
+
const email = `${text(64)}@${'b'.repeat(63)}.${'c'.repeat(63)}.${'d'.repeat(58)}.mx`;
|
|
90
|
+
expect(email).toHaveLength(SHORT - 1);
|
|
91
|
+
expect(await errorsOf(RetailUserEventPayload, { ...user, displayName: text(SHORT), phone: text(SHORT), email })).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it.each([
|
|
95
|
+
['displayName', { displayName: text(SHORT + 1) }],
|
|
96
|
+
['phone', { phone: text(SHORT + 1) }],
|
|
97
|
+
['email', { email: `${text(SHORT)}@ejemplo.mx` }],
|
|
98
|
+
])('rechaza %s sobre su tope', async (path, over) => {
|
|
99
|
+
expect(await errorsOf(RetailUserEventPayload, { ...user, ...over })).toContain(path);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('ProductEventPayload: topes', () => {
|
|
104
|
+
it('acepta textos, financieras y montos justo en su tope', async () => {
|
|
105
|
+
const atMax = {
|
|
106
|
+
...product, externalId: text(ID), brand: text(SHORT), model: text(SHORT), color: text(SHORT), capacity: text(SHORT), description: text(LONG),
|
|
107
|
+
price: { ...price, basePrice: '999999999999.99', listPrice: '999999999999.99' },
|
|
108
|
+
eligibleFinanciers: Array.from({ length: RETAIL_ELIGIBLE_FINANCIERS_MAX }, () => 'SUREKEEP'),
|
|
109
|
+
};
|
|
110
|
+
expect(await errorsOf(ProductEventPayload, atMax)).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it.each([
|
|
114
|
+
['externalId', { externalId: text(ID + 1) }],
|
|
115
|
+
['brand', { brand: text(SHORT + 1) }],
|
|
116
|
+
['model', { model: text(SHORT + 1) }],
|
|
117
|
+
['color', { color: text(SHORT + 1) }],
|
|
118
|
+
['capacity', { capacity: text(SHORT + 1) }],
|
|
119
|
+
['description', { description: text(LONG + 1) }],
|
|
120
|
+
['price.basePrice', { price: { ...price, basePrice: '1000000000000.00' } }],
|
|
121
|
+
['price.listPrice', { price: { ...price, listPrice: '1000000000000.00' } }],
|
|
122
|
+
['eligibleFinanciers', { eligibleFinanciers: Array.from({ length: RETAIL_ELIGIBLE_FINANCIERS_MAX + 1 }, () => 'SUREKEEP') }],
|
|
123
|
+
])('rechaza %s', async (path, over) => {
|
|
124
|
+
expect(await errorsOf(ProductEventPayload, { ...product, ...over })).toContain(path);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it.each([['4299.00'], [[price]]])('rechaza price que no es objeto: %j', async (value) => {
|
|
128
|
+
const errors = await validate(plainToInstance(ProductEventPayload, { ...product, price: value }));
|
|
129
|
+
expect(errors.find((e) => e.property === 'price')?.constraints).toHaveProperty('isObject');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('InventoryEventPayload: topes', () => {
|
|
134
|
+
const stockOf = (n: number) => Array.from({ length: n }, (_, i) => ({ sku: `MICA-${String(i)}`, storeId: '12', qty: 1 }));
|
|
135
|
+
|
|
136
|
+
it('acepta identifier y stock justo en su tope', async () => {
|
|
137
|
+
expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: [{ ...piece, identifier: text(SHORT) }], stock: stockOf(RETAIL_EVENT_STOCK_MAX) })).toEqual([]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('rechaza identifier sobre su tope', async () => {
|
|
141
|
+
expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: [{ ...piece, identifier: text(SHORT + 1) }] })).toContain('pieces.0.identifier');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('rechaza stock sobre su tope', async () => {
|
|
145
|
+
expect(await errorsOf(InventoryEventPayload, { ...movement, stock: stockOf(RETAIL_EVENT_STOCK_MAX + 1) })).toContain('stock');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -5,6 +5,7 @@ import { StoreEventPayload } from '../../../src/retailIntegration/dtos/StoreEven
|
|
|
5
5
|
import { RetailUserEventPayload } from '../../../src/retailIntegration/dtos/RetailUserEventPayload';
|
|
6
6
|
import { ProductEventPayload } from '../../../src/retailIntegration/dtos/ProductEventPayload';
|
|
7
7
|
import { InventoryEventPayload } from '../../../src/retailIntegration/dtos/InventoryEventPayload';
|
|
8
|
+
import { RETAIL_EVENT_PIECES_MAX } from '../../../src/retailIntegration/constants/retailIntegrationLimits';
|
|
8
9
|
|
|
9
10
|
function paths(errors: ValidationError[], prefix = ''): string[] {
|
|
10
11
|
return errors.flatMap((e) => {
|
|
@@ -130,4 +131,11 @@ describe('InventoryEventPayload', () => {
|
|
|
130
131
|
expect(await errorsOf(InventoryEventPayload, { ...movement, stock: [s, s] })).toContain('stock');
|
|
131
132
|
expect(await errorsOf(InventoryEventPayload, { ...movement, stock: [{ ...s, qty: -1 }] })).toContain('stock.0.qty');
|
|
132
133
|
});
|
|
134
|
+
|
|
135
|
+
it('acepta hasta RETAIL_EVENT_PIECES_MAX piezas y rechaza una más', async () => {
|
|
136
|
+
const piecesOf = (n: number) => Array.from({ length: n }, (_, i) => ({ ...piece, identifier: `35693803564${String(i).padStart(4, '0')}` }));
|
|
137
|
+
expect(RETAIL_EVENT_PIECES_MAX).toBe(30);
|
|
138
|
+
expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: piecesOf(RETAIL_EVENT_PIECES_MAX) })).toEqual([]);
|
|
139
|
+
expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: piecesOf(RETAIL_EVENT_PIECES_MAX + 1) })).toContain('pieces');
|
|
140
|
+
});
|
|
133
141
|
});
|
|
@@ -20,8 +20,8 @@ function propsWithErrors<T extends object>(cls: ClassConstructor<T>, payload: ob
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
describe('enums de la revisión', () => {
|
|
23
|
-
it('ReconciliationEntityEnum trae las
|
|
24
|
-
expect(Object.values(ReconciliationEntityEnum)).toEqual(['STORES', 'USERS', 'CATALOG', 'INVENTORY']);
|
|
23
|
+
it('ReconciliationEntityEnum trae las cinco entidades', () => {
|
|
24
|
+
expect(Object.values(ReconciliationEntityEnum)).toEqual(['STORES', 'USERS', 'CATALOG', 'INVENTORY', 'MOVEMENTS']);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('ReconciliationRunStatusEnum trae los cuatro estados de la corrida', () => {
|
|
@@ -29,7 +29,7 @@ describe('enums de la revisión', () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('ReconciliationTriggerEnum distingue el reloj del botón', () => {
|
|
32
|
-
expect(Object.values(ReconciliationTriggerEnum)).toEqual(['SCHEDULED', 'MANUAL', 'ACTIVATION']);
|
|
32
|
+
expect(Object.values(ReconciliationTriggerEnum)).toEqual(['SCHEDULED', 'MANUAL', 'ACTIVATION', 'DISCREPANCY', 'DAILY']);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
it('InactiveReasonEnum distingue la baja de la revisión de la del evento', () => {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance, type ClassConstructor } from 'class-transformer';
|
|
3
|
+
import { validateSync } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
GetRetailInventoryRequest,
|
|
6
|
+
type GetRetailInventoryResponse,
|
|
7
|
+
ListRetailInventoryMovementsRequest,
|
|
8
|
+
type ListRetailInventoryMovementsResponse,
|
|
9
|
+
ReconciliationEntityEnum,
|
|
10
|
+
ReconciliationPageMessage,
|
|
11
|
+
ReconciliationTriggerEnum,
|
|
12
|
+
} from '../../../src/retailIntegration';
|
|
13
|
+
|
|
14
|
+
function propsWithErrors<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
|
|
15
|
+
return validateSync(plainToInstance(cls, payload)).map((e) => e.property);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const tenant = { tenantId: 'surekeep', retailerId: 'ret-1' };
|
|
19
|
+
|
|
20
|
+
describe('ListRetailInventoryMovementsRequest', () => {
|
|
21
|
+
const valid = { ...tenant, from: '2026-08-17T19:55:00Z', to: '2026-08-17T21:00:00Z' };
|
|
22
|
+
|
|
23
|
+
it('acepta el rango con y sin paginación', () => {
|
|
24
|
+
expect(propsWithErrors(ListRetailInventoryMovementsRequest, valid)).toEqual([]);
|
|
25
|
+
expect(propsWithErrors(ListRetailInventoryMovementsRequest, { ...valid, limit: '200', nextToken: 'abc', runId: 'run-1' })).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('acepta un rango de 7 días exactos', () => {
|
|
29
|
+
expect(propsWithErrors(ListRetailInventoryMovementsRequest, { ...tenant, from: '2026-08-10T00:00:00Z', to: '2026-08-17T00:00:00Z' })).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it.each([
|
|
33
|
+
['to igual a from', '2026-08-17T20:00:00Z', '2026-08-17T20:00:00Z'],
|
|
34
|
+
['to antes de from', '2026-08-17T20:00:00Z', '2026-08-17T19:00:00Z'],
|
|
35
|
+
['más de 7 días', '2026-08-10T00:00:00Z', '2026-08-17T00:00:01Z'],
|
|
36
|
+
])('rechaza %s', (_label, from, to) => {
|
|
37
|
+
expect(propsWithErrors(ListRetailInventoryMovementsRequest, { ...tenant, from, to })).toEqual(['to']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it.each(['from', 'to'])('rechaza sin %s o con fecha sin zona', (field) => {
|
|
41
|
+
const payload: Record<string, unknown> = { ...valid };
|
|
42
|
+
delete payload[field];
|
|
43
|
+
expect(propsWithErrors(ListRetailInventoryMovementsRequest, payload)).toContain(field);
|
|
44
|
+
expect(propsWithErrors(ListRetailInventoryMovementsRequest, { ...valid, [field]: '2026-08-17T20:00:00' })).toContain(field);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it.each(['0', '201'])('rechaza limit=%p', (limit) => {
|
|
48
|
+
expect(propsWithErrors(ListRetailInventoryMovementsRequest, { ...valid, limit })).toEqual(['limit']);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('la respuesta trae items y meta de página', () => {
|
|
52
|
+
const response: ListRetailInventoryMovementsResponse = {
|
|
53
|
+
items: [
|
|
54
|
+
{
|
|
55
|
+
eventId: 'evt-1',
|
|
56
|
+
eventType: 'InventoryMovementV1',
|
|
57
|
+
occurredAt: '2026-08-17T20:32:07Z',
|
|
58
|
+
payload: { movementType: 'SALE', storeId: '12', targetStoreId: null, pieces: [], stock: [] },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
meta: { count: 1, nextToken: null },
|
|
62
|
+
};
|
|
63
|
+
expect(response.meta.nextToken).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('GetRetailInventoryRequest paginado', () => {
|
|
68
|
+
it('acepta limit y nextToken opcionales', () => {
|
|
69
|
+
expect(propsWithErrors(GetRetailInventoryRequest, { ...tenant, storeId: '12', limit: '50', nextToken: 'abc' })).toEqual([]);
|
|
70
|
+
expect(propsWithErrors(GetRetailInventoryRequest, { ...tenant, storeId: '12', limit: '0' })).toEqual(['limit']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('la respuesta admite meta.count y meta.nextToken', () => {
|
|
74
|
+
const response: GetRetailInventoryResponse = {
|
|
75
|
+
pieces: [],
|
|
76
|
+
stock: [],
|
|
77
|
+
meta: { storeId: '12', generatedAt: '2026-08-17T15:00:00Z', count: 0, nextToken: null },
|
|
78
|
+
};
|
|
79
|
+
expect(response.meta.count).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('enums de revisión para movimientos', () => {
|
|
84
|
+
it('ReconciliationEntityEnum suma MOVEMENTS', () => {
|
|
85
|
+
expect(ReconciliationEntityEnum.MOVEMENTS).toBe('MOVEMENTS');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('ReconciliationTriggerEnum suma DISCREPANCY y DAILY', () => {
|
|
89
|
+
expect(ReconciliationTriggerEnum.DISCREPANCY).toBe('DISCREPANCY');
|
|
90
|
+
expect(ReconciliationTriggerEnum.DAILY).toBe('DAILY');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('ReconciliationPageMessage con cursor y rango', () => {
|
|
95
|
+
const base = { ...tenant, runId: 'run-1', pageIndex: 0 };
|
|
96
|
+
|
|
97
|
+
it('acepta nextToken en inventario y from/to en movimientos', () => {
|
|
98
|
+
expect(propsWithErrors(ReconciliationPageMessage, { ...base, entity: 'INVENTORY', storeId: '12', nextToken: 'abc' })).toEqual([]);
|
|
99
|
+
expect(
|
|
100
|
+
propsWithErrors(ReconciliationPageMessage, { ...base, entity: 'MOVEMENTS', from: '2026-08-17T19:55:00Z', to: '2026-08-17T21:00:00Z' }),
|
|
101
|
+
).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('rechaza nextToken vacío y fechas sin zona', () => {
|
|
105
|
+
expect(propsWithErrors(ReconciliationPageMessage, { ...base, entity: 'INVENTORY', nextToken: '' })).toEqual(['nextToken']);
|
|
106
|
+
expect(propsWithErrors(ReconciliationPageMessage, { ...base, entity: 'MOVEMENTS', from: '2026-08-17' })).toEqual(['from']);
|
|
107
|
+
expect(propsWithErrors(ReconciliationPageMessage, { ...base, entity: 'MOVEMENTS', to: 'ayer' })).toEqual(['to']);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance, type ClassConstructor } from 'class-transformer';
|
|
3
|
+
import { validateSync, type ValidationError } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
RetailSystemErrorBody,
|
|
6
|
+
RetailSystemInventoryResponse,
|
|
7
|
+
RetailSystemMovementsResponse,
|
|
8
|
+
RetailSystemPageMeta,
|
|
9
|
+
RetailSystemPageResponse,
|
|
10
|
+
RetailSystemSaleResponse,
|
|
11
|
+
} from '../../../src/retailIntegration';
|
|
12
|
+
|
|
13
|
+
function errorPaths(errors: ValidationError[], prefix = ''): string[] {
|
|
14
|
+
return errors.flatMap((error) => {
|
|
15
|
+
const path = prefix ? `${prefix}.${error.property}` : error.property;
|
|
16
|
+
return error.children?.length ? errorPaths(error.children, path) : [path];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pathsWithErrors<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
|
|
21
|
+
return errorPaths(validateSync(plainToInstance(cls, payload)));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const movement = {
|
|
25
|
+
eventId: 'evt_01J8ZE5M8Y2Q4R7T1V3W6X8Z1C',
|
|
26
|
+
eventType: 'InventoryMovementV1',
|
|
27
|
+
occurredAt: '2026-08-17T20:32:07Z',
|
|
28
|
+
payload: { movementType: 'SALE', storeId: '12', targetStoreId: null, pieces: [], stock: [] },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe('RetailSystemPageMeta', () => {
|
|
32
|
+
it('acepta la última página y una intermedia', () => {
|
|
33
|
+
expect(pathsWithErrors(RetailSystemPageMeta, { count: 0, nextToken: null })).toEqual([]);
|
|
34
|
+
expect(pathsWithErrors(RetailSystemPageMeta, { count: 200, nextToken: 'eyJpZCI6MTQwMH0' })).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it.each([-1, 1.5, '3', undefined])('rechaza count=%p', (count) => {
|
|
38
|
+
expect(pathsWithErrors(RetailSystemPageMeta, { count, nextToken: null })).toEqual(['count']);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it.each([undefined, 7, 'x'.repeat(2049)])('rechaza nextToken=%p', (nextToken) => {
|
|
42
|
+
expect(pathsWithErrors(RetailSystemPageMeta, { count: 1, nextToken })).toEqual(['nextToken']);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('RetailSystemPageResponse', () => {
|
|
47
|
+
it('acepta data con renglones crudos', () => {
|
|
48
|
+
expect(pathsWithErrors(RetailSystemPageResponse, { data: [{ any: 'row' }], meta: { count: 1, nextToken: null } })).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('rechaza data que no es arreglo y meta inválida', () => {
|
|
52
|
+
expect(pathsWithErrors(RetailSystemPageResponse, { data: {}, meta: { count: 1, nextToken: null } })).toEqual(['data']);
|
|
53
|
+
expect(pathsWithErrors(RetailSystemPageResponse, { data: [], meta: { count: -1, nextToken: null } })).toEqual(['meta.count']);
|
|
54
|
+
expect(pathsWithErrors(RetailSystemPageResponse, { data: [] })).toEqual(['meta']);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('RetailSystemInventoryResponse', () => {
|
|
59
|
+
const valid = {
|
|
60
|
+
data: { pieces: [{ sku: 'SAM-A17' }], stock: [] },
|
|
61
|
+
meta: { storeId: '12', generatedAt: '2026-08-17T15:00:00Z', count: 1, nextToken: null },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
it('acepta una página del inventario', () => {
|
|
65
|
+
expect(pathsWithErrors(RetailSystemInventoryResponse, valid)).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('rechaza pieces o stock que no son arreglo', () => {
|
|
69
|
+
expect(pathsWithErrors(RetailSystemInventoryResponse, { ...valid, data: { pieces: null, stock: [] } })).toEqual(['data.pieces']);
|
|
70
|
+
expect(pathsWithErrors(RetailSystemInventoryResponse, { ...valid, data: { pieces: [] } })).toEqual(['data.stock']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it.each([
|
|
74
|
+
['storeId', 'tienda/12'],
|
|
75
|
+
['generatedAt', '2026-08-17 15:00'],
|
|
76
|
+
['count', -2],
|
|
77
|
+
['nextToken', ''.padEnd(2049, 'a')],
|
|
78
|
+
])('rechaza meta.%s inválido', (field, value) => {
|
|
79
|
+
expect(pathsWithErrors(RetailSystemInventoryResponse, { ...valid, meta: { ...valid.meta, [field]: value } })).toEqual([`meta.${field}`]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('RetailSystemSaleResponse', () => {
|
|
84
|
+
it.each(['58812', 'SALE-12_a.b:c', 'a/b#ñ'])('acepta externalSaleId=%p', (externalSaleId) => {
|
|
85
|
+
expect(pathsWithErrors(RetailSystemSaleResponse, { externalSaleId })).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it.each(['', 'con espacio', ' 58812', 'x'.repeat(65), 58812, undefined])('rechaza externalSaleId=%p', (externalSaleId) => {
|
|
89
|
+
expect(pathsWithErrors(RetailSystemSaleResponse, { externalSaleId })).toEqual(['externalSaleId']);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('RetailSystemErrorBody', () => {
|
|
94
|
+
it('acepta el error con y sin lineIndex', () => {
|
|
95
|
+
expect(pathsWithErrors(RetailSystemErrorBody, { code: 'IDENTIFIER_NOT_AVAILABLE', msg: 'texto', lineIndex: 1 })).toEqual([]);
|
|
96
|
+
expect(pathsWithErrors(RetailSystemErrorBody, { code: 'VALIDATION_ERROR' })).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('rechaza code ausente o largo y lineIndex fuera de rango', () => {
|
|
100
|
+
expect(pathsWithErrors(RetailSystemErrorBody, {})).toEqual(['code']);
|
|
101
|
+
expect(pathsWithErrors(RetailSystemErrorBody, { code: 'X'.repeat(65) })).toEqual(['code']);
|
|
102
|
+
expect(pathsWithErrors(RetailSystemErrorBody, { code: 'X', lineIndex: 1000 })).toEqual(['lineIndex']);
|
|
103
|
+
expect(pathsWithErrors(RetailSystemErrorBody, { code: 'X', lineIndex: -1 })).toEqual(['lineIndex']);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('RetailSystemMovementsResponse', () => {
|
|
108
|
+
it('acepta movimientos con el sobre del webhook', () => {
|
|
109
|
+
expect(pathsWithErrors(RetailSystemMovementsResponse, { data: [movement], meta: { count: 1, nextToken: null } })).toEqual([]);
|
|
110
|
+
expect(pathsWithErrors(RetailSystemMovementsResponse, { data: [], meta: { count: 0, nextToken: null } })).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('rechaza un movimiento con sobre inválido', () => {
|
|
114
|
+
const bad = { ...movement, eventType: 'StoreUpsertedV1', occurredAt: '2026-08-17' };
|
|
115
|
+
expect(pathsWithErrors(RetailSystemMovementsResponse, { data: [bad], meta: { count: 1, nextToken: null } }).sort()).toEqual([
|
|
116
|
+
'data.0.eventType',
|
|
117
|
+
'data.0.occurredAt',
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -6,7 +6,8 @@ import { PermissionScope } from '../enums/PermissionScope';
|
|
|
6
6
|
* Consumido por el alta de usuario orquestada de SureKeep F1 (retail-org → RBAC → RetailUser) vía api-invoker.
|
|
7
7
|
* - Usuario provisionado en estado INVITED (mitiga orphan si el paso siguiente en retail-org falla).
|
|
8
8
|
* - Idempotente por identidad Cognito.
|
|
9
|
-
* - `roleId`/`scope`/`scopeRef`: todos o ninguno; sin rol
|
|
9
|
+
* - `roleId`/`scope`/`scopeRef`: todos o ninguno; sin rol exige `suppressInvitation: true`
|
|
10
|
+
* y la invitación queda pendiente (`invitationPending`) hasta asignar el primer rol.
|
|
10
11
|
* - `createdBy` = cognitoSub del admin real (audit + grantedBy del assignment).
|
|
11
12
|
* - `customFields` = valores de los campos personalizados del tenant; si el correo ya tiene usuario, se fusionan.
|
|
12
13
|
*/
|
|
@@ -23,7 +23,8 @@ const hasAnyGrantField = (o) => o.roleId !== undefined || o.scope !== undefined
|
|
|
23
23
|
* Consumido por el alta de usuario orquestada de SureKeep F1 (retail-org → RBAC → RetailUser) vía api-invoker.
|
|
24
24
|
* - Usuario provisionado en estado INVITED (mitiga orphan si el paso siguiente en retail-org falla).
|
|
25
25
|
* - Idempotente por identidad Cognito.
|
|
26
|
-
* - `roleId`/`scope`/`scopeRef`: todos o ninguno; sin rol
|
|
26
|
+
* - `roleId`/`scope`/`scopeRef`: todos o ninguno; sin rol exige `suppressInvitation: true`
|
|
27
|
+
* y la invitación queda pendiente (`invitationPending`) hasta asignar el primer rol.
|
|
27
28
|
* - `createdBy` = cognitoSub del admin real (audit + grantedBy del assignment).
|
|
28
29
|
* - `customFields` = valores de los campos personalizados del tenant; si el correo ya tiene usuario, se fusionan.
|
|
29
30
|
*/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.UpdateTenantUserEmailRequest = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
/** Body M2M que cambia el correo de un usuario del tenant; el correo se guarda en minúsculas. */
|
|
16
|
+
class UpdateTenantUserEmailRequest {
|
|
17
|
+
}
|
|
18
|
+
exports.UpdateTenantUserEmailRequest = UpdateTenantUserEmailRequest;
|
|
19
|
+
__decorate([
|
|
20
|
+
(0, class_transformer_1.Expose)(),
|
|
21
|
+
(0, class_transformer_1.Transform)(({ value }) => (typeof value === 'string' ? value.trim().toLowerCase() : value)),
|
|
22
|
+
(0, class_validator_1.IsEmail)(),
|
|
23
|
+
__metadata("design:type", String)
|
|
24
|
+
], UpdateTenantUserEmailRequest.prototype, "email", void 0);
|
|
25
|
+
__decorate([
|
|
26
|
+
(0, class_transformer_1.Expose)(),
|
|
27
|
+
(0, class_validator_1.IsString)(),
|
|
28
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], UpdateTenantUserEmailRequest.prototype, "actedBy", void 0);
|
|
@@ -120,7 +120,8 @@ export declare enum Permission {
|
|
|
120
120
|
PLATFORM_INTEGRATION_KEY_ISSUE = "platform.integration.key.issue",
|
|
121
121
|
PLATFORM_INTEGRATION_KEY_REVOKE = "platform.integration.key.revoke",
|
|
122
122
|
TENANT_INTEGRATION_DIAGNOSTIC = "tenant.integration.diagnostic",
|
|
123
|
-
TENANT_API_USER_MANAGE = "tenant.api_user.manage"
|
|
123
|
+
TENANT_API_USER_MANAGE = "tenant.api_user.manage",
|
|
124
|
+
PLATFORM_RETAIL_INTEGRATION_ACTIVATE = "platform.retail_integration.activate"
|
|
124
125
|
}
|
|
125
126
|
/**
|
|
126
127
|
* Orden CANÓNICO y APPEND-ONLY de los permisos para el bitset del token.
|
|
@@ -166,6 +166,8 @@ var Permission;
|
|
|
166
166
|
Permission["TENANT_INTEGRATION_DIAGNOSTIC"] = "tenant.integration.diagnostic";
|
|
167
167
|
// Alta, rotación de secreto y edición de IPs de usuarios de API; se exige además de tenant.user.create.
|
|
168
168
|
Permission["TENANT_API_USER_MANAGE"] = "tenant.api_user.manage";
|
|
169
|
+
// Activa la integración de un retailer externo (Zentral); scope PLATFORM.
|
|
170
|
+
Permission["PLATFORM_RETAIL_INTEGRATION_ACTIVATE"] = "platform.retail_integration.activate";
|
|
169
171
|
})(Permission || (exports.Permission = Permission = {}));
|
|
170
172
|
/**
|
|
171
173
|
* Orden CANÓNICO y APPEND-ONLY de los permisos para el bitset del token.
|
|
@@ -286,6 +288,7 @@ exports.PERMISSION_BIT_ORDER = [
|
|
|
286
288
|
Permission.PLATFORM_INTEGRATION_KEY_REVOKE,
|
|
287
289
|
Permission.TENANT_INTEGRATION_DIAGNOSTIC,
|
|
288
290
|
Permission.TENANT_API_USER_MANAGE,
|
|
291
|
+
Permission.PLATFORM_RETAIL_INTEGRATION_ACTIVATE,
|
|
289
292
|
];
|
|
290
293
|
function djb2(input) {
|
|
291
294
|
let h = 5381;
|
|
@@ -73,6 +73,7 @@ export type { ListTenantUsersResponse } from './dtos/ListTenantUsersResponse';
|
|
|
73
73
|
export type { TenantUserListItemResponse } from './dtos/TenantUserListItemResponse';
|
|
74
74
|
export type { GetTenantUserResponse } from './dtos/GetTenantUserResponse';
|
|
75
75
|
export * from './dtos/UpdateTenantUserRequest';
|
|
76
|
+
export * from './dtos/UpdateTenantUserEmailRequest';
|
|
76
77
|
export * from './dtos/DeleteTenantUserRequest';
|
|
77
78
|
export * from './dtos/DisableTenantUserRequest';
|
|
78
79
|
export * from './dtos/EnableTenantUserRequest';
|
|
@@ -120,6 +120,7 @@ __exportStar(require("./dtos/UpdateIntegrationRequest"), exports);
|
|
|
120
120
|
__exportStar(require("./dtos/UpdateLevelEntityRequest"), exports);
|
|
121
121
|
__exportStar(require("./dtos/DeleteLevelEntityRequest"), exports);
|
|
122
122
|
__exportStar(require("./dtos/UpdateTenantUserRequest"), exports);
|
|
123
|
+
__exportStar(require("./dtos/UpdateTenantUserEmailRequest"), exports);
|
|
123
124
|
__exportStar(require("./dtos/DeleteTenantUserRequest"), exports);
|
|
124
125
|
__exportStar(require("./dtos/DisableTenantUserRequest"), exports);
|
|
125
126
|
__exportStar(require("./dtos/EnableTenantUserRequest"), exports);
|
|
@@ -20,3 +20,28 @@ export declare const RETAIL_SKU_PATTERN: RegExp;
|
|
|
20
20
|
export declare const RETAIL_DECIMAL_PATTERN: RegExp;
|
|
21
21
|
/** Tope de identificadores por consulta de inventario en vivo. */
|
|
22
22
|
export declare const RETAIL_LIVE_CHECK_IDENTIFIERS_MAX = 50;
|
|
23
|
+
/** Tope de piezas por evento `InventoryMovementV1`; más se rechaza con INVALID_PAYLOAD. */
|
|
24
|
+
export declare const RETAIL_EVENT_PIECES_MAX = 30;
|
|
25
|
+
/** Tope de ids y códigos libres del retailer, como `externalId`. */
|
|
26
|
+
export declare const RETAIL_ID_TEXT_MAX_LENGTH = 64;
|
|
27
|
+
/** Tope de textos cortos de los payloads: nombres, dirección, teléfono, marca, modelo, `identifier`. */
|
|
28
|
+
export declare const RETAIL_SHORT_TEXT_MAX_LENGTH = 255;
|
|
29
|
+
/** Tope de textos largos de los payloads: descripción y referencia. */
|
|
30
|
+
export declare const RETAIL_LONG_TEXT_MAX_LENGTH = 1000;
|
|
31
|
+
/** Tope de existencias por evento `InventoryMovementV1`. */
|
|
32
|
+
export declare const RETAIL_EVENT_STOCK_MAX = 200;
|
|
33
|
+
/** Tope de financieras elegibles por producto. */
|
|
34
|
+
export declare const RETAIL_ELIGIBLE_FINANCIERS_MAX = 50;
|
|
35
|
+
/** Decimal de los eventos del retailer: hasta 12 enteros y dos decimales, nunca negativo. */
|
|
36
|
+
export declare const RETAIL_EVENT_DECIMAL_PATTERN: RegExp;
|
|
37
|
+
/** Tope del `nextToken` opaco que devuelve Zentral. */
|
|
38
|
+
export declare const RETAIL_NEXT_TOKEN_MAX_LENGTH = 2048;
|
|
39
|
+
/** Id que asigna Zentral, como `externalSaleId`: no vacío, sin espacios y de a lo más 64. */
|
|
40
|
+
export declare const RETAIL_EXTERNAL_ID_PATTERN: RegExp;
|
|
41
|
+
/** Tope del `code` en el cuerpo de error de Zentral. */
|
|
42
|
+
export declare const RETAIL_ERROR_CODE_MAX_LENGTH = 64;
|
|
43
|
+
/** Rango de `lineIndex` en un error de línea de venta. */
|
|
44
|
+
export declare const RETAIL_LINE_INDEX_MIN = 0;
|
|
45
|
+
export declare const RETAIL_LINE_INDEX_MAX = 999;
|
|
46
|
+
/** Rango máximo de `GET /inventory/movements`: 7 días en milisegundos. */
|
|
47
|
+
export declare const RETAIL_MOVEMENTS_RANGE_MAX_MS: number;
|