Leela vadlamudi
4 years ago
committed by
GitHub
21 changed files with 391 additions and 8 deletions
@ -0,0 +1,14 @@ |
|||||
|
# Root editor config file |
||||
|
root = true |
||||
|
|
||||
|
# Common settings |
||||
|
[*] |
||||
|
end_of_line = lf |
||||
|
insert_final_newline = true |
||||
|
trim_trailing_whitespace = true |
||||
|
charset = utf-8 |
||||
|
|
||||
|
# python, js indentation settings |
||||
|
[{*.py,*.js}] |
||||
|
indent_style = tab |
||||
|
indent_size = 4 |
@ -0,0 +1,23 @@ |
|||||
|
frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( { |
||||
|
make_input() { |
||||
|
this._super(); |
||||
|
if (this.df.options == 'Phone') { |
||||
|
this.setup_phone(); |
||||
|
} |
||||
|
}, |
||||
|
setup_phone() { |
||||
|
if (frappe.phone_call.handler) { |
||||
|
this.$wrapper.find('.control-input') |
||||
|
.append(` |
||||
|
<span class="phone-btn"> |
||||
|
<a class="btn-open no-decoration" title="${__('Make a call')}"> |
||||
|
<i class="fa fa-phone"></i></a> |
||||
|
</span> |
||||
|
`)
|
||||
|
.find('.phone-btn') |
||||
|
.click(() => { |
||||
|
frappe.phone_call.handler(this.get_value(), this.frm); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
@ -0,0 +1,8 @@ |
|||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
|
// For license information, please see license.txt
|
||||
|
|
||||
|
frappe.ui.form.on('Call Log', { |
||||
|
// refresh: function(frm) {
|
||||
|
|
||||
|
// }
|
||||
|
}); |
@ -0,0 +1,10 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors |
||||
|
# See license.txt |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
# import frappe |
||||
|
import unittest |
||||
|
|
||||
|
class TestCallLog(unittest.TestCase): |
||||
|
pass |
@ -0,0 +1,60 @@ |
|||||
|
{ |
||||
|
"actions": [], |
||||
|
"creation": "2020-11-19 11:15:54.967710", |
||||
|
"doctype": "DocType", |
||||
|
"editable_grid": 1, |
||||
|
"engine": "InnoDB", |
||||
|
"field_order": [ |
||||
|
"day_of_week", |
||||
|
"from_time", |
||||
|
"to_time", |
||||
|
"agent_group" |
||||
|
], |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"fieldname": "day_of_week", |
||||
|
"fieldtype": "Select", |
||||
|
"in_list_view": 1, |
||||
|
"label": "Day Of Week", |
||||
|
"options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", |
||||
|
"reqd": 1 |
||||
|
}, |
||||
|
{ |
||||
|
"default": "9:00:00", |
||||
|
"fieldname": "from_time", |
||||
|
"fieldtype": "Time", |
||||
|
"in_list_view": 1, |
||||
|
"label": "From Time", |
||||
|
"reqd": 1 |
||||
|
}, |
||||
|
{ |
||||
|
"default": "17:00:00", |
||||
|
"fieldname": "to_time", |
||||
|
"fieldtype": "Time", |
||||
|
"in_list_view": 1, |
||||
|
"label": "To Time", |
||||
|
"reqd": 1 |
||||
|
}, |
||||
|
{ |
||||
|
"fieldname": "agent_group", |
||||
|
"fieldtype": "Link", |
||||
|
"in_list_view": 1, |
||||
|
"label": "Agent Group", |
||||
|
"options": "Employee Group", |
||||
|
"reqd": 1 |
||||
|
} |
||||
|
], |
||||
|
"index_web_pages_for_search": 1, |
||||
|
"istable": 1, |
||||
|
"links": [], |
||||
|
"modified": "2020-11-19 11:15:54.967710", |
||||
|
"modified_by": "Administrator", |
||||
|
"module": "Telephony", |
||||
|
"name": "Incoming Call Handling Schedule", |
||||
|
"owner": "Administrator", |
||||
|
"permissions": [], |
||||
|
"quick_entry": 1, |
||||
|
"sort_field": "modified", |
||||
|
"sort_order": "DESC", |
||||
|
"track_changes": 1 |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors |
||||
|
# For license information, please see license.txt |
||||
|
|
||||
|
from __future__ import unicode_literals |
||||
|
# import frappe |
||||
|
from frappe.model.document import Document |
||||
|
|
||||
|
class IncomingCallHandlingSchedule(Document): |
||||
|
pass |
@ -0,0 +1,102 @@ |
|||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
|
// For license information, please see license.txt
|
||||
|
|
||||
|
function time_to_seconds(time_str) { |
||||
|
// Convert time string of format HH:MM:SS into seconds.
|
||||
|
let seq = time_str.split(':'); |
||||
|
seq = seq.map((n) => parseInt(n)); |
||||
|
return (seq[0]*60*60) + (seq[1]*60) + seq[2]; |
||||
|
} |
||||
|
|
||||
|
function number_sort(array, ascending=true) { |
||||
|
let array_copy = [...array]; |
||||
|
if (ascending) { |
||||
|
array_copy.sort((a, b) => a-b); // ascending order
|
||||
|
} else { |
||||
|
array_copy.sort((a, b) => b-a); // descending order
|
||||
|
} |
||||
|
return array_copy; |
||||
|
} |
||||
|
|
||||
|
function groupby(items, key) { |
||||
|
// Group the list of items using the given key.
|
||||
|
const obj = {}; |
||||
|
items.forEach((item) => { |
||||
|
if (item[key] in obj) { |
||||
|
obj[item[key]].push(item); |
||||
|
} else { |
||||
|
obj[item[key]] = [item]; |
||||
|
} |
||||
|
}); |
||||
|
return obj; |
||||
|
} |
||||
|
|
||||
|
function check_timeslot_overlap(ts1, ts2) { |
||||
|
/// Timeslot is a an array of length 2 ex: [from_time, to_time]
|
||||
|
/// time in timeslot is an integer represents number of seconds.
|
||||
|
if ((ts1[0] < ts2[0] && ts1[1] <= ts2[0]) || (ts1[0] >= ts2[1] && ts1[1] > ts2[1])) { |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
function validate_call_schedule(schedule) { |
||||
|
validate_call_schedule_timeslot(schedule); |
||||
|
validate_call_schedule_overlaps(schedule); |
||||
|
} |
||||
|
|
||||
|
function validate_call_schedule_timeslot(schedule) { |
||||
|
// Make sure that to time slot is ahead of from time slot.
|
||||
|
let errors = []; |
||||
|
|
||||
|
for (let row in schedule) { |
||||
|
let record = schedule[row]; |
||||
|
let from_time_in_secs = time_to_seconds(record.from_time); |
||||
|
let to_time_in_secs = time_to_seconds(record.to_time); |
||||
|
if (from_time_in_secs >= to_time_in_secs) { |
||||
|
errors.push(__('Call Schedule Row {0}: To time slot should always be ahead of From time slot.', [row])); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (errors.length > 0) { |
||||
|
frappe.throw(errors.join("<br/>")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function is_call_schedule_overlapped(day_schedule) { |
||||
|
// Check if any time slots are overlapped in a day schedule.
|
||||
|
let timeslots = []; |
||||
|
day_schedule.forEach((record)=> { |
||||
|
timeslots.push([time_to_seconds(record.from_time), time_to_seconds(record.to_time)]); |
||||
|
}); |
||||
|
|
||||
|
if (timeslots.length < 2) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
timeslots = number_sort(timeslots); |
||||
|
|
||||
|
// Sorted timeslots will be in ascending order if not overlapped.
|
||||
|
for (let i=1; i < timeslots.length; i++) { |
||||
|
if (check_timeslot_overlap(timeslots[i-1], timeslots[i])) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
function validate_call_schedule_overlaps(schedule) { |
||||
|
let group_by_day = groupby(schedule, 'day_of_week'); |
||||
|
for (const [day, day_schedule] of Object.entries(group_by_day)) { |
||||
|
if (is_call_schedule_overlapped(day_schedule)) { |
||||
|
frappe.throw(__('Please fix overlapping time slots for {0}', [day])); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
frappe.ui.form.on('Incoming Call Settings', { |
||||
|
validate(frm) { |
||||
|
validate_call_schedule(frm.doc.call_handling_schedule); |
||||
|
} |
||||
|
}); |
||||
|
|
@ -0,0 +1,82 @@ |
|||||
|
{ |
||||
|
"actions": [], |
||||
|
"autoname": "Prompt", |
||||
|
"creation": "2020-11-19 10:37:20.734245", |
||||
|
"doctype": "DocType", |
||||
|
"editable_grid": 1, |
||||
|
"engine": "InnoDB", |
||||
|
"field_order": [ |
||||
|
"call_routing", |
||||
|
"column_break_2", |
||||
|
"greeting_message", |
||||
|
"agent_busy_message", |
||||
|
"agent_unavailable_message", |
||||
|
"section_break_6", |
||||
|
"call_handling_schedule" |
||||
|
], |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"default": "Sequential", |
||||
|
"fieldname": "call_routing", |
||||
|
"fieldtype": "Select", |
||||
|
"in_list_view": 1, |
||||
|
"label": "Call Routing", |
||||
|
"options": "Sequential\nSimultaneous" |
||||
|
}, |
||||
|
{ |
||||
|
"fieldname": "greeting_message", |
||||
|
"fieldtype": "Data", |
||||
|
"label": "Greeting Message" |
||||
|
}, |
||||
|
{ |
||||
|
"fieldname": "agent_busy_message", |
||||
|
"fieldtype": "Data", |
||||
|
"label": "Agent Busy Message" |
||||
|
}, |
||||
|
{ |
||||
|
"fieldname": "agent_unavailable_message", |
||||
|
"fieldtype": "Data", |
||||
|
"label": "Agent Unavailable Message" |
||||
|
}, |
||||
|
{ |
||||
|
"fieldname": "column_break_2", |
||||
|
"fieldtype": "Column Break" |
||||
|
}, |
||||
|
{ |
||||
|
"fieldname": "section_break_6", |
||||
|
"fieldtype": "Section Break" |
||||
|
}, |
||||
|
{ |
||||
|
"fieldname": "call_handling_schedule", |
||||
|
"fieldtype": "Table", |
||||
|
"label": "Call Handling Schedule", |
||||
|
"options": "Incoming Call Handling Schedule", |
||||
|
"reqd": 1 |
||||
|
} |
||||
|
], |
||||
|
"index_web_pages_for_search": 1, |
||||
|
"links": [], |
||||
|
"modified": "2020-11-19 11:17:14.527862", |
||||
|
"modified_by": "Administrator", |
||||
|
"module": "Telephony", |
||||
|
"name": "Incoming Call Settings", |
||||
|
"owner": "Administrator", |
||||
|
"permissions": [ |
||||
|
{ |
||||
|
"create": 1, |
||||
|
"delete": 1, |
||||
|
"email": 1, |
||||
|
"export": 1, |
||||
|
"print": 1, |
||||
|
"read": 1, |
||||
|
"report": 1, |
||||
|
"role": "System Manager", |
||||
|
"share": 1, |
||||
|
"write": 1 |
||||
|
} |
||||
|
], |
||||
|
"quick_entry": 1, |
||||
|
"sort_field": "modified", |
||||
|
"sort_order": "DESC", |
||||
|
"track_changes": 1 |
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors |
||||
|
# For license information, please see license.txt |
||||
|
|
||||
|
from __future__ import unicode_literals |
||||
|
import frappe |
||||
|
from frappe.model.document import Document |
||||
|
from datetime import datetime |
||||
|
from typing import Tuple |
||||
|
from frappe import _ |
||||
|
|
||||
|
class IncomingCallSettings(Document): |
||||
|
def validate(self): |
||||
|
"""List of validations |
||||
|
* Make sure that to time slot is ahead of from time slot in call schedule |
||||
|
* Make sure that no overlapping timeslots for a given day |
||||
|
""" |
||||
|
self.validate_call_schedule_timeslot(self.call_handling_schedule) |
||||
|
self.validate_call_schedule_overlaps(self.call_handling_schedule) |
||||
|
|
||||
|
def validate_call_schedule_timeslot(self, schedule: list): |
||||
|
""" Make sure that to time slot is ahead of from time slot. |
||||
|
""" |
||||
|
errors = [] |
||||
|
for record in schedule: |
||||
|
from_time = self.time_to_seconds(record.from_time) |
||||
|
to_time = self.time_to_seconds(record.to_time) |
||||
|
if from_time >= to_time: |
||||
|
errors.append( |
||||
|
_('Call Schedule Row {0}: To time slot should always be ahead of From time slot.').format(record.idx) |
||||
|
) |
||||
|
|
||||
|
if errors: |
||||
|
frappe.throw('<br/>'.join(errors)) |
||||
|
|
||||
|
def validate_call_schedule_overlaps(self, schedule: list): |
||||
|
"""Check if any time slots are overlapped in a day schedule. |
||||
|
""" |
||||
|
week_days = set([each.day_of_week for each in schedule]) |
||||
|
|
||||
|
for day in week_days: |
||||
|
timeslots = [(record.from_time, record.to_time) for record in schedule if record.day_of_week==day] |
||||
|
|
||||
|
# convert time in timeslot into an integer represents number of seconds |
||||
|
timeslots = sorted(map(lambda seq: tuple(map(self.time_to_seconds, seq)), timeslots)) |
||||
|
if len(timeslots) < 2: continue |
||||
|
|
||||
|
for i in range(1, len(timeslots)): |
||||
|
if self.check_timeslots_overlap(timeslots[i-1], timeslots[i]): |
||||
|
frappe.throw(_('Please fix overlapping time slots for {0}.').format(day)) |
||||
|
|
||||
|
@staticmethod |
||||
|
def check_timeslots_overlap(ts1: Tuple[int, int], ts2: Tuple[int, int]) -> bool: |
||||
|
if (ts1[0] < ts2[0] and ts1[1] <= ts2[0]) or (ts1[0] >= ts2[1] and ts1[1] > ts2[1]): |
||||
|
return False |
||||
|
return True |
||||
|
|
||||
|
@staticmethod |
||||
|
def time_to_seconds(time: str) -> int: |
||||
|
"""Convert time string of format HH:MM:SS into seconds |
||||
|
""" |
||||
|
date_time = datetime.strptime(time, "%H:%M:%S") |
||||
|
return date_time - datetime(1900, 1, 1) |
@ -0,0 +1,10 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors |
||||
|
# See license.txt |
||||
|
from __future__ import unicode_literals |
||||
|
|
||||
|
# import frappe |
||||
|
import unittest |
||||
|
|
||||
|
class TestIncomingCallSettings(unittest.TestCase): |
||||
|
pass |
Loading…
Reference in new issue