From 22e47423849851aebfd38a8f95d42254b99bdee4 Mon Sep 17 00:00:00 2001 From: Rafael Lopes Date: Fri, 22 May 2026 10:51:44 -0300 Subject: [PATCH] FEAT: Aprimora Templates Whatsapp --- src/modules/whatsapp/whatsapp.controller.ts | 23 +++- src/modules/whatsapp/whatsapp.service.ts | 129 ++++++++++++++++++-- 2 files changed, 141 insertions(+), 11 deletions(-) diff --git a/src/modules/whatsapp/whatsapp.controller.ts b/src/modules/whatsapp/whatsapp.controller.ts index 988e2a8..3621317 100644 --- a/src/modules/whatsapp/whatsapp.controller.ts +++ b/src/modules/whatsapp/whatsapp.controller.ts @@ -65,12 +65,27 @@ export class WhatsappController { } @Post('templates') - async saveTemplate(@Body() body: { name: string; content: string }) { - return this.whatsappService.saveTemplate(body.name, body.content); + async saveTemplate(@Body() body: { name: string; content: string; areaId?: number | null; requestedByRole?: string }) { + return this.whatsappService.saveTemplate(body.name, body.content, body.areaId, body.requestedByRole); } @Post('templates/update/:id') - async updateTemplate(@Param('id') id: string, @Body() body: { name: string; content: string }) { - return this.whatsappService.updateTemplate(Number(id), body.name, body.content); + async updateTemplate(@Param('id') id: string, @Body() body: { name: string; content: string; areaId?: number | null }) { + return this.whatsappService.updateTemplate(Number(id), body.name, body.content, body.areaId); + } + + @Post('templates/approve-admin/:id') + async approveTemplateByAdmin(@Param('id') id: string) { + return this.whatsappService.approveTemplateByAdmin(Number(id)); + } + + @Post('templates/reject-admin/:id') + async rejectTemplateByAdmin(@Param('id') id: string) { + return this.whatsappService.rejectTemplateByAdmin(Number(id)); + } + + @Delete('templates/:id') + async deleteTemplate(@Param('id') id: string) { + return this.whatsappService.deleteTemplate(Number(id)); } } diff --git a/src/modules/whatsapp/whatsapp.service.ts b/src/modules/whatsapp/whatsapp.service.ts index 26f5aae..8ace9e8 100644 --- a/src/modules/whatsapp/whatsapp.service.ts +++ b/src/modules/whatsapp/whatsapp.service.ts @@ -30,10 +30,25 @@ export class WhatsappService implements OnModuleInit { id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, content TEXT NOT NULL, + area_id INTEGER REFERENCES areas (id) ON DELETE SET NULL, + status VARCHAR(40) NOT NULL DEFAULT 'approved', + requested_by_role VARCHAR(40), + admin_approved_at TIMESTAMP WITH TIME ZONE, + meta_submitted_at TIMESTAMP WITH TIME ZONE, + meta_approved_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); `); + await this.db.query(` + ALTER TABLE whatsapp_templates + ADD COLUMN IF NOT EXISTS area_id INTEGER REFERENCES areas (id) ON DELETE SET NULL, + ADD COLUMN IF NOT EXISTS status VARCHAR(40) NOT NULL DEFAULT 'approved', + ADD COLUMN IF NOT EXISTS requested_by_role VARCHAR(40), + ADD COLUMN IF NOT EXISTS admin_approved_at TIMESTAMP WITH TIME ZONE, + ADD COLUMN IF NOT EXISTS meta_submitted_at TIMESTAMP WITH TIME ZONE, + ADD COLUMN IF NOT EXISTS meta_approved_at TIMESTAMP WITH TIME ZONE; + `); await this.db.query(` INSERT INTO whatsapp_templates (name, content) VALUES ('aviso_fatura', 'Olá, {nome}. Estamos entrando em contato para lembrá-lo que a sua fatura está programada para {data}.'), @@ -576,28 +591,128 @@ export class WhatsappService implements OnModuleInit { } async getTemplates() { - const res = await this.db.query('SELECT * FROM whatsapp_templates ORDER BY id ASC'); + await this.refreshFakeMetaApprovals(); + const res = await this.db.query(` + SELECT + wt.*, + a.nome AS area_nome + FROM whatsapp_templates wt + LEFT JOIN areas a ON a.id = wt.area_id + ORDER BY wt.id ASC + `); return res.rows; } private async getTemplateById(id: number) { + await this.refreshFakeMetaApprovals(); const res = await this.db.query('SELECT * FROM whatsapp_templates WHERE id = $1 LIMIT 1', [id]); return res.rows[0] || null; } - async saveTemplate(name: string, content: string) { + async saveTemplate(name: string, content: string, areaId?: number | null, requestedByRole = 'admin') { + const isSupervisor = requestedByRole === 'supervisor'; + const status = isSupervisor ? 'admin_review' : 'meta_review'; + const adminApprovedAt = isSupervisor ? null : 'CURRENT_TIMESTAMP'; + const metaSubmittedAt = isSupervisor ? null : 'CURRENT_TIMESTAMP'; const res = await this.db.query( - 'INSERT INTO whatsapp_templates (name, content) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET content = EXCLUDED.content, updated_at = CURRENT_TIMESTAMP RETURNING *', - [name, content] + ` + INSERT INTO whatsapp_templates ( + name, + content, + area_id, + status, + requested_by_role, + admin_approved_at, + meta_submitted_at, + meta_approved_at, + updated_at + ) + VALUES ($1, $2, $3, $4, $5, ${adminApprovedAt}, ${metaSubmittedAt}, NULL, CURRENT_TIMESTAMP) + ON CONFLICT (name) DO UPDATE SET + content = EXCLUDED.content, + area_id = EXCLUDED.area_id, + status = EXCLUDED.status, + requested_by_role = EXCLUDED.requested_by_role, + admin_approved_at = EXCLUDED.admin_approved_at, + meta_submitted_at = EXCLUDED.meta_submitted_at, + meta_approved_at = NULL, + updated_at = CURRENT_TIMESTAMP + RETURNING * + `, + [name, content, areaId || null, status, requestedByRole] ); return res.rows[0]; } - async updateTemplate(id: number, name: string, content: string) { + async updateTemplate(id: number, name: string, content: string, areaId?: number | null) { const res = await this.db.query( - 'UPDATE whatsapp_templates SET name = $1, content = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $3 RETURNING *', - [name, content, id] + ` + UPDATE whatsapp_templates + SET + name = $1, + content = $2, + area_id = $3, + status = 'meta_review', + admin_approved_at = CURRENT_TIMESTAMP, + meta_submitted_at = CURRENT_TIMESTAMP, + meta_approved_at = NULL, + updated_at = CURRENT_TIMESTAMP + WHERE id = $4 + RETURNING * + `, + [name, content, areaId || null, id] ); return res.rows[0]; } + + async approveTemplateByAdmin(id: number) { + const res = await this.db.query( + ` + UPDATE whatsapp_templates + SET + status = 'meta_review', + admin_approved_at = CURRENT_TIMESTAMP, + meta_submitted_at = CURRENT_TIMESTAMP, + meta_approved_at = NULL, + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + RETURNING * + `, + [id], + ); + return res.rows[0]; + } + + async rejectTemplateByAdmin(id: number) { + const res = await this.db.query( + ` + UPDATE whatsapp_templates + SET + status = 'rejected', + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + RETURNING * + `, + [id], + ); + return res.rows[0]; + } + + async deleteTemplate(id: number) { + await this.db.query('DELETE FROM whatsapp_templates WHERE id = $1', [id]); + return { success: true }; + } + + private async refreshFakeMetaApprovals() { + await this.db.query(` + UPDATE whatsapp_templates + SET + status = 'approved', + meta_approved_at = COALESCE(meta_approved_at, meta_submitted_at + INTERVAL '15 minutes'), + updated_at = CURRENT_TIMESTAMP + WHERE status = 'meta_review' + AND meta_submitted_at IS NOT NULL + AND meta_submitted_at <= CURRENT_TIMESTAMP - INTERVAL '15 minutes' + `); + } }