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