From 968ae6a314e1844794ac6fb96b8e3ceca29a61af Mon Sep 17 00:00:00 2001 From: Rodrigo Borba Date: Fri, 23 Jan 2026 00:30:11 -0300 Subject: [PATCH] =?UTF-8?q?ajuste=20no=20tabela=20de=20pre=C3=A7os?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 4 +- .../accounts/captain/pricings_controller.rb | 19 +++++ .../i18n/locale/en/integrations.json | 50 ++++++++++++ app/models/captain/pricing.rb | 7 ++ .../captain/tools/check_availability_tool.rb | 13 +++- lib/integrations/captain/processor_service.rb | 7 ++ progresso/resolucao_erro_tarifas_captain.md | 77 +++++++++++++++++++ 7 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 app/controllers/api/v1/accounts/captain/pricings_controller.rb create mode 100644 app/models/captain/pricing.rb create mode 100644 progresso/resolucao_erro_tarifas_captain.md diff --git a/Gemfile.lock b/Gemfile.lock index a45d34b..73f461f 100755 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1180,7 +1180,7 @@ DEPENDENCIES working_hours RUBY VERSION - ruby 3.4.4p34 + ruby 3.4.4p34 BUNDLED WITH - 2.5.11 + 2.5.11 diff --git a/app/controllers/api/v1/accounts/captain/pricings_controller.rb b/app/controllers/api/v1/accounts/captain/pricings_controller.rb new file mode 100644 index 0000000..649a106 --- /dev/null +++ b/app/controllers/api/v1/accounts/captain/pricings_controller.rb @@ -0,0 +1,19 @@ +class Api::V1::Accounts::Captain::PricingsController < Api::V1::Accounts::BaseController + before_action :fetch_pricings, only: [:index] + + def index + render json: @pricings + end + + private + + def fetch_pricings + @pricings = current_account.captain_pricings + + return unless params[:query].present? + + # Fuzzy search using ILIKE for case-insensitive matching + # We wrap the query in % for wildcard matching on both sides + @pricings = @pricings.where('suite_category ILIKE ?', "%#{params[:query]}%") + end +end diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index 35e9d7a..95bacf8 100755 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -1319,6 +1319,56 @@ "TITLE": "No Connected Inboxes", "SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you." } + }, + "PRICINGS": { + "HEADER": "Administrative Panel", + "TITLE": "Pricing Table", + "DESCRIPTION": "Configure pricing rules by inbox, brand, day, and category.", + "ADD_BUTTON": "New Rule", + "EMPTY_STATE": "No rules found with current filters.", + "FIELDS": { + "INBOX": "Inbox", + "BRAND": "Brand", + "DAY": "Day", + "DAYS": "Days", + "CATEGORY": "Category", + "DURATION": "Duration", + "PRICE": "Price", + "PRICE_DISPLAY": "$ {price}", + "ACTIONS": "Actions", + "MIN_PRICE": "Min price", + "MAX_PRICE": "Max price" + }, + "FILTERS": { + "TITLE": "Filters", + "ALL": "All", + "ALL_DAYS": "All", + "CLEAR": "Clear filters" + }, + "DELETE_CONFIRMATION": "Are you sure you want to delete this rule?", + "DELETE_SUCCESS": "Rule removed", + "DELETE_ERROR": "Error removing rule", + "FETCH_ERROR": "Error fetching data", + "MODAL": { + "ADD_TITLE": "New Pricing Rule", + "EDIT_TITLE": "Edit Rule", + "SELECT_DAYS_REQUIRED": "Select at least one day.", + "SELECT_BRAND_FIRST": "Select a brand first.", + "NO_CATEGORIES": "No categories registered for this brand.", + "NO_DURATIONS": "No durations registered for this brand.", + "SAVE_SUCCESS": "Price saved!", + "SAVE_ERROR": "Error saving price", + "CANCEL": "Cancel", + "SAVE": "Save", + "SELECT_CATEGORY": "Select a category", + "SELECT_DURATION": "Select a duration", + "PRICE_PLACEHOLDER": "0.00", + "REMOVE_INBOX": "Remove", + "CLOSE": "×", + "FIELDS": { + "DAYS_WEEK": "Days of the Week" + } + } } } } diff --git a/app/models/captain/pricing.rb b/app/models/captain/pricing.rb new file mode 100644 index 0000000..3113db7 --- /dev/null +++ b/app/models/captain/pricing.rb @@ -0,0 +1,7 @@ +module Captain + class Pricing < ApplicationRecord + belongs_to :account + belongs_to :captain_brand, optional: true + belongs_to :inbox, optional: true + end +end diff --git a/enterprise/app/services/captain/tools/check_availability_tool.rb b/enterprise/app/services/captain/tools/check_availability_tool.rb index cbab09d..87d5ae5 100644 --- a/enterprise/app/services/captain/tools/check_availability_tool.rb +++ b/enterprise/app/services/captain/tools/check_availability_tool.rb @@ -38,6 +38,9 @@ module Captain actual_params = resolve_params(args, params) File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f| f.puts "[#{Time.now}] STARTING CheckAvailabilityTool with params: #{actual_params}" + f.puts "[#{Time.now}] PRICING COUNT: #{Captain::Pricing.count}" + f.puts "[#{Time.now}] FIRST PRICING: #{Captain::Pricing.first.inspect}" + f.puts "[#{Time.now}] ALL PRICINGS: #{Captain::Pricing.all.inspect}" end suite_category = actual_params[:suite] @@ -56,8 +59,9 @@ module Captain File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] RESOLVED DATE: #{target_date} | SUITE: #{suite_category}" } # Find pricing strategy - pricing_scope = Captain::Pricing.where(account_id: @conversation.account_id) - .where('LOWER(suite_category) = ?', suite_category.downcase) + account_id = @conversation&.account_id || @assistant&.account_id + pricing_scope = Captain::Pricing.where(account_id: account_id) + .where('suite_category ILIKE ?', "%#{suite_category}%") pricing_scope = filter_pricings_by_day_range(pricing_scope, target_date) if target_date @@ -101,6 +105,11 @@ module Captain File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] FAILURE: #{msg}" } return msg end + rescue StandardError => e + File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f| + f.puts "[#{Time.now}] CRITICAL ERROR in CheckAvailabilityTool: #{e.message}\n#{e.backtrace.first(5).join("\n")}" + end + raise e end private diff --git a/lib/integrations/captain/processor_service.rb b/lib/integrations/captain/processor_service.rb index 1020a01..384cfbd 100755 --- a/lib/integrations/captain/processor_service.rb +++ b/lib/integrations/captain/processor_service.rb @@ -7,6 +7,13 @@ class Integrations::Captain::ProcessorService < Integrations::BotProcessorServic call_captain(message_content) end + # Prevent bot from replying to itself or other agents + def should_run_processor?(message) + return false if message.outgoing? || message.sender_type != 'Contact' + + super + end + def process_response(message, response) if response == 'conversation_handoff' message.conversation.bot_handoff! diff --git a/progresso/resolucao_erro_tarifas_captain.md b/progresso/resolucao_erro_tarifas_captain.md new file mode 100644 index 0000000..42e6a80 --- /dev/null +++ b/progresso/resolucao_erro_tarifas_captain.md @@ -0,0 +1,77 @@ +# Resolução de Erro: Captain Pricing (CheckAvailabilityTool) - 22/01/2026 + +## 1. O Problema + +O usuário recebia a mensagem "Estou com um erro no meu sistema" ou "Não encontrei tarifas" ao buscar preços (ex: "preço hidro") no Playground ou via WhatsApp. + +## 2. Diagnóstico Técnica + +Ao analisar os logs (`log/tool_debug.log`), identificamos um **CRITICAL ERROR**: + +``` +undefined method 'account_id' for nil:NilClass +``` + +Isso ocorria na linha: + +```ruby +pricing_scope = Captain::Pricing.where(account_id: @conversation.account_id) +``` + +**Causa Raiz:** +A ferramenta `CheckAvailabilityTool` assumia que sempre existiria um objeto `@conversation`. + +- No **WhatsApp** (produção), geralmente existe uma conversa persistida. +- No **Playground** ou testes diretos, a variável `@conversation` pode chegar `nil` (nula), pois o contexto é volátil. +- Ao tentar acessar `@conversation.account_id`, o sistema quebrava (Crash), retornando a mensagem genérica de erro. + +## 3. A Solução Implementada + +Alteramos o código para ser resiliente. Se não houver conversa, usamos o ID da conta do próprio Assistente (`@assistant`), que sempre existe. + +**Arquivo:** `enterprise/app/services/captain/tools/check_availability_tool.rb` + +```ruby +# ANTES (Quebrava se @conversation fosse nil) +account_id = @conversation.account_id + +# DEPOIS (Correção Robusta) +account_id = @conversation&.account_id || @assistant&.account_id +``` + +## 4. Requisitos de Configuração (Dados) + +Para que a tarifa seja encontrada, ela precisa seguir estas regras no Banco de Dados (`Captain::Pricing`): + +1. **Account ID:** Deve ser idêntico ao da conta do bot (ex: ID 1). +2. **Inbox ID (CRÍTICO):** + - Se `inbox_id` for `nil` (vazio): A tarifa é **GLOBAL** e funciona para qualquer canal (WhatsApp, Web, API, Playground). **Recomendado para testes.** + - Se `inbox_id` estiver preenchido: A tarifa só será encontrada se a conversa vier _exatamente_ daquele inbox. +3. **Nome da Suíte (Fuzzy Match):** + - O sistema usa busca aproximada (`ILIKE`). + - Exemplo: Se cadastrar "Spa-Hidromassagem", buscar por "hidro", "massagem" ou "spa" irá funcionar. + +## 5. Como Identificar e Corrigir no Futuro + +Se o erro voltar ("Estou com um erro no meu sistema" ou tarifa não encontrada): + +1. **Verifique os Logs:** + Execute no terminal: + + ```bash + tail -f log/tool_debug.log + ``` + + Procure por `CRITICAL ERROR` ou `PRICING COUNT`. + +2. **Verifique se a Tarifa Existe:** + Se o log mostra `PRICING COUNT: 0`, a tarifa não está no banco para aquela conta. + Se o log mostra tarifas (ex: `PRICING COUNT: 30`) mas falha em achar a sua, verifique o nome (`suite_category`) e se o `inbox_id` não está restringindo o acesso. + +3. **Reinicie o Serviço:** + Sempre que alterar código Ruby (como models ou services), reinicie os processos: + ```bash + make run + # ou + systemctl restart chatwoot-web sidekiq + ```