feat: Business hours

Data models and APIs for business hours

ref: #234
This commit is contained in:
Adam Zysko
2020-10-31 19:44:33 +01:00
committed by GitHub
parent 3d64ba49fc
commit 65ed4c78a4
23 changed files with 329 additions and 7 deletions

View File

@@ -80,6 +80,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def inbox_update_params
params.permit(:enable_auto_assignment, :name, :avatar, :greeting_message, :greeting_enabled,
:working_hours_enabled, :out_of_office_message,
channel: [
:website_url,
:widget_color,

View File

@@ -0,0 +1,18 @@
class Api::V1::Accounts::WorkingHoursController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :fetch_webhook, only: [:update]
def update
@working_hour.update!(working_hour_params)
end
private
def working_hour_params
params.require(:working_hour).permit(:inbox_id, :open_hour, :open_minutes, :close_hour, :close_minutes, :closed_all_day)
end
def fetch_working_hour
@working_hour = Current.account.working_hours.find(params[:id])
end
end

View File

@@ -9,6 +9,7 @@
# name :string not null
# settings_flags :integer default(0), not null
# support_email :string(100)
# timezone :string default("UTC")
# created_at :datetime not null
# updated_at :datetime not null
#
@@ -48,6 +49,7 @@ class Account < ApplicationRecord
has_many :labels, dependent: :destroy
has_many :notification_settings, dependent: :destroy
has_many :hooks, dependent: :destroy, class_name: 'Integrations::Hook'
has_many :working_hours, dependent: :destroy
has_many :kbase_portals, dependent: :destroy, class_name: '::Kbase::Portal'
has_many :kbase_categories, dependent: :destroy, class_name: '::Kbase::Category'
has_many :kbase_articles, dependent: :destroy, class_name: '::Kbase::Article'

View File

@@ -4,7 +4,7 @@
#
# id :integer not null, primary key
# feature_flags :integer default(3), not null
# reply_time :integer default(0)
# reply_time :integer default("in_a_few_minutes")
# website_token :string
# website_url :string
# welcome_tagline :string

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
module OutOfOffisable
extend ActiveSupport::Concern
included do
has_many :working_hours, dependent: :destroy
after_create :create_default_working_hours
end
def out_of_office?
working_hours_enabled? && working_hours.today.closed_now?
end
def working_now?
!out_of_office?
end
private
def create_default_working_hours
working_hours.create!(day_of_week: 1, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0)
working_hours.create!(day_of_week: 2, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0)
working_hours.create!(day_of_week: 3, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0)
working_hours.create!(day_of_week: 4, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0)
working_hours.create!(day_of_week: 5, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0)
working_hours.create!(day_of_week: 6, closed_all_day: true)
working_hours.create!(day_of_week: 7, closed_all_day: true)
end
end

View File

@@ -11,6 +11,8 @@
# greeting_enabled :boolean default(FALSE)
# greeting_message :string
# name :string not null
# out_of_office_message :string
# working_hours_enabled :boolean default(FALSE)
# created_at :datetime not null
# updated_at :datetime not null
# account_id :integer not null
@@ -24,6 +26,7 @@
class Inbox < ApplicationRecord
include Reportable
include Avatarable
include OutOfOffisable
validates :account_id, presence: true

View File

@@ -62,6 +62,7 @@ class Message < ApplicationRecord
# .succ is a hack to avoid https://makandracards.com/makandra/1057-why-two-ruby-time-objects-are-not-equal-although-they-appear-to-be
scope :unread_since, ->(datetime) { where('EXTRACT(EPOCH FROM created_at) > (?)', datetime.to_i.succ) }
scope :chat, -> { where.not(message_type: :activity).where(private: false) }
scope :today, -> { where("date_trunc('day', created_at) = ?", Date.current) }
default_scope { order(created_at: :asc) }
belongs_to :account

View File

@@ -0,0 +1,71 @@
# == Schema Information
#
# Table name: working_hours
#
# id :bigint not null, primary key
# close_hour :integer
# close_minutes :integer
# closed_all_day :boolean default(FALSE)
# day_of_week :integer not null
# open_hour :integer
# open_minutes :integer
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint
# inbox_id :bigint
#
# Indexes
#
# index_working_hours_on_account_id (account_id)
# index_working_hours_on_inbox_id (inbox_id)
#
class WorkingHour < ApplicationRecord
belongs_to :inbox
before_save :assign_account
validates :open_hour, presence: true, unless: :closed_all_day?
validates :open_minutes, presence: true, unless: :closed_all_day?
validates :close_hour, presence: true, unless: :closed_all_day?
validates :close_minutes, presence: true, unless: :closed_all_day?
validates :open_hour, inclusion: 0..23, unless: :closed_all_day?
validates :close_hour, inclusion: 0..23, unless: :closed_all_day?
validates :open_minutes, inclusion: 0..59, unless: :closed_all_day?
validates :close_minutes, inclusion: 0..59, unless: :closed_all_day?
validate :close_after_open, unless: :closed_all_day?
def self.today
find_by(day_of_week: Date.current.cwday)
end
def open_at?(time)
return false if closed_all_day?
time.hour >= open_hour &&
time.min >= open_minutes &&
time.hour <= close_hour &&
time.min <= close_minutes
end
def open_now?
open_at?(Time.zone.now)
end
def closed_now?
!open_now?
end
private
def assign_account
self.account_id = inbox.account_id
end
def close_after_open
return unless open_hour.hours + open_minutes.minutes >= close_hour.hours + close_minutes.minutes
errors.add(:close_hour, 'Closing time cannot be before opening time')
end
end

View File

@@ -4,8 +4,8 @@ class MessageTemplates::HookExecutionService
def perform
return if inbox.agent_bot_inbox&.active?
::MessageTemplates::Template::OutOfOffice.new(conversation: conversation).perform if should_send_out_of_office_message?
::MessageTemplates::Template::Greeting.new(conversation: conversation).perform if should_send_greeting?
::MessageTemplates::Template::EmailCollect.new(conversation: conversation).perform if should_send_email_collect?
end
@@ -14,12 +14,16 @@ class MessageTemplates::HookExecutionService
delegate :inbox, :conversation, to: :message
delegate :contact, to: :conversation
def should_send_out_of_office_message?
inbox.out_of_office? && conversation.messages.today.template.empty? && inbox.out_of_office_message.present?
end
def first_message_from_contact?
conversation.messages.outgoing.count.zero? && conversation.messages.template.count.zero?
end
def should_send_greeting?
first_message_from_contact? && conversation.inbox.greeting_enabled? && conversation.inbox.greeting_message.present?
first_message_from_contact? && inbox.greeting_enabled? && inbox.greeting_message.present?
end
def email_collect_was_sent?
@@ -27,7 +31,7 @@ class MessageTemplates::HookExecutionService
end
def should_send_email_collect?
!contact_has_email? && conversation.inbox.web_widget? && !email_collect_was_sent?
!contact_has_email? && inbox.web_widget? && !email_collect_was_sent?
end
def contact_has_email?

View File

@@ -0,0 +1,28 @@
class MessageTemplates::Template::OutOfOffice
pattr_initialize [:conversation!]
def perform
ActiveRecord::Base.transaction do
conversation.messages.create!(out_of_office_message_params)
end
rescue StandardError => e
Raven.capture_exception(e)
true
end
private
delegate :contact, :account, to: :conversation
delegate :inbox, to: :message
def out_of_office_message_params
content = @conversation.inbox&.out_of_office_message
{
account_id: @conversation.account_id,
inbox_id: @conversation.inbox_id,
message_type: :template,
content: content
}
end
end