tychat-contracts 1.0.95 → 1.0.97

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.
@@ -1,5 +1,10 @@
1
1
  import { AppointmentDto } from './appointment.dto';
2
2
  import { AppointmentStatusDto } from './appointment-status.dto';
3
+ /** Campo principal de ordenação na listagem de agendamentos. */
4
+ export declare const APPOINTMENT_LIST_SORT_BY: readonly ["date", "checkin_completed"];
5
+ export type AppointmentListSortByDto = (typeof APPOINTMENT_LIST_SORT_BY)[number];
6
+ export declare const APPOINTMENT_LIST_SORT_ORDER: readonly ["ASC", "DESC"];
7
+ export type AppointmentListSortOrderDto = (typeof APPOINTMENT_LIST_SORT_ORDER)[number];
3
8
  export declare class ListAppointmentsQueryDto {
4
9
  page?: number;
5
10
  limit?: number;
@@ -9,6 +14,8 @@ export declare class ListAppointmentsQueryDto {
9
14
  checkinCompleted?: boolean;
10
15
  patientId?: string;
11
16
  status?: AppointmentStatusDto;
17
+ sortBy?: AppointmentListSortByDto;
18
+ sortOrder?: AppointmentListSortOrderDto;
12
19
  }
13
20
  export interface AppointmentListResult {
14
21
  items: AppointmentDto[];
@@ -1 +1 @@
1
- {"version":3,"file":"list-appointments-query.dto.d.ts","sourceRoot":"","sources":["../../src/appointments/list-appointments-query.dto.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAwB,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEtF,qBAAa,wBAAwB;IAUnC,IAAI,CAAC,EAAE,MAAM,CAAC;IAad,KAAK,CAAC,EAAE,MAAM,CAAC;IAQf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAQlB,MAAM,CAAC,EAAE,MAAM,CAAC;IAQhB,MAAM,CAAC,EAAE,MAAM,CAAC;IAQhB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAQ3B,SAAS,CAAC,EAAE,MAAM,CAAC;IAUnB,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf"}
1
+ {"version":3,"file":"list-appointments-query.dto.d.ts","sourceRoot":"","sources":["../../src/appointments/list-appointments-query.dto.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAwB,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEtF,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,wCAAyC,CAAC;AAC/E,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF,eAAO,MAAM,2BAA2B,0BAA2B,CAAC;AACpE,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvF,qBAAa,wBAAwB;IAUnC,IAAI,CAAC,EAAE,MAAM,CAAC;IAad,KAAK,CAAC,EAAE,MAAM,CAAC;IAQf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAQlB,MAAM,CAAC,EAAE,MAAM,CAAC;IAQhB,MAAM,CAAC,EAAE,MAAM,CAAC;IAQhB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAQ3B,SAAS,CAAC,EAAE,MAAM,CAAC;IAUnB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAU9B,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAUlC,SAAS,CAAC,EAAE,2BAA2B,CAAC;CACzC;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf"}
@@ -9,10 +9,13 @@ 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.ListAppointmentsQueryDto = void 0;
12
+ exports.ListAppointmentsQueryDto = exports.APPOINTMENT_LIST_SORT_ORDER = exports.APPOINTMENT_LIST_SORT_BY = void 0;
13
13
  const swagger_1 = require("@nestjs/swagger");
14
14
  const class_validator_1 = require("class-validator");
15
15
  const appointment_status_dto_1 = require("./appointment-status.dto");
16
+ /** Campo principal de ordenação na listagem de agendamentos. */
17
+ exports.APPOINTMENT_LIST_SORT_BY = ['date', 'checkin_completed'];
18
+ exports.APPOINTMENT_LIST_SORT_ORDER = ['ASC', 'DESC'];
16
19
  class ListAppointmentsQueryDto {
17
20
  page;
18
21
  limit;
@@ -22,6 +25,8 @@ class ListAppointmentsQueryDto {
22
25
  checkinCompleted;
23
26
  patientId;
24
27
  status;
28
+ sortBy;
29
+ sortOrder;
25
30
  }
26
31
  exports.ListAppointmentsQueryDto = ListAppointmentsQueryDto;
27
32
  __decorate([
@@ -106,3 +111,23 @@ __decorate([
106
111
  (0, class_validator_1.IsIn)(appointment_status_dto_1.APPOINTMENT_STATUSES),
107
112
  __metadata("design:type", String)
108
113
  ], ListAppointmentsQueryDto.prototype, "status", void 0);
114
+ __decorate([
115
+ (0, swagger_1.ApiPropertyOptional)({
116
+ description: 'Ordenação principal: `date` (data do agendamento) ou `checkin_completed` (check-in feito ou pendente). Omisso = `date`.',
117
+ enum: exports.APPOINTMENT_LIST_SORT_BY,
118
+ example: 'checkin_completed',
119
+ }),
120
+ (0, class_validator_1.IsOptional)(),
121
+ (0, class_validator_1.IsIn)(exports.APPOINTMENT_LIST_SORT_BY),
122
+ __metadata("design:type", String)
123
+ ], ListAppointmentsQueryDto.prototype, "sortBy", void 0);
124
+ __decorate([
125
+ (0, swagger_1.ApiPropertyOptional)({
126
+ description: 'Direção: `ASC` ou `DESC`. Com `sortBy=checkin_completed`, `ASC` coloca pendentes (false) primeiro; `DESC` coloca concluídos (true) primeiro. Com ordenação por data, aplica-se à coluna `date`.',
127
+ enum: exports.APPOINTMENT_LIST_SORT_ORDER,
128
+ example: 'ASC',
129
+ }),
130
+ (0, class_validator_1.IsOptional)(),
131
+ (0, class_validator_1.IsIn)(exports.APPOINTMENT_LIST_SORT_ORDER),
132
+ __metadata("design:type", String)
133
+ ], ListAppointmentsQueryDto.prototype, "sortOrder", void 0);
@@ -2,6 +2,7 @@ export * from './create-patient.dto';
2
2
  export * from './update-patient.dto';
3
3
  export * from './patient-history-entry.dto';
4
4
  export * from './list-patients-query.dto';
5
+ export * from './list-patients-filters.dto';
5
6
  export * from './upsert-patient-by-phone.dto';
6
7
  export * from './patient-status.dto';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patients/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patients/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC"}
@@ -18,5 +18,6 @@ __exportStar(require("./create-patient.dto"), exports);
18
18
  __exportStar(require("./update-patient.dto"), exports);
19
19
  __exportStar(require("./patient-history-entry.dto"), exports);
20
20
  __exportStar(require("./list-patients-query.dto"), exports);
21
+ __exportStar(require("./list-patients-filters.dto"), exports);
21
22
  __exportStar(require("./upsert-patient-by-phone.dto"), exports);
22
23
  __exportStar(require("./patient-status.dto"), exports);
@@ -0,0 +1,19 @@
1
+ import { ValidationArguments, ValidatorConstraintInterface } from 'class-validator';
2
+ /** Propriedades da entidade paciente permitidas em `filters[].key` (API camelCase). */
3
+ export declare const PATIENT_LIST_FILTER_KEYS: readonly ["id", "status", "name", "cpf", "birthDate", "email", "phone", "gender", "address", "hasDisability", "disabilityDescription", "createdAt", "updatedAt"];
4
+ export type PatientListFilterKeyDto = (typeof PATIENT_LIST_FILTER_KEYS)[number];
5
+ export declare const PATIENT_LIST_FILTER_OPS: readonly ["==", ">", "<", ">=", "<=", "!=", "in"];
6
+ export type PatientListFilterOpDto = (typeof PATIENT_LIST_FILTER_OPS)[number];
7
+ export declare class PatientListFilterValueConstraint implements ValidatorConstraintInterface {
8
+ validate(value: unknown, args: ValidationArguments): boolean;
9
+ defaultMessage(): string;
10
+ }
11
+ /**
12
+ * Um critério de filtro na listagem de pacientes. Apenas `key`, `op` e `value`.
13
+ */
14
+ export declare class PatientListFilterDto {
15
+ key: PatientListFilterKeyDto;
16
+ op: PatientListFilterOpDto;
17
+ value: unknown;
18
+ }
19
+ //# sourceMappingURL=list-patients-filters.dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-patients-filters.dto.d.ts","sourceRoot":"","sources":["../../src/patients/list-patients-filters.dto.ts"],"names":[],"mappings":"AAEA,OAAO,EAQL,mBAAmB,EAEnB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AAEzB,uFAAuF;AACvF,eAAO,MAAM,wBAAwB,kKAc3B,CAAC;AACX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF,eAAO,MAAM,uBAAuB,mDAQ1B,CAAC;AACX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,qBACa,gCAAiC,YAAW,4BAA4B;IACnF,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO;IAS5D,cAAc,IAAI,MAAM;CAKzB;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAQ/B,GAAG,EAAE,uBAAuB,CAAC;IAS7B,EAAE,EAAE,sBAAsB,CAAC;IAQ3B,KAAK,EAAE,OAAO,CAAC;CAChB"}
@@ -0,0 +1,93 @@
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.PatientListFilterDto = exports.PatientListFilterValueConstraint = exports.PATIENT_LIST_FILTER_OPS = exports.PATIENT_LIST_FILTER_KEYS = void 0;
13
+ const swagger_1 = require("@nestjs/swagger");
14
+ const class_validator_1 = require("class-validator");
15
+ /** Propriedades da entidade paciente permitidas em `filters[].key` (API camelCase). */
16
+ exports.PATIENT_LIST_FILTER_KEYS = [
17
+ 'id',
18
+ 'status',
19
+ 'name',
20
+ 'cpf',
21
+ 'birthDate',
22
+ 'email',
23
+ 'phone',
24
+ 'gender',
25
+ 'address',
26
+ 'hasDisability',
27
+ 'disabilityDescription',
28
+ 'createdAt',
29
+ 'updatedAt',
30
+ ];
31
+ exports.PATIENT_LIST_FILTER_OPS = [
32
+ '==',
33
+ '>',
34
+ '<',
35
+ '>=',
36
+ '<=',
37
+ '!=',
38
+ 'in',
39
+ ];
40
+ let PatientListFilterValueConstraint = class PatientListFilterValueConstraint {
41
+ validate(value, args) {
42
+ const obj = args.object;
43
+ const op = obj?.op;
44
+ if (op === 'in') {
45
+ return Array.isArray(value) && value.length >= 1;
46
+ }
47
+ return !Array.isArray(value);
48
+ }
49
+ defaultMessage() {
50
+ return ('Para op "in", value deve ser um array não vazio; para as demais operações, value não pode ser array');
51
+ }
52
+ };
53
+ exports.PatientListFilterValueConstraint = PatientListFilterValueConstraint;
54
+ exports.PatientListFilterValueConstraint = PatientListFilterValueConstraint = __decorate([
55
+ (0, class_validator_1.ValidatorConstraint)({ name: 'patientListFilterValueShape', async: false })
56
+ ], PatientListFilterValueConstraint);
57
+ /**
58
+ * Um critério de filtro na listagem de pacientes. Apenas `key`, `op` e `value`.
59
+ */
60
+ class PatientListFilterDto {
61
+ key;
62
+ op;
63
+ value;
64
+ }
65
+ exports.PatientListFilterDto = PatientListFilterDto;
66
+ __decorate([
67
+ (0, swagger_1.ApiProperty)({
68
+ description: 'Campo do paciente (camelCase, alinhado ao contrato da API)',
69
+ enum: exports.PATIENT_LIST_FILTER_KEYS,
70
+ example: 'cpf',
71
+ }),
72
+ (0, class_validator_1.IsString)(),
73
+ (0, class_validator_1.IsIn)(exports.PATIENT_LIST_FILTER_KEYS),
74
+ __metadata("design:type", String)
75
+ ], PatientListFilterDto.prototype, "key", void 0);
76
+ __decorate([
77
+ (0, swagger_1.ApiProperty)({
78
+ description: 'Operador de comparação',
79
+ enum: exports.PATIENT_LIST_FILTER_OPS,
80
+ example: '==',
81
+ }),
82
+ (0, class_validator_1.IsString)(),
83
+ (0, class_validator_1.IsIn)(exports.PATIENT_LIST_FILTER_OPS),
84
+ __metadata("design:type", String)
85
+ ], PatientListFilterDto.prototype, "op", void 0);
86
+ __decorate([
87
+ (0, swagger_1.ApiPropertyOptional)({
88
+ description: 'Valor escalar ou, com op "in", array de valores. null permitido para == / != em colunas opcionais.',
89
+ example: '12225217416',
90
+ }),
91
+ (0, class_validator_1.Validate)(PatientListFilterValueConstraint),
92
+ __metadata("design:type", Object)
93
+ ], PatientListFilterDto.prototype, "value", void 0);
@@ -1,5 +1,7 @@
1
+ import { PatientListFilterDto } from './list-patients-filters.dto';
1
2
  export declare class ListPatientsQueryDto {
2
3
  page?: number;
3
4
  limit?: number;
5
+ filters?: PatientListFilterDto[];
4
6
  }
5
7
  //# sourceMappingURL=list-patients-query.dto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"list-patients-query.dto.d.ts","sourceRoot":"","sources":["../../src/patients/list-patients-query.dto.ts"],"names":[],"mappings":"AAGA,qBAAa,oBAAoB;IAU/B,IAAI,CAAC,EAAE,MAAM,CAAC;IAad,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
1
+ {"version":3,"file":"list-patients-query.dto.d.ts","sourceRoot":"","sources":["../../src/patients/list-patients-query.dto.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAuBnE,qBAAa,oBAAoB;IAU/B,IAAI,CAAC,EAAE,MAAM,CAAC;IAad,KAAK,CAAC,EAAE,MAAM,CAAC;IAcf,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAClC"}
@@ -11,10 +11,34 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ListPatientsQueryDto = void 0;
13
13
  const swagger_1 = require("@nestjs/swagger");
14
+ const class_transformer_1 = require("class-transformer");
14
15
  const class_validator_1 = require("class-validator");
16
+ const list_patients_filters_dto_1 = require("./list-patients-filters.dto");
17
+ function parseFiltersFromQuery(value) {
18
+ if (value === undefined || value === null || value === '') {
19
+ return undefined;
20
+ }
21
+ if (Array.isArray(value)) {
22
+ return value;
23
+ }
24
+ if (typeof value === 'string') {
25
+ try {
26
+ const parsed = JSON.parse(value);
27
+ if (!Array.isArray(parsed)) {
28
+ return undefined;
29
+ }
30
+ return parsed;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ return undefined;
37
+ }
15
38
  class ListPatientsQueryDto {
16
39
  page;
17
40
  limit;
41
+ filters;
18
42
  }
19
43
  exports.ListPatientsQueryDto = ListPatientsQueryDto;
20
44
  __decorate([
@@ -43,3 +67,17 @@ __decorate([
43
67
  (0, class_validator_1.Max)(100),
44
68
  __metadata("design:type", Number)
45
69
  ], ListPatientsQueryDto.prototype, "limit", void 0);
70
+ __decorate([
71
+ (0, swagger_1.ApiPropertyOptional)({
72
+ description: 'Lista de filtros `{ key, op, value }`. Query HTTP: enviar JSON string (ex.: `filters=[{"key":"cpf","op":"==","value":"12225217416"}]`). Operações: ==, !=, >, <, >=, <=, in (value array).',
73
+ type: [list_patients_filters_dto_1.PatientListFilterDto],
74
+ isArray: true,
75
+ }),
76
+ (0, class_validator_1.IsOptional)(),
77
+ (0, class_transformer_1.Transform)(({ value }) => parseFiltersFromQuery(value)),
78
+ (0, class_validator_1.IsArray)(),
79
+ (0, class_validator_1.ArrayMinSize)(0),
80
+ (0, class_validator_1.ValidateNested)({ each: true }),
81
+ (0, class_transformer_1.Type)(() => list_patients_filters_dto_1.PatientListFilterDto),
82
+ __metadata("design:type", Array)
83
+ ], ListPatientsQueryDto.prototype, "filters", void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tychat-contracts",
3
- "version": "1.0.95",
3
+ "version": "1.0.97",
4
4
  "description": "DTOs compartilhados com class-validator (API e microserviços)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -3,6 +3,13 @@ import { IsBoolean, IsDateString, IsIn, IsInt, IsOptional, IsString, IsUUID, Max
3
3
  import { AppointmentDto } from './appointment.dto';
4
4
  import { APPOINTMENT_STATUSES, AppointmentStatusDto } from './appointment-status.dto';
5
5
 
6
+ /** Campo principal de ordenação na listagem de agendamentos. */
7
+ export const APPOINTMENT_LIST_SORT_BY = ['date', 'checkin_completed'] as const;
8
+ export type AppointmentListSortByDto = (typeof APPOINTMENT_LIST_SORT_BY)[number];
9
+
10
+ export const APPOINTMENT_LIST_SORT_ORDER = ['ASC', 'DESC'] as const;
11
+ export type AppointmentListSortOrderDto = (typeof APPOINTMENT_LIST_SORT_ORDER)[number];
12
+
6
13
  export class ListAppointmentsQueryDto {
7
14
  @ApiPropertyOptional({
8
15
  description: 'Número da página (iniciando em 1)',
@@ -77,6 +84,26 @@ export class ListAppointmentsQueryDto {
77
84
  @IsString()
78
85
  @IsIn(APPOINTMENT_STATUSES)
79
86
  status?: AppointmentStatusDto;
87
+
88
+ @ApiPropertyOptional({
89
+ description:
90
+ 'Ordenação principal: `date` (data do agendamento) ou `checkin_completed` (check-in feito ou pendente). Omisso = `date`.',
91
+ enum: APPOINTMENT_LIST_SORT_BY,
92
+ example: 'checkin_completed',
93
+ })
94
+ @IsOptional()
95
+ @IsIn(APPOINTMENT_LIST_SORT_BY)
96
+ sortBy?: AppointmentListSortByDto;
97
+
98
+ @ApiPropertyOptional({
99
+ description:
100
+ 'Direção: `ASC` ou `DESC`. Com `sortBy=checkin_completed`, `ASC` coloca pendentes (false) primeiro; `DESC` coloca concluídos (true) primeiro. Com ordenação por data, aplica-se à coluna `date`.',
101
+ enum: APPOINTMENT_LIST_SORT_ORDER,
102
+ example: 'ASC',
103
+ })
104
+ @IsOptional()
105
+ @IsIn(APPOINTMENT_LIST_SORT_ORDER)
106
+ sortOrder?: AppointmentListSortOrderDto;
80
107
  }
81
108
 
82
109
  export interface AppointmentListResult {
@@ -2,6 +2,7 @@ export * from './create-patient.dto';
2
2
  export * from './update-patient.dto';
3
3
  export * from './patient-history-entry.dto';
4
4
  export * from './list-patients-query.dto';
5
+ export * from './list-patients-filters.dto';
5
6
  export * from './upsert-patient-by-phone.dto';
6
7
  export * from './patient-status.dto';
7
8
 
@@ -0,0 +1,92 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import {
4
+ ArrayMinSize,
5
+ IsArray,
6
+ IsIn,
7
+ IsOptional,
8
+ IsString,
9
+ Validate,
10
+ ValidateNested,
11
+ ValidationArguments,
12
+ ValidatorConstraint,
13
+ ValidatorConstraintInterface,
14
+ } from 'class-validator';
15
+
16
+ /** Propriedades da entidade paciente permitidas em `filters[].key` (API camelCase). */
17
+ export const PATIENT_LIST_FILTER_KEYS = [
18
+ 'id',
19
+ 'status',
20
+ 'name',
21
+ 'cpf',
22
+ 'birthDate',
23
+ 'email',
24
+ 'phone',
25
+ 'gender',
26
+ 'address',
27
+ 'hasDisability',
28
+ 'disabilityDescription',
29
+ 'createdAt',
30
+ 'updatedAt',
31
+ ] as const;
32
+ export type PatientListFilterKeyDto = (typeof PATIENT_LIST_FILTER_KEYS)[number];
33
+
34
+ export const PATIENT_LIST_FILTER_OPS = [
35
+ '==',
36
+ '>',
37
+ '<',
38
+ '>=',
39
+ '<=',
40
+ '!=',
41
+ 'in',
42
+ ] as const;
43
+ export type PatientListFilterOpDto = (typeof PATIENT_LIST_FILTER_OPS)[number];
44
+
45
+ @ValidatorConstraint({ name: 'patientListFilterValueShape', async: false })
46
+ export class PatientListFilterValueConstraint implements ValidatorConstraintInterface {
47
+ validate(value: unknown, args: ValidationArguments): boolean {
48
+ const obj = args.object as PatientListFilterDto;
49
+ const op = obj?.op;
50
+ if (op === 'in') {
51
+ return Array.isArray(value) && value.length >= 1;
52
+ }
53
+ return !Array.isArray(value);
54
+ }
55
+
56
+ defaultMessage(): string {
57
+ return (
58
+ 'Para op "in", value deve ser um array não vazio; para as demais operações, value não pode ser array'
59
+ );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Um critério de filtro na listagem de pacientes. Apenas `key`, `op` e `value`.
65
+ */
66
+ export class PatientListFilterDto {
67
+ @ApiProperty({
68
+ description: 'Campo do paciente (camelCase, alinhado ao contrato da API)',
69
+ enum: PATIENT_LIST_FILTER_KEYS,
70
+ example: 'cpf',
71
+ })
72
+ @IsString()
73
+ @IsIn(PATIENT_LIST_FILTER_KEYS)
74
+ key: PatientListFilterKeyDto;
75
+
76
+ @ApiProperty({
77
+ description: 'Operador de comparação',
78
+ enum: PATIENT_LIST_FILTER_OPS,
79
+ example: '==',
80
+ })
81
+ @IsString()
82
+ @IsIn(PATIENT_LIST_FILTER_OPS)
83
+ op: PatientListFilterOpDto;
84
+
85
+ @ApiPropertyOptional({
86
+ description:
87
+ 'Valor escalar ou, com op "in", array de valores. null permitido para == / != em colunas opcionais.',
88
+ example: '12225217416',
89
+ })
90
+ @Validate(PatientListFilterValueConstraint)
91
+ value: unknown;
92
+ }
@@ -1,5 +1,36 @@
1
1
  import { ApiPropertyOptional } from '@nestjs/swagger';
2
- import { IsInt, IsOptional, Max, Min } from 'class-validator';
2
+ import { Transform, Type } from 'class-transformer';
3
+ import {
4
+ ArrayMinSize,
5
+ IsArray,
6
+ IsInt,
7
+ IsOptional,
8
+ Max,
9
+ Min,
10
+ ValidateNested,
11
+ } from 'class-validator';
12
+ import { PatientListFilterDto } from './list-patients-filters.dto';
13
+
14
+ function parseFiltersFromQuery(value: unknown): PatientListFilterDto[] | undefined {
15
+ if (value === undefined || value === null || value === '') {
16
+ return undefined;
17
+ }
18
+ if (Array.isArray(value)) {
19
+ return value as PatientListFilterDto[];
20
+ }
21
+ if (typeof value === 'string') {
22
+ try {
23
+ const parsed = JSON.parse(value) as unknown;
24
+ if (!Array.isArray(parsed)) {
25
+ return undefined;
26
+ }
27
+ return parsed as PatientListFilterDto[];
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ return undefined;
33
+ }
3
34
 
4
35
  export class ListPatientsQueryDto {
5
36
  @ApiPropertyOptional({
@@ -25,5 +56,19 @@ export class ListPatientsQueryDto {
25
56
  @Min(1)
26
57
  @Max(100)
27
58
  limit?: number;
59
+
60
+ @ApiPropertyOptional({
61
+ description:
62
+ 'Lista de filtros `{ key, op, value }`. Query HTTP: enviar JSON string (ex.: `filters=[{"key":"cpf","op":"==","value":"12225217416"}]`). Operações: ==, !=, >, <, >=, <=, in (value array).',
63
+ type: [PatientListFilterDto],
64
+ isArray: true,
65
+ })
66
+ @IsOptional()
67
+ @Transform(({ value }) => parseFiltersFromQuery(value))
68
+ @IsArray()
69
+ @ArrayMinSize(0)
70
+ @ValidateNested({ each: true })
71
+ @Type(() => PatientListFilterDto)
72
+ filters?: PatientListFilterDto[];
28
73
  }
29
74