@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.
- package/_test_/unit/loanCredit/inStorePaymentDtos.test.ts +19 -0
- package/_test_/unit/platformRbac/dtos/CreateTenantUserRequest.test.ts +31 -0
- package/_test_/unit/retailIntegration/applyRetailEventRequest.test.ts +11 -0
- package/_test_/unit/retailIntegration/eventPayloads.test.ts +124 -0
- package/_test_/unit/retailIntegration/payloadValidators.test.ts +34 -0
- package/_test_/unit/retailIntegration/retailSystemEnums.test.ts +18 -0
- package/_test_/unit/retailOrg/dtos/Store.test.ts +6 -0
- package/bin/loanCredit/dtos/responses/LoanPaymentListItem.d.ts +2 -0
- package/bin/platformRbac/dtos/CreateTenantUserRequest.d.ts +6 -4
- package/bin/platformRbac/dtos/CreateTenantUserRequest.js +11 -1
- package/bin/platformRbac/validators/SuppressInvitationRequiredWithoutRole.d.ts +6 -0
- package/bin/platformRbac/validators/SuppressInvitationRequiredWithoutRole.js +24 -0
- package/bin/retailIntegration/constants/retailIntegrationLimits.d.ts +13 -0
- package/bin/retailIntegration/constants/retailIntegrationLimits.js +14 -1
- package/bin/retailIntegration/dtos/ApplyRetailEventRequest.d.ts +8 -0
- package/bin/retailIntegration/dtos/ApplyRetailEventRequest.js +20 -0
- package/bin/retailIntegration/dtos/InventoryEventPayload.d.ts +25 -0
- package/bin/retailIntegration/dtos/InventoryEventPayload.js +94 -0
- package/bin/retailIntegration/dtos/ProductEventPayload.d.ts +25 -0
- package/bin/retailIntegration/dtos/ProductEventPayload.js +113 -0
- package/bin/retailIntegration/dtos/RetailUserEventPayload.d.ts +12 -0
- package/bin/retailIntegration/dtos/RetailUserEventPayload.js +58 -0
- package/bin/retailIntegration/dtos/StoreEventPayload.d.ts +30 -0
- package/bin/retailIntegration/dtos/StoreEventPayload.js +137 -0
- package/bin/retailIntegration/enums/RetailEventRejectCodeEnum.d.ts +16 -0
- package/bin/retailIntegration/enums/RetailEventRejectCodeEnum.js +20 -0
- package/bin/retailIntegration/enums/RetailMovementTypeEnum.d.ts +10 -0
- package/bin/retailIntegration/enums/RetailMovementTypeEnum.js +14 -0
- package/bin/retailIntegration/enums/index.d.ts +2 -0
- package/bin/retailIntegration/enums/index.js +2 -0
- package/bin/retailIntegration/index.d.ts +5 -0
- package/bin/retailIntegration/index.js +5 -0
- package/bin/retailIntegration/validators/payloadValidators.d.ts +18 -0
- package/bin/retailIntegration/validators/payloadValidators.js +72 -0
- package/bin/retailOrg/dtos/Store.d.ts +2 -0
- package/bin/retailOrg/dtos/Store.js +6 -0
- package/package.json +1 -1
- package/src/loanCredit/dtos/responses/LoanPaymentListItem.ts +2 -0
- package/src/platformRbac/dtos/CreateTenantUserRequest.ts +11 -5
- package/src/platformRbac/validators/SuppressInvitationRequiredWithoutRole.ts +14 -0
- package/src/retailIntegration/constants/retailIntegrationLimits.ts +19 -0
- package/src/retailIntegration/dtos/ApplyRetailEventRequest.ts +20 -0
- package/src/retailIntegration/dtos/InventoryEventPayload.ts +33 -0
- package/src/retailIntegration/dtos/ProductEventPayload.ts +51 -0
- package/src/retailIntegration/dtos/RetailUserEventPayload.ts +17 -0
- package/src/retailIntegration/dtos/StoreEventPayload.ts +47 -0
- package/src/retailIntegration/enums/RetailEventRejectCodeEnum.ts +16 -0
- package/src/retailIntegration/enums/RetailMovementTypeEnum.ts +10 -0
- package/src/retailIntegration/enums/index.ts +2 -0
- package/src/retailIntegration/index.ts +5 -0
- package/src/retailIntegration/validators/payloadValidators.ts +56 -0
- package/src/retailOrg/dtos/Store.ts +7 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Expose } from 'class-transformer';
|
|
2
|
-
import { IsEmail, IsEnum, IsNotEmpty, IsObject, IsOptional, IsString, Validate } from 'class-validator';
|
|
2
|
+
import { IsEmail, IsEnum, IsNotEmpty, IsObject, IsOptional, IsString, Validate, ValidateIf } from 'class-validator';
|
|
3
3
|
import { PermissionScope } from '../enums/PermissionScope';
|
|
4
4
|
import { IsStringValueRecord } from '../../common/validators/IsStringValueRecord';
|
|
5
|
+
import { SuppressInvitationRequiredWithoutRole } from '../validators/SuppressInvitationRequiredWithoutRole';
|
|
6
|
+
|
|
7
|
+
const hasAnyGrantField = (o: CreateTenantUserRequest): boolean =>
|
|
8
|
+
o.roleId !== undefined || o.scope !== undefined || o.scopeRef !== undefined;
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Body del POST /internal/tenants/{tenantId}/users (endpoint privado, VPC/ANONIMUS). `tenantId` viaja en el path.
|
|
@@ -10,16 +14,18 @@ import { IsStringValueRecord } from '../../common/validators/IsStringValueRecord
|
|
|
10
14
|
* Consumido por el alta de usuario orquestada de SureKeep F1 (retail-org → RBAC → RetailUser) vía api-invoker.
|
|
11
15
|
* - Usuario provisionado en estado INVITED (mitiga orphan si el paso siguiente en retail-org falla).
|
|
12
16
|
* - Idempotente por identidad Cognito.
|
|
13
|
-
* - `scope`/`scopeRef
|
|
17
|
+
* - `roleId`/`scope`/`scopeRef`: todos o ninguno; sin rol no hay invitación.
|
|
14
18
|
* - `createdBy` = cognitoSub del admin real (audit + grantedBy del assignment).
|
|
15
19
|
* - `customFields` = valores de los campos personalizados del tenant; si el correo ya tiene usuario, se fusionan.
|
|
16
20
|
*/
|
|
17
21
|
export class CreateTenantUserRequest {
|
|
18
22
|
@Expose() @IsEmail() email!: string;
|
|
19
23
|
@Expose() @IsOptional() @IsString() @IsNotEmpty() displayName?: string;
|
|
20
|
-
@Expose() @IsString() @IsNotEmpty() roleId
|
|
21
|
-
@Expose() @IsEnum(PermissionScope) scope
|
|
22
|
-
@Expose() @IsString() @IsNotEmpty() scopeRef
|
|
24
|
+
@Expose() @ValidateIf(hasAnyGrantField) @IsString() @IsNotEmpty() roleId?: string;
|
|
25
|
+
@Expose() @ValidateIf(hasAnyGrantField) @IsEnum(PermissionScope) scope?: PermissionScope;
|
|
26
|
+
@Expose() @ValidateIf(hasAnyGrantField) @IsString() @IsNotEmpty() scopeRef?: string;
|
|
27
|
+
/** Solo en el alta sin rol: no se manda invitación hasta que alguien asigne el primer rol. */
|
|
28
|
+
@Expose() @Validate(SuppressInvitationRequiredWithoutRole) suppressInvitation?: boolean;
|
|
23
29
|
@Expose() @IsString() @IsNotEmpty() createdBy!: string;
|
|
24
30
|
@Expose()
|
|
25
31
|
@IsOptional()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
/** Sin rol el alta exige `suppressInvitation: true`; con rol no se admite, porque el alta con rol siempre invita. */
|
|
4
|
+
@ValidatorConstraint({ name: 'suppressInvitationRequiredWithoutRole', async: false })
|
|
5
|
+
export class SuppressInvitationRequiredWithoutRole implements ValidatorConstraintInterface {
|
|
6
|
+
validate(value: unknown, args: ValidationArguments): boolean {
|
|
7
|
+
const hasRole = (args.object as { roleId?: unknown }).roleId !== undefined;
|
|
8
|
+
return hasRole ? value === undefined : value === true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
defaultMessage(): string {
|
|
12
|
+
return 'suppressInvitation: true va solo y siempre en el alta sin rol';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -7,3 +7,22 @@ export const RETAIL_LIST_LIMIT_MAX = 200;
|
|
|
7
7
|
|
|
8
8
|
/** Forma válida de un `tenantId`; también arma el nombre del secreto del retailer. */
|
|
9
9
|
export const RETAIL_TENANT_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
10
|
+
|
|
11
|
+
/** Id de tienda, usuario o zona del retailer: llave de DynamoDB y BYOID de RBAC. */
|
|
12
|
+
export const RETAIL_ENTITY_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13
|
+
|
|
14
|
+
/** Zona del retailer; `NOZONE-` queda reservado para la zona centinela de Fiado. */
|
|
15
|
+
export const RETAIL_ZONE_ID_PATTERN = /^(?!NOZONE-)[A-Za-z0-9_-]{1,64}$/;
|
|
16
|
+
|
|
17
|
+
/** Código corto de tienda: de 1 a 10 caracteres. */
|
|
18
|
+
export const RETAIL_STORE_CODE_MAX_LENGTH = 10;
|
|
19
|
+
|
|
20
|
+
/** Código postal: de 4 a 10 caracteres. */
|
|
21
|
+
export const RETAIL_ZIP_CODE_MIN_LENGTH = 4;
|
|
22
|
+
export const RETAIL_ZIP_CODE_MAX_LENGTH = 10;
|
|
23
|
+
|
|
24
|
+
/** SKU del catálogo del retailer. */
|
|
25
|
+
export const RETAIL_SKU_PATTERN = /^[A-Za-z0-9-]{1,64}$/;
|
|
26
|
+
|
|
27
|
+
/** Decimal del contrato: string con dos decimales, nunca negativo. */
|
|
28
|
+
export const RETAIL_DECIMAL_PATTERN = /^\d+\.\d{2}$/;
|
|
@@ -11,6 +11,11 @@ export class ApplyStoreEventRequest extends ReceiveStoreEventRequest {
|
|
|
11
11
|
@IsString()
|
|
12
12
|
@Matches(RETAIL_TENANT_ID_PATTERN)
|
|
13
13
|
tenantId!: string;
|
|
14
|
+
|
|
15
|
+
/** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
|
|
16
|
+
@IsString()
|
|
17
|
+
@Matches(/\S/)
|
|
18
|
+
retailerId!: string;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
/** Evento de usuario que el connector manda a retail-org-business, con el tenant de la credencial. */
|
|
@@ -18,6 +23,11 @@ export class ApplyRetailUserEventRequest extends ReceiveRetailUserEventRequest {
|
|
|
18
23
|
@IsString()
|
|
19
24
|
@Matches(RETAIL_TENANT_ID_PATTERN)
|
|
20
25
|
tenantId!: string;
|
|
26
|
+
|
|
27
|
+
/** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
|
|
28
|
+
@IsString()
|
|
29
|
+
@Matches(/\S/)
|
|
30
|
+
retailerId!: string;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
/** Evento de producto que el connector manda a retail-catalog-business, con el tenant de la credencial. */
|
|
@@ -25,6 +35,11 @@ export class ApplyProductEventRequest extends ReceiveProductEventRequest {
|
|
|
25
35
|
@IsString()
|
|
26
36
|
@Matches(RETAIL_TENANT_ID_PATTERN)
|
|
27
37
|
tenantId!: string;
|
|
38
|
+
|
|
39
|
+
/** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
|
|
40
|
+
@IsString()
|
|
41
|
+
@Matches(/\S/)
|
|
42
|
+
retailerId!: string;
|
|
28
43
|
}
|
|
29
44
|
|
|
30
45
|
/** Evento de inventario que el connector manda a retail-catalog-business, con el tenant de la credencial. */
|
|
@@ -32,6 +47,11 @@ export class ApplyInventoryEventRequest extends ReceiveInventoryEventRequest {
|
|
|
32
47
|
@IsString()
|
|
33
48
|
@Matches(RETAIL_TENANT_ID_PATTERN)
|
|
34
49
|
tenantId!: string;
|
|
50
|
+
|
|
51
|
+
/** Retailer de la conexión; lo pone el connector, nunca viene del payload. */
|
|
52
|
+
@IsString()
|
|
53
|
+
@Matches(/\S/)
|
|
54
|
+
retailerId!: string;
|
|
35
55
|
}
|
|
36
56
|
|
|
37
57
|
/** Cualquiera de los cuatro eventos que el connector manda a un destino. */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Type } from 'class-transformer';
|
|
2
|
+
import { IsArray, IsIn, IsInt, IsString, Matches, Min, Validate, ValidateNested } from 'class-validator';
|
|
3
|
+
import { RETAIL_ENTITY_ID_PATTERN, RETAIL_SKU_PATTERN } from '../constants/retailIntegrationLimits';
|
|
4
|
+
import { RetailMovementTypeEnum, RetailPieceStatusEnum } from '../enums';
|
|
5
|
+
import type { RetailPieceReason, RetailPieceStatus, RetailSystemPiece, RetailSystemStock } from '../models/RetailSystemInventory';
|
|
6
|
+
import { HasUniqueKeys, ReasonMatchesStatus, TargetStoreMatchesMovement } from '../validators/payloadValidators';
|
|
7
|
+
|
|
8
|
+
export type RetailMovementType = `${RetailMovementTypeEnum}`;
|
|
9
|
+
|
|
10
|
+
/** Pieza con IMEI o serie, como quedó después del movimiento. */
|
|
11
|
+
export class PieceEventPayload implements RetailSystemPiece {
|
|
12
|
+
@IsString() @Matches(RETAIL_SKU_PATTERN) sku!: string;
|
|
13
|
+
@IsString() @Matches(/\S/) identifier!: string;
|
|
14
|
+
@IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) storeId!: string;
|
|
15
|
+
@IsIn(Object.values(RetailPieceStatusEnum)) status!: RetailPieceStatus;
|
|
16
|
+
@Validate(ReasonMatchesStatus) reason!: RetailPieceReason | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Existencia de un SKU por cantidad, como quedó después del movimiento. */
|
|
20
|
+
export class StockEventPayload implements RetailSystemStock {
|
|
21
|
+
@IsString() @Matches(RETAIL_SKU_PATTERN) sku!: string;
|
|
22
|
+
@IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) storeId!: string;
|
|
23
|
+
@IsInt() @Min(0) qty!: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Payload de `InventoryMovementV1`. */
|
|
27
|
+
export class InventoryEventPayload {
|
|
28
|
+
@IsIn(Object.values(RetailMovementTypeEnum)) movementType!: RetailMovementType;
|
|
29
|
+
@IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) storeId!: string;
|
|
30
|
+
@Validate(TargetStoreMatchesMovement) targetStoreId!: string | null;
|
|
31
|
+
@IsArray() @ValidateNested({ each: true }) @Type(() => PieceEventPayload) @Validate(HasUniqueKeys, ['identifier']) pieces!: PieceEventPayload[];
|
|
32
|
+
@IsArray() @ValidateNested({ each: true }) @Type(() => StockEventPayload) @Validate(HasUniqueKeys, ['storeId', 'sku']) stock!: StockEventPayload[];
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Type } from 'class-transformer';
|
|
2
|
+
import { IsArray, IsDefined, IsIn, IsString, Matches, Validate, ValidateIf, ValidateNested } from 'class-validator';
|
|
3
|
+
import { RETAIL_DECIMAL_PATTERN, RETAIL_SKU_PATTERN } from '../constants/retailIntegrationLimits';
|
|
4
|
+
import {
|
|
5
|
+
RetailCurrencyEnum,
|
|
6
|
+
RetailFinancierEnum,
|
|
7
|
+
RetailMdmProviderEnum,
|
|
8
|
+
RetailProductCategoryEnum,
|
|
9
|
+
RetailProductStatusEnum,
|
|
10
|
+
RetailProductTierEnum,
|
|
11
|
+
RetailTrackingModeEnum,
|
|
12
|
+
} from '../enums';
|
|
13
|
+
import type {
|
|
14
|
+
RetailCurrency,
|
|
15
|
+
RetailFinancier,
|
|
16
|
+
RetailMdmProvider,
|
|
17
|
+
RetailProductCategory,
|
|
18
|
+
RetailProductStatus,
|
|
19
|
+
RetailProductTier,
|
|
20
|
+
RetailSystemProduct,
|
|
21
|
+
RetailSystemProductPrice,
|
|
22
|
+
RetailTrackingMode,
|
|
23
|
+
} from '../models/RetailSystemProduct';
|
|
24
|
+
import { IsIsoDateTimeWithZone } from '../validators/IsIsoDateTimeWithZone';
|
|
25
|
+
import { isNullOrValidated } from '../validators/payloadValidators';
|
|
26
|
+
|
|
27
|
+
/** Precio del producto en decimales string. */
|
|
28
|
+
export class ProductEventPricePayload implements RetailSystemProductPrice {
|
|
29
|
+
@IsString() @Matches(RETAIL_DECIMAL_PATTERN) basePrice!: string;
|
|
30
|
+
@ValidateIf(isNullOrValidated) @IsString() @Matches(RETAIL_DECIMAL_PATTERN) listPrice!: string | null;
|
|
31
|
+
@Validate(IsIsoDateTimeWithZone) effectiveFrom!: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Payload de los eventos de producto: el producto completo. */
|
|
35
|
+
export class ProductEventPayload implements RetailSystemProduct {
|
|
36
|
+
@IsString() @Matches(RETAIL_SKU_PATTERN) sku!: string;
|
|
37
|
+
@IsString() @Matches(/\S/) externalId!: string;
|
|
38
|
+
@IsString() @Matches(/\S/) brand!: string;
|
|
39
|
+
@IsString() @Matches(/\S/) model!: string;
|
|
40
|
+
@ValidateIf(isNullOrValidated) @IsString() color!: string | null;
|
|
41
|
+
@ValidateIf(isNullOrValidated) @IsString() capacity!: string | null;
|
|
42
|
+
@ValidateIf(isNullOrValidated) @IsString() description!: string | null;
|
|
43
|
+
@IsIn(Object.values(RetailProductCategoryEnum)) category!: RetailProductCategory;
|
|
44
|
+
@IsIn(Object.values(RetailTrackingModeEnum)) trackingMode!: RetailTrackingMode;
|
|
45
|
+
@ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailProductTierEnum)) tier!: RetailProductTier | null;
|
|
46
|
+
@IsIn(Object.values(RetailCurrencyEnum)) currencyId!: RetailCurrency;
|
|
47
|
+
@IsDefined() @ValidateNested() @Type(() => ProductEventPricePayload) price!: ProductEventPricePayload;
|
|
48
|
+
@IsArray() @IsIn(Object.values(RetailFinancierEnum), { each: true }) eligibleFinanciers!: RetailFinancier[];
|
|
49
|
+
@ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailMdmProviderEnum)) mdmProvider!: RetailMdmProvider | null;
|
|
50
|
+
@IsIn(Object.values(RetailProductStatusEnum)) status!: RetailProductStatus;
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IsEmail, IsIn, IsString, Matches, ValidateIf } from 'class-validator';
|
|
2
|
+
import { RETAIL_ENTITY_ID_PATTERN, RETAIL_ZONE_ID_PATTERN } from '../constants/retailIntegrationLimits';
|
|
3
|
+
import { RetailUserAccessStatusEnum, RetailUserRoleEnum } from '../enums';
|
|
4
|
+
import type { RetailSystemUser, RetailUserAccessStatus, RetailUserRole } from '../models/RetailSystemUser';
|
|
5
|
+
import { isNullOrValidated } from '../validators/payloadValidators';
|
|
6
|
+
|
|
7
|
+
/** Payload de los cuatro eventos de usuario: el usuario completo. */
|
|
8
|
+
export class RetailUserEventPayload implements RetailSystemUser {
|
|
9
|
+
@IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) retailUserId!: string;
|
|
10
|
+
@IsString() @Matches(/\S/) displayName!: string;
|
|
11
|
+
@IsEmail() email!: string;
|
|
12
|
+
@ValidateIf(isNullOrValidated) @IsString() phone!: string | null;
|
|
13
|
+
@IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) homeStoreId!: string;
|
|
14
|
+
@ValidateIf(isNullOrValidated) @IsString() @Matches(RETAIL_ZONE_ID_PATTERN) zoneId!: string | null;
|
|
15
|
+
@IsIn(Object.values(RetailUserRoleEnum)) role!: RetailUserRole;
|
|
16
|
+
@IsIn(Object.values(RetailUserAccessStatusEnum)) status!: RetailUserAccessStatus;
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Type } from 'class-transformer';
|
|
2
|
+
import { IsDefined, IsIn, IsNumber, IsString, Length, Matches, Max, Min, ValidateIf, ValidateNested } from 'class-validator';
|
|
3
|
+
import {
|
|
4
|
+
RETAIL_ENTITY_ID_PATTERN,
|
|
5
|
+
RETAIL_STORE_CODE_MAX_LENGTH,
|
|
6
|
+
RETAIL_ZIP_CODE_MAX_LENGTH,
|
|
7
|
+
RETAIL_ZIP_CODE_MIN_LENGTH,
|
|
8
|
+
RETAIL_ZONE_ID_PATTERN,
|
|
9
|
+
} from '../constants/retailIntegrationLimits';
|
|
10
|
+
import { RetailStoreStatusEnum } from '../enums';
|
|
11
|
+
import type { RetailStoreStatus, RetailSystemStore, RetailSystemStoreAddress, RetailSystemStoreLocation } from '../models/RetailSystemStore';
|
|
12
|
+
import { isNullOrValidated } from '../validators/payloadValidators';
|
|
13
|
+
|
|
14
|
+
const LAT_MAX = 90;
|
|
15
|
+
const LNG_MAX = 180;
|
|
16
|
+
|
|
17
|
+
/** Dirección de la tienda; los opcionales llegan como `null`, nunca se omiten. */
|
|
18
|
+
export class StoreEventAddressPayload implements RetailSystemStoreAddress {
|
|
19
|
+
@IsString() @Matches(/\S/) address1!: string;
|
|
20
|
+
@ValidateIf(isNullOrValidated) @IsString() externalNumber!: string | null;
|
|
21
|
+
@ValidateIf(isNullOrValidated) @IsString() internalNumber!: string | null;
|
|
22
|
+
@ValidateIf(isNullOrValidated) @IsString() neighborhood!: string | null;
|
|
23
|
+
@IsString() @Matches(/\S/) city!: string;
|
|
24
|
+
@IsString() @Matches(/\S/) state!: string;
|
|
25
|
+
@IsString() @Length(RETAIL_ZIP_CODE_MIN_LENGTH, RETAIL_ZIP_CODE_MAX_LENGTH) zipCode!: string;
|
|
26
|
+
@ValidateIf(isNullOrValidated) @IsString() country!: string | null;
|
|
27
|
+
@ValidateIf(isNullOrValidated) @IsString() reference!: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Coordenada de la tienda; `null` si no se informa. */
|
|
31
|
+
export class StoreEventLocationPayload implements RetailSystemStoreLocation {
|
|
32
|
+
@ValidateIf(isNullOrValidated) @IsNumber() @Min(-LAT_MAX) @Max(LAT_MAX) lat!: number | null;
|
|
33
|
+
@ValidateIf(isNullOrValidated) @IsNumber() @Min(-LNG_MAX) @Max(LNG_MAX) lng!: number | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Payload de `StoreCreatedV1`, `StoreUpdatedV1` y `StoreStatusChangedV1`: la tienda completa. */
|
|
37
|
+
export class StoreEventPayload implements RetailSystemStore {
|
|
38
|
+
@IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) storeId!: string;
|
|
39
|
+
@IsString() @Length(1, RETAIL_STORE_CODE_MAX_LENGTH) code!: string;
|
|
40
|
+
@IsString() @Matches(/\S/) name!: string;
|
|
41
|
+
@ValidateIf(isNullOrValidated) @IsString() @Matches(RETAIL_ZONE_ID_PATTERN) zoneId!: string | null;
|
|
42
|
+
@IsDefined() @ValidateNested() @Type(() => StoreEventAddressPayload) address!: StoreEventAddressPayload;
|
|
43
|
+
@IsDefined() @ValidateNested() @Type(() => StoreEventLocationPayload) location!: StoreEventLocationPayload;
|
|
44
|
+
@IsString() @Matches(/\S/) phone!: string;
|
|
45
|
+
@ValidateIf(isNullOrValidated) @IsString() schedule!: string | null;
|
|
46
|
+
@IsIn(Object.values(RetailStoreStatusEnum)) status!: RetailStoreStatus;
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** `code` de un `REJECTED`; uno solo para los cuatro endpoints que aplican eventos del retailer. */
|
|
2
|
+
export enum RetailEventRejectCodeEnum {
|
|
3
|
+
INVALID_PAYLOAD = 'INVALID_PAYLOAD',
|
|
4
|
+
STORE_RETAILER_MISMATCH = 'STORE_RETAILER_MISMATCH',
|
|
5
|
+
RETAIL_USER_RETAILER_MISMATCH = 'RETAIL_USER_RETAILER_MISMATCH',
|
|
6
|
+
STORE_CODE_TAKEN = 'STORE_CODE_TAKEN',
|
|
7
|
+
EMAIL_ALREADY_IN_USE = 'EMAIL_ALREADY_IN_USE',
|
|
8
|
+
UNSUPPORTED_CURRENCY = 'UNSUPPORTED_CURRENCY',
|
|
9
|
+
FUTURE_PRICE_NOT_SUPPORTED = 'FUTURE_PRICE_NOT_SUPPORTED',
|
|
10
|
+
SKU_OWNED_BY_BACKOFFICE = 'SKU_OWNED_BY_BACKOFFICE',
|
|
11
|
+
STORE_NOT_IN_RETAILER = 'STORE_NOT_IN_RETAILER',
|
|
12
|
+
SKU_NOT_STOCKABLE = 'SKU_NOT_STOCKABLE',
|
|
13
|
+
TRACKING_MODE_MISMATCH = 'TRACKING_MODE_MISMATCH',
|
|
14
|
+
INVALID_IMEI = 'INVALID_IMEI',
|
|
15
|
+
IDENTIFIER_CONFLICT = 'IDENTIFIER_CONFLICT',
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `payload.movementType` de `InventoryMovementV1`. */
|
|
2
|
+
export enum RetailMovementTypeEnum {
|
|
3
|
+
ENTRY = 'ENTRY',
|
|
4
|
+
SALE = 'SALE',
|
|
5
|
+
RETURN = 'RETURN',
|
|
6
|
+
ADJUSTMENT = 'ADJUSTMENT',
|
|
7
|
+
TRANSFER_OUT = 'TRANSFER_OUT',
|
|
8
|
+
TRANSFER_IN = 'TRANSFER_IN',
|
|
9
|
+
TRANSFER_CANCEL = 'TRANSFER_CANCEL',
|
|
10
|
+
}
|
|
@@ -19,3 +19,5 @@ export * from './RetailSaleTypeEnum';
|
|
|
19
19
|
export * from './RetailSaleFinancierEnum';
|
|
20
20
|
export * from './RetailPaymentMethodEnum';
|
|
21
21
|
export * from './RetailLinePaymentTypeEnum';
|
|
22
|
+
export * from './RetailEventRejectCodeEnum';
|
|
23
|
+
export * from './RetailMovementTypeEnum';
|
|
@@ -9,6 +9,11 @@ export * from './dtos/ReceiveInventoryEventRequest';
|
|
|
9
9
|
export * from './dtos/ReceiveRetailEventRequest';
|
|
10
10
|
export * from './dtos/RetailEventResult';
|
|
11
11
|
export * from './dtos/ApplyRetailEventRequest';
|
|
12
|
+
export * from './validators/payloadValidators';
|
|
13
|
+
export * from './dtos/StoreEventPayload';
|
|
14
|
+
export * from './dtos/RetailUserEventPayload';
|
|
15
|
+
export * from './dtos/ProductEventPayload';
|
|
16
|
+
export * from './dtos/InventoryEventPayload';
|
|
12
17
|
export * from './dtos/RetailTenantQuery';
|
|
13
18
|
export * from './dtos/RetailPagedQuery';
|
|
14
19
|
export * from './dtos/ListRetailStoresRequest';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
import { RETAIL_ENTITY_ID_PATTERN } from '../constants/retailIntegrationLimits';
|
|
3
|
+
import { RetailMovementTypeEnum, RetailPieceReasonEnum, RetailPieceStatusEnum } from '../enums';
|
|
4
|
+
|
|
5
|
+
/** `ValidateIf` de un campo nullable: con `null` no valida; ausente sí, porque el contrato nunca omite llaves. */
|
|
6
|
+
export function isNullOrValidated(_object: object, value: unknown): boolean {
|
|
7
|
+
return value !== null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Rechaza un arreglo de objetos con dos elementos que repiten la llave compuesta de `constraints`. */
|
|
11
|
+
@ValidatorConstraint({ name: 'hasUniqueKeys', async: false })
|
|
12
|
+
export class HasUniqueKeys implements ValidatorConstraintInterface {
|
|
13
|
+
validate(value: unknown, args: ValidationArguments): boolean {
|
|
14
|
+
if (!Array.isArray(value)) return true;
|
|
15
|
+
const keys = args.constraints as string[];
|
|
16
|
+
const seen = new Set<string>();
|
|
17
|
+
for (const item of value) {
|
|
18
|
+
const key = keys.map((k) => String((item as Record<string, unknown>)?.[k])).join('#');
|
|
19
|
+
if (seen.has(key)) return false;
|
|
20
|
+
seen.add(key);
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
defaultMessage(args: ValidationArguments): string {
|
|
26
|
+
return `no puede repetir ${(args.constraints as string[]).join(' + ')}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** `targetStoreId`: obligatorio en `TRANSFER_OUT` y `null` en los demás movimientos. */
|
|
31
|
+
@ValidatorConstraint({ name: 'targetStoreMatchesMovement', async: false })
|
|
32
|
+
export class TargetStoreMatchesMovement implements ValidatorConstraintInterface {
|
|
33
|
+
validate(value: unknown, args: ValidationArguments): boolean {
|
|
34
|
+
const movementType = (args.object as { movementType?: string }).movementType;
|
|
35
|
+
if (movementType === RetailMovementTypeEnum.TRANSFER_OUT) return typeof value === 'string' && RETAIL_ENTITY_ID_PATTERN.test(value);
|
|
36
|
+
return value === null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
defaultMessage(): string {
|
|
40
|
+
return 'targetStoreId es obligatorio en TRANSFER_OUT y null en los demás movimientos';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `reason`: obligatorio en `DAMAGED` y `null` en los demás estados. */
|
|
45
|
+
@ValidatorConstraint({ name: 'reasonMatchesStatus', async: false })
|
|
46
|
+
export class ReasonMatchesStatus implements ValidatorConstraintInterface {
|
|
47
|
+
validate(value: unknown, args: ValidationArguments): boolean {
|
|
48
|
+
const status = (args.object as { status?: string }).status;
|
|
49
|
+
if (status === RetailPieceStatusEnum.DAMAGED) return (Object.values(RetailPieceReasonEnum) as unknown[]).includes(value);
|
|
50
|
+
return value === null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
defaultMessage(): string {
|
|
54
|
+
return 'reason es obligatorio con DAMAGED y null en los demás estados';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Expose, Type } from 'class-transformer';
|
|
2
2
|
import {
|
|
3
|
+
IsBoolean,
|
|
3
4
|
IsDefined,
|
|
4
5
|
IsEnum,
|
|
5
6
|
IsNumber,
|
|
@@ -89,6 +90,12 @@ export class Store {
|
|
|
89
90
|
@IsEnum(StoreStatusEnum)
|
|
90
91
|
status!: StoreStatusEnum;
|
|
91
92
|
|
|
93
|
+
/** `true` si la tienda la administra la integración del retailer y no se edita a mano. */
|
|
94
|
+
@Expose()
|
|
95
|
+
@IsOptional()
|
|
96
|
+
@IsBoolean()
|
|
97
|
+
managedByIntegration?: boolean;
|
|
98
|
+
|
|
92
99
|
@Expose()
|
|
93
100
|
@IsString()
|
|
94
101
|
@Matches(regexIso8601, { message: 'createdAt debe ser una fecha ISO 8601' })
|