@fiado/type-kit 3.602.0 → 3.604.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/retailIntegration/retailMovementsContracts.test.ts +13 -0
- package/_test_/unit/retailIntegration/retailSaleContracts.test.ts +171 -10
- package/_test_/unit/retailIntegration/retailSystemEnums.test.ts +3 -1
- package/bin/retailIntegration/constants/retailIntegrationLimits.d.ts +16 -0
- package/bin/retailIntegration/constants/retailIntegrationLimits.js +17 -1
- package/bin/retailIntegration/dtos/CancelRetailSaleRequest.d.ts +7 -0
- package/bin/retailIntegration/dtos/CancelRetailSaleRequest.js +34 -1
- package/bin/retailIntegration/dtos/RetailOutboundResponses.d.ts +10 -1
- package/bin/retailIntegration/enums/RetailPaymentFrequencyEnum.d.ts +6 -0
- package/bin/retailIntegration/enums/RetailPaymentFrequencyEnum.js +10 -0
- package/bin/retailIntegration/enums/RetailPaymentMethodEnum.d.ts +3 -2
- package/bin/retailIntegration/enums/RetailPaymentMethodEnum.js +3 -2
- package/bin/retailIntegration/enums/index.d.ts +1 -0
- package/bin/retailIntegration/enums/index.js +1 -0
- package/bin/retailIntegration/index.d.ts +2 -0
- package/bin/retailIntegration/index.js +2 -0
- package/bin/retailIntegration/models/RetailSystemSale.d.ts +23 -2
- package/bin/retailIntegration/models/RetailSystemSale.js +89 -11
- package/bin/retailIntegration/validators/IsCalendarDate.d.ts +6 -0
- package/bin/retailIntegration/validators/IsCalendarDate.js +25 -0
- package/bin/retailIntegration/validators/IsIsoDateTimeWithZone.js +2 -6
- package/bin/retailIntegration/validators/calendarDate.d.ts +2 -0
- package/bin/retailIntegration/validators/calendarDate.js +8 -0
- package/bin/retailIntegration/validators/saleValidators.d.ts +26 -0
- package/bin/retailIntegration/validators/saleValidators.js +100 -0
- package/package.json +1 -1
- package/src/retailIntegration/constants/retailIntegrationLimits.ts +24 -0
- package/src/retailIntegration/dtos/CancelRetailSaleRequest.ts +14 -2
- package/src/retailIntegration/dtos/RetailOutboundResponses.ts +11 -1
- package/src/retailIntegration/enums/RetailPaymentFrequencyEnum.ts +6 -0
- package/src/retailIntegration/enums/RetailPaymentMethodEnum.ts +3 -2
- package/src/retailIntegration/enums/index.ts +1 -0
- package/src/retailIntegration/index.ts +2 -0
- package/src/retailIntegration/models/RetailSystemSale.ts +57 -10
- package/src/retailIntegration/validators/IsCalendarDate.ts +16 -0
- package/src/retailIntegration/validators/IsIsoDateTimeWithZone.ts +1 -6
- package/src/retailIntegration/validators/calendarDate.ts +5 -0
- package/src/retailIntegration/validators/saleValidators.ts +80 -0
|
@@ -9,13 +9,16 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.RetailSystemSale = exports.RetailSystemSaleLine = exports.RetailSystemSaleComponent = exports.RetailSystemSaleCustomer = void 0;
|
|
12
|
+
exports.RetailSystemSale = exports.RetailSystemSaleLine = exports.RetailSystemSaleComponent = exports.RetailSystemSalePayment = exports.RetailSystemSaleCredit = exports.RetailSystemSaleCustomer = void 0;
|
|
13
13
|
const class_transformer_1 = require("class-transformer");
|
|
14
14
|
const class_validator_1 = require("class-validator");
|
|
15
15
|
const retailIntegrationLimits_1 = require("../constants/retailIntegrationLimits");
|
|
16
16
|
const enums_1 = require("../enums");
|
|
17
|
+
const IsCalendarDate_1 = require("../validators/IsCalendarDate");
|
|
17
18
|
const IsIsoDateTimeWithZone_1 = require("../validators/IsIsoDateTimeWithZone");
|
|
18
19
|
const payloadValidators_1 = require("../validators/payloadValidators");
|
|
20
|
+
const saleValidators_1 = require("../validators/saleValidators");
|
|
21
|
+
const { CARD, WIRE } = enums_1.RetailPaymentMethodEnum;
|
|
19
22
|
class RetailSystemSaleCustomer {
|
|
20
23
|
}
|
|
21
24
|
exports.RetailSystemSaleCustomer = RetailSystemSaleCustomer;
|
|
@@ -41,9 +44,70 @@ __decorate([
|
|
|
41
44
|
], RetailSystemSaleCustomer.prototype, "maternalLastName", void 0);
|
|
42
45
|
__decorate([
|
|
43
46
|
(0, class_validator_1.IsString)(),
|
|
44
|
-
(0, class_validator_1.Matches)(
|
|
47
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_CUSTOMER_PHONE_PATTERN),
|
|
45
48
|
__metadata("design:type", String)
|
|
46
49
|
], RetailSystemSaleCustomer.prototype, "phone", void 0);
|
|
50
|
+
/** Condiciones del crédito; todas obligatorias cuando `credit` no es `null`. */
|
|
51
|
+
class RetailSystemSaleCredit {
|
|
52
|
+
}
|
|
53
|
+
exports.RetailSystemSaleCredit = RetailSystemSaleCredit;
|
|
54
|
+
__decorate([
|
|
55
|
+
(0, class_validator_1.IsIn)(Object.values(enums_1.RetailPaymentFrequencyEnum)),
|
|
56
|
+
__metadata("design:type", String)
|
|
57
|
+
], RetailSystemSaleCredit.prototype, "paymentFrequency", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
(0, class_validator_1.IsInt)(),
|
|
60
|
+
(0, class_validator_1.Min)(1),
|
|
61
|
+
__metadata("design:type", Number)
|
|
62
|
+
], RetailSystemSaleCredit.prototype, "installments", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
(0, class_validator_1.IsString)(),
|
|
65
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
66
|
+
__metadata("design:type", String)
|
|
67
|
+
], RetailSystemSaleCredit.prototype, "installmentAmount", void 0);
|
|
68
|
+
__decorate([
|
|
69
|
+
(0, class_validator_1.IsString)(),
|
|
70
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
71
|
+
__metadata("design:type", String)
|
|
72
|
+
], RetailSystemSaleCredit.prototype, "downPayment", void 0);
|
|
73
|
+
__decorate([
|
|
74
|
+
(0, class_validator_1.IsString)(),
|
|
75
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
76
|
+
__metadata("design:type", String)
|
|
77
|
+
], RetailSystemSaleCredit.prototype, "totalToPay", void 0);
|
|
78
|
+
__decorate([
|
|
79
|
+
(0, class_validator_1.Validate)(IsCalendarDate_1.IsCalendarDate),
|
|
80
|
+
__metadata("design:type", String)
|
|
81
|
+
], RetailSystemSaleCredit.prototype, "firstPaymentDate", void 0);
|
|
82
|
+
/** Un medio de pago de `amountPaid`; las seis llaves van siempre, en `null` las que no aplican. */
|
|
83
|
+
class RetailSystemSalePayment {
|
|
84
|
+
}
|
|
85
|
+
exports.RetailSystemSalePayment = RetailSystemSalePayment;
|
|
86
|
+
__decorate([
|
|
87
|
+
(0, class_validator_1.IsIn)(Object.values(enums_1.RetailPaymentMethodEnum)),
|
|
88
|
+
__metadata("design:type", String)
|
|
89
|
+
], RetailSystemSalePayment.prototype, "method", void 0);
|
|
90
|
+
__decorate([
|
|
91
|
+
(0, class_validator_1.IsString)(),
|
|
92
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_POSITIVE_DECIMAL_PATTERN),
|
|
93
|
+
__metadata("design:type", String)
|
|
94
|
+
], RetailSystemSalePayment.prototype, "amount", void 0);
|
|
95
|
+
__decorate([
|
|
96
|
+
(0, class_validator_1.Validate)(saleValidators_1.MatchesForPaymentMethod, [CARD, retailIntegrationLimits_1.RETAIL_CARD_LAST4_PATTERN]),
|
|
97
|
+
__metadata("design:type", String)
|
|
98
|
+
], RetailSystemSalePayment.prototype, "cardLast4", void 0);
|
|
99
|
+
__decorate([
|
|
100
|
+
(0, class_validator_1.Validate)(saleValidators_1.MatchesForPaymentMethod, [CARD, retailIntegrationLimits_1.RETAIL_SALE_ID_TEXT_PATTERN]),
|
|
101
|
+
__metadata("design:type", String)
|
|
102
|
+
], RetailSystemSalePayment.prototype, "authCode", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
(0, class_validator_1.Validate)(saleValidators_1.MatchesForPaymentMethod, [WIRE, retailIntegrationLimits_1.RETAIL_SPEI_TRACKING_KEY_PATTERN]),
|
|
105
|
+
__metadata("design:type", String)
|
|
106
|
+
], RetailSystemSalePayment.prototype, "speiTrackingKey", void 0);
|
|
107
|
+
__decorate([
|
|
108
|
+
(0, class_validator_1.Validate)(saleValidators_1.MatchesForPaymentMethod, [WIRE, retailIntegrationLimits_1.RETAIL_CLABE_PATTERN, true]),
|
|
109
|
+
__metadata("design:type", String)
|
|
110
|
+
], RetailSystemSalePayment.prototype, "senderClabe", void 0);
|
|
47
111
|
class RetailSystemSaleComponent {
|
|
48
112
|
}
|
|
49
113
|
exports.RetailSystemSaleComponent = RetailSystemSaleComponent;
|
|
@@ -83,12 +147,12 @@ __decorate([
|
|
|
83
147
|
], RetailSystemSaleLine.prototype, "qty", void 0);
|
|
84
148
|
__decorate([
|
|
85
149
|
(0, class_validator_1.IsString)(),
|
|
86
|
-
(0, class_validator_1.Matches)(retailIntegrationLimits_1.
|
|
150
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
87
151
|
__metadata("design:type", String)
|
|
88
152
|
], RetailSystemSaleLine.prototype, "basePrice", void 0);
|
|
89
153
|
__decorate([
|
|
90
154
|
(0, class_validator_1.IsString)(),
|
|
91
|
-
(0, class_validator_1.Matches)(retailIntegrationLimits_1.
|
|
155
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
92
156
|
__metadata("design:type", String)
|
|
93
157
|
], RetailSystemSaleLine.prototype, "discount", void 0);
|
|
94
158
|
__decorate([
|
|
@@ -97,7 +161,7 @@ __decorate([
|
|
|
97
161
|
], RetailSystemSaleLine.prototype, "paymentType", void 0);
|
|
98
162
|
__decorate([
|
|
99
163
|
(0, class_validator_1.IsString)(),
|
|
100
|
-
(0, class_validator_1.Matches)(retailIntegrationLimits_1.
|
|
164
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
101
165
|
__metadata("design:type", String)
|
|
102
166
|
], RetailSystemSaleLine.prototype, "financedAmount", void 0);
|
|
103
167
|
__decorate([
|
|
@@ -105,6 +169,10 @@ __decorate([
|
|
|
105
169
|
(0, class_validator_1.IsIn)(Object.values(enums_1.RetailSaleLineRoleEnum)),
|
|
106
170
|
__metadata("design:type", String)
|
|
107
171
|
], RetailSystemSaleLine.prototype, "role", void 0);
|
|
172
|
+
__decorate([
|
|
173
|
+
(0, class_validator_1.Validate)(saleValidators_1.PromotionIdMatchesRole),
|
|
174
|
+
__metadata("design:type", String)
|
|
175
|
+
], RetailSystemSaleLine.prototype, "promotionId", void 0);
|
|
108
176
|
__decorate([
|
|
109
177
|
(0, class_validator_1.IsArray)(),
|
|
110
178
|
(0, class_validator_1.ValidateNested)({ each: true }),
|
|
@@ -145,6 +213,7 @@ __decorate([
|
|
|
145
213
|
], RetailSystemSale.prototype, "saleType", void 0);
|
|
146
214
|
__decorate([
|
|
147
215
|
(0, class_validator_1.IsIn)(Object.values(enums_1.RetailSaleFinancierEnum)),
|
|
216
|
+
(0, class_validator_1.Validate)(saleValidators_1.CreditMatchesFinancier),
|
|
148
217
|
__metadata("design:type", String)
|
|
149
218
|
], RetailSystemSale.prototype, "financier", void 0);
|
|
150
219
|
__decorate([
|
|
@@ -153,6 +222,13 @@ __decorate([
|
|
|
153
222
|
(0, class_validator_1.Matches)(/\S/),
|
|
154
223
|
__metadata("design:type", String)
|
|
155
224
|
], RetailSystemSale.prototype, "creditId", void 0);
|
|
225
|
+
__decorate([
|
|
226
|
+
(0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
|
|
227
|
+
(0, class_validator_1.IsObject)(),
|
|
228
|
+
(0, class_validator_1.ValidateNested)(),
|
|
229
|
+
(0, class_transformer_1.Type)(() => RetailSystemSaleCredit),
|
|
230
|
+
__metadata("design:type", RetailSystemSaleCredit)
|
|
231
|
+
], RetailSystemSale.prototype, "credit", void 0);
|
|
156
232
|
__decorate([
|
|
157
233
|
(0, class_validator_1.IsIn)(Object.values(enums_1.RetailCurrencyEnum)),
|
|
158
234
|
__metadata("design:type", String)
|
|
@@ -165,19 +241,21 @@ __decorate([
|
|
|
165
241
|
], RetailSystemSale.prototype, "customer", void 0);
|
|
166
242
|
__decorate([
|
|
167
243
|
(0, class_validator_1.IsString)(),
|
|
168
|
-
(0, class_validator_1.Matches)(retailIntegrationLimits_1.
|
|
244
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
169
245
|
__metadata("design:type", String)
|
|
170
246
|
], RetailSystemSale.prototype, "equipmentPrice", void 0);
|
|
171
247
|
__decorate([
|
|
172
248
|
(0, class_validator_1.IsString)(),
|
|
173
|
-
(0, class_validator_1.Matches)(retailIntegrationLimits_1.
|
|
249
|
+
(0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN),
|
|
174
250
|
__metadata("design:type", String)
|
|
175
251
|
], RetailSystemSale.prototype, "amountPaid", void 0);
|
|
176
252
|
__decorate([
|
|
177
|
-
(0, class_validator_1.
|
|
178
|
-
(0, class_validator_1.
|
|
179
|
-
|
|
180
|
-
|
|
253
|
+
(0, class_validator_1.IsArray)(),
|
|
254
|
+
(0, class_validator_1.ValidateNested)({ each: true }),
|
|
255
|
+
(0, class_transformer_1.Type)(() => RetailSystemSalePayment),
|
|
256
|
+
(0, class_validator_1.Validate)(saleValidators_1.PaymentsMatchAmountPaid),
|
|
257
|
+
__metadata("design:type", Array)
|
|
258
|
+
], RetailSystemSale.prototype, "payments", void 0);
|
|
181
259
|
__decorate([
|
|
182
260
|
(0, class_validator_1.IsArray)(),
|
|
183
261
|
(0, class_validator_1.ArrayNotEmpty)(),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
/** Acepta una fecha `YYYY-MM-DD` que exista en el calendario. */
|
|
3
|
+
export declare class IsCalendarDate implements ValidatorConstraintInterface {
|
|
4
|
+
validate(value: unknown): boolean;
|
|
5
|
+
defaultMessage(): string;
|
|
6
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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.IsCalendarDate = void 0;
|
|
10
|
+
const class_validator_1 = require("class-validator");
|
|
11
|
+
const calendarDate_1 = require("./calendarDate");
|
|
12
|
+
const CALENDAR_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
13
|
+
/** Acepta una fecha `YYYY-MM-DD` que exista en el calendario. */
|
|
14
|
+
let IsCalendarDate = class IsCalendarDate {
|
|
15
|
+
validate(value) {
|
|
16
|
+
return typeof value === 'string' && CALENDAR_DATE.test(value) && (0, calendarDate_1.isRealCalendarDate)(value);
|
|
17
|
+
}
|
|
18
|
+
defaultMessage() {
|
|
19
|
+
return 'debe ser una fecha YYYY-MM-DD, por ejemplo 2026-08-21';
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
exports.IsCalendarDate = IsCalendarDate;
|
|
23
|
+
exports.IsCalendarDate = IsCalendarDate = __decorate([
|
|
24
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'isCalendarDate', async: false })
|
|
25
|
+
], IsCalendarDate);
|
|
@@ -8,16 +8,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.IsIsoDateTimeWithZone = void 0;
|
|
10
10
|
const class_validator_1 = require("class-validator");
|
|
11
|
+
const calendarDate_1 = require("./calendarDate");
|
|
11
12
|
const ISO_DATE_TIME_WITH_ZONE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
|
|
12
|
-
/** Descarta fechas que `Date` recorre en silencio, como el 31 de febrero. */
|
|
13
|
-
function isRealCalendarDate(value) {
|
|
14
|
-
const [year, month, day] = value.slice(0, 10).split('-').map(Number);
|
|
15
|
-
return !Number.isNaN(Date.parse(value)) && new Date(Date.UTC(year, month - 1, day)).getUTCDate() === day;
|
|
16
|
-
}
|
|
17
13
|
/** Acepta fecha y hora ISO-8601 con segundos y zona; sin zona se rechaza, como dice la doc. */
|
|
18
14
|
let IsIsoDateTimeWithZone = class IsIsoDateTimeWithZone {
|
|
19
15
|
validate(value) {
|
|
20
|
-
return typeof value === 'string' && ISO_DATE_TIME_WITH_ZONE.test(value) && isRealCalendarDate(value);
|
|
16
|
+
return typeof value === 'string' && ISO_DATE_TIME_WITH_ZONE.test(value) && (0, calendarDate_1.isRealCalendarDate)(value);
|
|
21
17
|
}
|
|
22
18
|
defaultMessage() {
|
|
23
19
|
return 'debe ser fecha y hora ISO-8601 con zona, por ejemplo 2026-08-17T20:32:05Z';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isRealCalendarDate = isRealCalendarDate;
|
|
4
|
+
/** Descarta fechas que `Date` recorre en silencio, como el 31 de febrero; lee los primeros 10 caracteres. */
|
|
5
|
+
function isRealCalendarDate(value) {
|
|
6
|
+
const [year, month, day] = value.slice(0, 10).split('-').map(Number);
|
|
7
|
+
return !Number.isNaN(Date.parse(value)) && new Date(Date.UTC(year, month - 1, day)).getUTCDate() === day;
|
|
8
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ValidationArguments, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
/** Campo de un pago que solo aplica a un medio: con ese medio cumple el patrón; con los demás es `null`. */
|
|
3
|
+
export declare class MatchesForPaymentMethod implements ValidatorConstraintInterface {
|
|
4
|
+
validate(value: unknown, args: ValidationArguments): boolean;
|
|
5
|
+
defaultMessage(args: ValidationArguments): string;
|
|
6
|
+
}
|
|
7
|
+
/** La suma de `payments[].amount` es `amountPaid`; con `amountPaid` en `"0.00"` el arreglo va vacío. */
|
|
8
|
+
export declare class PaymentsMatchAmountPaid implements ValidatorConstraintInterface {
|
|
9
|
+
validate(value: unknown, args: ValidationArguments): boolean;
|
|
10
|
+
defaultMessage(): string;
|
|
11
|
+
}
|
|
12
|
+
/** `credit` y `creditId`: obligatorios con `SUREKEEP` y `null` en contado. */
|
|
13
|
+
export declare class CreditMatchesFinancier implements ValidatorConstraintInterface {
|
|
14
|
+
validate(value: unknown, args: ValidationArguments): boolean;
|
|
15
|
+
defaultMessage(): string;
|
|
16
|
+
}
|
|
17
|
+
/** `promotionId`: texto o `null`, nunca omitido; un regalo siempre lo trae. */
|
|
18
|
+
export declare class PromotionIdMatchesRole implements ValidatorConstraintInterface {
|
|
19
|
+
validate(value: unknown, args: ValidationArguments): boolean;
|
|
20
|
+
defaultMessage(): string;
|
|
21
|
+
}
|
|
22
|
+
/** Un dañado con `identifier` es una sola pieza: `qty` debe ser 1. */
|
|
23
|
+
export declare class QtyMatchesIdentifier implements ValidatorConstraintInterface {
|
|
24
|
+
validate(value: unknown, args: ValidationArguments): boolean;
|
|
25
|
+
defaultMessage(): string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
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.QtyMatchesIdentifier = exports.PromotionIdMatchesRole = exports.CreditMatchesFinancier = exports.PaymentsMatchAmountPaid = exports.MatchesForPaymentMethod = void 0;
|
|
10
|
+
const class_validator_1 = require("class-validator");
|
|
11
|
+
const retailIntegrationLimits_1 = require("../constants/retailIntegrationLimits");
|
|
12
|
+
const enums_1 = require("../enums");
|
|
13
|
+
function decimalToCents(value) {
|
|
14
|
+
return Number(value.replace('.', ''));
|
|
15
|
+
}
|
|
16
|
+
/** Campo de un pago que solo aplica a un medio: con ese medio cumple el patrón; con los demás es `null`. */
|
|
17
|
+
let MatchesForPaymentMethod = class MatchesForPaymentMethod {
|
|
18
|
+
validate(value, args) {
|
|
19
|
+
const [method, pattern, nullable] = args.constraints;
|
|
20
|
+
if (args.object.method !== method)
|
|
21
|
+
return value === null;
|
|
22
|
+
if (value === null)
|
|
23
|
+
return nullable === true;
|
|
24
|
+
return typeof value === 'string' && pattern.test(value);
|
|
25
|
+
}
|
|
26
|
+
defaultMessage(args) {
|
|
27
|
+
return `${args.property} es obligatorio y válido en ${args.constraints[0]} y null en los demás medios`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.MatchesForPaymentMethod = MatchesForPaymentMethod;
|
|
31
|
+
exports.MatchesForPaymentMethod = MatchesForPaymentMethod = __decorate([
|
|
32
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'matchesForPaymentMethod', async: false })
|
|
33
|
+
], MatchesForPaymentMethod);
|
|
34
|
+
/** La suma de `payments[].amount` es `amountPaid`; con `amountPaid` en `"0.00"` el arreglo va vacío. */
|
|
35
|
+
let PaymentsMatchAmountPaid = class PaymentsMatchAmountPaid {
|
|
36
|
+
validate(value, args) {
|
|
37
|
+
const amountPaid = args.object.amountPaid;
|
|
38
|
+
if (!Array.isArray(value) || typeof amountPaid !== 'string' || !retailIntegrationLimits_1.RETAIL_SALE_DECIMAL_PATTERN.test(amountPaid))
|
|
39
|
+
return true;
|
|
40
|
+
const amounts = value.map((p) => p?.amount);
|
|
41
|
+
if (!amounts.every((a) => typeof a === 'string' && retailIntegrationLimits_1.RETAIL_SALE_POSITIVE_DECIMAL_PATTERN.test(a)))
|
|
42
|
+
return true;
|
|
43
|
+
const total = amounts.reduce((sum, a) => sum + decimalToCents(a), 0);
|
|
44
|
+
return total === decimalToCents(amountPaid);
|
|
45
|
+
}
|
|
46
|
+
defaultMessage() {
|
|
47
|
+
return 'la suma de payments[].amount debe ser amountPaid; [] solo si amountPaid es 0.00';
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.PaymentsMatchAmountPaid = PaymentsMatchAmountPaid;
|
|
51
|
+
exports.PaymentsMatchAmountPaid = PaymentsMatchAmountPaid = __decorate([
|
|
52
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'paymentsMatchAmountPaid', async: false })
|
|
53
|
+
], PaymentsMatchAmountPaid);
|
|
54
|
+
/** `credit` y `creditId`: obligatorios con `SUREKEEP` y `null` en contado. */
|
|
55
|
+
let CreditMatchesFinancier = class CreditMatchesFinancier {
|
|
56
|
+
validate(value, args) {
|
|
57
|
+
const { saleType, credit, creditId } = args.object;
|
|
58
|
+
if (value === enums_1.RetailSaleFinancierEnum.SUREKEEP)
|
|
59
|
+
return credit !== null && creditId !== null;
|
|
60
|
+
if (value === enums_1.RetailSaleFinancierEnum.NONE || saleType === enums_1.RetailSaleTypeEnum.CASH)
|
|
61
|
+
return credit == null && creditId == null;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
defaultMessage() {
|
|
65
|
+
return 'credit y creditId son obligatorios con SUREKEEP y null en contado';
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
exports.CreditMatchesFinancier = CreditMatchesFinancier;
|
|
69
|
+
exports.CreditMatchesFinancier = CreditMatchesFinancier = __decorate([
|
|
70
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'creditMatchesFinancier', async: false })
|
|
71
|
+
], CreditMatchesFinancier);
|
|
72
|
+
/** `promotionId`: texto o `null`, nunca omitido; un regalo siempre lo trae. */
|
|
73
|
+
let PromotionIdMatchesRole = class PromotionIdMatchesRole {
|
|
74
|
+
validate(value, args) {
|
|
75
|
+
if (value === null)
|
|
76
|
+
return args.object.role !== enums_1.RetailSaleLineRoleEnum.GIFT;
|
|
77
|
+
return typeof value === 'string' && retailIntegrationLimits_1.RETAIL_SALE_ID_TEXT_PATTERN.test(value);
|
|
78
|
+
}
|
|
79
|
+
defaultMessage() {
|
|
80
|
+
return 'promotionId es texto de 1 a 64 o null, y obligatorio con role GIFT';
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
exports.PromotionIdMatchesRole = PromotionIdMatchesRole;
|
|
84
|
+
exports.PromotionIdMatchesRole = PromotionIdMatchesRole = __decorate([
|
|
85
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'promotionIdMatchesRole', async: false })
|
|
86
|
+
], PromotionIdMatchesRole);
|
|
87
|
+
/** Un dañado con `identifier` es una sola pieza: `qty` debe ser 1. */
|
|
88
|
+
let QtyMatchesIdentifier = class QtyMatchesIdentifier {
|
|
89
|
+
validate(value, args) {
|
|
90
|
+
const identifier = args.object.identifier;
|
|
91
|
+
return typeof identifier !== 'string' || value === 1;
|
|
92
|
+
}
|
|
93
|
+
defaultMessage() {
|
|
94
|
+
return 'qty debe ser 1 cuando viene identifier';
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
exports.QtyMatchesIdentifier = QtyMatchesIdentifier;
|
|
98
|
+
exports.QtyMatchesIdentifier = QtyMatchesIdentifier = __decorate([
|
|
99
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'qtyMatchesIdentifier', async: false })
|
|
100
|
+
], QtyMatchesIdentifier);
|
package/package.json
CHANGED
|
@@ -66,3 +66,27 @@ export const RETAIL_LINE_INDEX_MAX = 999;
|
|
|
66
66
|
|
|
67
67
|
/** Rango máximo de `GET /inventory/movements`: 7 días en milisegundos. */
|
|
68
68
|
export const RETAIL_MOVEMENTS_RANGE_MAX_MS = 7 * 24 * 60 * 60 * 1000;
|
|
69
|
+
|
|
70
|
+
/** Decimal de la venta y la cancelación: hasta 12 enteros y dos decimales, nunca negativo. */
|
|
71
|
+
export const RETAIL_SALE_DECIMAL_PATTERN = /^\d{1,12}\.\d{2}$/;
|
|
72
|
+
|
|
73
|
+
/** Decimal de la venta mayor que cero, como `payments[].amount`. */
|
|
74
|
+
export const RETAIL_SALE_POSITIVE_DECIMAL_PATTERN = /^(?=.*[1-9])\d{1,12}\.\d{2}$/;
|
|
75
|
+
|
|
76
|
+
/** Texto libre corto de la venta, como `authCode` y `promotionId`: no vacío y de a lo más 64. */
|
|
77
|
+
export const RETAIL_SALE_ID_TEXT_PATTERN = /^(?=.*\S).{1,64}$/;
|
|
78
|
+
|
|
79
|
+
/** Últimos 4 dígitos de la tarjeta en un pago `CARD`. */
|
|
80
|
+
export const RETAIL_CARD_LAST4_PATTERN = /^\d{4}$/;
|
|
81
|
+
|
|
82
|
+
/** Clave de rastreo SPEI de un pago `WIRE`: alfanumérica de 1 a 30, como la define Banxico. */
|
|
83
|
+
export const RETAIL_SPEI_TRACKING_KEY_PATTERN = /^[A-Za-z0-9]{1,30}$/;
|
|
84
|
+
|
|
85
|
+
/** CLABE de la cuenta que envió un pago `WIRE`: 18 dígitos. */
|
|
86
|
+
export const RETAIL_CLABE_PATTERN = /^\d{18}$/;
|
|
87
|
+
|
|
88
|
+
/** Tope del motivo de cancelación de una venta. */
|
|
89
|
+
export const RETAIL_CANCEL_REASON_MAX_LENGTH = 200;
|
|
90
|
+
|
|
91
|
+
/** Teléfono del cliente en la venta: 10 dígitos. */
|
|
92
|
+
export const RETAIL_CUSTOMER_PHONE_PATTERN = /^\d{10}$/;
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Type } from 'class-transformer';
|
|
2
|
+
import { IsArray, IsInt, IsString, Matches, MaxLength, Min, Validate, ValidateIf, ValidateNested } from 'class-validator';
|
|
3
|
+
import { RETAIL_CANCEL_REASON_MAX_LENGTH, RETAIL_SHORT_TEXT_MAX_LENGTH, RETAIL_SKU_PATTERN } from '../constants/retailIntegrationLimits';
|
|
2
4
|
import { IsIsoDateTimeWithZone } from '../validators/IsIsoDateTimeWithZone';
|
|
5
|
+
import { isNullOrValidated } from '../validators/payloadValidators';
|
|
6
|
+
import { QtyMatchesIdentifier } from '../validators/saleValidators';
|
|
7
|
+
|
|
8
|
+
/** Lo que regresa dañado al cancelar; `identifier` en `null` si el producto es `QUANTITY`. */
|
|
9
|
+
export class RetailSystemDamagedItem {
|
|
10
|
+
@IsString() @Matches(RETAIL_SKU_PATTERN) sku!: string;
|
|
11
|
+
@ValidateIf(isNullOrValidated) @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) identifier!: string | null;
|
|
12
|
+
@IsInt() @Min(1) @Validate(QtyMatchesIdentifier) qty!: number;
|
|
13
|
+
}
|
|
3
14
|
|
|
4
15
|
/** Cuerpo de `POST /private/retail/sales/cancel`; el `saleId` es el mismo del registro. */
|
|
5
16
|
export class CancelRetailSaleRequest {
|
|
6
17
|
@IsString() @Matches(/\S/) saleId!: string;
|
|
7
18
|
@Validate(IsIsoDateTimeWithZone) cancelledAt!: string;
|
|
8
|
-
@IsString() @Matches(/\S/) reason!: string;
|
|
19
|
+
@IsString() @Matches(/\S/) @MaxLength(RETAIL_CANCEL_REASON_MAX_LENGTH) reason!: string;
|
|
20
|
+
@IsArray() @ValidateNested({ each: true }) @Type(() => RetailSystemDamagedItem) damaged!: RetailSystemDamagedItem[];
|
|
9
21
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RetailEventRejectCodeEnum } from '../enums';
|
|
1
2
|
import { RetailInventoryMeta, RetailPageMeta } from '../models/RetailPageMeta';
|
|
2
3
|
import { RetailSystemInventoryMovement, RetailSystemPiece, RetailSystemStock } from '../models/RetailSystemInventory';
|
|
3
4
|
import { RetailSystemProduct } from '../models/RetailSystemProduct';
|
|
@@ -23,10 +24,19 @@ export interface GetRetailInventoryResponse {
|
|
|
23
24
|
meta: RetailInventoryMeta;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
+
/** Movimiento de la página que no cumple el sobre del webhook; catalog lo salta y lo cuenta. */
|
|
28
|
+
export interface InvalidRetailInventoryMovement {
|
|
29
|
+
index: number;
|
|
30
|
+
eventId?: string;
|
|
31
|
+
code: RetailEventRejectCodeEnum.INVALID_PAYLOAD;
|
|
32
|
+
invalidPaths: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `data` de `GET /private/retail/inventory/movements`; `invalidItems` lista los movimientos descartados. */
|
|
27
36
|
export interface ListRetailInventoryMovementsResponse {
|
|
28
37
|
items: RetailSystemInventoryMovement[];
|
|
29
38
|
meta: RetailPageMeta;
|
|
39
|
+
invalidItems?: InvalidRetailInventoryMovement[];
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
/** `data` de `GET /private/retail/users`. */
|
|
@@ -18,6 +18,7 @@ export * from './RetailUserAccessStatusEnum';
|
|
|
18
18
|
export * from './RetailSaleTypeEnum';
|
|
19
19
|
export * from './RetailSaleFinancierEnum';
|
|
20
20
|
export * from './RetailPaymentMethodEnum';
|
|
21
|
+
export * from './RetailPaymentFrequencyEnum';
|
|
21
22
|
export * from './RetailLinePaymentTypeEnum';
|
|
22
23
|
export * from './RetailSaleLineRoleEnum';
|
|
23
24
|
export * from './RetailEventRejectCodeEnum';
|
|
@@ -3,6 +3,8 @@ export * from './constants/retailIntegrationErrorHttpStatus';
|
|
|
3
3
|
export * from './enums';
|
|
4
4
|
export * from './validators/IsIsoDateTimeWithZone';
|
|
5
5
|
export * from './validators/IsMovementsRangeValid';
|
|
6
|
+
export * from './validators/IsCalendarDate';
|
|
7
|
+
export * from './validators/saleValidators';
|
|
6
8
|
export * from './dtos/RetailEventEnvelope';
|
|
7
9
|
export * from './dtos/ReceiveStoreEventRequest';
|
|
8
10
|
export * from './dtos/ReceiveRetailUserEventRequest';
|
|
@@ -1,23 +1,66 @@
|
|
|
1
1
|
import { Type } from 'class-transformer';
|
|
2
2
|
import { ArrayNotEmpty, IsArray, IsIn, IsInt, IsObject, IsString, Matches, Min, Validate, ValidateIf, ValidateNested } from 'class-validator';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
RETAIL_CARD_LAST4_PATTERN,
|
|
5
|
+
RETAIL_CLABE_PATTERN,
|
|
6
|
+
RETAIL_CUSTOMER_PHONE_PATTERN,
|
|
7
|
+
RETAIL_ENTITY_ID_PATTERN,
|
|
8
|
+
RETAIL_SALE_DECIMAL_PATTERN,
|
|
9
|
+
RETAIL_SALE_ID_TEXT_PATTERN,
|
|
10
|
+
RETAIL_SALE_POSITIVE_DECIMAL_PATTERN,
|
|
11
|
+
RETAIL_SKU_PATTERN,
|
|
12
|
+
RETAIL_SPEI_TRACKING_KEY_PATTERN,
|
|
13
|
+
} from '../constants/retailIntegrationLimits';
|
|
14
|
+
import {
|
|
15
|
+
RetailCurrencyEnum,
|
|
16
|
+
RetailLinePaymentTypeEnum,
|
|
17
|
+
RetailPaymentFrequencyEnum,
|
|
18
|
+
RetailPaymentMethodEnum,
|
|
19
|
+
RetailSaleFinancierEnum,
|
|
20
|
+
RetailSaleLineRoleEnum,
|
|
21
|
+
RetailSaleTypeEnum,
|
|
22
|
+
} from '../enums';
|
|
23
|
+
import { IsCalendarDate } from '../validators/IsCalendarDate';
|
|
5
24
|
import { IsIsoDateTimeWithZone } from '../validators/IsIsoDateTimeWithZone';
|
|
6
25
|
import { HasUniqueKeys, isNullOrValidated } from '../validators/payloadValidators';
|
|
26
|
+
import { CreditMatchesFinancier, MatchesForPaymentMethod, PaymentsMatchAmountPaid, PromotionIdMatchesRole } from '../validators/saleValidators';
|
|
7
27
|
import { RetailCurrency } from './RetailSystemProduct';
|
|
8
28
|
|
|
9
29
|
export type RetailSaleType = `${RetailSaleTypeEnum}`;
|
|
10
30
|
export type RetailSaleFinancier = `${RetailSaleFinancierEnum}`;
|
|
11
31
|
export type RetailPaymentMethod = `${RetailPaymentMethodEnum}`;
|
|
32
|
+
export type RetailPaymentFrequency = `${RetailPaymentFrequencyEnum}`;
|
|
12
33
|
export type RetailLinePaymentType = `${RetailLinePaymentTypeEnum}`;
|
|
13
34
|
export type RetailSaleLineRole = `${RetailSaleLineRoleEnum}`;
|
|
14
35
|
|
|
36
|
+
const { CARD, WIRE } = RetailPaymentMethodEnum;
|
|
37
|
+
|
|
15
38
|
export class RetailSystemSaleCustomer {
|
|
16
39
|
@IsString() @Matches(/\S/) customerId!: string;
|
|
17
40
|
@IsString() @Matches(/\S/) names!: string;
|
|
18
41
|
@IsString() @Matches(/\S/) paternalLastName!: string;
|
|
19
42
|
@ValidateIf(isNullOrValidated) @IsString() maternalLastName!: string | null;
|
|
20
|
-
@IsString() @Matches(
|
|
43
|
+
@IsString() @Matches(RETAIL_CUSTOMER_PHONE_PATTERN) phone!: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Condiciones del crédito; todas obligatorias cuando `credit` no es `null`. */
|
|
47
|
+
export class RetailSystemSaleCredit {
|
|
48
|
+
@IsIn(Object.values(RetailPaymentFrequencyEnum)) paymentFrequency!: RetailPaymentFrequency;
|
|
49
|
+
@IsInt() @Min(1) installments!: number;
|
|
50
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) installmentAmount!: string;
|
|
51
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) downPayment!: string;
|
|
52
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) totalToPay!: string;
|
|
53
|
+
@Validate(IsCalendarDate) firstPaymentDate!: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Un medio de pago de `amountPaid`; las seis llaves van siempre, en `null` las que no aplican. */
|
|
57
|
+
export class RetailSystemSalePayment {
|
|
58
|
+
@IsIn(Object.values(RetailPaymentMethodEnum)) method!: RetailPaymentMethod;
|
|
59
|
+
@IsString() @Matches(RETAIL_SALE_POSITIVE_DECIMAL_PATTERN) amount!: string;
|
|
60
|
+
@Validate(MatchesForPaymentMethod, [CARD, RETAIL_CARD_LAST4_PATTERN]) cardLast4!: string | null;
|
|
61
|
+
@Validate(MatchesForPaymentMethod, [CARD, RETAIL_SALE_ID_TEXT_PATTERN]) authCode!: string | null;
|
|
62
|
+
@Validate(MatchesForPaymentMethod, [WIRE, RETAIL_SPEI_TRACKING_KEY_PATTERN]) speiTrackingKey!: string | null;
|
|
63
|
+
@Validate(MatchesForPaymentMethod, [WIRE, RETAIL_CLABE_PATTERN, true]) senderClabe!: string | null;
|
|
21
64
|
}
|
|
22
65
|
|
|
23
66
|
export class RetailSystemSaleComponent {
|
|
@@ -30,11 +73,12 @@ export class RetailSystemSaleLine {
|
|
|
30
73
|
@IsString() @Matches(/\S/) lineId!: string;
|
|
31
74
|
@IsString() @Matches(RETAIL_SKU_PATTERN) sku!: string;
|
|
32
75
|
@IsInt() @Min(1) qty!: number;
|
|
33
|
-
@IsString() @Matches(
|
|
34
|
-
@IsString() @Matches(
|
|
76
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) basePrice!: string;
|
|
77
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) discount!: string;
|
|
35
78
|
@IsIn(Object.values(RetailLinePaymentTypeEnum)) paymentType!: RetailLinePaymentType;
|
|
36
|
-
@IsString() @Matches(
|
|
79
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) financedAmount!: string;
|
|
37
80
|
@ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailSaleLineRoleEnum)) role!: RetailSaleLineRole | null;
|
|
81
|
+
@Validate(PromotionIdMatchesRole) promotionId!: string | null;
|
|
38
82
|
@IsArray() @ValidateNested({ each: true }) @Type(() => RetailSystemSaleComponent) components!: RetailSystemSaleComponent[];
|
|
39
83
|
}
|
|
40
84
|
|
|
@@ -46,13 +90,16 @@ export class RetailSystemSale {
|
|
|
46
90
|
@IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) sellerRetailUserId!: string;
|
|
47
91
|
@Validate(IsIsoDateTimeWithZone) soldAt!: string;
|
|
48
92
|
@IsIn(Object.values(RetailSaleTypeEnum)) saleType!: RetailSaleType;
|
|
49
|
-
@IsIn(Object.values(RetailSaleFinancierEnum)) financier!: RetailSaleFinancier;
|
|
93
|
+
@IsIn(Object.values(RetailSaleFinancierEnum)) @Validate(CreditMatchesFinancier) financier!: RetailSaleFinancier;
|
|
50
94
|
@ValidateIf(isNullOrValidated) @IsString() @Matches(/\S/) creditId!: string | null;
|
|
95
|
+
@ValidateIf(isNullOrValidated) @IsObject() @ValidateNested() @Type(() => RetailSystemSaleCredit) credit!: RetailSystemSaleCredit | null;
|
|
51
96
|
@IsIn(Object.values(RetailCurrencyEnum)) currencyId!: RetailCurrency;
|
|
52
97
|
@IsObject() @ValidateNested() @Type(() => RetailSystemSaleCustomer) customer!: RetailSystemSaleCustomer;
|
|
53
|
-
@IsString() @Matches(
|
|
54
|
-
@IsString() @Matches(
|
|
55
|
-
|
|
98
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) equipmentPrice!: string;
|
|
99
|
+
@IsString() @Matches(RETAIL_SALE_DECIMAL_PATTERN) amountPaid!: string;
|
|
100
|
+
|
|
101
|
+
@IsArray() @ValidateNested({ each: true }) @Type(() => RetailSystemSalePayment) @Validate(PaymentsMatchAmountPaid)
|
|
102
|
+
payments!: RetailSystemSalePayment[];
|
|
56
103
|
|
|
57
104
|
@IsArray() @ArrayNotEmpty() @ValidateNested({ each: true }) @Type(() => RetailSystemSaleLine) @Validate(HasUniqueKeys, ['lineId'])
|
|
58
105
|
lines!: RetailSystemSaleLine[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
import { isRealCalendarDate } from './calendarDate';
|
|
3
|
+
|
|
4
|
+
const CALENDAR_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
5
|
+
|
|
6
|
+
/** Acepta una fecha `YYYY-MM-DD` que exista en el calendario. */
|
|
7
|
+
@ValidatorConstraint({ name: 'isCalendarDate', async: false })
|
|
8
|
+
export class IsCalendarDate implements ValidatorConstraintInterface {
|
|
9
|
+
validate(value: unknown): boolean {
|
|
10
|
+
return typeof value === 'string' && CALENDAR_DATE.test(value) && isRealCalendarDate(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
defaultMessage(): string {
|
|
14
|
+
return 'debe ser una fecha YYYY-MM-DD, por ejemplo 2026-08-21';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
import { isRealCalendarDate } from './calendarDate';
|
|
2
3
|
|
|
3
4
|
const ISO_DATE_TIME_WITH_ZONE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
|
|
4
5
|
|
|
5
|
-
/** Descarta fechas que `Date` recorre en silencio, como el 31 de febrero. */
|
|
6
|
-
function isRealCalendarDate(value: string): boolean {
|
|
7
|
-
const [year, month, day] = value.slice(0, 10).split('-').map(Number);
|
|
8
|
-
return !Number.isNaN(Date.parse(value)) && new Date(Date.UTC(year, month - 1, day)).getUTCDate() === day;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
6
|
/** Acepta fecha y hora ISO-8601 con segundos y zona; sin zona se rechaza, como dice la doc. */
|
|
12
7
|
@ValidatorConstraint({ name: 'isIsoDateTimeWithZone', async: false })
|
|
13
8
|
export class IsIsoDateTimeWithZone implements ValidatorConstraintInterface {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Descarta fechas que `Date` recorre en silencio, como el 31 de febrero; lee los primeros 10 caracteres. */
|
|
2
|
+
export function isRealCalendarDate(value: string): boolean {
|
|
3
|
+
const [year, month, day] = value.slice(0, 10).split('-').map(Number);
|
|
4
|
+
return !Number.isNaN(Date.parse(value)) && new Date(Date.UTC(year, month - 1, day)).getUTCDate() === day;
|
|
5
|
+
}
|