tychat-contracts 1.0.95 → 1.0.98

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 (29) hide show
  1. package/dist/appointments/list-appointments-query.dto.d.ts +7 -0
  2. package/dist/appointments/list-appointments-query.dto.d.ts.map +1 -1
  3. package/dist/appointments/list-appointments-query.dto.js +26 -1
  4. package/dist/conversations/index.d.ts +2 -0
  5. package/dist/conversations/index.d.ts.map +1 -1
  6. package/dist/conversations/index.js +2 -0
  7. package/dist/conversations/list-conversation-contacts-filters.dto.d.ts +20 -0
  8. package/dist/conversations/list-conversation-contacts-filters.dto.d.ts.map +1 -0
  9. package/dist/conversations/list-conversation-contacts-filters.dto.js +91 -0
  10. package/dist/conversations/list-conversation-contacts-query.dto.d.ts +7 -0
  11. package/dist/conversations/list-conversation-contacts-query.dto.d.ts.map +1 -0
  12. package/dist/conversations/list-conversation-contacts-query.dto.js +83 -0
  13. package/dist/patients/index.d.ts +1 -0
  14. package/dist/patients/index.d.ts.map +1 -1
  15. package/dist/patients/index.js +1 -0
  16. package/dist/patients/list-patients-filters.dto.d.ts +19 -0
  17. package/dist/patients/list-patients-filters.dto.d.ts.map +1 -0
  18. package/dist/patients/list-patients-filters.dto.js +93 -0
  19. package/dist/patients/list-patients-query.dto.d.ts +2 -0
  20. package/dist/patients/list-patients-query.dto.d.ts.map +1 -1
  21. package/dist/patients/list-patients-query.dto.js +38 -0
  22. package/package.json +1 -1
  23. package/src/appointments/list-appointments-query.dto.ts +27 -0
  24. package/src/conversations/index.ts +2 -0
  25. package/src/conversations/list-conversation-contacts-filters.dto.ts +91 -0
  26. package/src/conversations/list-conversation-contacts-query.dto.ts +75 -0
  27. package/src/patients/index.ts +1 -0
  28. package/src/patients/list-patients-filters.dto.ts +92 -0
  29. package/src/patients/list-patients-query.dto.ts +46 -1
@@ -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);
@@ -1,5 +1,7 @@
1
1
  export * from './conversation-type.dto';
2
2
  export * from './conversation-intention.dto';
3
+ export * from './list-conversation-contacts-filters.dto';
4
+ export * from './list-conversation-contacts-query.dto';
3
5
  export * from './create-conversation-session.dto';
4
6
  export * from './conversation-session-response.dto';
5
7
  export * from './create-conversation.dto';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conversations/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conversations/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
@@ -16,6 +16,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./conversation-type.dto"), exports);
18
18
  __exportStar(require("./conversation-intention.dto"), exports);
19
+ __exportStar(require("./list-conversation-contacts-filters.dto"), exports);
20
+ __exportStar(require("./list-conversation-contacts-query.dto"), exports);
19
21
  __exportStar(require("./create-conversation-session.dto"), exports);
20
22
  __exportStar(require("./conversation-session-response.dto"), exports);
21
23
  __exportStar(require("./create-conversation.dto"), exports);
@@ -0,0 +1,20 @@
1
+ import { ValidationArguments, ValidatorConstraintInterface } from 'class-validator';
2
+ /** Campos da sessão (`conversation_sessions`) em `filters[].key` (camelCase da API). */
3
+ export declare const CONVERSATION_CONTACT_LIST_FILTER_KEYS: readonly ["id", "sessionUuid", "patientId", "unitId", "tenantEvogoInstanceId", "conversationType", "intention", "unreadCount", "startedAt", "expiresAt"];
4
+ export type ConversationContactListFilterKeyDto = (typeof CONVERSATION_CONTACT_LIST_FILTER_KEYS)[number];
5
+ export declare const CONVERSATION_CONTACT_LIST_FILTER_OPS: readonly ["==", ">", "<", ">=", "<=", "!=", "in"];
6
+ export type ConversationContactListFilterOpDto = (typeof CONVERSATION_CONTACT_LIST_FILTER_OPS)[number];
7
+ export declare class ConversationContactListFilterValueConstraint implements ValidatorConstraintInterface {
8
+ validate(value: unknown, args: ValidationArguments): boolean;
9
+ defaultMessage(): string;
10
+ }
11
+ /**
12
+ * Critério de filtro na listagem de contatos (sessão mais recente por paciente).
13
+ * Apenas `key`, `op` e `value`.
14
+ */
15
+ export declare class ConversationContactListFilterDto {
16
+ key: ConversationContactListFilterKeyDto;
17
+ op: ConversationContactListFilterOpDto;
18
+ value: unknown;
19
+ }
20
+ //# sourceMappingURL=list-conversation-contacts-filters.dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-conversation-contacts-filters.dto.d.ts","sourceRoot":"","sources":["../../src/conversations/list-conversation-contacts-filters.dto.ts"],"names":[],"mappings":"AACA,OAAO,EAML,mBAAmB,EAEnB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AAEzB,wFAAwF;AACxF,eAAO,MAAM,qCAAqC,0JAWxC,CAAC;AACX,MAAM,MAAM,mCAAmC,GAC7C,CAAC,OAAO,qCAAqC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,oCAAoC,mDAQvC,CAAC;AACX,MAAM,MAAM,kCAAkC,GAC5C,CAAC,OAAO,oCAAoC,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,qBACa,4CACX,YAAW,4BAA4B;IAEvC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO;IAS5D,cAAc,IAAI,MAAM;CAKzB;AAED;;;GAGG;AACH,qBAAa,gCAAgC;IAQ3C,GAAG,EAAE,mCAAmC,CAAC;IASzC,EAAE,EAAE,kCAAkC,CAAC;IAQvC,KAAK,EAAE,OAAO,CAAC;CAChB"}
@@ -0,0 +1,91 @@
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.ConversationContactListFilterDto = exports.ConversationContactListFilterValueConstraint = exports.CONVERSATION_CONTACT_LIST_FILTER_OPS = exports.CONVERSATION_CONTACT_LIST_FILTER_KEYS = void 0;
13
+ const swagger_1 = require("@nestjs/swagger");
14
+ const class_validator_1 = require("class-validator");
15
+ /** Campos da sessão (`conversation_sessions`) em `filters[].key` (camelCase da API). */
16
+ exports.CONVERSATION_CONTACT_LIST_FILTER_KEYS = [
17
+ 'id',
18
+ 'sessionUuid',
19
+ 'patientId',
20
+ 'unitId',
21
+ 'tenantEvogoInstanceId',
22
+ 'conversationType',
23
+ 'intention',
24
+ 'unreadCount',
25
+ 'startedAt',
26
+ 'expiresAt',
27
+ ];
28
+ exports.CONVERSATION_CONTACT_LIST_FILTER_OPS = [
29
+ '==',
30
+ '>',
31
+ '<',
32
+ '>=',
33
+ '<=',
34
+ '!=',
35
+ 'in',
36
+ ];
37
+ let ConversationContactListFilterValueConstraint = class ConversationContactListFilterValueConstraint {
38
+ validate(value, args) {
39
+ const obj = args.object;
40
+ const op = obj?.op;
41
+ if (op === 'in') {
42
+ return Array.isArray(value) && value.length >= 1;
43
+ }
44
+ return !Array.isArray(value);
45
+ }
46
+ defaultMessage() {
47
+ return ('Para op "in", value deve ser um array não vazio; para as demais operações, value não pode ser array');
48
+ }
49
+ };
50
+ exports.ConversationContactListFilterValueConstraint = ConversationContactListFilterValueConstraint;
51
+ exports.ConversationContactListFilterValueConstraint = ConversationContactListFilterValueConstraint = __decorate([
52
+ (0, class_validator_1.ValidatorConstraint)({ name: 'conversationContactListFilterValueShape', async: false })
53
+ ], ConversationContactListFilterValueConstraint);
54
+ /**
55
+ * Critério de filtro na listagem de contatos (sessão mais recente por paciente).
56
+ * Apenas `key`, `op` e `value`.
57
+ */
58
+ class ConversationContactListFilterDto {
59
+ key;
60
+ op;
61
+ value;
62
+ }
63
+ exports.ConversationContactListFilterDto = ConversationContactListFilterDto;
64
+ __decorate([
65
+ (0, swagger_1.ApiProperty)({
66
+ description: 'Campo da sessão (camelCase)',
67
+ enum: exports.CONVERSATION_CONTACT_LIST_FILTER_KEYS,
68
+ example: 'patientId',
69
+ }),
70
+ (0, class_validator_1.IsString)(),
71
+ (0, class_validator_1.IsIn)(exports.CONVERSATION_CONTACT_LIST_FILTER_KEYS),
72
+ __metadata("design:type", String)
73
+ ], ConversationContactListFilterDto.prototype, "key", void 0);
74
+ __decorate([
75
+ (0, swagger_1.ApiProperty)({
76
+ description: 'Operador de comparação',
77
+ enum: exports.CONVERSATION_CONTACT_LIST_FILTER_OPS,
78
+ example: '==',
79
+ }),
80
+ (0, class_validator_1.IsString)(),
81
+ (0, class_validator_1.IsIn)(exports.CONVERSATION_CONTACT_LIST_FILTER_OPS),
82
+ __metadata("design:type", String)
83
+ ], ConversationContactListFilterDto.prototype, "op", void 0);
84
+ __decorate([
85
+ (0, swagger_1.ApiPropertyOptional)({
86
+ description: 'Valor escalar ou, com op "in", array. null permitido para == / != em colunas opcionais (ex.: intention, unitId).',
87
+ example: '00000000-0000-4000-8000-000000000000',
88
+ }),
89
+ (0, class_validator_1.Validate)(ConversationContactListFilterValueConstraint),
90
+ __metadata("design:type", Object)
91
+ ], ConversationContactListFilterDto.prototype, "value", void 0);
@@ -0,0 +1,7 @@
1
+ import { ConversationContactListFilterDto } from './list-conversation-contacts-filters.dto';
2
+ export declare class ListConversationContactsQueryDto {
3
+ page?: number;
4
+ limit?: number;
5
+ filters?: ConversationContactListFilterDto[];
6
+ }
7
+ //# sourceMappingURL=list-conversation-contacts-query.dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-conversation-contacts-query.dto.d.ts","sourceRoot":"","sources":["../../src/conversations/list-conversation-contacts-query.dto.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,gCAAgC,EAAE,MAAM,0CAA0C,CAAC;AAyB5F,qBAAa,gCAAgC;IAU3C,IAAI,CAAC,EAAE,MAAM,CAAC;IAad,KAAK,CAAC,EAAE,MAAM,CAAC;IAcf,OAAO,CAAC,EAAE,gCAAgC,EAAE,CAAC;CAC9C"}
@@ -0,0 +1,83 @@
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.ListConversationContactsQueryDto = void 0;
13
+ const swagger_1 = require("@nestjs/swagger");
14
+ const class_transformer_1 = require("class-transformer");
15
+ const class_validator_1 = require("class-validator");
16
+ const list_conversation_contacts_filters_dto_1 = require("./list-conversation-contacts-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
+ }
38
+ class ListConversationContactsQueryDto {
39
+ page;
40
+ limit;
41
+ filters;
42
+ }
43
+ exports.ListConversationContactsQueryDto = ListConversationContactsQueryDto;
44
+ __decorate([
45
+ (0, swagger_1.ApiPropertyOptional)({
46
+ description: 'Número da página (iniciando em 1)',
47
+ example: 1,
48
+ minimum: 1,
49
+ default: 1,
50
+ }),
51
+ (0, class_validator_1.IsOptional)(),
52
+ (0, class_validator_1.IsInt)(),
53
+ (0, class_validator_1.Min)(1),
54
+ __metadata("design:type", Number)
55
+ ], ListConversationContactsQueryDto.prototype, "page", void 0);
56
+ __decorate([
57
+ (0, swagger_1.ApiPropertyOptional)({
58
+ description: 'Registros por página',
59
+ example: 20,
60
+ minimum: 1,
61
+ maximum: 100,
62
+ default: 20,
63
+ }),
64
+ (0, class_validator_1.IsOptional)(),
65
+ (0, class_validator_1.IsInt)(),
66
+ (0, class_validator_1.Min)(1),
67
+ (0, class_validator_1.Max)(100),
68
+ __metadata("design:type", Number)
69
+ ], ListConversationContactsQueryDto.prototype, "limit", void 0);
70
+ __decorate([
71
+ (0, swagger_1.ApiPropertyOptional)({
72
+ description: 'Lista `{ key, op, value }`. Na query HTTP: string JSON (ex.: `[{"key":"conversationType","op":"==","value":"ia"}]`). Ops: ==, !=, >, <, >=, <=, in.',
73
+ type: [list_conversation_contacts_filters_dto_1.ConversationContactListFilterDto],
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_conversation_contacts_filters_dto_1.ConversationContactListFilterDto),
82
+ __metadata("design:type", Array)
83
+ ], ListConversationContactsQueryDto.prototype, "filters", 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.98",
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 {
@@ -1,5 +1,7 @@
1
1
  export * from './conversation-type.dto';
2
2
  export * from './conversation-intention.dto';
3
+ export * from './list-conversation-contacts-filters.dto';
4
+ export * from './list-conversation-contacts-query.dto';
3
5
  export * from './create-conversation-session.dto';
4
6
  export * from './conversation-session-response.dto';
5
7
  export * from './create-conversation.dto';
@@ -0,0 +1,91 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+ import {
3
+ ArrayMinSize,
4
+ IsArray,
5
+ IsIn,
6
+ IsString,
7
+ Validate,
8
+ ValidationArguments,
9
+ ValidatorConstraint,
10
+ ValidatorConstraintInterface,
11
+ } from 'class-validator';
12
+
13
+ /** Campos da sessão (`conversation_sessions`) em `filters[].key` (camelCase da API). */
14
+ export const CONVERSATION_CONTACT_LIST_FILTER_KEYS = [
15
+ 'id',
16
+ 'sessionUuid',
17
+ 'patientId',
18
+ 'unitId',
19
+ 'tenantEvogoInstanceId',
20
+ 'conversationType',
21
+ 'intention',
22
+ 'unreadCount',
23
+ 'startedAt',
24
+ 'expiresAt',
25
+ ] as const;
26
+ export type ConversationContactListFilterKeyDto =
27
+ (typeof CONVERSATION_CONTACT_LIST_FILTER_KEYS)[number];
28
+
29
+ export const CONVERSATION_CONTACT_LIST_FILTER_OPS = [
30
+ '==',
31
+ '>',
32
+ '<',
33
+ '>=',
34
+ '<=',
35
+ '!=',
36
+ 'in',
37
+ ] as const;
38
+ export type ConversationContactListFilterOpDto =
39
+ (typeof CONVERSATION_CONTACT_LIST_FILTER_OPS)[number];
40
+
41
+ @ValidatorConstraint({ name: 'conversationContactListFilterValueShape', async: false })
42
+ export class ConversationContactListFilterValueConstraint
43
+ implements ValidatorConstraintInterface
44
+ {
45
+ validate(value: unknown, args: ValidationArguments): boolean {
46
+ const obj = args.object as ConversationContactListFilterDto;
47
+ const op = obj?.op;
48
+ if (op === 'in') {
49
+ return Array.isArray(value) && value.length >= 1;
50
+ }
51
+ return !Array.isArray(value);
52
+ }
53
+
54
+ defaultMessage(): string {
55
+ return (
56
+ 'Para op "in", value deve ser um array não vazio; para as demais operações, value não pode ser array'
57
+ );
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Critério de filtro na listagem de contatos (sessão mais recente por paciente).
63
+ * Apenas `key`, `op` e `value`.
64
+ */
65
+ export class ConversationContactListFilterDto {
66
+ @ApiProperty({
67
+ description: 'Campo da sessão (camelCase)',
68
+ enum: CONVERSATION_CONTACT_LIST_FILTER_KEYS,
69
+ example: 'patientId',
70
+ })
71
+ @IsString()
72
+ @IsIn(CONVERSATION_CONTACT_LIST_FILTER_KEYS)
73
+ key: ConversationContactListFilterKeyDto;
74
+
75
+ @ApiProperty({
76
+ description: 'Operador de comparação',
77
+ enum: CONVERSATION_CONTACT_LIST_FILTER_OPS,
78
+ example: '==',
79
+ })
80
+ @IsString()
81
+ @IsIn(CONVERSATION_CONTACT_LIST_FILTER_OPS)
82
+ op: ConversationContactListFilterOpDto;
83
+
84
+ @ApiPropertyOptional({
85
+ description:
86
+ 'Valor escalar ou, com op "in", array. null permitido para == / != em colunas opcionais (ex.: intention, unitId).',
87
+ example: '00000000-0000-4000-8000-000000000000',
88
+ })
89
+ @Validate(ConversationContactListFilterValueConstraint)
90
+ value: unknown;
91
+ }
@@ -0,0 +1,75 @@
1
+ import { ApiPropertyOptional } from '@nestjs/swagger';
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 { ConversationContactListFilterDto } from './list-conversation-contacts-filters.dto';
13
+
14
+ function parseFiltersFromQuery(
15
+ value: unknown,
16
+ ): ConversationContactListFilterDto[] | undefined {
17
+ if (value === undefined || value === null || value === '') {
18
+ return undefined;
19
+ }
20
+ if (Array.isArray(value)) {
21
+ return value as ConversationContactListFilterDto[];
22
+ }
23
+ if (typeof value === 'string') {
24
+ try {
25
+ const parsed = JSON.parse(value) as unknown;
26
+ if (!Array.isArray(parsed)) {
27
+ return undefined;
28
+ }
29
+ return parsed as ConversationContactListFilterDto[];
30
+ } catch {
31
+ return undefined;
32
+ }
33
+ }
34
+ return undefined;
35
+ }
36
+
37
+ export class ListConversationContactsQueryDto {
38
+ @ApiPropertyOptional({
39
+ description: 'Número da página (iniciando em 1)',
40
+ example: 1,
41
+ minimum: 1,
42
+ default: 1,
43
+ })
44
+ @IsOptional()
45
+ @IsInt()
46
+ @Min(1)
47
+ page?: number;
48
+
49
+ @ApiPropertyOptional({
50
+ description: 'Registros por página',
51
+ example: 20,
52
+ minimum: 1,
53
+ maximum: 100,
54
+ default: 20,
55
+ })
56
+ @IsOptional()
57
+ @IsInt()
58
+ @Min(1)
59
+ @Max(100)
60
+ limit?: number;
61
+
62
+ @ApiPropertyOptional({
63
+ description:
64
+ 'Lista `{ key, op, value }`. Na query HTTP: string JSON (ex.: `[{"key":"conversationType","op":"==","value":"ia"}]`). Ops: ==, !=, >, <, >=, <=, in.',
65
+ type: [ConversationContactListFilterDto],
66
+ isArray: true,
67
+ })
68
+ @IsOptional()
69
+ @Transform(({ value }) => parseFiltersFromQuery(value))
70
+ @IsArray()
71
+ @ArrayMinSize(0)
72
+ @ValidateNested({ each: true })
73
+ @Type(() => ConversationContactListFilterDto)
74
+ filters?: ConversationContactListFilterDto[];
75
+ }
@@ -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