+
+
+
+ {{ readableTime }}
+
+
+
diff --git a/app/models/concerns/json_schema_validator.rb b/app/models/concerns/json_schema_validator.rb
index 4ae94df12..808564be9 100644
--- a/app/models/concerns/json_schema_validator.rb
+++ b/app/models/concerns/json_schema_validator.rb
@@ -48,7 +48,6 @@ class JsonSchemaValidator < ActiveModel::Validator
# Add validation errors to the record with a formatted statement
validation_errors.each do |error|
- # byebug
format_and_append_error(error, record)
end
end
diff --git a/app/services/action_service.rb b/app/services/action_service.rb
index 79f1234a0..4f33a302b 100644
--- a/app/services/action_service.rb
+++ b/app/services/action_service.rb
@@ -50,7 +50,11 @@ class ActionService
end
def assign_team(team_ids = [])
- return unassign_team if team_ids[0]&.zero?
+ # FIXME: The explicit checks for zero or nil (string) is bad. Move
+ # this to a separate unassign action.
+ should_unassign = team_ids.blank? || %w[nil 0].include?(team_ids[0].to_s)
+ return @conversation.update!(team_id: nil) if should_unassign
+
# check if team belongs to account only if team_id is present
# if team_id is nil, then it means that the team is being unassigned
return unless !team_ids[0].nil? && team_belongs_to_account?(team_ids)
diff --git a/app/services/notification/push_notification_service.rb b/app/services/notification/push_notification_service.rb
index 263800d4a..9878107c1 100644
--- a/app/services/notification/push_notification_service.rb
+++ b/app/services/notification/push_notification_service.rb
@@ -66,7 +66,7 @@ class Notification::PushNotificationService
def send_browser_push(subscription)
return unless can_send_browser_push?(subscription)
- WebPush.payload_send(browser_push_payload(subscription))
+ WebPush.payload_send(**browser_push_payload(subscription))
Rails.logger.info("Browser push sent to #{user.email} with title #{push_message[:title]}")
rescue WebPush::ExpiredSubscription, WebPush::InvalidSubscription, WebPush::Unauthorized => e
Rails.logger.info "WebPush subscription expired: #{e.message}"
diff --git a/config/app.yml b/config/app.yml
index 8a133ee53..b8c238254 100644
--- a/config/app.yml
+++ b/config/app.yml
@@ -1,5 +1,5 @@
shared: &shared
- version: '3.10.1'
+ version: '3.10.2'
development:
<<: *shared
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index 189184d4d..8fb3e4507 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -31,7 +31,7 @@ vi:
disposable_email: Chúng tôi không cho phép các email dùng một lần
invalid_email: Bạn đã nhập một email không hợp lệ
email_already_exists: "Bạn đã đăng ký một tài khoản với %{email}"
- invalid_params: 'Invalid, please check the signup paramters and try again'
+ invalid_params: 'Không hợp lệ, vui lòng kiểm tra thông số đăng ký và thử lại'
failed: Đăng ký thât bại
data_import:
data_type:
@@ -51,7 +51,7 @@ vi:
dyte:
invalid_message_type: "Loại tin nhắn không hợp lệ. Hành động không được phép"
slack:
- invalid_channel_id: "Invalid slack channel. Please try again"
+ invalid_channel_id: "Kênh chùng không hợp lệ. Vui lòng thử lại"
inboxes:
imap:
socket_error: Vui lòng kiểm tra kết nối mạng, địa chỉ IMAP và thử lại.
@@ -63,43 +63,43 @@ vi:
name: không nên bắt đầu hoặc kết thúc bằng các ký hiệu và không nên có kí tự < > / \ @.
custom_filters:
number_of_records: Đã đạt giới hạn. Số lượng tuỳ chọn lọc tối đa cho mỗi mỗi người dùng mỗi tài khoản là 50.
- invalid_attribute: Invalid attribute key - [%{key}]. The key should be one of [%{allowed_keys}] or a custom attribute defined in the account.
- invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
- invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
+ invalid_attribute: Khóa thuộc tính không hợp lệ - [%{key}]. Chìa khóa phải là một trong [%{allowed_keys}] hoặc thuộc tính tùy chỉnh được xác định trong tài khoản.
+ invalid_operator: Toán tử không hợp lệ. Các toán tử được phép cho %{attribute_name} là [%{allowed_keys}].
+ invalid_value: Giá trị không hợp lệ. Các giá trị được cung cấp cho %{attribute_name} không hợp lệ
reports:
period: Thời gian báo cáo từ %{since} đến %{until}
utc_warning: Báo cáo đã được tạo với múi giờ UTC
agent_csv:
agent_name: Tên tổng đài viên
- conversations_count: Assigned conversations
- avg_first_response_time: Avg first response time
+ conversations_count: Cuộc trò chuyện được chỉ định
+ avg_first_response_time: Thời gian phản hồi đầu tiên trung bình
avg_resolution_time: Avg resolution time
resolution_count: Số lượng giải quyết
- avg_customer_waiting_time: Avg customer waiting time
+ avg_customer_waiting_time: Thời gian chờ đợi trung bình của khách hàng
inbox_csv:
inbox_name: Tên kênh
inbox_type: Kiểu kênh
conversations_count: Số hội thoại
- avg_first_response_time: Avg first response time
- avg_resolution_time: Avg resolution time
+ avg_first_response_time: Thời gian phản hồi đầu tiên trung bình
+ avg_resolution_time: Thời gian giải quyết trung bình
label_csv:
label_title: Nhãn
conversations_count: Số hội thoại
- avg_first_response_time: Avg first response time
- avg_resolution_time: Avg resolution time
+ avg_first_response_time: Thời gian phản hồi đầu tiên trung bình
+ avg_resolution_time: Thời gian giải quyết trung bình
team_csv:
team_name: Tên nhóm
conversations_count: Số hội thoại
- avg_first_response_time: Avg first response time
- avg_resolution_time: Avg resolution time
+ avg_first_response_time: Thời gian phản hồi đầu tiên trung bình
+ avg_resolution_time: Thời gian giải quyết trung bình
resolution_count: Số lượng giải quyết
- avg_customer_waiting_time: Avg customer waiting time
+ avg_customer_waiting_time: Thời gian chờ đợi trung bình của khách hàng
conversation_traffic_csv:
timezone: Múi giờ
sla_csv:
- conversation_id: Conversation ID
+ conversation_id: ID hội thoại
sla_policy_breached: SLA Policy
- assignee: Assignee
+ assignee: Đại lý được chỉ định
team: Nhóm
inbox: Hộp thư đến
labels: Nhãn
@@ -118,22 +118,22 @@ vi:
recorded_at: Ngày nghi
notifications:
notification_title:
- conversation_creation: "A conversation (#%{display_id}) has been created in %{inbox_name}"
- conversation_assignment: "A conversation (#%{display_id}) has been assigned to you"
- assigned_conversation_new_message: "A new message is created in conversation (#%{display_id})"
- conversation_mention: "You have been mentioned in conversation (#%{display_id})"
- sla_missed_first_response: "SLA target first response missed for conversation (#%{display_id})"
- sla_missed_next_response: "SLA target next response missed for conversation (#%{display_id})"
- sla_missed_resolution: "SLA target resolution missed for conversation (#%{display_id})"
- attachment: "Attachment"
- no_content: "No content"
+ conversation_creation: "Một cuộc trò chuyện (#%{display_id}) đã được tạo trong %{inbox_name}"
+ conversation_assignment: "Một cuộc trò chuyện (#%{display_id}) đã được chỉ định cho bạn"
+ assigned_conversation_new_message: "Một tin nhắn mới được tạo trong cuộc trò chuyện (#%{display_id})"
+ conversation_mention: "Bạn đã được nhắc đến trong cuộc trò chuyện (#%{display_id})"
+ sla_missed_first_response: "Mục tiêu SLA phản hồi đầu tiên bị bỏ lỡ cho cuộc trò chuyện (#%{display_id})"
+ sla_missed_next_response: "Mục tiêu SLA phản hồi tiếp theo bị bỏ lỡ cho cuộc trò chuyện (#%{display_id})"
+ sla_missed_resolution: "Độ phân giải mục tiêu SLA bị bỏ lỡ cho cuộc trò chuyện (#%{display_id})"
+ attachment: "Tập tin đính kèm"
+ no_content: "Không có nội dung"
conversations:
messages:
instagram_story_content: "%{story_sender} đã đề cập đến bạn trong hội thoại: "
instagram_deleted_story_content: Hội thoại này không còn nữa.
deleted: Tin nhắn đã bị xoá
delivery_status:
- error_code: "Error code: %{error_code}"
+ error_code: "Mã lỗi: %{error_code}"
activity:
status:
resolved: "Cuộc trò chuyện được đánh dấu là đã giải quyết bởi %{user_name}"
@@ -221,7 +221,7 @@ vi:
common:
home: Trang Chủ
last_updated_on: 'Cập nhật lần cuối: %{last_updated_on}'
- view_all_articles: View all
+ view_all_articles: Xem tất cả
article: bài viết
articles: bài viết
author: tác giả
@@ -233,17 +233,17 @@ vi:
footer:
made_with: Tạo bởi
header:
- go_to_homepage: Website
+ go_to_homepage: Trang web
appearance:
- system: System
- light: Light
- dark: Dark
- featured_articles: Featured Articles
+ system: Hệ thống
+ light: Sáng
+ dark: Tối
+ featured_articles: Bài viết nổi bật
uncategorized: Chưa được phân loại
404:
- title: Page not found
- description: We couldn't find the page you were looking for.
- back_to_home: Go to home page
+ title: Không tìm thấy trang
+ description: Chúng tôi không thể tìm thấy trang bạn đang tìm kiếm.
+ back_to_home: Tới trang chủ
slack_unfurl:
fields:
name: Tên
@@ -255,10 +255,10 @@ vi:
button: Mở cuộc trò chuyện
time_units:
days:
- other: "%{count} days"
+ other: "%{count} ngày"
hours:
- other: "%{count} hours"
+ other: "%{count} giờ"
minutes:
- other: "%{count} minutes"
+ other: "%{count} phút"
seconds:
- other: "%{count} seconds"
+ other: "%{count} giây"
diff --git a/package.json b/package.json
index 6b017503e..79ac41e02 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@chatwoot/chatwoot",
- "version": "3.10.1",
+ "version": "3.10.2",
"license": "MIT",
"scripts": {
"eslint": "eslint app/**/*.{js,vue}",
diff --git a/spec/factories/conversations.rb b/spec/factories/conversations.rb
index 6f2bc34ef..c552e6c30 100644
--- a/spec/factories/conversations.rb
+++ b/spec/factories/conversations.rb
@@ -16,5 +16,17 @@ FactoryBot.define do
conversation.contact ||= create(:contact, :with_email, account: conversation.account)
conversation.contact_inbox ||= create(:contact_inbox, contact: conversation.contact, inbox: conversation.inbox)
end
+
+ trait :with_team do
+ after(:build) do |conversation|
+ conversation.team ||= create(:team, account: conversation.account)
+ end
+ end
+
+ trait :with_assignee do
+ after(:build) do |conversation|
+ conversation.assignee ||= create(:user, account: conversation.account, role: :agent)
+ end
+ end
end
end
diff --git a/spec/services/action_service_spec.rb b/spec/services/action_service_spec.rb
index bdc9c88ff..c28761844 100644
--- a/spec/services/action_service_spec.rb
+++ b/spec/services/action_service_spec.rb
@@ -31,18 +31,54 @@ describe ActionService do
describe '#assign_agent' do
let(:agent) { create(:user, account: account, role: :agent) }
- let(:conversation) { create(:conversation, account: account) }
let(:inbox_member) { create(:inbox_member, inbox: conversation.inbox, user: agent) }
+ let(:conversation) { create(:conversation, :with_assignee, account: account) }
let(:action_service) { described_class.new(conversation) }
it 'unassigns the conversation if agent id is nil' do
action_service.assign_agent(['nil'])
expect(conversation.reload.assignee).to be_nil
end
+ end
- it 'unassigns the team if team_id is nil' do
- action_service.assign_team([nil])
- expect(conversation.reload.team).to be_nil
+ describe '#assign_team' do
+ let(:agent) { create(:user, account: account, role: :agent) }
+ let(:inbox_member) { create(:inbox_member, inbox: conversation.inbox, user: agent) }
+ let(:team) { create(:team, name: 'ConversationTeam', account: account) }
+ let(:conversation) { create(:conversation, :with_team, account: account) }
+ let(:action_service) { described_class.new(conversation) }
+
+ context 'when team_id is not present' do
+ it 'unassign the if team_id is "nil"' do
+ expect do
+ action_service.assign_team(['nil'])
+ end.not_to raise_error
+ expect(conversation.reload.team).to be_nil
+ end
+
+ it 'unassign the if team_id is 0' do
+ expect do
+ action_service.assign_team([0])
+ end.not_to raise_error
+ expect(conversation.reload.team).to be_nil
+ end
+ end
+
+ context 'when team_id is present' do
+ it 'assign the team if the team is part of the account' do
+ original_team = conversation.team
+ expect do
+ action_service.assign_team([team.id])
+ end.to change { conversation.reload.team }.from(original_team)
+ end
+
+ it 'does not assign the team if the team is part of the account' do
+ original_team = conversation.team
+ invalid_team_id = 999_999_999
+ expect do
+ action_service.assign_team([invalid_team_id])
+ end.not_to change { conversation.reload.team }.from(original_team)
+ end
end
end
end