mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	Merge branch 'develop' into fix/CW-5679
This commit is contained in:
		| @@ -644,7 +644,7 @@ GEM | ||||
|       activesupport (>= 3.0.0) | ||||
|     raabro (1.4.0) | ||||
|     racc (1.8.1) | ||||
|     rack (3.2.2) | ||||
|     rack (3.2.3) | ||||
|     rack-attack (6.7.0) | ||||
|       rack (>= 1.0, < 4) | ||||
|     rack-contrib (2.5.0) | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| <script setup> | ||||
| import { computed, onMounted } from 'vue'; | ||||
| import { computed, onMounted, ref } from 'vue'; | ||||
| import { useRouter } from 'vue-router'; | ||||
| import { useMapGetter, useStore } from 'dashboard/composables/store.js'; | ||||
| import { useAccount } from 'dashboard/composables/useAccount'; | ||||
| import { useCaptain } from 'dashboard/composables/useCaptain'; | ||||
| import { format } from 'date-fns'; | ||||
| import sessionStorage from 'shared/helpers/sessionStorage'; | ||||
|  | ||||
| import BillingMeter from './components/BillingMeter.vue'; | ||||
| import BillingCard from './components/BillingCard.vue'; | ||||
| @@ -13,7 +15,8 @@ import BaseSettingsHeader from '../components/BaseSettingsHeader.vue'; | ||||
| import SettingsLayout from '../SettingsLayout.vue'; | ||||
| import ButtonV4 from 'next/button/Button.vue'; | ||||
|  | ||||
| const { currentAccount } = useAccount(); | ||||
| const router = useRouter(); | ||||
| const { currentAccount, isOnChatwootCloud } = useAccount(); | ||||
| const { | ||||
|   captainEnabled, | ||||
|   captainLimits, | ||||
| @@ -24,6 +27,12 @@ const { | ||||
|  | ||||
| const uiFlags = useMapGetter('accounts/getUIFlags'); | ||||
| const store = useStore(); | ||||
|  | ||||
| const BILLING_REFRESH_ATTEMPTED = 'billing_refresh_attempted'; | ||||
|  | ||||
| // State for handling refresh attempts and loading | ||||
| const isWaitingForBilling = ref(false); | ||||
|  | ||||
| const customAttributes = computed(() => { | ||||
|   return currentAccount.value.custom_attributes || {}; | ||||
| }); | ||||
| @@ -61,11 +70,45 @@ const hasABillingPlan = computed(() => { | ||||
|  | ||||
| const fetchAccountDetails = async () => { | ||||
|   if (!hasABillingPlan.value) { | ||||
|     store.dispatch('accounts/subscription'); | ||||
|     await store.dispatch('accounts/subscription'); | ||||
|     fetchLimits(); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const handleBillingPageLogic = async () => { | ||||
|   // If self-hosted, redirect to dashboard | ||||
|   if (!isOnChatwootCloud.value) { | ||||
|     router.push({ name: 'home' }); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Check if we've already attempted a refresh for billing setup | ||||
|   const billingRefreshAttempted = sessionStorage.get(BILLING_REFRESH_ATTEMPTED); | ||||
|  | ||||
|   // If cloud user, fetch account details first | ||||
|   await fetchAccountDetails(); | ||||
|  | ||||
|   // If still no billing plan after fetch | ||||
|   if (!hasABillingPlan.value) { | ||||
|     // If we haven't attempted refresh yet, do it once | ||||
|     if (!billingRefreshAttempted) { | ||||
|       isWaitingForBilling.value = true; | ||||
|       sessionStorage.set(BILLING_REFRESH_ATTEMPTED, true); | ||||
|  | ||||
|       setTimeout(() => { | ||||
|         window.location.reload(); | ||||
|       }, 5000); | ||||
|     } else { | ||||
|       // We've already tried refreshing, so just show the no billing message | ||||
|       // Clear the flag for future visits | ||||
|       sessionStorage.remove(BILLING_REFRESH_ATTEMPTED); | ||||
|     } | ||||
|   } else { | ||||
|     // Billing plan found, clear any existing refresh flag | ||||
|     sessionStorage.remove(BILLING_REFRESH_ATTEMPTED); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const onClickBillingPortal = () => { | ||||
|   store.dispatch('accounts/checkout'); | ||||
| }; | ||||
| @@ -76,14 +119,18 @@ const onToggleChatWindow = () => { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| onMounted(fetchAccountDetails); | ||||
| onMounted(handleBillingPageLogic); | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|   <SettingsLayout | ||||
|     :is-loading="uiFlags.isFetchingItem" | ||||
|     :loading-message="$t('ATTRIBUTES_MGMT.LOADING')" | ||||
|     :no-records-found="!hasABillingPlan" | ||||
|     :is-loading="uiFlags.isFetchingItem || isWaitingForBilling" | ||||
|     :loading-message=" | ||||
|       isWaitingForBilling | ||||
|         ? $t('BILLING_SETTINGS.NO_BILLING_USER') | ||||
|         : $t('ATTRIBUTES_MGMT.LOADING') | ||||
|     " | ||||
|     :no-records-found="!hasABillingPlan && !isWaitingForBilling" | ||||
|     :no-records-message="$t('BILLING_SETTINGS.NO_BILLING_USER')" | ||||
|   > | ||||
|     <template #header> | ||||
|   | ||||
| @@ -47,6 +47,15 @@ module Whatsapp::IncomingMessageServiceHelpers | ||||
|     %w[reaction ephemeral unsupported request_welcome].include?(message_type) | ||||
|   end | ||||
|  | ||||
|   def argentina_phone_number?(phone_number) | ||||
|     phone_number.match(/^54/) | ||||
|   end | ||||
|  | ||||
|   def normalised_argentina_mobil_number(phone_number) | ||||
|     # Remove 9 before country code | ||||
|     phone_number.sub(/^549/, '54') | ||||
|   end | ||||
|  | ||||
|   def processed_waid(waid) | ||||
|     Whatsapp::PhoneNumberNormalizationService.new(inbox).normalize_and_find_contact(waid) | ||||
|   end | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Handles Argentina phone number normalization | ||||
| # | ||||
| # Argentina phone numbers can appear with or without "9" after country code | ||||
| # This normalizer removes the "9" when present to create consistent format: 54 + area + number | ||||
| class Whatsapp::PhoneNormalizers::ArgentinaPhoneNormalizer < Whatsapp::PhoneNormalizers::BasePhoneNormalizer | ||||
|   def normalize(waid) | ||||
|     return waid unless handles_country?(waid) | ||||
|  | ||||
|     # Remove "9" after country code if present (549 → 54) | ||||
|     waid.sub(/^549/, '54') | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def country_code_pattern | ||||
|     /^54/ | ||||
|   end | ||||
| end | ||||
| @@ -1,5 +1,5 @@ | ||||
| # Service to handle phone number normalization for WhatsApp messages | ||||
| # Currently supports Brazil phone number format variations | ||||
| # Currently supports Brazil and Argentina phone number format variations | ||||
| # Designed to be extensible for additional countries in future PRs | ||||
| # | ||||
| # Usage: Whatsapp::PhoneNumberNormalizationService.new(inbox).normalize_and_find_contact(waid) | ||||
| @@ -34,6 +34,7 @@ class Whatsapp::PhoneNumberNormalizationService | ||||
|   end | ||||
|  | ||||
|   NORMALIZERS = [ | ||||
|     Whatsapp::PhoneNormalizers::BrazilPhoneNormalizer | ||||
|     Whatsapp::PhoneNormalizers::BrazilPhoneNormalizer, | ||||
|     Whatsapp::PhoneNormalizers::ArgentinaPhoneNormalizer | ||||
|   ].freeze | ||||
| end | ||||
|   | ||||
| @@ -70,7 +70,9 @@ module Integrations::Slack::SlackMessageHelper | ||||
|     case attachment[:filetype] | ||||
|     when 'png', 'jpeg', 'gif', 'bmp', 'tiff', 'jpg' | ||||
|       :image | ||||
|     when 'pdf' | ||||
|     when 'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm' | ||||
|       :video | ||||
|     else | ||||
|       :file | ||||
|     end | ||||
|   end | ||||
|   | ||||
| @@ -157,6 +157,19 @@ describe Integrations::Slack::IncomingMessageBuilder do | ||||
|  | ||||
|         expect(conversation.messages.count).to eql(messages_count) | ||||
|       end | ||||
|  | ||||
|       it 'handles different file types correctly' do | ||||
|         expect(hook).not_to be_nil | ||||
|         video_attachment_params = message_with_attachments.deep_dup | ||||
|         video_attachment_params[:event][:files][0][:filetype] = 'mp4' | ||||
|         video_attachment_params[:event][:files][0][:mimetype] = 'video/mp4' | ||||
|  | ||||
|         builder = described_class.new(video_attachment_params) | ||||
|         allow(builder).to receive(:sender).and_return(nil) | ||||
|  | ||||
|         expect { builder.perform }.not_to raise_error | ||||
|         expect(conversation.messages.last.attachments).to be_any | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when link shared' do | ||||
|   | ||||
| @@ -341,6 +341,58 @@ describe Whatsapp::IncomingMessageService do | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     describe 'When the incoming waid is an Argentine number with 9 after country code' do | ||||
|       let(:wa_id) { '5491123456789' } | ||||
|  | ||||
|       it 'creates appropriate conversations, message and contacts if contact does not exist' do | ||||
|         described_class.new(inbox: whatsapp_channel.inbox, params: params).perform | ||||
|         expect(whatsapp_channel.inbox.conversations.count).not_to eq(0) | ||||
|         expect(Contact.all.first.name).to eq('Sojan Jose') | ||||
|         expect(whatsapp_channel.inbox.messages.first.content).to eq('Test') | ||||
|         expect(whatsapp_channel.inbox.contact_inboxes.first.source_id).to eq(wa_id) | ||||
|       end | ||||
|  | ||||
|       it 'appends to existing contact if contact inbox exists with normalized format' do | ||||
|         # Normalized format removes the 9 after country code | ||||
|         normalized_wa_id = '541123456789' | ||||
|         contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: normalized_wa_id) | ||||
|         last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox) | ||||
|         described_class.new(inbox: whatsapp_channel.inbox, params: params).perform | ||||
|         # no new conversation should be created | ||||
|         expect(whatsapp_channel.inbox.conversations.count).to eq(1) | ||||
|         # message appended to the last conversation | ||||
|         expect(last_conversation.messages.last.content).to eq(params[:messages].first[:text][:body]) | ||||
|         # should use the normalized wa_id from existing contact | ||||
|         expect(whatsapp_channel.inbox.contact_inboxes.first.source_id).to eq(normalized_wa_id) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     describe 'When incoming waid is an Argentine number without 9 after country code' do | ||||
|       let(:wa_id) { '541123456789' } | ||||
|  | ||||
|       context 'when a contact inbox exists with the same format' do | ||||
|         it 'appends to existing contact' do | ||||
|           contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id) | ||||
|           last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox) | ||||
|           described_class.new(inbox: whatsapp_channel.inbox, params: params).perform | ||||
|           # no new conversation should be created | ||||
|           expect(whatsapp_channel.inbox.conversations.count).to eq(1) | ||||
|           # message appended to the last conversation | ||||
|           expect(last_conversation.messages.last.content).to eq(params[:messages].first[:text][:body]) | ||||
|         end | ||||
|       end | ||||
|  | ||||
|       context 'when a contact inbox does not exist' do | ||||
|         it 'creates contact inbox with the incoming waid' do | ||||
|           described_class.new(inbox: whatsapp_channel.inbox, params: params).perform | ||||
|           expect(whatsapp_channel.inbox.conversations.count).not_to eq(0) | ||||
|           expect(Contact.all.first.name).to eq('Sojan Jose') | ||||
|           expect(whatsapp_channel.inbox.messages.first.content).to eq('Test') | ||||
|           expect(whatsapp_channel.inbox.contact_inboxes.first.source_id).to eq(wa_id) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     describe 'when message processing is in progress' do | ||||
|       it 'ignores the current message creation request' do | ||||
|         params = { 'contacts' => [{ 'profile' => { 'name' => 'Kedar' }, 'wa_id' => '919746334593' }], | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Sivin Varghese
					Sivin Varghese