Merge branch 'develop' into develop
diff --git a/erpnext/crm/doctype/appointment/__init__.py b/erpnext/crm/doctype/appointment/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/appointment/__init__.py
diff --git a/erpnext/crm/doctype/appointment/appointment.js b/erpnext/crm/doctype/appointment/appointment.js
new file mode 100644
index 0000000..4e41047
--- /dev/null
+++ b/erpnext/crm/doctype/appointment/appointment.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Appointment', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json
new file mode 100644
index 0000000..aee16f7
--- /dev/null
+++ b/erpnext/crm/doctype/appointment/appointment.json
@@ -0,0 +1,85 @@
+{
+ "autoname": "format:APMT-{scheduled_time}-{####}",
+ "creation": "2019-08-27 10:48:27.926283",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "scheduled_time",
+ "customer_details_section",
+ "customer_name",
+ "customer_phone_number",
+ "customer_skype",
+ "customer_details"
+ ],
+ "fields": [
+ {
+ "fieldname": "customer_details_section",
+ "fieldtype": "Section Break",
+ "label": "Customer Details"
+ },
+ {
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Name",
+ "reqd": 1
+ },
+ {
+ "fieldname": "customer_phone_number",
+ "fieldtype": "Data",
+ "label": "Phone Number"
+ },
+ {
+ "fieldname": "customer_skype",
+ "fieldtype": "Data",
+ "label": "Skype ID"
+ },
+ {
+ "fieldname": "customer_details",
+ "fieldtype": "Long Text",
+ "label": "Details"
+ },
+ {
+ "fieldname": "scheduled_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Scheduled Time",
+ "reqd": 1
+ }
+ ],
+ "modified": "2019-09-03 14:07:16.837591",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Appointment",
+ "name_case": "UPPER CASE",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Guest",
+ "share": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
new file mode 100644
index 0000000..cce6a1d
--- /dev/null
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, 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 Appointment(Document):
+ def validate(self):
+ number_of_appointments_in_same_slot = frappe.db.count('Appointment',filters={'scheduled_time':self.scheduled_time})
+ settings = frappe.get_doc('Appointment Booking Settings')
+ if(number_of_appointments_in_same_slot>=settings.number_of_agents):
+ frappe.throw('Time slot is not available')
+
diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py
new file mode 100644
index 0000000..702ac71
--- /dev/null
+++ b/erpnext/crm/doctype/appointment/test_appointment.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestAppointment(unittest.TestCase):
+ pass
diff --git a/erpnext/crm/doctype/appointment_booking_settings/__init__.py b/erpnext/crm/doctype/appointment_booking_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/appointment_booking_settings/__init__.py
diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
new file mode 100644
index 0000000..465df2c
--- /dev/null
+++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
@@ -0,0 +1,18 @@
+// frappe.ui.form.on('Availability Of Slots', 'from_time', check_time)
+// frappe.ui.form.on('Availability Of Slots', 'to_time', check_time)
+
+frappe.ui.form.on('Appointment Booking Settings', 'validate',check_times)
+function check_times(frm) {
+ $.each(frm.doc.availability_of_slots || [], function (i, d) {
+ let from_time = Date.parse('01/01/2019 ' + d.from_time);
+ console.log(from_time);
+ let to_time = Date.parse('01/01/2019 ' + d.to_time);
+ if (from_time > to_time) {
+ frappe.throw(__(`In row ${i + 1} of Availability Of Slots : "To Time" must be later than "From Time"`))
+ }
+ })
+}
+// function check_times(frm, cdt, cdn) {
+ // let d = locals[cdt][cdn];
+//
+// }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json
new file mode 100644
index 0000000..cf27f77
--- /dev/null
+++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json
@@ -0,0 +1,79 @@
+{
+ "creation": "2019-08-27 10:56:48.309824",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "availability_of_slots",
+ "number_of_agents",
+ "holiday_list",
+ "email_reminders",
+ "appointment_duration"
+ ],
+ "fields": [
+ {
+ "fieldname": "availability_of_slots",
+ "fieldtype": "Table",
+ "label": "Availability Of Slots",
+ "options": "Availability Of Slots",
+ "reqd": 1
+ },
+ {
+ "fieldname": "number_of_agents",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "No. Of Agents",
+ "reqd": 1
+ },
+ {
+ "fieldname": "holiday_list",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Holiday List",
+ "options": "Holiday List",
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "email_reminders",
+ "fieldtype": "Check",
+ "label": "Email Reminders"
+ },
+ {
+ "default": "60",
+ "fieldname": "appointment_duration",
+ "fieldtype": "Int",
+ "label": "Appointment Duration",
+ "reqd": 1
+ }
+ ],
+ "issingle": 1,
+ "modified": "2019-09-03 12:27:09.763730",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Appointment Booking Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Guest",
+ "share": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py
new file mode 100644
index 0000000..8f1fb14
--- /dev/null
+++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+import datetime
+from frappe.model.document import Document
+
+class AppointmentBookingSettings(Document):
+ def validate(self):
+ # Day of week should not be repeated
+ list_of_days = []
+ date = '01/01/1970 '
+ format_string = "%d/%m/%Y %H:%M:%S"
+ for record in self.availability_of_slots:
+ list_of_days.append(record.day_of_week)
+ # Difference between from_time and to_time is multiple of appointment_duration
+ from_time = datetime.datetime.strptime(date+record.from_time,format_string)
+ to_time = datetime.datetime.strptime(date+record.to_time,format_string)
+ timedelta = to_time-from_time
+ if(from_time>to_time):
+ frappe.throw('From Time cannot be later than To Time for '+record.day_of_week)
+ if timedelta.total_seconds() % (self.appointment_duration*60):
+ frappe.throw('The difference between from time and To Time must be a multiple of Appointment ')
+ set_of_days = set(list_of_days)
+ if len(list_of_days) > len(set_of_days):
+ frappe.throw(_('Days of week must be unique'))
+
diff --git a/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py
new file mode 100644
index 0000000..3dc3c39
--- /dev/null
+++ b/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestAppointmentBookingSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/crm/doctype/availability_of_slots/__init__.py b/erpnext/crm/doctype/availability_of_slots/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/availability_of_slots/__init__.py
diff --git a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json
new file mode 100644
index 0000000..d26f7ce
--- /dev/null
+++ b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json
@@ -0,0 +1,46 @@
+{
+ "creation": "2019-08-27 10:52:54.204677",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "day_of_week",
+ "from_time",
+ "to_time"
+ ],
+ "fields": [
+ {
+ "fieldname": "day_of_week",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Day Of Week",
+ "options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
+ "reqd": 1
+ },
+ {
+ "fieldname": "from_time",
+ "fieldtype": "Time",
+ "in_list_view": 1,
+ "label": "From Time ",
+ "reqd": 1
+ },
+ {
+ "fieldname": "to_time",
+ "fieldtype": "Time",
+ "in_list_view": 1,
+ "label": "To Time",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "modified": "2019-08-27 10:52:54.204677",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Availabilty Of Slots",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py
new file mode 100644
index 0000000..8258471
--- /dev/null
+++ b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, 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 AvailabilityOfSlots(Document):
+ pass
diff --git a/erpnext/crm/doctype/timezone/__init__.py b/erpnext/crm/doctype/timezone/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/timezone/__init__.py
diff --git a/erpnext/crm/doctype/timezone/test_timezone.py b/erpnext/crm/doctype/timezone/test_timezone.py
new file mode 100644
index 0000000..92a8889
--- /dev/null
+++ b/erpnext/crm/doctype/timezone/test_timezone.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestTimezone(unittest.TestCase):
+ pass
diff --git a/erpnext/crm/doctype/timezone/timezone.js b/erpnext/crm/doctype/timezone/timezone.js
new file mode 100644
index 0000000..4dc57db
--- /dev/null
+++ b/erpnext/crm/doctype/timezone/timezone.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Timezone', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/crm/doctype/timezone/timezone.json b/erpnext/crm/doctype/timezone/timezone.json
new file mode 100644
index 0000000..b998e6c
--- /dev/null
+++ b/erpnext/crm/doctype/timezone/timezone.json
@@ -0,0 +1,61 @@
+{
+ "autoname": "field:timezone_name",
+ "creation": "2019-08-27 11:39:30.328670",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "offset",
+ "timezone_name"
+ ],
+ "fields": [
+ {
+ "fieldname": "offset",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Offset In Minutes",
+ "reqd": 1
+ },
+ {
+ "fieldname": "timezone_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Name",
+ "reqd": 1,
+ "unique": 1
+ }
+ ],
+ "modified": "2019-09-03 11:59:27.729561",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Timezone",
+ "name_case": "Title Case",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Guest",
+ "share": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/timezone/timezone.py b/erpnext/crm/doctype/timezone/timezone.py
new file mode 100644
index 0000000..f0da6e3
--- /dev/null
+++ b/erpnext/crm/doctype/timezone/timezone.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, 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 Timezone(Document):
+ def validate(self):
+ if self.offset > 720 or self.offset < -720:
+ frappe.throw(
+ 'Timezone offsets must be between -720 and +720 minutes')
diff --git a/erpnext/public/js/date_polyfill.js b/erpnext/public/js/date_polyfill.js
new file mode 100644
index 0000000..6899d82
--- /dev/null
+++ b/erpnext/public/js/date_polyfill.js
@@ -0,0 +1 @@
+(function(a,b){'object'==typeof exports&&'undefined'!=typeof module?b():'function'==typeof define&&define.amd?define(b):b()})(this,function(){'use strict';(function(a){if(a&&'undefined'!=typeof window){var b=document.createElement('style');return b.setAttribute('type','text/css'),b.innerHTML=a,document.head.appendChild(b),a}})('date-input-polyfill {\n background: #fff;\n color: #000;\n text-shadow: none;\n border: 0;\n padding: 0;\n height: auto;\n width: auto;\n line-height: normal;\n border-radius: 0;\n font-family: sans-serif;\n font-size: 14px;\n position: absolute !important;\n text-align: center;\n box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 12px 17px 2px rgba(0, 0, 0, 0.14), 0 5px 22px 4px rgba(0, 0, 0, 0.12);\n cursor: default;\n z-index: 1; }\n date-input-polyfill[data-open="false"] {\n display: none; }\n date-input-polyfill[data-open="true"] {\n display: block; }\n date-input-polyfill select, date-input-polyfill table, date-input-polyfill th, date-input-polyfill td {\n background: #fff;\n color: #000;\n text-shadow: none;\n border: 0;\n padding: 0;\n height: auto;\n width: auto;\n line-height: normal;\n border-radius: 0;\n font-family: sans-serif;\n font-size: 14px;\n box-shadow: none; }\n date-input-polyfill select, date-input-polyfill button {\n border: 0;\n border-bottom: 1px solid #E0E0E0;\n height: 24px;\n vertical-align: top; }\n date-input-polyfill select {\n width: 50%; }\n date-input-polyfill select:first-of-type {\n border-right: 1px solid #E0E0E0;\n width: 30%; }\n date-input-polyfill button {\n padding: 0;\n width: 20%;\n background: #E0E0E0; }\n date-input-polyfill table {\n border-collapse: collapse; }\n date-input-polyfill th, date-input-polyfill td {\n width: 32px;\n padding: 4px;\n text-align: center; }\n date-input-polyfill td[data-day] {\n cursor: pointer; }\n date-input-polyfill td[data-day]:hover {\n background: #E0E0E0; }\n date-input-polyfill [data-selected] {\n font-weight: bold;\n background: #D8EAF6; }\n\ninput[data-has-picker]::-ms-clear {\n display: none; }\n');var a=function(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')},b=function(){function a(a,b){for(var c,d=0;d<b.length;d++)c=b[d],c.enumerable=c.enumerable||!1,c.configurable=!0,'value'in c&&(c.writable=!0),Object.defineProperty(a,c.key,c)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}(),c=function(){function c(){var b=this;if(a(this,c),c.instance)return c.instance;this.date=new Date,this.input=null,this.isOpen=!1,this.container=document.createElement('date-input-polyfill'),this.year=document.createElement('select'),c.createRangeSelect(this.year,this.date.getFullYear()-80,this.date.getFullYear()+20),this.year.className='yearSelect',this.year.addEventListener('change',function(){b.date.setYear(b.year.value),b.refreshDaysMatrix()}),this.container.appendChild(this.year),this.month=document.createElement('select'),this.month.className='monthSelect',this.month.addEventListener('change',function(){b.date.setMonth(b.month.value),b.refreshDaysMatrix()}),this.container.appendChild(this.month),this.today=document.createElement('button'),this.today.textContent='Today',this.today.addEventListener('click',function(){b.date=new Date,b.setInput()}),this.container.appendChild(this.today);var d=document.createElement('table');this.daysHead=document.createElement('thead'),this.days=document.createElement('tbody'),this.days.addEventListener('click',function(a){var c=a.target;if(!c.hasAttribute('data-day'))return!1;var d=b.days.querySelector('[data-selected]');d&&d.removeAttribute('data-selected'),c.setAttribute('data-selected',''),b.date.setDate(parseInt(c.textContent)),b.setInput()}),d.appendChild(this.daysHead),d.appendChild(this.days),this.container.appendChild(d),this.hide(),document.body.appendChild(this.container),document.addEventListener('click',function(a){for(var c=a.target,d=c===b.container;!d&&(c=c.parentNode);)d=c===b.container;'date'===a.target.getAttribute('type')||d||b.hide()})}return b(c,[{key:'hide',value:function(){this.container.setAttribute('data-open',this.isOpen=!1)}},{key:'show',value:function(){this.container.setAttribute('data-open',this.isOpen=!0)}},{key:'goto',value:function(a){var b=a.getBoundingClientRect();this.container.style.top=b.top+b.height+(document.documentElement.scrollTop||document.body.scrollTop)+'px',this.container.style.left=b.left+(document.documentElement.scrollLeft||document.body.scrollLeft)+'px',this.show()}},{key:'attachTo',value:function(a){return a===this.input&&this.isOpen?!1:void(this.input=a,this.sync(),this.goto(this.input.element))}},{key:'sync',value:function(){this.date=this.input.element.valueAsDate?c.absoluteDate(this.input.element.valueAsDate):new Date,this.year.value=this.date.getFullYear(),this.month.value=this.date.getMonth(),this.refreshDaysMatrix()}},{key:'setInput',value:function(){var a=this;this.input.element.value=this.date.getFullYear()+'-'+('0'+(this.date.getMonth()+1)).slice(-2)+'-'+('0'+this.date.getDate()).slice(-2),this.input.element.focus(),setTimeout(function(){a.hide()},100),this.pingInput()}},{key:'refreshLocale',value:function(){if(this.locale===this.input.locale)return!1;this.locale=this.input.locale;for(var a=['<tr>'],b=0,d=this.input.localeText.days.length;b<d;++b)a.push('<th scope="col">'+this.input.localeText.days[b]+'</th>');this.daysHead.innerHTML=a.join(''),c.createRangeSelect(this.month,0,11,this.input.localeText.months,this.date.getMonth()),this.today.textContent=this.input.localeText.today}},{key:'refreshDaysMatrix',value:function(){this.refreshLocale();for(var a=this.date.getFullYear(),b=this.date.getMonth(),d=new Date(a,b,1).getDay(),e=new Date(this.date.getFullYear(),b+1,0).getDate(),f=c.absoluteDate(this.input.element.valueAsDate)||!1,g=f&&a===f.getFullYear()&&b===f.getMonth(),h=[],j=0;j<e+d;++j){if(0==j%7&&h.push('\n '+(0===j?'':'</tr>')+'\n <tr>\n '),j+1<=d){h.push('<td></td>');continue}var i=j+1-d,k=g&&f.getDate()===i;h.push('<td data-day '+(k?'data-selected':'')+'>\n '+i+'\n </td>')}this.days.innerHTML=h.join('')}},{key:'pingInput',value:function(){var a,b;try{a=new Event('input'),b=new Event('change')}catch(c){a=document.createEvent('KeyboardEvent'),a.initEvent('input',!0,!1),b=document.createEvent('KeyboardEvent'),b.initEvent('change',!0,!1)}this.input.element.dispatchEvent(a),this.input.element.dispatchEvent(b)}}],[{key:'createRangeSelect',value:function(a,b,c,d,e){a.innerHTML='';for(var f,g=b;g<=c;++g){f=document.createElement('option'),a.appendChild(f);var h=d?d[g-b]:g;f.text=h,f.value=g,g===e&&(f.selected='selected')}return a}},{key:'absoluteDate',value:function(a){return a&&new Date(a.getTime()+1e3*(60*a.getTimezoneOffset()))}}]),c}();c.instance=null;var d={"en_en-US":{days:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],months:['January','February','March','April','May','June','July','August','September','October','November','December'],today:'Today',format:'M/D/Y'},"en-GB":{days:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],months:['January','February','March','April','May','June','July','August','September','October','November','December'],today:'Today',format:'D/M/Y'},"zh_zh-CN":{days:['\u661F\u671F\u5929','\u661F\u671F\u4E00','\u661F\u671F\u4E8C','\u661F\u671F\u4E09','\u661F\u671F\u56DB','\u661F\u671F\u4E94','\u661F\u671F\u516D'],months:['\u4E00\u6708','\u4E8C\u6708','\u4E09\u6708','\u56DB\u6708','\u4E94\u6708','\u516D\u6708','\u4E03\u6708','\u516B\u6708','\u4E5D\u6708','\u5341\u6708','\u5341\u4E00\u6708','\u5341\u4E8C\u6708'],today:'\u4ECA\u5929',format:'Y/M/D'},"zh-Hans_zh-Hans-CN":{days:['\u5468\u65E5','\u5468\u4E00','\u5468\u4E8C','\u5468\u4E09','\u5468\u56DB','\u5468\u4E94','\u5468\u516D'],months:['\u4E00\u6708','\u4E8C\u6708','\u4E09\u6708','\u56DB\u6708','\u4E94\u6708','\u516D\u6708','\u4E03\u6708','\u516B\u6708','\u4E5D\u6708','\u5341\u6708','\u5341\u4E00\u6708','\u5341\u4E8C\u6708'],today:'\u4ECA\u5929',format:'Y/M/D'},"zh-Hant_zh-Hant-TW":{days:['\u9031\u65E5','\u9031\u4E00','\u9031\u4E8C','\u9031\u4E09','\u9031\u56DB','\u9031\u4E94','\u9031\u516D'],months:['\u4E00\u6708','\u4E8C\u6708','\u4E09\u6708','\u56DB\u6708','\u4E94\u6708','\u516D\u6708','\u4E03\u6708','\u516B\u6708','\u4E5D\u6708','\u5341\u6708','\u5341\u4E00\u6708','\u5341\u4E8C\u6708'],today:'\u4ECA\u5929',format:'Y/M/D'},"de_de-DE":{days:['So','Mo','Di','Mi','Do','Fr','Sa'],months:['Januar','Februar','M\xE4rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],today:'Heute',format:'D.M.Y'},"da_da-DA":{days:['S\xF8n','Man','Tirs','Ons','Tors','Fre','L\xF8r'],months:['Januar','Februar','Marts','April','Maj','Juni','Juli','August','September','Oktober','November','December'],today:'I dag',format:'D/M/Y'},es:{days:['Dom','Lun','Mar','Mi\xE9','Jue','Vie','S\xE1b'],months:['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],today:'Hoy',format:'D/M/Y'},hi:{days:['\u0930\u0935\u093F','\u0938\u094B\u092E','\u092E\u0902\u0917\u0932','\u092C\u0941\u0927','\u0917\u0941\u0930\u0941','\u0936\u0941\u0915\u094D\u0930','\u0936\u0928\u093F'],months:['\u091C\u0928\u0935\u0930\u0940','\u092B\u0930\u0935\u0930\u0940','\u092E\u093E\u0930\u094D\u091A','\u0905\u092A\u094D\u0930\u0947\u0932','\u092E\u0948','\u091C\u0942\u0928','\u091C\u0942\u0932\u093E\u0908','\u0905\u0917\u0938\u094D\u0924','\u0938\u093F\u0924\u092E\u094D\u092C\u0930','\u0906\u0915\u094D\u091F\u094B\u092C\u0930','\u0928\u0935\u092E\u094D\u092C\u0930','\u0926\u093F\u0938\u092E\u094D\u092C\u0930'],today:'\u0906\u091C',format:'D/M/Y'},pt:{days:['Dom','Seg','Ter','Qua','Qui','Sex','S\xE1b'],months:['Janeiro','Fevereiro','Mar\xE7o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],today:'Hoje',format:'D/M/Y'},ja:{days:['\u65E5','\u6708','\u706B','\u6C34','\u6728','\u91D1','\u571F'],months:['1\u6708','2\u6708','3\u6708','4\u6708','5\u6708','6\u6708','7\u6708','8\u6708','9\u6708','10\u6708','11\u6708','12\u6708'],today:'\u4ECA\u65E5',format:'Y/M/D'},"nl_nl-NL_nl-BE":{days:['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'],months:['Januari','Februari','Maart','April','Mei','Juni','Juli','Augustus','September','Oktober','November','December'],today:'Vandaag',format:'D/M/Y'},"tr_tr-TR":{days:['Pzr','Pzt','Sal','\xC7r\u015F','Pr\u015F','Cum','Cmt'],months:['Ocak','\u015Eubat','Mart','Nisan','May\u0131s','Haziran','Temmuz','A\u011Fustos','Eyl\xFCl','Ekim','Kas\u0131m','Aral\u0131k'],today:'Bug\xFCn',format:'D/M/Y'},"fr_fr-FR":{days:['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],months:['Janvier','F\xE9vrier','Mars','Avril','Mai','Juin','Juillet','Ao\xFBt','Septembre','Octobre','Novembre','D\xE9cembre'],today:'Auj.',format:'D/M/Y'},"uk_uk-UA":{days:['\u041D\u0434','\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431'],months:['\u0421\u0456\u0447\u0435\u043D\u044C','\u041B\u044E\u0442\u0438\u0439','\u0411\u0435\u0440\u0435\u0437\u0435\u043D\u044C','\u041A\u0432\u0456\u0442\u0435\u043D\u044C','\u0422\u0440\u0430\u0432\u0435\u043D\u044C','\u0427\u0435\u0440\u0432\u0435\u043D\u044C','\u041B\u0438\u043F\u0435\u043D\u044C','\u0421\u0435\u0440\u043F\u0435\u043D\u044C','\u0412\u0435\u0440\u0435\u0441\u0435\u043D\u044C','\u0416\u043E\u0432\u0442\u0435\u043D\u044C','\u041B\u0438\u0441\u0442\u043E\u043F\u0430\u0434','\u0413\u0440\u0443\u0434\u0435\u043D\u044C'],today:'\u0421\u044C\u043E\u0433\u043E\u0434\u043D\u0456',format:'D.M.Y'},it:{days:['Dom','Lun','Mar','Mer','Gio','Ven','Sab'],months:['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno','Luglio','Agosto','Settembre','ottobre','Novembre','Dicembre'],today:'Oggi',format:'D/M/Y'},pl:{days:['Nie','Pon','Wto','\u015Aro','Czw','Pt','Sob'],months:['Stycze\u0144','Luty','Marzec','Kwiecie\u0144','Maj','Czerwiec','Lipiec','Sierpie\u0144','Wrzesie\u0144','Pa\u017Adziernik','Listopad','Grudzie\u0144'],today:'Dzisiaj',format:'D.M.Y'},cs:{days:['Po','\xDAt','St','\u010Ct','P\xE1','So','Ne'],months:['Leden','\xDAnor','B\u0159ezen','Duben','Kv\u011Bten','\u010Cerven','\u010Cervenec','Srpen','Z\xE1\u0159\xED','\u0158\xEDjen','Listopad','Prosinec'],today:'Dnes',format:'D.M.Y'},ru:{days:['\u0412\u0441','\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431'],months:['\u042F\u043D\u0432\u0430\u0440\u044C','\u0424\u0435\u0432\u0440\u0430\u043B\u044C','\u041C\u0430\u0440\u0442','\u0410\u043F\u0440\u0435\u043B\u044C','\u041C\u0430\u0439','\u0418\u044E\u043D\u044C','\u0418\u044E\u043B\u044C','\u0410\u0432\u0433\u0443\u0441\u0442','\u0421\u0435\u043D\u0442\u044F\u0431\u0440\u044C','\u041E\u043A\u0442\u044F\u0431\u0440\u044C','\u041D\u043E\u044F\u0431\u0440\u044C','\u0414\u0435\u043A\u0430\u0431\u0440\u044C'],today:'\u0421\u0435\u0433\u043E\u0434\u043D\u044F',format:'D.M.Y'}},e=function(){function e(b){var d=this;a(this,e),this.element=b,this.element.setAttribute('data-has-picker','');for(var f=this.element,g='';f.parentNode&&(g=f.getAttribute('lang'),!g);)f=f.parentNode;this.locale=g||'en',this.localeText=this.getLocaleText(),Object.defineProperties(this.element,{value:{get:function(){return d.element.polyfillValue},set:function(a){if(!/^\d{4}-\d{2}-\d{2}$/.test(a))return d.element.polyfillValue='',d.element.setAttribute('value',''),!1;d.element.polyfillValue=a;var b=a.split('-');d.element.setAttribute('value',d.localeText.format.replace('Y',b[0]).replace('M',b[1]).replace('D',b[2]))}},valueAsDate:{get:function(){return d.element.polyfillValue?new Date(d.element.polyfillValue):null},set:function(a){d.element.value=a.toISOString().slice(0,10)}},valueAsNumber:{get:function(){return d.element.value?d.element.valueAsDate.getTime():NaN},set:function(a){d.element.valueAsDate=new Date(a)}}}),this.element.value=this.element.getAttribute('value');var h=function(){c.instance.attachTo(d)};this.element.addEventListener('focus',h),this.element.addEventListener('mousedown',h),this.element.addEventListener('mouseup',h),this.element.addEventListener('keydown',function(a){var b=new Date;switch(a.keyCode){case 27:c.instance.hide();break;case 38:d.element.valueAsDate&&(b.setDate(d.element.valueAsDate.getDate()+1),d.element.valueAsDate=b,c.instance.pingInput());break;case 40:d.element.valueAsDate&&(b.setDate(d.element.valueAsDate.getDate()-1),d.element.valueAsDate=b,c.instance.pingInput());break;default:}c.instance.sync()})}return b(e,[{key:'getLocaleText',value:function(){var a=this.locale.toLowerCase();for(var b in d){var c=b.split('_').map(function(a){return a.toLowerCase()});if(!!~c.indexOf(a))return d[b]}for(var e in d){var f=e.split('_').map(function(a){return a.toLowerCase()});if(!!~f.indexOf(a.substr(0,2)))return d[e]}return this.locale='en',this.getLocaleText()}}],[{key:'supportsDateInput',value:function(){var a=document.createElement('input');a.setAttribute('type','date');var b='not-a-date';return a.setAttribute('value',b),document.currentScript&&!document.currentScript.hasAttribute('data-nodep-date-input-polyfill-debug')&&a.value!==b}},{key:'addPickerToDateInputs',value:function(){var a=document.querySelectorAll('input[type="date"]:not([data-has-picker]):not([readonly])'),b=a.length;if(!b)return!1;for(var c=0;c<b;++c)new e(a[c])}}]),e}();if(!e.supportsDateInput()){var f=function(){c.instance=new c,e.addPickerToDateInputs(),document.querySelector('body').addEventListener('mousedown',function(){e.addPickerToDateInputs()})};if('complete'===document.readyState)f();else{var g=!1;document.addEventListener('DOMContentLoaded',function(){g=!0,f()}),window.addEventListener('load',function(){g||f()})}}});
diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css
new file mode 100644
index 0000000..3ffe996
--- /dev/null
+++ b/erpnext/www/book-appointment/index.css
@@ -0,0 +1,25 @@
+.time-slot {
+ margin: 0 0;
+ border: 0.5px solid #cccccc;
+ min-height: 100px;
+}
+
+.time-slot:hover {
+ background: #ddd;
+}
+
+.time-slot.unavailable {
+ background: #bbb;
+
+ color: #777777
+}
+
+input[type="radio"] {
+ visibility: hidden;
+ display: none;
+}
+
+.time-slot.selected {
+ color: white;
+ background: #5e64ff;
+}
\ No newline at end of file
diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html
new file mode 100644
index 0000000..b705f9e
--- /dev/null
+++ b/erpnext/www/book-appointment/index.html
@@ -0,0 +1,70 @@
+{% extends "templates/web.html" %}
+
+{% block title %}{{ _("Book Appointment") }}{% endblock %}
+
+{% block page_content %}
+<div class="container">
+ <!-- title: Book an appointment -->
+ <div id="select-date">
+ <div class="text-center mb-5">
+ <h3>Book an appointment</h3>
+ <p class="lead">Select the date and your timezone</p>
+ </div>
+ <div class="row justify-content-center mt-3">
+ <div class="col-md-4 align-self-center ">
+ <form name="myform">
+ <input type="date" onchange="validate_date()" name="appointment-date" id="appointment-date"
+ class="form-control mt-3">
+ <select name="appointment-timezone" id="appointment-timezone" class="form-control mt-3">
+ </select>
+ </form>
+ <button class="form-control mt-3 btn btn-dark" id="next-button" onclick="navigate_to_time_select()">
+ Next
+ </button>
+ </div>
+ </div>
+ </div>
+
+ <!--Select Time Slot-->
+ <div id="select-time">
+ <div class="text-center mb-5">
+ <h3>Pick A Time Slot</h3>
+ <p class="lead">Selected date is <span class="date-span">Date Span</span></p>
+ </div>
+ <div class="mt-3 justify-content-center">
+ <div class="row" id="timeslot-container">
+
+ </div>
+ <div class="row justify-content-center">
+ <div class="col-md-4 align-self-center">
+ <button class="form-control mt-5 btn btn-dark" onclick="initialise_enter_details()">
+ Next
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!--Enter Details-->
+ <div id="enter-details">
+ <div class="text-center mb-5">
+ <h3>Add details</h3>
+ <p class="lead">Selected date is <span class="date-span">Date Span</span> at <span class="time-span"> time </span></p>
+ </div>
+ <div class="row justify-content-center mt-3">
+ <div class="col-md-4 align-items-center">
+ <input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name"
+ required>
+ <input class="form-control mt-3" type="tel" name="customer_number" id="customer_number"
+ placeholder="Contact Number" required>
+ <input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype"
+ required>
+ <textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
+ placeholder="Notes"></textarea>
+ <button class="btn btn-primary form-control mt-3" onclick="submit()">Submit</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js
new file mode 100644
index 0000000..e1a2338
--- /dev/null
+++ b/erpnext/www/book-appointment/index.js
@@ -0,0 +1,155 @@
+
+frappe.ready(() => {
+ initialise_select_date()
+})
+var holiday_list = [];
+
+function navigator(page_no) {
+ let select_date_div = document.getElementById('select-date');
+ select_date_div.style.display = 'none';
+ let select_time_div = document.getElementById('select-time');
+ select_time_div.style.display = 'none';
+ let contact_details_div = document.getElementById('enter-details');
+ contact_details_div.style.display = 'none';
+ let page;
+ switch (page_no) {
+ case 1: page = select_date_div; break;
+ case 2: page = select_time_div; break;
+ case 3: page = contact_details_div; break;
+ }
+ page.style.display = 'block'
+}
+
+// Page 1
+async function initialise_select_date() {
+ navigator(1);
+ let timezones, settings;
+ settings = (await frappe.call({
+ method: 'erpnext.www.book-appointment.index.get_appointment_settings'
+ })).message
+ timezones = (await frappe.call({
+ method: 'erpnext.www.book-appointment.index.get_timezones'
+ })).message;
+ holiday_list = (await frappe.call({
+ method: 'erpnext.www.book-appointment.index.get_holiday_list',
+ args: {
+ 'holiday_list_name': settings.holiday_list
+ }
+ })).message;
+ let date_picker = document.getElementById('appointment-date');
+ date_picker.max = holiday_list.to_date;
+ date_picker.min = holiday_list.from_date;
+ date_picker.value = (new Date()).toISOString().substr(0, 10);
+ let timezones_element = document.getElementById('appointment-timezone');
+ var offset = new Date().getTimezoneOffset();
+ timezones.forEach(timezone => {
+ var opt = document.createElement('option');
+ opt.value = timezone.offset;
+ opt.innerHTML = timezone.timezone_name;
+ opt.defaultSelected = (offset == timezone.offset)
+ timezones_element.appendChild(opt)
+ });
+}
+
+function validate_date() {
+ let date_picker = document.getElementById('appointment-date');
+ if (date_picker.value === '') {
+ frappe.throw('Please select a date')
+ }
+}
+
+// Page 2
+async function navigate_to_time_select() {
+ navigator(2);
+ timezone = document.getElementById('appointment-timezone').value
+ date = document.getElementById('appointment-date').value;
+ var date_spans = document.getElementsByClassName('date-span');
+ for (var i = 0; i < date_spans.length; i++) date_spans[i].innerHTML = date;
+ // date_span.addEventListener('click',initialise_select_date)
+ // date_span.style.color = '#5e64ff';
+ // date_span.style.textDecoration = 'underline';
+ // date_span.style.cursor = 'pointer';
+ var slots = (await frappe.call({
+ method: 'erpnext.www.book-appointment.index.get_appointment_slots',
+ args: {
+ date: date,
+ timezone: timezone
+ }
+ })).message;
+ let timeslot_container = document.getElementById('timeslot-container');
+ console.log(slots)
+ if (slots.length <= 0) {
+ let message_div = document.createElement('p');
+
+ message_div.innerHTML = "There are no slots available on this date";
+ timeslot_container.appendChild(message_div);
+ }
+ for (let i = 0; i < slots.length; i++) {
+ const slot = slots[i];
+ var timeslot_div = document.createElement('div');
+ timeslot_div.classList.add('time-slot');
+ timeslot_div.classList.add('col-md');
+ if (!slot.availability) {
+ timeslot_div.classList.add('unavailable')
+ }
+ timeslot_div.innerHTML = slot.time.substr(11, 20);
+ timeslot_div.id = slot.time.substr(11, 20);
+ timeslot_container.appendChild(timeslot_div);
+ }
+ set_default_timeslot()
+ let time_slot_divs = document.getElementsByClassName('time-slot');
+ for (var i = 0; i < time_slot_divs.length; i++) {
+ time_slot_divs[i].addEventListener('click', select_time);
+ }
+}
+
+function select_time() {
+ if (this.classList.contains("unavailable")) {
+ return
+ }
+ try {
+ selected_element = document.getElementsByClassName('selected')[0]
+ } catch (e) {
+ this.classList.add("selected")
+ }
+ selected_element.classList.remove("selected");
+ this.classList.add("selected");
+}
+
+function set_default_timeslot() {
+ let timeslots = document.getElementsByClassName('time-slot')
+ for (let i = 0; i < timeslots.length; i++) {
+ const timeslot = timeslots[i];
+ if (!timeslot.classList.contains('unavailable')) {
+ timeslot.classList.add("selected");
+ break;
+ }
+ }
+}
+
+function initialise_enter_details() {
+ navigator(3);
+ let time_div = document.getElementsByClassName('selected')[0];
+ let time_span = document.getElementsByClassName('time-span')[0];
+ time_span.innerHTML = time_div.id
+}
+
+async function submit() {
+ var date = document.getElementById('appointment-date').value;
+ var time = document.getElementsByClassName('selected')[0].id;
+ contact = {};
+ contact.name = document.getElementById('customer_name').value;
+ contact.number = document.getElementById('customer_number').value;
+ contact.skype = document.getElementById('customer_skype').value;
+ contact.notes = document.getElementById('customer_notes').value;
+ console.log({ date, time, contact });
+ let abc = (await frappe.call({
+ method: 'erpnext.www.book-appointment.index.create_appointment',
+ args: {
+ 'date': date,
+ 'time': time,
+ 'contact': contact
+ }
+ })).message;
+ console.log(abc)
+}
diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py
new file mode 100644
index 0000000..340f3ad
--- /dev/null
+++ b/erpnext/www/book-appointment/index.py
@@ -0,0 +1,130 @@
+import frappe
+import datetime
+import json
+
+@frappe.whitelist(allow_guest=True)
+def get_appointment_settings():
+ settings = frappe.get_doc('Appointment Booking Settings')
+ return settings
+
+@frappe.whitelist(allow_guest=True)
+def get_holiday_list(holiday_list_name):
+ holiday_list = frappe.get_doc('Holiday List',holiday_list_name)
+ return holiday_list
+
+@frappe.whitelist(allow_guest=True)
+def get_timezones():
+ timezones = frappe.get_list('Timezone',fields='*')
+ return timezones
+
+@frappe.whitelist(allow_guest=True)
+def get_appointment_slots(date,timezone):
+ timezone = int(timezone)
+ format_string = '%Y-%m-%d %H:%M:%S'
+ query_start_time = datetime.datetime.strptime(date + ' 00:00:00',format_string)
+ query_end_time = datetime.datetime.strptime(date + ' 23:59:59',format_string)
+ query_start_time = _convert_to_ist(query_start_time,timezone)
+ query_end_time = _convert_to_ist(query_end_time,timezone)
+ # Database queries
+ settings = frappe.get_doc('Appointment Booking Settings')
+ holiday_list = frappe.get_doc('Holiday List', settings.holiday_list)
+ timeslots = get_available_slots_between(query_start_time, query_end_time, settings)
+
+ # Filter timeslots based on date
+ converted_timeslots = []
+ for timeslot in timeslots:
+ # Check if holiday
+ if _is_holiday(timeslot.date(),holiday_list):
+ converted_timeslots.append(dict(time=_convert_to_tz(timeslot,timezone),availability=False))
+ continue
+ # Check availability
+ if check_availabilty(timeslot,settings):
+ converted_timeslots.append(dict(time=_convert_to_tz(timeslot,timezone),availability=True))
+ else:
+ converted_timeslots.append(dict(time=_convert_to_tz(timeslot,timezone),availability=False))
+ date_required = datetime.datetime.strptime(date + ' 00:00:00',format_string).date()
+ converted_timeslots = filter_timeslots(date_required,converted_timeslots)
+ return converted_timeslots
+
+def get_available_slots_between(query_start_time, query_end_time, settings):
+ records = _get_records(query_start_time, query_end_time, settings)
+ timeslots = []
+ appointment_duration = datetime.timedelta(
+ minutes=settings.appointment_duration)
+ for record in records:
+ if record.day_of_week == WEEKDAYS[query_start_time.weekday()]:
+ current_time = _deltatime_to_datetime(
+ query_start_time, record.from_time)
+ end_time = _deltatime_to_datetime(
+ query_start_time, record.to_time)
+ else :
+ current_time = _deltatime_to_datetime(
+ query_end_time, record.from_time)
+ end_time = _deltatime_to_datetime(
+ query_end_time, record.to_time)
+ while current_time + appointment_duration <= end_time:
+ timeslots.append(current_time)
+ current_time += appointment_duration
+ return timeslots
+
+@frappe.whitelist(allow_guest=True)
+def create_appointment(date,time,contact):
+ appointment = frappe.new_doc('Appointment')
+ format_string = '%Y-%m-%d %H:%M:%S'
+ appointment.scheduled_time = datetime.datetime.strptime(date+" "+time,format_string)
+ contact = json.loads(contact)
+ appointment.customer_name = contact['name']
+ appointment.customer_phone_no = contact['number']
+ appointment.customer_skype = contact['skype']
+ appointment.customer_details = contact['notes']
+ appointment.insert()
+
+
+# Helper Functions
+def filter_timeslots(date,timeslots):
+ filtered_timeslots = []
+ for timeslot in timeslots:
+ if(timeslot['time'].date() == date):
+ filtered_timeslots.append(timeslot)
+ return filtered_timeslots
+
+def check_availabilty(timeslot,settings):
+ return frappe.db.count('Appointment',{'scheduled_time':timeslot})<settings.number_of_agents
+
+def _is_holiday(date, holiday_list):
+ for holiday in holiday_list.holidays:
+ if holiday.holiday_date == date:
+ return True
+ return False
+
+def _get_records(start_time, end_time, settings):
+ records = []
+ for record in settings.availability_of_slots:
+ if record.day_of_week == WEEKDAYS[start_time.weekday()] or record.day_of_week == WEEKDAYS[end_time.weekday()]:
+ records.append(record)
+ return records
+
+def _deltatime_to_datetime(date, deltatime):
+ time = (datetime.datetime.min + deltatime).time()
+ return datetime.datetime.combine(date.date(), time)
+
+def _datetime_to_deltatime(date_time):
+ midnight = datetime.datetime.combine(date_time.date(),datetime.time.min)
+ return (date_time-midnight)
+
+def _convert_to_ist(datetime_object, timezone):
+ offset = datetime.timedelta(minutes=timezone)
+ datetime_object = datetime_object + offset
+ offset = datetime.timedelta(minutes=-330)
+ datetime_object = datetime_object - offset
+ return datetime_object
+
+def _convert_to_tz(datetime_object, timezone):
+ offset = datetime.timedelta(minutes=timezone)
+ datetime_object = datetime_object - offset
+ offset = datetime.timedelta(minutes=-330)
+ datetime_object = datetime_object + offset
+ return datetime_object
+
+WEEKDAYS = ["Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday", "Sunday"]
\ No newline at end of file