@fiado/type-kit 3.501.0 → 3.503.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.
Files changed (52) hide show
  1. package/_test_/unit/loanCredit/inStorePaymentDtos.test.ts +19 -0
  2. package/_test_/unit/platformRbac/dtos/CreateTenantUserRequest.test.ts +31 -0
  3. package/_test_/unit/retailIntegration/applyRetailEventRequest.test.ts +11 -0
  4. package/_test_/unit/retailIntegration/eventPayloads.test.ts +124 -0
  5. package/_test_/unit/retailIntegration/payloadValidators.test.ts +34 -0
  6. package/_test_/unit/retailIntegration/retailSystemEnums.test.ts +18 -0
  7. package/_test_/unit/retailOrg/dtos/Store.test.ts +6 -0
  8. package/bin/loanCredit/dtos/responses/LoanPaymentListItem.d.ts +2 -0
  9. package/bin/platformRbac/dtos/CreateTenantUserRequest.d.ts +6 -4
  10. package/bin/platformRbac/dtos/CreateTenantUserRequest.js +11 -1
  11. package/bin/platformRbac/validators/SuppressInvitationRequiredWithoutRole.d.ts +6 -0
  12. package/bin/platformRbac/validators/SuppressInvitationRequiredWithoutRole.js +24 -0
  13. package/bin/retailIntegration/constants/retailIntegrationLimits.d.ts +13 -0
  14. package/bin/retailIntegration/constants/retailIntegrationLimits.js +14 -1
  15. package/bin/retailIntegration/dtos/ApplyRetailEventRequest.d.ts +8 -0
  16. package/bin/retailIntegration/dtos/ApplyRetailEventRequest.js +20 -0
  17. package/bin/retailIntegration/dtos/InventoryEventPayload.d.ts +25 -0
  18. package/bin/retailIntegration/dtos/InventoryEventPayload.js +94 -0
  19. package/bin/retailIntegration/dtos/ProductEventPayload.d.ts +25 -0
  20. package/bin/retailIntegration/dtos/ProductEventPayload.js +113 -0
  21. package/bin/retailIntegration/dtos/RetailUserEventPayload.d.ts +12 -0
  22. package/bin/retailIntegration/dtos/RetailUserEventPayload.js +58 -0
  23. package/bin/retailIntegration/dtos/StoreEventPayload.d.ts +30 -0
  24. package/bin/retailIntegration/dtos/StoreEventPayload.js +137 -0
  25. package/bin/retailIntegration/enums/RetailEventRejectCodeEnum.d.ts +16 -0
  26. package/bin/retailIntegration/enums/RetailEventRejectCodeEnum.js +20 -0
  27. package/bin/retailIntegration/enums/RetailMovementTypeEnum.d.ts +10 -0
  28. package/bin/retailIntegration/enums/RetailMovementTypeEnum.js +14 -0
  29. package/bin/retailIntegration/enums/index.d.ts +2 -0
  30. package/bin/retailIntegration/enums/index.js +2 -0
  31. package/bin/retailIntegration/index.d.ts +5 -0
  32. package/bin/retailIntegration/index.js +5 -0
  33. package/bin/retailIntegration/validators/payloadValidators.d.ts +18 -0
  34. package/bin/retailIntegration/validators/payloadValidators.js +72 -0
  35. package/bin/retailOrg/dtos/Store.d.ts +2 -0
  36. package/bin/retailOrg/dtos/Store.js +6 -0
  37. package/package.json +1 -1
  38. package/src/loanCredit/dtos/responses/LoanPaymentListItem.ts +2 -0
  39. package/src/platformRbac/dtos/CreateTenantUserRequest.ts +11 -5
  40. package/src/platformRbac/validators/SuppressInvitationRequiredWithoutRole.ts +14 -0
  41. package/src/retailIntegration/constants/retailIntegrationLimits.ts +19 -0
  42. package/src/retailIntegration/dtos/ApplyRetailEventRequest.ts +20 -0
  43. package/src/retailIntegration/dtos/InventoryEventPayload.ts +33 -0
  44. package/src/retailIntegration/dtos/ProductEventPayload.ts +51 -0
  45. package/src/retailIntegration/dtos/RetailUserEventPayload.ts +17 -0
  46. package/src/retailIntegration/dtos/StoreEventPayload.ts +47 -0
  47. package/src/retailIntegration/enums/RetailEventRejectCodeEnum.ts +16 -0
  48. package/src/retailIntegration/enums/RetailMovementTypeEnum.ts +10 -0
  49. package/src/retailIntegration/enums/index.ts +2 -0
  50. package/src/retailIntegration/index.ts +5 -0
  51. package/src/retailIntegration/validators/payloadValidators.ts +56 -0
  52. package/src/retailOrg/dtos/Store.ts +7 -0
@@ -112,6 +112,25 @@ describe('loanCredit — contrato del cobro en tienda', () => {
112
112
  expect(item.mechanism).toBeUndefined();
113
113
  });
114
114
 
115
+ it('el historial lista todas las cuotas que cubrió el pago, en orden', () => {
116
+ const item: LoanPaymentListItem = {
117
+ paymentId: 'P-2',
118
+ receipt: 'SK-TD-0123456789ac',
119
+ installmentNumber: 3,
120
+ installmentNumbers: [3, 4],
121
+ attemptId: 'IN_STORE#k2',
122
+ result: 'FULL' as never,
123
+ currency: 'MXN' as never,
124
+ collectedCents: 97_400,
125
+ appliedCents: 97_400,
126
+ overpaidCents: 0,
127
+ reservedCents: 0,
128
+ needsReview: false,
129
+ appliedAt: '2026-10-08T17:00:00.000Z',
130
+ };
131
+ expect(item.installmentNumbers).toEqual([3, 4]);
132
+ });
133
+
115
134
  it('la fila de Clientes trae el último pago con su lugar', () => {
116
135
  const row: CreditCustomerListItem = {
117
136
  retailCustomerId: 'RC-1',
@@ -0,0 +1,31 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { CreateTenantUserRequest } from '../../../../src/platformRbac/dtos/CreateTenantUserRequest';
5
+ import { PermissionScope } from '../../../../src/platformRbac/enums/PermissionScope';
6
+
7
+ const base = { email: 'u@x.mx', displayName: 'U', createdBy: 'lambda:retail-integration-connector' };
8
+ const grant = { roleId: 'vendedor', scope: PermissionScope.LEVEL_3, scopeRef: '12' };
9
+
10
+ async function props(plain: Record<string, unknown>): Promise<string[]> {
11
+ return (await validate(plainToInstance(CreateTenantUserRequest, plain))).map((e) => e.property).sort();
12
+ }
13
+
14
+ describe('CreateTenantUserRequest — rol todos o ninguno', () => {
15
+ it('con rol completo es válido; suppressInvitation con rol se rechaza (sin rol es lo único que lo admite)', async () => {
16
+ expect(await props({ ...base, ...grant })).toEqual([]);
17
+ expect(await props({ ...base, ...grant, suppressInvitation: true })).toEqual(['suppressInvitation']);
18
+ expect(await props({ ...base, ...grant, suppressInvitation: false })).toEqual(['suppressInvitation']);
19
+ });
20
+
21
+ it('sin rol exige suppressInvitation: true', async () => {
22
+ expect(await props({ ...base, suppressInvitation: true })).toEqual([]);
23
+ expect(await props(base)).toEqual(['suppressInvitation']);
24
+ expect(await props({ ...base, suppressInvitation: false })).toEqual(['suppressInvitation']);
25
+ });
26
+
27
+ it('rol a medias es inválido', async () => {
28
+ expect(await props({ ...base, roleId: 'vendedor', suppressInvitation: true })).toEqual(['scope', 'scopeRef', 'suppressInvitation']);
29
+ expect(await props({ ...base, scopeRef: '12', suppressInvitation: true })).toEqual(['roleId', 'scope']);
30
+ });
31
+ });
@@ -10,6 +10,7 @@ import {
10
10
 
11
11
  const base = {
12
12
  tenantId: 'surekeep',
13
+ retailerId: '01KXPB422AFGZHQKHVM7E5YDBR',
13
14
  eventId: 'evt_01J8ZA1B2C3D4E5F6G7H8J9K0L',
14
15
  occurredAt: '2026-08-01T15:00:00Z',
15
16
  payload: { storeId: '12' },
@@ -40,4 +41,14 @@ describe('DTOs del contrato connector → destino', () => {
40
41
  }
41
42
  },
42
43
  );
44
+
45
+ it.each([ApplyStoreEventRequest, ApplyRetailUserEventRequest, ApplyProductEventRequest, ApplyInventoryEventRequest])(
46
+ '%p exige retailerId no vacío',
47
+ async (cls) => {
48
+ const eventType = { ApplyStoreEventRequest: 'StoreCreatedV1', ApplyRetailUserEventRequest: 'RetailUserCreatedV1', ApplyProductEventRequest: 'ProductCreatedV1', ApplyInventoryEventRequest: 'InventoryMovementV1' }[cls.name];
49
+ for (const retailerId of [undefined, '', ' ']) {
50
+ expect(await propertiesWithErrors(cls, { ...base, eventType, retailerId })).toEqual(['retailerId']);
51
+ }
52
+ },
53
+ );
43
54
  });
@@ -0,0 +1,124 @@
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
+
9
+ function paths(errors: ValidationError[], prefix = ''): string[] {
10
+ return errors.flatMap((e) => {
11
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
12
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
13
+ });
14
+ }
15
+
16
+ async function errorsOf(cls: new () => object, plain: Record<string, unknown>): Promise<string[]> {
17
+ return paths(await validate(plainToInstance(cls, plain)));
18
+ }
19
+
20
+ export const store = {
21
+ storeId: '12', code: 'T12', name: 'Sucursal Centro', zoneId: 'SUR',
22
+ address: { address1: 'Av. Juárez', externalNumber: '120', internalNumber: null, neighborhood: 'Centro', city: 'Toluca', state: 'Estado de México', zipCode: '50000', country: 'MX', reference: null },
23
+ location: { lat: 19.2826, lng: -99.6557 }, phone: '7221234567', schedule: 'Lun a Dom 10:00 a 20:00', status: 'ACTIVE',
24
+ };
25
+
26
+ export const user = {
27
+ retailUserId: '431', displayName: 'Grecia Cruz Mendoza', email: 'gcruz@ejemplo.mx', phone: null,
28
+ homeStoreId: '12', zoneId: null, role: 'SELLER', status: 'ACTIVE',
29
+ };
30
+
31
+ describe('StoreEventPayload', () => {
32
+ it('acepta el ejemplo del contrato', async () => {
33
+ expect(await errorsOf(StoreEventPayload, store)).toEqual([]);
34
+ });
35
+
36
+ it('acepta nullables en null y location nula', async () => {
37
+ expect(await errorsOf(StoreEventPayload, { ...store, zoneId: null, schedule: null, location: { lat: null, lng: null } })).toEqual([]);
38
+ });
39
+
40
+ it.each([
41
+ ['storeId', { storeId: 'a/b' }],
42
+ ['code', { code: 'ABCDEFGHIJK' }],
43
+ ['zoneId', { zoneId: 'NOZONE-R1' }],
44
+ ['schedule', { schedule: undefined }],
45
+ ['status', { status: 'OPEN' }],
46
+ ['address.zipCode', { address: { ...store.address, zipCode: '1' } }],
47
+ ['address.reference', { address: { ...store.address, reference: undefined } }],
48
+ ['location.lat', { location: { lat: 91, lng: 0 } }],
49
+ ])('rechaza %s', async (path, over) => {
50
+ expect(await errorsOf(StoreEventPayload, { ...store, ...over })).toContain(path);
51
+ });
52
+
53
+ it('rechaza el payload parcial del ejemplo de StoreCreatedV1', async () => {
54
+ const partial = { storeId: '12', code: 'T12', name: 'Sucursal Centro', status: 'ACTIVE' };
55
+ expect((await errorsOf(StoreEventPayload, partial)).length).toBeGreaterThan(0);
56
+ });
57
+ });
58
+
59
+ describe('RetailUserEventPayload', () => {
60
+ it('acepta el ejemplo del contrato', async () => {
61
+ expect(await errorsOf(RetailUserEventPayload, user)).toEqual([]);
62
+ });
63
+
64
+ it.each([
65
+ ['retailUserId', { retailUserId: '' }],
66
+ ['email', { email: 'no-es-correo' }],
67
+ ['phone', { phone: undefined }],
68
+ ['role', { role: 'ADMIN' }],
69
+ ['status', { status: 'BLOCKED' }],
70
+ ['zoneId', { zoneId: 'NOZONE-X' }],
71
+ ])('rechaza %s', async (path, over) => {
72
+ expect(await errorsOf(RetailUserEventPayload, { ...user, ...over })).toContain(path);
73
+ });
74
+ });
75
+
76
+ const product = {
77
+ sku: 'SAM-A17-128-NEG', externalId: '9001', brand: 'Samsung', model: 'A17', color: 'Negro', capacity: '128 GB', description: null,
78
+ category: 'PHONE', trackingMode: 'IMEI', tier: null, currencyId: 'MXN',
79
+ price: { basePrice: '4299.00', listPrice: null, effectiveFrom: '2026-08-17T20:32:05Z' },
80
+ eligibleFinanciers: [], mdmProvider: null, status: 'ACTIVE',
81
+ };
82
+
83
+ const piece = { sku: 'SAM-A17-128-NEG', identifier: '356938035643809', storeId: '12', status: 'SOLD', reason: null };
84
+ const movement = { movementType: 'SALE', storeId: '12', targetStoreId: null, pieces: [piece], stock: [{ sku: 'MICA-A17', storeId: '12', qty: 39 }] };
85
+
86
+ describe('ProductEventPayload', () => {
87
+ it('acepta un producto completo', async () => {
88
+ expect(await errorsOf(ProductEventPayload, product)).toEqual([]);
89
+ });
90
+
91
+ it.each([
92
+ ['sku', { sku: 'SAM_A17' }],
93
+ ['price.basePrice', { price: { ...product.price, basePrice: '4299' } }],
94
+ ['price.effectiveFrom', { price: { ...product.price, effectiveFrom: '2026-08-17T20:32:05' } }],
95
+ ['eligibleFinanciers', { eligibleFinanciers: ['NADIE'] }],
96
+ ['tier', { tier: undefined }],
97
+ ])('rechaza %s', async (path, over) => {
98
+ expect(await errorsOf(ProductEventPayload, { ...product, ...over })).toContain(path);
99
+ });
100
+ });
101
+
102
+ describe('InventoryEventPayload', () => {
103
+ it('acepta el ejemplo del contrato', async () => {
104
+ expect(await errorsOf(InventoryEventPayload, movement)).toEqual([]);
105
+ });
106
+
107
+ it('TRANSFER_OUT exige targetStoreId y los demás lo exigen null', async () => {
108
+ expect(await errorsOf(InventoryEventPayload, { ...movement, movementType: 'TRANSFER_OUT' })).toContain('targetStoreId');
109
+ expect(await errorsOf(InventoryEventPayload, { ...movement, movementType: 'TRANSFER_OUT', targetStoreId: '14', pieces: [{ ...piece, status: 'IN_TRANSIT' }] })).toEqual([]);
110
+ expect(await errorsOf(InventoryEventPayload, { ...movement, targetStoreId: '14' })).toContain('targetStoreId');
111
+ });
112
+
113
+ it('DAMAGED exige reason y los demás lo exigen null', async () => {
114
+ expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: [{ ...piece, status: 'DAMAGED' }] })).toContain('pieces.0.reason');
115
+ expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: [{ ...piece, reason: 'SHRINKAGE' }] })).toContain('pieces.0.reason');
116
+ });
117
+
118
+ it('rechaza identifier repetido, (storeId, sku) repetido y qty negativa', async () => {
119
+ expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: [piece, piece] })).toContain('pieces');
120
+ const s = { sku: 'MICA-A17', storeId: '12', qty: 1 };
121
+ expect(await errorsOf(InventoryEventPayload, { ...movement, stock: [s, s] })).toContain('stock');
122
+ expect(await errorsOf(InventoryEventPayload, { ...movement, stock: [{ ...s, qty: -1 }] })).toContain('stock.0.qty');
123
+ });
124
+ });
@@ -0,0 +1,34 @@
1
+ import 'reflect-metadata';
2
+ import { IsArray, Validate, validate } from 'class-validator';
3
+ import { HasUniqueKeys, isNullOrValidated } from '../../../src/retailIntegration/validators/payloadValidators';
4
+ import { RETAIL_ENTITY_ID_PATTERN, RETAIL_ZONE_ID_PATTERN, RETAIL_DECIMAL_PATTERN } from '../../../src/retailIntegration/constants/retailIntegrationLimits';
5
+
6
+ class Holder {
7
+ @IsArray()
8
+ @Validate(HasUniqueKeys, ['storeId', 'sku'])
9
+ items!: Array<{ storeId: string; sku: string }>;
10
+ }
11
+
12
+ describe('validadores de payload', () => {
13
+ it('HasUniqueKeys rechaza una llave repetida', async () => {
14
+ const ok = Object.assign(new Holder(), { items: [{ storeId: '1', sku: 'A' }, { storeId: '2', sku: 'A' }] });
15
+ const dup = Object.assign(new Holder(), { items: [{ storeId: '1', sku: 'A' }, { storeId: '1', sku: 'A' }] });
16
+ expect(await validate(ok)).toHaveLength(0);
17
+ expect((await validate(dup)).map((e) => e.property)).toEqual(['items']);
18
+ });
19
+
20
+ it('isNullOrValidated solo salta la validación con null, no con la llave ausente', () => {
21
+ expect(isNullOrValidated({}, null)).toBe(false);
22
+ expect(isNullOrValidated({}, undefined)).toBe(true);
23
+ expect(isNullOrValidated({}, 'x')).toBe(true);
24
+ });
25
+
26
+ it('patrones', () => {
27
+ expect(RETAIL_ENTITY_ID_PATTERN.test('12')).toBe(true);
28
+ expect(RETAIL_ENTITY_ID_PATTERN.test('a/b')).toBe(false);
29
+ expect(RETAIL_ZONE_ID_PATTERN.test('SUR')).toBe(true);
30
+ expect(RETAIL_ZONE_ID_PATTERN.test('NOZONE-R1')).toBe(false);
31
+ expect(RETAIL_DECIMAL_PATTERN.test('4299.00')).toBe(true);
32
+ expect(RETAIL_DECIMAL_PATTERN.test('4299.0')).toBe(false);
33
+ });
34
+ });
@@ -1,4 +1,5 @@
1
1
  import 'reflect-metadata';
2
+ import { RetailEventRejectCodeEnum, RetailMovementTypeEnum } from '../../../src/retailIntegration/enums';
2
3
  import {
3
4
  RetailCurrencyEnum,
4
5
  RetailFinancierEnum,
@@ -40,3 +41,20 @@ describe('enums de los objetos del retailer', () => {
40
41
  expect(Object.values(enumObject)).toEqual(values);
41
42
  });
42
43
  });
44
+
45
+ describe('codes y movimientos de los eventos del retailer', () => {
46
+ it('un solo enum de REJECTED para los 4 endpoints', () => {
47
+ expect(Object.values(RetailEventRejectCodeEnum).sort()).toEqual(
48
+ [
49
+ 'EMAIL_ALREADY_IN_USE', 'FUTURE_PRICE_NOT_SUPPORTED', 'IDENTIFIER_CONFLICT', 'INVALID_IMEI',
50
+ 'INVALID_PAYLOAD', 'RETAIL_USER_RETAILER_MISMATCH', 'SKU_NOT_STOCKABLE', 'SKU_OWNED_BY_BACKOFFICE',
51
+ 'STORE_CODE_TAKEN', 'STORE_NOT_IN_RETAILER', 'STORE_RETAILER_MISMATCH', 'TRACKING_MODE_MISMATCH',
52
+ 'UNSUPPORTED_CURRENCY',
53
+ ].sort(),
54
+ );
55
+ });
56
+
57
+ it('los 7 movementType del contrato', () => {
58
+ expect(Object.values(RetailMovementTypeEnum)).toEqual(['ENTRY', 'SALE', 'RETURN', 'ADJUSTMENT', 'TRANSFER_OUT', 'TRANSFER_IN', 'TRANSFER_CANCEL']);
59
+ });
60
+ });
@@ -66,4 +66,10 @@ describe('Store', () => {
66
66
  const errors = await validate(dto);
67
67
  expect(errors.some(e => e.property === 'status')).toBe(true);
68
68
  });
69
+
70
+ it('acepta managedByIntegration booleano y rechaza otro tipo', async () => {
71
+ expect(await validate(plainToInstance(Store, { ...valid, managedByIntegration: true }))).toEqual([]);
72
+ const errors = await validate(plainToInstance(Store, { ...valid, managedByIntegration: 'sí' }));
73
+ expect(errors.some((e) => e.property === 'managedByIntegration')).toBe(true);
74
+ });
69
75
  });
@@ -11,6 +11,8 @@ export interface LoanPaymentListItem {
11
11
  /** Folio del pago — la referencia de conciliación. */
12
12
  receipt: string;
13
13
  installmentNumber: number;
14
+ /** Todas las cuotas que cubrió el pago, ascendente; ausente en abono, liquidación y filas viejas (usar `installmentNumber`). */
15
+ installmentNumbers?: number[];
14
16
  attemptId: string;
15
17
  /** Ausente en los pagos en tienda: ahí no hubo motor. */
16
18
  mechanism?: MechanismType;
@@ -6,16 +6,18 @@ 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
- * - `scope`/`scopeRef` = grant del rol (ej. LEVEL_3 + storeId para un vendedor de tienda).
9
+ * - `roleId`/`scope`/`scopeRef`: todos o ninguno; sin rol no hay invitación.
10
10
  * - `createdBy` = cognitoSub del admin real (audit + grantedBy del assignment).
11
11
  * - `customFields` = valores de los campos personalizados del tenant; si el correo ya tiene usuario, se fusionan.
12
12
  */
13
13
  export declare class CreateTenantUserRequest {
14
14
  email: string;
15
15
  displayName?: string;
16
- roleId: string;
17
- scope: PermissionScope;
18
- scopeRef: string;
16
+ roleId?: string;
17
+ scope?: PermissionScope;
18
+ scopeRef?: string;
19
+ /** Solo en el alta sin rol: no se manda invitación hasta que alguien asigne el primer rol. */
20
+ suppressInvitation?: boolean;
19
21
  createdBy: string;
20
22
  customFields?: {
21
23
  [key: string]: string;
@@ -14,6 +14,8 @@ const class_transformer_1 = require("class-transformer");
14
14
  const class_validator_1 = require("class-validator");
15
15
  const PermissionScope_1 = require("../enums/PermissionScope");
16
16
  const IsStringValueRecord_1 = require("../../common/validators/IsStringValueRecord");
17
+ const SuppressInvitationRequiredWithoutRole_1 = require("../validators/SuppressInvitationRequiredWithoutRole");
18
+ const hasAnyGrantField = (o) => o.roleId !== undefined || o.scope !== undefined || o.scopeRef !== undefined;
17
19
  /**
18
20
  * Body del POST /internal/tenants/{tenantId}/users (endpoint privado, VPC/ANONIMUS). `tenantId` viaja en el path.
19
21
  * Provisiona Cognito + SharedUser + RoleAssignment (rol + scopeRef) en RBAC — reusa UserProvisioningService.
@@ -21,7 +23,7 @@ const IsStringValueRecord_1 = require("../../common/validators/IsStringValueReco
21
23
  * Consumido por el alta de usuario orquestada de SureKeep F1 (retail-org → RBAC → RetailUser) vía api-invoker.
22
24
  * - Usuario provisionado en estado INVITED (mitiga orphan si el paso siguiente en retail-org falla).
23
25
  * - Idempotente por identidad Cognito.
24
- * - `scope`/`scopeRef` = grant del rol (ej. LEVEL_3 + storeId para un vendedor de tienda).
26
+ * - `roleId`/`scope`/`scopeRef`: todos o ninguno; sin rol no hay invitación.
25
27
  * - `createdBy` = cognitoSub del admin real (audit + grantedBy del assignment).
26
28
  * - `customFields` = valores de los campos personalizados del tenant; si el correo ya tiene usuario, se fusionan.
27
29
  */
@@ -42,21 +44,29 @@ __decorate([
42
44
  ], CreateTenantUserRequest.prototype, "displayName", void 0);
43
45
  __decorate([
44
46
  (0, class_transformer_1.Expose)(),
47
+ (0, class_validator_1.ValidateIf)(hasAnyGrantField),
45
48
  (0, class_validator_1.IsString)(),
46
49
  (0, class_validator_1.IsNotEmpty)(),
47
50
  __metadata("design:type", String)
48
51
  ], CreateTenantUserRequest.prototype, "roleId", void 0);
49
52
  __decorate([
50
53
  (0, class_transformer_1.Expose)(),
54
+ (0, class_validator_1.ValidateIf)(hasAnyGrantField),
51
55
  (0, class_validator_1.IsEnum)(PermissionScope_1.PermissionScope),
52
56
  __metadata("design:type", String)
53
57
  ], CreateTenantUserRequest.prototype, "scope", void 0);
54
58
  __decorate([
55
59
  (0, class_transformer_1.Expose)(),
60
+ (0, class_validator_1.ValidateIf)(hasAnyGrantField),
56
61
  (0, class_validator_1.IsString)(),
57
62
  (0, class_validator_1.IsNotEmpty)(),
58
63
  __metadata("design:type", String)
59
64
  ], CreateTenantUserRequest.prototype, "scopeRef", void 0);
65
+ __decorate([
66
+ (0, class_transformer_1.Expose)(),
67
+ (0, class_validator_1.Validate)(SuppressInvitationRequiredWithoutRole_1.SuppressInvitationRequiredWithoutRole),
68
+ __metadata("design:type", Boolean)
69
+ ], CreateTenantUserRequest.prototype, "suppressInvitation", void 0);
60
70
  __decorate([
61
71
  (0, class_transformer_1.Expose)(),
62
72
  (0, class_validator_1.IsString)(),
@@ -0,0 +1,6 @@
1
+ import { ValidationArguments, ValidatorConstraintInterface } from 'class-validator';
2
+ /** Sin rol el alta exige `suppressInvitation: true`; con rol no se admite, porque el alta con rol siempre invita. */
3
+ export declare class SuppressInvitationRequiredWithoutRole implements ValidatorConstraintInterface {
4
+ validate(value: unknown, args: ValidationArguments): boolean;
5
+ defaultMessage(): string;
6
+ }
@@ -0,0 +1,24 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SuppressInvitationRequiredWithoutRole = void 0;
10
+ const class_validator_1 = require("class-validator");
11
+ /** Sin rol el alta exige `suppressInvitation: true`; con rol no se admite, porque el alta con rol siempre invita. */
12
+ let SuppressInvitationRequiredWithoutRole = class SuppressInvitationRequiredWithoutRole {
13
+ validate(value, args) {
14
+ const hasRole = args.object.roleId !== undefined;
15
+ return hasRole ? value === undefined : value === true;
16
+ }
17
+ defaultMessage() {
18
+ return 'suppressInvitation: true va solo y siempre en el alta sin rol';
19
+ }
20
+ };
21
+ exports.SuppressInvitationRequiredWithoutRole = SuppressInvitationRequiredWithoutRole;
22
+ exports.SuppressInvitationRequiredWithoutRole = SuppressInvitationRequiredWithoutRole = __decorate([
23
+ (0, class_validator_1.ValidatorConstraint)({ name: 'suppressInvitationRequiredWithoutRole', async: false })
24
+ ], SuppressInvitationRequiredWithoutRole);
@@ -5,3 +5,16 @@ export declare const RETAIL_LIST_LIMIT_MIN = 1;
5
5
  export declare const RETAIL_LIST_LIMIT_MAX = 200;
6
6
  /** Forma válida de un `tenantId`; también arma el nombre del secreto del retailer. */
7
7
  export declare const RETAIL_TENANT_ID_PATTERN: RegExp;
8
+ /** Id de tienda, usuario o zona del retailer: llave de DynamoDB y BYOID de RBAC. */
9
+ export declare const RETAIL_ENTITY_ID_PATTERN: RegExp;
10
+ /** Zona del retailer; `NOZONE-` queda reservado para la zona centinela de Fiado. */
11
+ export declare const RETAIL_ZONE_ID_PATTERN: RegExp;
12
+ /** Código corto de tienda: de 1 a 10 caracteres. */
13
+ export declare const RETAIL_STORE_CODE_MAX_LENGTH = 10;
14
+ /** Código postal: de 4 a 10 caracteres. */
15
+ export declare const RETAIL_ZIP_CODE_MIN_LENGTH = 4;
16
+ export declare const RETAIL_ZIP_CODE_MAX_LENGTH = 10;
17
+ /** SKU del catálogo del retailer. */
18
+ export declare const RETAIL_SKU_PATTERN: RegExp;
19
+ /** Decimal del contrato: string con dos decimales, nunca negativo. */
20
+ export declare const RETAIL_DECIMAL_PATTERN: RegExp;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RETAIL_TENANT_ID_PATTERN = exports.RETAIL_LIST_LIMIT_MAX = exports.RETAIL_LIST_LIMIT_MIN = exports.RETAIL_EVENT_ID_MAX_LENGTH = void 0;
3
+ exports.RETAIL_DECIMAL_PATTERN = exports.RETAIL_SKU_PATTERN = exports.RETAIL_ZIP_CODE_MAX_LENGTH = exports.RETAIL_ZIP_CODE_MIN_LENGTH = exports.RETAIL_STORE_CODE_MAX_LENGTH = exports.RETAIL_ZONE_ID_PATTERN = exports.RETAIL_ENTITY_ID_PATTERN = exports.RETAIL_TENANT_ID_PATTERN = exports.RETAIL_LIST_LIMIT_MAX = exports.RETAIL_LIST_LIMIT_MIN = exports.RETAIL_EVENT_ID_MAX_LENGTH = void 0;
4
4
  /** Tope de `eventId`: el límite seguro de una llave de DynamoDB. */
5
5
  exports.RETAIL_EVENT_ID_MAX_LENGTH = 1024;
6
6
  /** Rango de `limit` en las listas paginadas del retailer. */
@@ -8,3 +8,16 @@ exports.RETAIL_LIST_LIMIT_MIN = 1;
8
8
  exports.RETAIL_LIST_LIMIT_MAX = 200;
9
9
  /** Forma válida de un `tenantId`; también arma el nombre del secreto del retailer. */
10
10
  exports.RETAIL_TENANT_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
11
+ /** Id de tienda, usuario o zona del retailer: llave de DynamoDB y BYOID de RBAC. */
12
+ exports.RETAIL_ENTITY_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
13
+ /** Zona del retailer; `NOZONE-` queda reservado para la zona centinela de Fiado. */
14
+ exports.RETAIL_ZONE_ID_PATTERN = /^(?!NOZONE-)[A-Za-z0-9_-]{1,64}$/;
15
+ /** Código corto de tienda: de 1 a 10 caracteres. */
16
+ exports.RETAIL_STORE_CODE_MAX_LENGTH = 10;
17
+ /** Código postal: de 4 a 10 caracteres. */
18
+ exports.RETAIL_ZIP_CODE_MIN_LENGTH = 4;
19
+ exports.RETAIL_ZIP_CODE_MAX_LENGTH = 10;
20
+ /** SKU del catálogo del retailer. */
21
+ exports.RETAIL_SKU_PATTERN = /^[A-Za-z0-9-]{1,64}$/;
22
+ /** Decimal del contrato: string con dos decimales, nunca negativo. */
23
+ exports.RETAIL_DECIMAL_PATTERN = /^\d+\.\d{2}$/;
@@ -6,18 +6,26 @@ import { RetailEventResult } from './RetailEventResult';
6
6
  /** Evento de tienda que el connector manda a retail-org-business, con el tenant de la credencial. */
7
7
  export declare class ApplyStoreEventRequest extends ReceiveStoreEventRequest {
8
8
  tenantId: string;
9
+ /** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
10
+ retailerId: string;
9
11
  }
10
12
  /** Evento de usuario que el connector manda a retail-org-business, con el tenant de la credencial. */
11
13
  export declare class ApplyRetailUserEventRequest extends ReceiveRetailUserEventRequest {
12
14
  tenantId: string;
15
+ /** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
16
+ retailerId: string;
13
17
  }
14
18
  /** Evento de producto que el connector manda a retail-catalog-business, con el tenant de la credencial. */
15
19
  export declare class ApplyProductEventRequest extends ReceiveProductEventRequest {
16
20
  tenantId: string;
21
+ /** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
22
+ retailerId: string;
17
23
  }
18
24
  /** Evento de inventario que el connector manda a retail-catalog-business, con el tenant de la credencial. */
19
25
  export declare class ApplyInventoryEventRequest extends ReceiveInventoryEventRequest {
20
26
  tenantId: string;
27
+ /** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
28
+ retailerId: string;
21
29
  }
22
30
  /** Cualquiera de los cuatro eventos que el connector manda a un destino. */
23
31
  export type ApplyRetailEventRequest = ApplyStoreEventRequest | ApplyRetailUserEventRequest | ApplyProductEventRequest | ApplyInventoryEventRequest;
@@ -25,6 +25,11 @@ __decorate([
25
25
  (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_TENANT_ID_PATTERN),
26
26
  __metadata("design:type", String)
27
27
  ], ApplyStoreEventRequest.prototype, "tenantId", void 0);
28
+ __decorate([
29
+ (0, class_validator_1.IsString)(),
30
+ (0, class_validator_1.Matches)(/\S/),
31
+ __metadata("design:type", String)
32
+ ], ApplyStoreEventRequest.prototype, "retailerId", void 0);
28
33
  /** Evento de usuario que el connector manda a retail-org-business, con el tenant de la credencial. */
29
34
  class ApplyRetailUserEventRequest extends ReceiveRetailUserEventRequest_1.ReceiveRetailUserEventRequest {
30
35
  }
@@ -34,6 +39,11 @@ __decorate([
34
39
  (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_TENANT_ID_PATTERN),
35
40
  __metadata("design:type", String)
36
41
  ], ApplyRetailUserEventRequest.prototype, "tenantId", void 0);
42
+ __decorate([
43
+ (0, class_validator_1.IsString)(),
44
+ (0, class_validator_1.Matches)(/\S/),
45
+ __metadata("design:type", String)
46
+ ], ApplyRetailUserEventRequest.prototype, "retailerId", void 0);
37
47
  /** Evento de producto que el connector manda a retail-catalog-business, con el tenant de la credencial. */
38
48
  class ApplyProductEventRequest extends ReceiveProductEventRequest_1.ReceiveProductEventRequest {
39
49
  }
@@ -43,6 +53,11 @@ __decorate([
43
53
  (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_TENANT_ID_PATTERN),
44
54
  __metadata("design:type", String)
45
55
  ], ApplyProductEventRequest.prototype, "tenantId", void 0);
56
+ __decorate([
57
+ (0, class_validator_1.IsString)(),
58
+ (0, class_validator_1.Matches)(/\S/),
59
+ __metadata("design:type", String)
60
+ ], ApplyProductEventRequest.prototype, "retailerId", void 0);
46
61
  /** Evento de inventario que el connector manda a retail-catalog-business, con el tenant de la credencial. */
47
62
  class ApplyInventoryEventRequest extends ReceiveInventoryEventRequest_1.ReceiveInventoryEventRequest {
48
63
  }
@@ -52,3 +67,8 @@ __decorate([
52
67
  (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_TENANT_ID_PATTERN),
53
68
  __metadata("design:type", String)
54
69
  ], ApplyInventoryEventRequest.prototype, "tenantId", void 0);
70
+ __decorate([
71
+ (0, class_validator_1.IsString)(),
72
+ (0, class_validator_1.Matches)(/\S/),
73
+ __metadata("design:type", String)
74
+ ], ApplyInventoryEventRequest.prototype, "retailerId", void 0);
@@ -0,0 +1,25 @@
1
+ import { RetailMovementTypeEnum } from '../enums';
2
+ import type { RetailPieceReason, RetailPieceStatus, RetailSystemPiece, RetailSystemStock } from '../models/RetailSystemInventory';
3
+ export type RetailMovementType = `${RetailMovementTypeEnum}`;
4
+ /** Pieza con IMEI o serie, como quedó después del movimiento. */
5
+ export declare class PieceEventPayload implements RetailSystemPiece {
6
+ sku: string;
7
+ identifier: string;
8
+ storeId: string;
9
+ status: RetailPieceStatus;
10
+ reason: RetailPieceReason | null;
11
+ }
12
+ /** Existencia de un SKU por cantidad, como quedó después del movimiento. */
13
+ export declare class StockEventPayload implements RetailSystemStock {
14
+ sku: string;
15
+ storeId: string;
16
+ qty: number;
17
+ }
18
+ /** Payload de `InventoryMovementV1`. */
19
+ export declare class InventoryEventPayload {
20
+ movementType: RetailMovementType;
21
+ storeId: string;
22
+ targetStoreId: string | null;
23
+ pieces: PieceEventPayload[];
24
+ stock: StockEventPayload[];
25
+ }
@@ -0,0 +1,94 @@
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.InventoryEventPayload = exports.StockEventPayload = exports.PieceEventPayload = void 0;
13
+ const class_transformer_1 = require("class-transformer");
14
+ const class_validator_1 = require("class-validator");
15
+ const retailIntegrationLimits_1 = require("../constants/retailIntegrationLimits");
16
+ const enums_1 = require("../enums");
17
+ const payloadValidators_1 = require("../validators/payloadValidators");
18
+ /** Pieza con IMEI o serie, como quedó después del movimiento. */
19
+ class PieceEventPayload {
20
+ }
21
+ exports.PieceEventPayload = PieceEventPayload;
22
+ __decorate([
23
+ (0, class_validator_1.IsString)(),
24
+ (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SKU_PATTERN),
25
+ __metadata("design:type", String)
26
+ ], PieceEventPayload.prototype, "sku", void 0);
27
+ __decorate([
28
+ (0, class_validator_1.IsString)(),
29
+ (0, class_validator_1.Matches)(/\S/),
30
+ __metadata("design:type", String)
31
+ ], PieceEventPayload.prototype, "identifier", void 0);
32
+ __decorate([
33
+ (0, class_validator_1.IsString)(),
34
+ (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_ENTITY_ID_PATTERN),
35
+ __metadata("design:type", String)
36
+ ], PieceEventPayload.prototype, "storeId", void 0);
37
+ __decorate([
38
+ (0, class_validator_1.IsIn)(Object.values(enums_1.RetailPieceStatusEnum)),
39
+ __metadata("design:type", String)
40
+ ], PieceEventPayload.prototype, "status", void 0);
41
+ __decorate([
42
+ (0, class_validator_1.Validate)(payloadValidators_1.ReasonMatchesStatus),
43
+ __metadata("design:type", String)
44
+ ], PieceEventPayload.prototype, "reason", void 0);
45
+ /** Existencia de un SKU por cantidad, como quedó después del movimiento. */
46
+ class StockEventPayload {
47
+ }
48
+ exports.StockEventPayload = StockEventPayload;
49
+ __decorate([
50
+ (0, class_validator_1.IsString)(),
51
+ (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SKU_PATTERN),
52
+ __metadata("design:type", String)
53
+ ], StockEventPayload.prototype, "sku", void 0);
54
+ __decorate([
55
+ (0, class_validator_1.IsString)(),
56
+ (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_ENTITY_ID_PATTERN),
57
+ __metadata("design:type", String)
58
+ ], StockEventPayload.prototype, "storeId", void 0);
59
+ __decorate([
60
+ (0, class_validator_1.IsInt)(),
61
+ (0, class_validator_1.Min)(0),
62
+ __metadata("design:type", Number)
63
+ ], StockEventPayload.prototype, "qty", void 0);
64
+ /** Payload de `InventoryMovementV1`. */
65
+ class InventoryEventPayload {
66
+ }
67
+ exports.InventoryEventPayload = InventoryEventPayload;
68
+ __decorate([
69
+ (0, class_validator_1.IsIn)(Object.values(enums_1.RetailMovementTypeEnum)),
70
+ __metadata("design:type", String)
71
+ ], InventoryEventPayload.prototype, "movementType", void 0);
72
+ __decorate([
73
+ (0, class_validator_1.IsString)(),
74
+ (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_ENTITY_ID_PATTERN),
75
+ __metadata("design:type", String)
76
+ ], InventoryEventPayload.prototype, "storeId", void 0);
77
+ __decorate([
78
+ (0, class_validator_1.Validate)(payloadValidators_1.TargetStoreMatchesMovement),
79
+ __metadata("design:type", String)
80
+ ], InventoryEventPayload.prototype, "targetStoreId", void 0);
81
+ __decorate([
82
+ (0, class_validator_1.IsArray)(),
83
+ (0, class_validator_1.ValidateNested)({ each: true }),
84
+ (0, class_transformer_1.Type)(() => PieceEventPayload),
85
+ (0, class_validator_1.Validate)(payloadValidators_1.HasUniqueKeys, ['identifier']),
86
+ __metadata("design:type", Array)
87
+ ], InventoryEventPayload.prototype, "pieces", void 0);
88
+ __decorate([
89
+ (0, class_validator_1.IsArray)(),
90
+ (0, class_validator_1.ValidateNested)({ each: true }),
91
+ (0, class_transformer_1.Type)(() => StockEventPayload),
92
+ (0, class_validator_1.Validate)(payloadValidators_1.HasUniqueKeys, ['storeId', 'sku']),
93
+ __metadata("design:type", Array)
94
+ ], InventoryEventPayload.prototype, "stock", void 0);