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