evo360-types 1.3.715 → 1.3.717
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/dist/apps/evo-chat/forward/zod-schemas.d.ts +576 -0
- package/dist/apps/evo-chat/forward/zod-schemas.js +154 -0
- package/dist/apps/evo-chat/forward/zod-schemas.ts +175 -0
- package/dist/apps/evo-chat/thread-message/zod-schemas.d.ts +204 -0
- package/dist/apps/evo-chat/thread-message/zod-schemas.js +3 -0
- package/dist/apps/evo-chat/thread-message/zod-schemas.ts +3 -0
- package/dist/apps/evo-finops/zod-schemas.d.ts +2 -2
- package/dist/apps/evo-med/appointment/zod-schemas.d.ts +8 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.ts +1 -0
- package/dist/types/evo-chat/forward/index.d.ts +168 -0
- package/dist/types/evo-chat/forward/index.js +75 -0
- package/dist/types/evo-chat/forward/index.ts +256 -0
- package/dist/types/evo-chat/index.d.ts +3 -0
- package/dist/types/evo-chat/index.js +3 -0
- package/dist/types/evo-chat/index.ts +3 -0
- package/dist/types/evo-chat/thread-message/index.d.ts +4 -0
- package/dist/types/evo-chat/thread-message/index.ts +6 -0
- package/package.json +1 -1
|
@@ -278,12 +278,12 @@ export declare const zAppointmentRescheduleSchema: z.ZodObject<{
|
|
|
278
278
|
userRef?: any;
|
|
279
279
|
external_id?: string | null | undefined;
|
|
280
280
|
user_name?: string | undefined;
|
|
281
|
+
reason?: string | undefined;
|
|
281
282
|
appointmentSource?: {
|
|
282
283
|
code: string;
|
|
283
284
|
name: string;
|
|
284
285
|
external_id?: string | null | undefined;
|
|
285
286
|
} | undefined;
|
|
286
|
-
reason?: string | undefined;
|
|
287
287
|
oldStartDate?: Date | undefined;
|
|
288
288
|
oldEndDate?: Date | undefined;
|
|
289
289
|
newStartDate?: Date | undefined;
|
|
@@ -293,12 +293,12 @@ export declare const zAppointmentRescheduleSchema: z.ZodObject<{
|
|
|
293
293
|
userRef?: any;
|
|
294
294
|
external_id?: string | null | undefined;
|
|
295
295
|
user_name?: string | undefined;
|
|
296
|
+
reason?: string | undefined;
|
|
296
297
|
appointmentSource?: {
|
|
297
298
|
code: string;
|
|
298
299
|
name: string;
|
|
299
300
|
external_id?: string | null | undefined;
|
|
300
301
|
} | undefined;
|
|
301
|
-
reason?: string | undefined;
|
|
302
302
|
oldStartDate?: unknown;
|
|
303
303
|
oldEndDate?: unknown;
|
|
304
304
|
newStartDate?: unknown;
|
|
@@ -650,12 +650,12 @@ export declare const zAppointmentSchema: z.ZodObject<{
|
|
|
650
650
|
userRef?: any;
|
|
651
651
|
external_id?: string | null | undefined;
|
|
652
652
|
user_name?: string | undefined;
|
|
653
|
+
reason?: string | undefined;
|
|
653
654
|
appointmentSource?: {
|
|
654
655
|
code: string;
|
|
655
656
|
name: string;
|
|
656
657
|
external_id?: string | null | undefined;
|
|
657
658
|
} | undefined;
|
|
658
|
-
reason?: string | undefined;
|
|
659
659
|
oldStartDate?: Date | undefined;
|
|
660
660
|
oldEndDate?: Date | undefined;
|
|
661
661
|
newStartDate?: Date | undefined;
|
|
@@ -665,12 +665,12 @@ export declare const zAppointmentSchema: z.ZodObject<{
|
|
|
665
665
|
userRef?: any;
|
|
666
666
|
external_id?: string | null | undefined;
|
|
667
667
|
user_name?: string | undefined;
|
|
668
|
+
reason?: string | undefined;
|
|
668
669
|
appointmentSource?: {
|
|
669
670
|
code: string;
|
|
670
671
|
name: string;
|
|
671
672
|
external_id?: string | null | undefined;
|
|
672
673
|
} | undefined;
|
|
673
|
-
reason?: string | undefined;
|
|
674
674
|
oldStartDate?: unknown;
|
|
675
675
|
oldEndDate?: unknown;
|
|
676
676
|
newStartDate?: unknown;
|
|
@@ -1017,12 +1017,12 @@ export declare const zAppointmentSchema: z.ZodObject<{
|
|
|
1017
1017
|
userRef?: any;
|
|
1018
1018
|
external_id?: string | null | undefined;
|
|
1019
1019
|
user_name?: string | undefined;
|
|
1020
|
+
reason?: string | undefined;
|
|
1020
1021
|
appointmentSource?: {
|
|
1021
1022
|
code: string;
|
|
1022
1023
|
name: string;
|
|
1023
1024
|
external_id?: string | null | undefined;
|
|
1024
1025
|
} | undefined;
|
|
1025
|
-
reason?: string | undefined;
|
|
1026
1026
|
oldStartDate?: Date | undefined;
|
|
1027
1027
|
oldEndDate?: Date | undefined;
|
|
1028
1028
|
newStartDate?: Date | undefined;
|
|
@@ -1032,12 +1032,12 @@ export declare const zAppointmentSchema: z.ZodObject<{
|
|
|
1032
1032
|
userRef?: any;
|
|
1033
1033
|
external_id?: string | null | undefined;
|
|
1034
1034
|
user_name?: string | undefined;
|
|
1035
|
+
reason?: string | undefined;
|
|
1035
1036
|
appointmentSource?: {
|
|
1036
1037
|
code: string;
|
|
1037
1038
|
name: string;
|
|
1038
1039
|
external_id?: string | null | undefined;
|
|
1039
1040
|
} | undefined;
|
|
1040
|
-
reason?: string | undefined;
|
|
1041
1041
|
oldStartDate?: unknown;
|
|
1042
1042
|
oldEndDate?: unknown;
|
|
1043
1043
|
newStartDate?: unknown;
|
|
@@ -1384,12 +1384,12 @@ export declare const zAppointmentSchema: z.ZodObject<{
|
|
|
1384
1384
|
userRef?: any;
|
|
1385
1385
|
external_id?: string | null | undefined;
|
|
1386
1386
|
user_name?: string | undefined;
|
|
1387
|
+
reason?: string | undefined;
|
|
1387
1388
|
appointmentSource?: {
|
|
1388
1389
|
code: string;
|
|
1389
1390
|
name: string;
|
|
1390
1391
|
external_id?: string | null | undefined;
|
|
1391
1392
|
} | undefined;
|
|
1392
|
-
reason?: string | undefined;
|
|
1393
1393
|
oldStartDate?: Date | undefined;
|
|
1394
1394
|
oldEndDate?: Date | undefined;
|
|
1395
1395
|
newStartDate?: Date | undefined;
|
|
@@ -1399,12 +1399,12 @@ export declare const zAppointmentSchema: z.ZodObject<{
|
|
|
1399
1399
|
userRef?: any;
|
|
1400
1400
|
external_id?: string | null | undefined;
|
|
1401
1401
|
user_name?: string | undefined;
|
|
1402
|
+
reason?: string | undefined;
|
|
1402
1403
|
appointmentSource?: {
|
|
1403
1404
|
code: string;
|
|
1404
1405
|
name: string;
|
|
1405
1406
|
external_id?: string | null | undefined;
|
|
1406
1407
|
} | undefined;
|
|
1407
|
-
reason?: string | undefined;
|
|
1408
1408
|
oldStartDate?: unknown;
|
|
1409
1409
|
oldEndDate?: unknown;
|
|
1410
1410
|
newStartDate?: unknown;
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export * from "./apps/evo-chat/waba-template/zod-schemas";
|
|
|
58
58
|
export * from "./apps/evo-chat/waba-setup/zod-schemas";
|
|
59
59
|
export * from "./apps/evo-chat/ig-setup/zod-schemas";
|
|
60
60
|
export * from "./apps/evo-chat/quick-reply/zod-schemas";
|
|
61
|
+
export * from "./apps/evo-chat/forward/zod-schemas";
|
|
61
62
|
export * from "./apps/evo-hub-ia/channel/zod-schemas";
|
|
62
63
|
export * from "./apps/evo-hub-ia/v1/zod-schemas";
|
|
63
64
|
export * from "./apps/evo-hub-ia/v1/adapter-tool-registry";
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ __exportStar(require("./apps/evo-chat/waba-template/zod-schemas"), exports);
|
|
|
76
76
|
__exportStar(require("./apps/evo-chat/waba-setup/zod-schemas"), exports);
|
|
77
77
|
__exportStar(require("./apps/evo-chat/ig-setup/zod-schemas"), exports);
|
|
78
78
|
__exportStar(require("./apps/evo-chat/quick-reply/zod-schemas"), exports);
|
|
79
|
+
__exportStar(require("./apps/evo-chat/forward/zod-schemas"), exports);
|
|
79
80
|
__exportStar(require("./apps/evo-hub-ia/channel/zod-schemas"), exports);
|
|
80
81
|
__exportStar(require("./apps/evo-hub-ia/v1/zod-schemas"), exports);
|
|
81
82
|
__exportStar(require("./apps/evo-hub-ia/v1/adapter-tool-registry"), exports);
|
package/dist/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export * from "./apps/evo-chat/waba-template/zod-schemas";
|
|
|
61
61
|
export * from "./apps/evo-chat/waba-setup/zod-schemas";
|
|
62
62
|
export * from "./apps/evo-chat/ig-setup/zod-schemas";
|
|
63
63
|
export * from "./apps/evo-chat/quick-reply/zod-schemas";
|
|
64
|
+
export * from "./apps/evo-chat/forward/zod-schemas";
|
|
64
65
|
export * from "./apps/evo-hub-ia/channel/zod-schemas";
|
|
65
66
|
export * from "./apps/evo-hub-ia/v1/zod-schemas";
|
|
66
67
|
export * from "./apps/evo-hub-ia/v1/adapter-tool-registry";
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { IFireDoc } from "../../shared";
|
|
2
|
+
import type { IThreadMessage } from "../thread-message";
|
|
3
|
+
/**
|
|
4
|
+
* feat-282 — encaminhamento de mensagens no Hub Omni.
|
|
5
|
+
*
|
|
6
|
+
* A Cloud API da Meta não tem forward: o encaminhamento é nosso. Cada ação do
|
|
7
|
+
* operador vira um LOTE (`IForwardBatch`) em
|
|
8
|
+
* `tenants/{t}/apps/evo-chat/forwards/{forwardId}`, com um item por
|
|
9
|
+
* (destinatário × mensagem) na subcoleção `items/` e, por destinatário, um item
|
|
10
|
+
* `intro` opcional (texto de apresentação). Cada entrega é uma mensagem NORMAL de
|
|
11
|
+
* saída na thread de destino, com o bloco `forward` (`IMessageForward`) apontando
|
|
12
|
+
* para a origem. A mensagem de origem só ganha `forward_count`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const FORWARDS_COLLECTION: "forwards";
|
|
15
|
+
export declare const FORWARD_ITEMS_COLLECTION: "items";
|
|
16
|
+
/** Tetos do lote (plan.md D6). Constantes de plataforma, sem config por tenant. */
|
|
17
|
+
export declare const FORWARD_MAX_MESSAGES = 20;
|
|
18
|
+
export declare const FORWARD_MAX_RECIPIENTS = 10;
|
|
19
|
+
export declare const FORWARD_INTRO_MAX_CHARS = 1000;
|
|
20
|
+
/** Lote `running` sem atualização há mais que isto é mostrado como interrompido. */
|
|
21
|
+
export declare const FORWARD_STALE_AFTER_MS: number;
|
|
22
|
+
/** Evento de timeline gravado na thread de ORIGEM ao criar o lote. */
|
|
23
|
+
export declare const FORWARD_TIMELINE_EVENT_TYPE: "messages_forwarded";
|
|
24
|
+
export type ForwardBatchStatus = "running" | "done" | "done_with_failures";
|
|
25
|
+
export type ForwardItemStatus = "pending" | "sending" | "sent" | "failed" | "skipped";
|
|
26
|
+
/** Motivos decididos na pré-validação (item nasce `skipped`, nenhuma chamada ao provedor). */
|
|
27
|
+
export type ForwardSkipReason = "window_closed" | "type_not_supported" | "media_too_large" | "media_unavailable" | "channel_not_supported";
|
|
28
|
+
/** Motivos de falha no envio (item passou por `sending`). */
|
|
29
|
+
export type ForwardFailReason = "provider_error" | "window_closed" | "send_budget_exceeded" | "interrupted";
|
|
30
|
+
export type ForwardItemReason = ForwardSkipReason | ForwardFailReason;
|
|
31
|
+
export type ForwardContentKind = "text" | "image" | "video" | "audio" | "document";
|
|
32
|
+
/** Canais aceitos como DESTINO na F1. E-mail e SMS ficam para a F2. */
|
|
33
|
+
export type ForwardDestinationKind = "whatsapp" | "instagram";
|
|
34
|
+
export type ForwardRecipientProvider = "hub-waba" | "hub-instagram";
|
|
35
|
+
export type ForwardSpecialKind = "doctor" | "secretary" | "employee";
|
|
36
|
+
export interface IForwardActor {
|
|
37
|
+
user_id: string;
|
|
38
|
+
display_name: string;
|
|
39
|
+
}
|
|
40
|
+
export interface IForwardSource {
|
|
41
|
+
thread_id: string;
|
|
42
|
+
ticket_id: string;
|
|
43
|
+
/** Departamento do ticket de origem: é onde o ticket novo do destino abre (Q4-A). */
|
|
44
|
+
department_id: string | null;
|
|
45
|
+
contact_id: string;
|
|
46
|
+
contact_name: string;
|
|
47
|
+
channel_id: string | null;
|
|
48
|
+
channel_type: string | null;
|
|
49
|
+
}
|
|
50
|
+
export interface IForwardRecipient {
|
|
51
|
+
thread_id: string;
|
|
52
|
+
contact_id: string;
|
|
53
|
+
contact_name: string;
|
|
54
|
+
channel_id: string;
|
|
55
|
+
channel_type: string;
|
|
56
|
+
provider: ForwardRecipientProvider;
|
|
57
|
+
special_kind?: ForwardSpecialKind | null;
|
|
58
|
+
}
|
|
59
|
+
export interface IForwardDispatchState {
|
|
60
|
+
claimed_at: Date;
|
|
61
|
+
finished_at?: Date | null;
|
|
62
|
+
}
|
|
63
|
+
export interface IForwardCounts {
|
|
64
|
+
total: number;
|
|
65
|
+
sent: number;
|
|
66
|
+
failed: number;
|
|
67
|
+
skipped: number;
|
|
68
|
+
}
|
|
69
|
+
export interface IForwardBatch extends IFireDoc {
|
|
70
|
+
forward_id: string;
|
|
71
|
+
tenant: string;
|
|
72
|
+
created_by: IForwardActor;
|
|
73
|
+
source: IForwardSource;
|
|
74
|
+
/** Ordem cronológica da origem (o backend ordena por `created_at`, não pelo payload). */
|
|
75
|
+
message_ids: string[];
|
|
76
|
+
intro_text: string | null;
|
|
77
|
+
recipients: IForwardRecipient[];
|
|
78
|
+
status: ForwardBatchStatus;
|
|
79
|
+
counts: IForwardCounts;
|
|
80
|
+
/** Destinatários com pelo menos um item `pending` (os que recebem mensagem PubSub). */
|
|
81
|
+
recipients_to_dispatch: number;
|
|
82
|
+
recipients_done: number;
|
|
83
|
+
/** Chave = `thread_id` do destinatário. Claim do worker contra entrega duplicada. */
|
|
84
|
+
dispatch?: Record<string, IForwardDispatchState>;
|
|
85
|
+
finished_at?: Date | null;
|
|
86
|
+
}
|
|
87
|
+
export interface IForwardItem {
|
|
88
|
+
/** `${recipient_thread_id}__${message_id}` ou `${recipient_thread_id}__intro`. */
|
|
89
|
+
item_id: string;
|
|
90
|
+
forward_id: string;
|
|
91
|
+
recipient_thread_id: string;
|
|
92
|
+
recipient_index: number;
|
|
93
|
+
/** 0 = intro; 1..N = mensagens na ordem da origem. */
|
|
94
|
+
seq: number;
|
|
95
|
+
kind: "intro" | "message";
|
|
96
|
+
message_id: string | null;
|
|
97
|
+
content_kind: ForwardContentKind;
|
|
98
|
+
status: ForwardItemStatus;
|
|
99
|
+
reason?: ForwardItemReason | null;
|
|
100
|
+
error?: string | null;
|
|
101
|
+
error_code?: number | null;
|
|
102
|
+
dest_message_id?: string | null;
|
|
103
|
+
sent_at?: Date | null;
|
|
104
|
+
updated_at?: Date;
|
|
105
|
+
}
|
|
106
|
+
export type ForwardWindowState = "open" | "human_agent" | "closed";
|
|
107
|
+
export interface IForwardPlanItem {
|
|
108
|
+
item_id: string;
|
|
109
|
+
kind: "intro" | "message";
|
|
110
|
+
message_id: string | null;
|
|
111
|
+
status: "pending" | "skipped";
|
|
112
|
+
reason?: ForwardSkipReason;
|
|
113
|
+
}
|
|
114
|
+
export interface IForwardPlanRecipient {
|
|
115
|
+
thread_id: string;
|
|
116
|
+
channel_id: string;
|
|
117
|
+
channel_type: string;
|
|
118
|
+
contact_name: string;
|
|
119
|
+
window: ForwardWindowState;
|
|
120
|
+
needs_ticket: boolean;
|
|
121
|
+
items: IForwardPlanItem[];
|
|
122
|
+
}
|
|
123
|
+
export interface IForwardPlan {
|
|
124
|
+
recipients: IForwardPlanRecipient[];
|
|
125
|
+
counts: {
|
|
126
|
+
total: number;
|
|
127
|
+
pending: number;
|
|
128
|
+
skipped: number;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export interface IMessageForward {
|
|
132
|
+
forward_id: string;
|
|
133
|
+
/** Ponte para o item do lote (o status-processor reflete falha assíncrona por aqui). */
|
|
134
|
+
item_id: string;
|
|
135
|
+
/** Vazio no item `intro`: o front não mostra selo quando está vazio. */
|
|
136
|
+
source_message_id: string;
|
|
137
|
+
source_thread_id: string;
|
|
138
|
+
source_ticket_id: string;
|
|
139
|
+
source_contact_id: string;
|
|
140
|
+
source_contact_name: string;
|
|
141
|
+
source_channel_type: string | null;
|
|
142
|
+
source_direction: "in" | "out";
|
|
143
|
+
source_created_at: Date;
|
|
144
|
+
forwarded_by: IForwardActor;
|
|
145
|
+
forwarded_at: Date;
|
|
146
|
+
}
|
|
147
|
+
export type ForwardableMessage = Pick<IThreadMessage, "type" | "direction" | "channel_type" | "body" | "media" | "template"> & {
|
|
148
|
+
revoked?: unknown;
|
|
149
|
+
deleted_at?: unknown;
|
|
150
|
+
};
|
|
151
|
+
export type ForwardContentResolution = {
|
|
152
|
+
ok: true;
|
|
153
|
+
kind: ForwardContentKind;
|
|
154
|
+
text?: string;
|
|
155
|
+
} | {
|
|
156
|
+
ok: false;
|
|
157
|
+
reason: ForwardSkipReason;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Decide se uma mensagem pode ser encaminhada e como vira conteúdo no destino.
|
|
161
|
+
*
|
|
162
|
+
* `dest = null` é a checagem agnóstica do modo de seleção no front (sem canal
|
|
163
|
+
* escolhido). A regra que depende do destino é só uma: documento não vai para
|
|
164
|
+
* Instagram. O teto de tamanho NÃO fica aqui: depende do objeto no Storage.
|
|
165
|
+
*/
|
|
166
|
+
export declare function resolveForwardContent(m: ForwardableMessage, dest: ForwardDestinationKind | null): ForwardContentResolution;
|
|
167
|
+
/** Id determinístico do item: um destinatário aparece uma vez no lote, então é único. */
|
|
168
|
+
export declare function buildForwardItemId(recipientThreadId: string, messageId: string | null): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FORWARD_TIMELINE_EVENT_TYPE = exports.FORWARD_STALE_AFTER_MS = exports.FORWARD_INTRO_MAX_CHARS = exports.FORWARD_MAX_RECIPIENTS = exports.FORWARD_MAX_MESSAGES = exports.FORWARD_ITEMS_COLLECTION = exports.FORWARDS_COLLECTION = void 0;
|
|
4
|
+
exports.resolveForwardContent = resolveForwardContent;
|
|
5
|
+
exports.buildForwardItemId = buildForwardItemId;
|
|
6
|
+
/**
|
|
7
|
+
* feat-282 — encaminhamento de mensagens no Hub Omni.
|
|
8
|
+
*
|
|
9
|
+
* A Cloud API da Meta não tem forward: o encaminhamento é nosso. Cada ação do
|
|
10
|
+
* operador vira um LOTE (`IForwardBatch`) em
|
|
11
|
+
* `tenants/{t}/apps/evo-chat/forwards/{forwardId}`, com um item por
|
|
12
|
+
* (destinatário × mensagem) na subcoleção `items/` e, por destinatário, um item
|
|
13
|
+
* `intro` opcional (texto de apresentação). Cada entrega é uma mensagem NORMAL de
|
|
14
|
+
* saída na thread de destino, com o bloco `forward` (`IMessageForward`) apontando
|
|
15
|
+
* para a origem. A mensagem de origem só ganha `forward_count`.
|
|
16
|
+
*/
|
|
17
|
+
// ── Constants ──
|
|
18
|
+
exports.FORWARDS_COLLECTION = "forwards";
|
|
19
|
+
exports.FORWARD_ITEMS_COLLECTION = "items";
|
|
20
|
+
/** Tetos do lote (plan.md D6). Constantes de plataforma, sem config por tenant. */
|
|
21
|
+
exports.FORWARD_MAX_MESSAGES = 20;
|
|
22
|
+
exports.FORWARD_MAX_RECIPIENTS = 10;
|
|
23
|
+
exports.FORWARD_INTRO_MAX_CHARS = 1000;
|
|
24
|
+
/** Lote `running` sem atualização há mais que isto é mostrado como interrompido. */
|
|
25
|
+
exports.FORWARD_STALE_AFTER_MS = 10 * 60 * 1000;
|
|
26
|
+
/** Evento de timeline gravado na thread de ORIGEM ao criar o lote. */
|
|
27
|
+
exports.FORWARD_TIMELINE_EVENT_TYPE = "messages_forwarded";
|
|
28
|
+
const MEDIA_KINDS = new Set(["image", "video", "audio", "document"]);
|
|
29
|
+
/** Canais que não entram na F1 nem como origem (e-mail, SMS). */
|
|
30
|
+
const UNSUPPORTED_SOURCE_CHANNELS = new Set(["email", "sms"]);
|
|
31
|
+
/**
|
|
32
|
+
* Decide se uma mensagem pode ser encaminhada e como vira conteúdo no destino.
|
|
33
|
+
*
|
|
34
|
+
* `dest = null` é a checagem agnóstica do modo de seleção no front (sem canal
|
|
35
|
+
* escolhido). A regra que depende do destino é só uma: documento não vai para
|
|
36
|
+
* Instagram. O teto de tamanho NÃO fica aqui: depende do objeto no Storage.
|
|
37
|
+
*/
|
|
38
|
+
function resolveForwardContent(m, dest) {
|
|
39
|
+
const direction = String(m.direction ?? "").toLowerCase();
|
|
40
|
+
if (direction !== "in" && direction !== "out")
|
|
41
|
+
return { ok: false, reason: "type_not_supported" };
|
|
42
|
+
if (m.revoked === true)
|
|
43
|
+
return { ok: false, reason: "type_not_supported" };
|
|
44
|
+
if (m.deleted_at !== undefined && m.deleted_at !== null)
|
|
45
|
+
return { ok: false, reason: "type_not_supported" };
|
|
46
|
+
const channelType = String(m.channel_type ?? "").toLowerCase();
|
|
47
|
+
if (UNSUPPORTED_SOURCE_CHANNELS.has(channelType))
|
|
48
|
+
return { ok: false, reason: "type_not_supported" };
|
|
49
|
+
const type = String(m.type ?? "").toLowerCase();
|
|
50
|
+
if (type === "text") {
|
|
51
|
+
const text = (m.body?.text ?? "").trim();
|
|
52
|
+
if (!text)
|
|
53
|
+
return { ok: false, reason: "type_not_supported" };
|
|
54
|
+
return { ok: true, kind: "text", text };
|
|
55
|
+
}
|
|
56
|
+
if (type === "template") {
|
|
57
|
+
const text = (m.template?.rendered?.text ?? "").trim();
|
|
58
|
+
if (!text)
|
|
59
|
+
return { ok: false, reason: "type_not_supported" };
|
|
60
|
+
return { ok: true, kind: "text", text };
|
|
61
|
+
}
|
|
62
|
+
if (MEDIA_KINDS.has(type)) {
|
|
63
|
+
const uri = (m.media?.gcs_uri ?? "").trim();
|
|
64
|
+
if (!uri)
|
|
65
|
+
return { ok: false, reason: "media_unavailable" };
|
|
66
|
+
if (type === "document" && dest === "instagram")
|
|
67
|
+
return { ok: false, reason: "type_not_supported" };
|
|
68
|
+
return { ok: true, kind: type };
|
|
69
|
+
}
|
|
70
|
+
return { ok: false, reason: "type_not_supported" };
|
|
71
|
+
}
|
|
72
|
+
/** Id determinístico do item: um destinatário aparece uma vez no lote, então é único. */
|
|
73
|
+
function buildForwardItemId(recipientThreadId, messageId) {
|
|
74
|
+
return `${recipientThreadId}__${messageId ?? "intro"}`;
|
|
75
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { IFireDoc } from "../../shared";
|
|
2
|
+
import type { IThreadMessage } from "../thread-message";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* feat-282 — encaminhamento de mensagens no Hub Omni.
|
|
6
|
+
*
|
|
7
|
+
* A Cloud API da Meta não tem forward: o encaminhamento é nosso. Cada ação do
|
|
8
|
+
* operador vira um LOTE (`IForwardBatch`) em
|
|
9
|
+
* `tenants/{t}/apps/evo-chat/forwards/{forwardId}`, com um item por
|
|
10
|
+
* (destinatário × mensagem) na subcoleção `items/` e, por destinatário, um item
|
|
11
|
+
* `intro` opcional (texto de apresentação). Cada entrega é uma mensagem NORMAL de
|
|
12
|
+
* saída na thread de destino, com o bloco `forward` (`IMessageForward`) apontando
|
|
13
|
+
* para a origem. A mensagem de origem só ganha `forward_count`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ── Constants ──
|
|
17
|
+
|
|
18
|
+
export const FORWARDS_COLLECTION = "forwards" as const;
|
|
19
|
+
export const FORWARD_ITEMS_COLLECTION = "items" as const;
|
|
20
|
+
|
|
21
|
+
/** Tetos do lote (plan.md D6). Constantes de plataforma, sem config por tenant. */
|
|
22
|
+
export const FORWARD_MAX_MESSAGES = 20;
|
|
23
|
+
export const FORWARD_MAX_RECIPIENTS = 10;
|
|
24
|
+
export const FORWARD_INTRO_MAX_CHARS = 1000;
|
|
25
|
+
|
|
26
|
+
/** Lote `running` sem atualização há mais que isto é mostrado como interrompido. */
|
|
27
|
+
export const FORWARD_STALE_AFTER_MS = 10 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
/** Evento de timeline gravado na thread de ORIGEM ao criar o lote. */
|
|
30
|
+
export const FORWARD_TIMELINE_EVENT_TYPE = "messages_forwarded" as const;
|
|
31
|
+
|
|
32
|
+
// ── Enums ──
|
|
33
|
+
|
|
34
|
+
export type ForwardBatchStatus = "running" | "done" | "done_with_failures";
|
|
35
|
+
|
|
36
|
+
export type ForwardItemStatus = "pending" | "sending" | "sent" | "failed" | "skipped";
|
|
37
|
+
|
|
38
|
+
/** Motivos decididos na pré-validação (item nasce `skipped`, nenhuma chamada ao provedor). */
|
|
39
|
+
export type ForwardSkipReason =
|
|
40
|
+
| "window_closed"
|
|
41
|
+
| "type_not_supported"
|
|
42
|
+
| "media_too_large"
|
|
43
|
+
| "media_unavailable"
|
|
44
|
+
| "channel_not_supported";
|
|
45
|
+
|
|
46
|
+
/** Motivos de falha no envio (item passou por `sending`). */
|
|
47
|
+
export type ForwardFailReason =
|
|
48
|
+
| "provider_error"
|
|
49
|
+
| "window_closed"
|
|
50
|
+
| "send_budget_exceeded"
|
|
51
|
+
| "interrupted";
|
|
52
|
+
|
|
53
|
+
export type ForwardItemReason = ForwardSkipReason | ForwardFailReason;
|
|
54
|
+
|
|
55
|
+
export type ForwardContentKind = "text" | "image" | "video" | "audio" | "document";
|
|
56
|
+
|
|
57
|
+
/** Canais aceitos como DESTINO na F1. E-mail e SMS ficam para a F2. */
|
|
58
|
+
export type ForwardDestinationKind = "whatsapp" | "instagram";
|
|
59
|
+
|
|
60
|
+
export type ForwardRecipientProvider = "hub-waba" | "hub-instagram";
|
|
61
|
+
|
|
62
|
+
export type ForwardSpecialKind = "doctor" | "secretary" | "employee";
|
|
63
|
+
|
|
64
|
+
// ── Batch ──
|
|
65
|
+
|
|
66
|
+
export interface IForwardActor {
|
|
67
|
+
user_id: string;
|
|
68
|
+
display_name: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface IForwardSource {
|
|
72
|
+
thread_id: string;
|
|
73
|
+
ticket_id: string;
|
|
74
|
+
/** Departamento do ticket de origem: é onde o ticket novo do destino abre (Q4-A). */
|
|
75
|
+
department_id: string | null;
|
|
76
|
+
contact_id: string;
|
|
77
|
+
contact_name: string;
|
|
78
|
+
channel_id: string | null;
|
|
79
|
+
channel_type: string | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface IForwardRecipient {
|
|
83
|
+
thread_id: string;
|
|
84
|
+
contact_id: string;
|
|
85
|
+
contact_name: string;
|
|
86
|
+
channel_id: string;
|
|
87
|
+
channel_type: string;
|
|
88
|
+
provider: ForwardRecipientProvider;
|
|
89
|
+
special_kind?: ForwardSpecialKind | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface IForwardDispatchState {
|
|
93
|
+
claimed_at: Date;
|
|
94
|
+
finished_at?: Date | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface IForwardCounts {
|
|
98
|
+
total: number;
|
|
99
|
+
sent: number;
|
|
100
|
+
failed: number;
|
|
101
|
+
skipped: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface IForwardBatch extends IFireDoc {
|
|
105
|
+
forward_id: string;
|
|
106
|
+
tenant: string;
|
|
107
|
+
created_by: IForwardActor;
|
|
108
|
+
source: IForwardSource;
|
|
109
|
+
/** Ordem cronológica da origem (o backend ordena por `created_at`, não pelo payload). */
|
|
110
|
+
message_ids: string[];
|
|
111
|
+
intro_text: string | null;
|
|
112
|
+
recipients: IForwardRecipient[];
|
|
113
|
+
status: ForwardBatchStatus;
|
|
114
|
+
counts: IForwardCounts;
|
|
115
|
+
/** Destinatários com pelo menos um item `pending` (os que recebem mensagem PubSub). */
|
|
116
|
+
recipients_to_dispatch: number;
|
|
117
|
+
recipients_done: number;
|
|
118
|
+
/** Chave = `thread_id` do destinatário. Claim do worker contra entrega duplicada. */
|
|
119
|
+
dispatch?: Record<string, IForwardDispatchState>;
|
|
120
|
+
finished_at?: Date | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Item ──
|
|
124
|
+
|
|
125
|
+
export interface IForwardItem {
|
|
126
|
+
/** `${recipient_thread_id}__${message_id}` ou `${recipient_thread_id}__intro`. */
|
|
127
|
+
item_id: string;
|
|
128
|
+
forward_id: string;
|
|
129
|
+
recipient_thread_id: string;
|
|
130
|
+
recipient_index: number;
|
|
131
|
+
/** 0 = intro; 1..N = mensagens na ordem da origem. */
|
|
132
|
+
seq: number;
|
|
133
|
+
kind: "intro" | "message";
|
|
134
|
+
message_id: string | null;
|
|
135
|
+
content_kind: ForwardContentKind;
|
|
136
|
+
status: ForwardItemStatus;
|
|
137
|
+
reason?: ForwardItemReason | null;
|
|
138
|
+
error?: string | null;
|
|
139
|
+
error_code?: number | null;
|
|
140
|
+
dest_message_id?: string | null;
|
|
141
|
+
sent_at?: Date | null;
|
|
142
|
+
updated_at?: Date;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Plan (resposta do `dry_run` e do POST) ──
|
|
146
|
+
|
|
147
|
+
export type ForwardWindowState = "open" | "human_agent" | "closed";
|
|
148
|
+
|
|
149
|
+
export interface IForwardPlanItem {
|
|
150
|
+
item_id: string;
|
|
151
|
+
kind: "intro" | "message";
|
|
152
|
+
message_id: string | null;
|
|
153
|
+
status: "pending" | "skipped";
|
|
154
|
+
reason?: ForwardSkipReason;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface IForwardPlanRecipient {
|
|
158
|
+
thread_id: string;
|
|
159
|
+
channel_id: string;
|
|
160
|
+
channel_type: string;
|
|
161
|
+
contact_name: string;
|
|
162
|
+
window: ForwardWindowState;
|
|
163
|
+
needs_ticket: boolean;
|
|
164
|
+
items: IForwardPlanItem[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface IForwardPlan {
|
|
168
|
+
recipients: IForwardPlanRecipient[];
|
|
169
|
+
counts: { total: number; pending: number; skipped: number };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Bloco gravado na mensagem de DESTINO ──
|
|
173
|
+
|
|
174
|
+
export interface IMessageForward {
|
|
175
|
+
forward_id: string;
|
|
176
|
+
/** Ponte para o item do lote (o status-processor reflete falha assíncrona por aqui). */
|
|
177
|
+
item_id: string;
|
|
178
|
+
/** Vazio no item `intro`: o front não mostra selo quando está vazio. */
|
|
179
|
+
source_message_id: string;
|
|
180
|
+
source_thread_id: string;
|
|
181
|
+
source_ticket_id: string;
|
|
182
|
+
source_contact_id: string;
|
|
183
|
+
source_contact_name: string;
|
|
184
|
+
source_channel_type: string | null;
|
|
185
|
+
source_direction: "in" | "out";
|
|
186
|
+
source_created_at: Date;
|
|
187
|
+
forwarded_by: IForwardActor;
|
|
188
|
+
forwarded_at: Date;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Matriz tipo × canal (pura; usada pelo backend na pré-validação e pelo front na seleção) ──
|
|
192
|
+
|
|
193
|
+
export type ForwardableMessage = Pick<
|
|
194
|
+
IThreadMessage,
|
|
195
|
+
"type" | "direction" | "channel_type" | "body" | "media" | "template"
|
|
196
|
+
> & {
|
|
197
|
+
revoked?: unknown;
|
|
198
|
+
deleted_at?: unknown;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export type ForwardContentResolution =
|
|
202
|
+
| { ok: true; kind: ForwardContentKind; text?: string }
|
|
203
|
+
| { ok: false; reason: ForwardSkipReason };
|
|
204
|
+
|
|
205
|
+
const MEDIA_KINDS: ReadonlySet<string> = new Set(["image", "video", "audio", "document"]);
|
|
206
|
+
|
|
207
|
+
/** Canais que não entram na F1 nem como origem (e-mail, SMS). */
|
|
208
|
+
const UNSUPPORTED_SOURCE_CHANNELS: ReadonlySet<string> = new Set(["email", "sms"]);
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Decide se uma mensagem pode ser encaminhada e como vira conteúdo no destino.
|
|
212
|
+
*
|
|
213
|
+
* `dest = null` é a checagem agnóstica do modo de seleção no front (sem canal
|
|
214
|
+
* escolhido). A regra que depende do destino é só uma: documento não vai para
|
|
215
|
+
* Instagram. O teto de tamanho NÃO fica aqui: depende do objeto no Storage.
|
|
216
|
+
*/
|
|
217
|
+
export function resolveForwardContent(
|
|
218
|
+
m: ForwardableMessage,
|
|
219
|
+
dest: ForwardDestinationKind | null,
|
|
220
|
+
): ForwardContentResolution {
|
|
221
|
+
const direction = String(m.direction ?? "").toLowerCase();
|
|
222
|
+
if (direction !== "in" && direction !== "out") return { ok: false, reason: "type_not_supported" };
|
|
223
|
+
if (m.revoked === true) return { ok: false, reason: "type_not_supported" };
|
|
224
|
+
if (m.deleted_at !== undefined && m.deleted_at !== null) return { ok: false, reason: "type_not_supported" };
|
|
225
|
+
|
|
226
|
+
const channelType = String(m.channel_type ?? "").toLowerCase();
|
|
227
|
+
if (UNSUPPORTED_SOURCE_CHANNELS.has(channelType)) return { ok: false, reason: "type_not_supported" };
|
|
228
|
+
|
|
229
|
+
const type = String(m.type ?? "").toLowerCase();
|
|
230
|
+
|
|
231
|
+
if (type === "text") {
|
|
232
|
+
const text = (m.body?.text ?? "").trim();
|
|
233
|
+
if (!text) return { ok: false, reason: "type_not_supported" };
|
|
234
|
+
return { ok: true, kind: "text", text };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (type === "template") {
|
|
238
|
+
const text = (m.template?.rendered?.text ?? "").trim();
|
|
239
|
+
if (!text) return { ok: false, reason: "type_not_supported" };
|
|
240
|
+
return { ok: true, kind: "text", text };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (MEDIA_KINDS.has(type)) {
|
|
244
|
+
const uri = (m.media?.gcs_uri ?? "").trim();
|
|
245
|
+
if (!uri) return { ok: false, reason: "media_unavailable" };
|
|
246
|
+
if (type === "document" && dest === "instagram") return { ok: false, reason: "type_not_supported" };
|
|
247
|
+
return { ok: true, kind: type as ForwardContentKind };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { ok: false, reason: "type_not_supported" };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Id determinístico do item: um destinatário aparece uma vez no lote, então é único. */
|
|
254
|
+
export function buildForwardItemId(recipientThreadId: string, messageId: string | null): string {
|
|
255
|
+
return `${recipientThreadId}__${messageId ?? "intro"}`;
|
|
256
|
+
}
|
|
@@ -14,11 +14,14 @@ export * from "./waba-history-import";
|
|
|
14
14
|
export * from "./ig-setup";
|
|
15
15
|
export * from "./quick-reply";
|
|
16
16
|
export * from "./search";
|
|
17
|
+
export * from "./forward";
|
|
17
18
|
export declare const EvoChatPermissions: {
|
|
18
19
|
readonly Read: "evo_chat_read";
|
|
19
20
|
readonly Write: "evo_chat_write";
|
|
20
21
|
readonly InboxManage: "evo_chat_inbox_manage";
|
|
21
22
|
readonly TicketClose: "evo_chat_ticket_close";
|
|
22
23
|
readonly TicketTransfer: "evo_chat_ticket_transfer";
|
|
24
|
+
/** feat-254 B2: baixar anexo de e-mail bloqueado pela política (quarentena). Sem papel padrão. */
|
|
25
|
+
readonly QuarantineRead: "evo_chat_quarantine_read";
|
|
23
26
|
};
|
|
24
27
|
export type EvoChatPermissions = (typeof EvoChatPermissions)[keyof typeof EvoChatPermissions];
|
|
@@ -31,6 +31,7 @@ __exportStar(require("./waba-history-import"), exports);
|
|
|
31
31
|
__exportStar(require("./ig-setup"), exports);
|
|
32
32
|
__exportStar(require("./quick-reply"), exports);
|
|
33
33
|
__exportStar(require("./search"), exports);
|
|
34
|
+
__exportStar(require("./forward"), exports);
|
|
34
35
|
// Permissões para o evo-chat
|
|
35
36
|
exports.EvoChatPermissions = {
|
|
36
37
|
Read: "evo_chat_read",
|
|
@@ -38,4 +39,6 @@ exports.EvoChatPermissions = {
|
|
|
38
39
|
InboxManage: "evo_chat_inbox_manage",
|
|
39
40
|
TicketClose: "evo_chat_ticket_close",
|
|
40
41
|
TicketTransfer: "evo_chat_ticket_transfer",
|
|
42
|
+
/** feat-254 B2: baixar anexo de e-mail bloqueado pela política (quarentena). Sem papel padrão. */
|
|
43
|
+
QuarantineRead: "evo_chat_quarantine_read",
|
|
41
44
|
};
|