evo360-types 1.3.713 → 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-campaigns/index.d.ts +41 -0
- package/dist/types/evo-campaigns/index.ts +43 -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
|
@@ -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
|
};
|
|
@@ -14,6 +14,7 @@ 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
|
|
|
18
19
|
// Permissões para o evo-chat
|
|
19
20
|
export const EvoChatPermissions = {
|
|
@@ -22,6 +23,8 @@ export const EvoChatPermissions = {
|
|
|
22
23
|
InboxManage: "evo_chat_inbox_manage",
|
|
23
24
|
TicketClose: "evo_chat_ticket_close",
|
|
24
25
|
TicketTransfer: "evo_chat_ticket_transfer",
|
|
26
|
+
/** feat-254 B2: baixar anexo de e-mail bloqueado pela política (quarentena). Sem papel padrão. */
|
|
27
|
+
QuarantineRead: "evo_chat_quarantine_read",
|
|
25
28
|
} as const;
|
|
26
29
|
|
|
27
30
|
export type EvoChatPermissions =
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IFireDoc } from "../../shared";
|
|
2
2
|
import type { IMessageCall } from "../call-session";
|
|
3
|
+
import type { IMessageForward } from "../forward";
|
|
3
4
|
export type EvoChatMessageType = 'text' | 'image' | 'audio' | 'video' | 'document' | 'sticker' | 'location' | 'contacts' | 'interactive' | 'reaction' | 'template' | 'system' | 'call';
|
|
4
5
|
export type MessageDirection = 'in' | 'out';
|
|
5
6
|
export interface IMessageStatus {
|
|
@@ -159,6 +160,9 @@ export interface IThreadMessage extends IFireDoc {
|
|
|
159
160
|
rendered?: IRenderedTemplateSnapshot;
|
|
160
161
|
};
|
|
161
162
|
context?: IMessageContext;
|
|
163
|
+
forward?: IMessageForward;
|
|
164
|
+
/** feat-282: na mensagem de ORIGEM, quantas entregas de encaminhamento já saíram dela. */
|
|
165
|
+
forward_count?: number;
|
|
162
166
|
call?: IMessageCall;
|
|
163
167
|
provider_message_id?: string;
|
|
164
168
|
status?: IMessageStatus;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IFireDoc } from "../../shared";
|
|
2
2
|
import type { IMessageCall } from "../call-session";
|
|
3
|
+
import type { IMessageForward } from "../forward";
|
|
3
4
|
|
|
4
5
|
// ── Message type/direction enums ──
|
|
5
6
|
|
|
@@ -197,6 +198,11 @@ export interface IThreadMessage extends IFireDoc {
|
|
|
197
198
|
};
|
|
198
199
|
context?: IMessageContext;
|
|
199
200
|
|
|
201
|
+
// feat-282: presente na mensagem de DESTINO de um encaminhamento (ver ../forward).
|
|
202
|
+
forward?: IMessageForward;
|
|
203
|
+
/** feat-282: na mensagem de ORIGEM, quantas entregas de encaminhamento já saíram dela. */
|
|
204
|
+
forward_count?: number;
|
|
205
|
+
|
|
200
206
|
// Call timeline projection (feat-029). Fonte de verdade em ICallSession.
|
|
201
207
|
call?: IMessageCall;
|
|
202
208
|
|