diff --git a/erpnext/www/book-appointment/1.html b/erpnext/www/book-appointment/1.html deleted file mode 100644 index da4fb259191..00000000000 --- a/erpnext/www/book-appointment/1.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %}{{ _("Book Appointment") }}{% endblock %} - -{% block page_content %} -
- -
-

Book an appointment

-

Select the date and your timezone

-
-
-
-
- - -
- -
-
-
- -{% endblock %} \ No newline at end of file diff --git a/erpnext/www/book-appointment/1.js b/erpnext/www/book-appointment/1.js deleted file mode 100644 index 9f6c4872410..00000000000 --- a/erpnext/www/book-appointment/1.js +++ /dev/null @@ -1,25 +0,0 @@ - -{% include 'erpnext/public/js/date_polyfill.js' %} -let holidays = []; -{% if holidays %} - holidays = {{holidays}} -{% endif %} - -function next() { - let date = document.getElementsByName('appointment-date')[0].value; - if(holidays.includes(date)){ - frappe.throw("That day is a holiday") - } - if(date === ""){ - frappe.throw("Please select a date") - } - let tz = document.getElementsByName('appointment-timezone')[0].value; - window.location = `/book-appointment/2?date=${date}&tz=${tz}`; -} - -function ondatechange(){ - let date = document.getElementById('appointment-date') - if(holidays.includes(date.value)){ - frappe.throw("That day is a holiday") - } -} \ No newline at end of file diff --git a/erpnext/www/book-appointment/1.py b/erpnext/www/book-appointment/1.py deleted file mode 100644 index 95169b9bf20..00000000000 --- a/erpnext/www/book-appointment/1.py +++ /dev/null @@ -1,17 +0,0 @@ -import frappe - -def get_context(context): - settings = frappe.get_doc('Appointment Booking Settings') - holiday_list = frappe.get_doc('Holiday List',settings.holiday_list) - holidays = [] - for holiday in holiday_list.holidays: - print(str(holiday.holiday_date)) - holidays.append(str(holiday.holiday_date)) - context.holidays = holidays - context.from_date = holiday_list.from_date - context.to_date = holiday_list.to_date - timezones = frappe.get_all('Timezone',fields=["timezone_name","offset"]) - context.timezones = timezones - - return context - diff --git a/erpnext/www/book-appointment/2.html b/erpnext/www/book-appointment/2.html deleted file mode 100644 index 2a8c5c916c8..00000000000 --- a/erpnext/www/book-appointment/2.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %}{{ _("Book Appointment") }}{% endblock %} - -{% block page_content %} - -
-
- {% if is_holiday %} -

This day is a holiday

- {% else %} -

Pick A Time Slot

-

Selected date is {{ date }}

-
- - -
-
- {% for timeslot in timeslots %} -
{{ timeslot.time.time().strftime('%H : %M') }}
- {% endfor %} -
-
-
- -
-
- {% endif %} -
-
- -{% endblock %} \ No newline at end of file diff --git a/erpnext/www/book-appointment/2.js b/erpnext/www/book-appointment/2.js deleted file mode 100644 index 113564a7228..00000000000 --- a/erpnext/www/book-appointment/2.js +++ /dev/null @@ -1,31 +0,0 @@ -let time_slot_divs = document.getElementsByClassName('time-slot'); - -function get_available_slots() { - frappe.db -} - -function select_time() { - if (this.classList.contains("unavailable")) { - return - } - console.log(this.id) - try{ - selected_element = document.getElementsByClassName('selected')[0] - }catch(e){ - this.classList.add('selected') - } - selected_element.classList.remove('selected'); - this.classList.add('selected'); -} - -for (var i = 0; i < time_slot_divs.length; i++) { - time_slot_divs[i].addEventListener('click', select_time); -} - -function next() { - let urlParams = new URLSearchParams(window.location.search); - let date = urlParams.get("date"); - let tz = urlParams.get("tz"); - let time_slot = document.querySelector(".selected").id; - window.location.href = `/book-appointment/3?date=${date}&tz=${tz}&time=${time_slot}`; -} \ No newline at end of file diff --git a/erpnext/www/book-appointment/2.py b/erpnext/www/book-appointment/2.py deleted file mode 100644 index fa8aafac0b1..00000000000 --- a/erpnext/www/book-appointment/2.py +++ /dev/null @@ -1,93 +0,0 @@ -import frappe -import datetime - - -def get_context(context): - # Get query parameters - date = frappe.form_dict['date'] - tz = frappe.form_dict['tz'] - tz = int(tz) - # Database queries - settings = frappe.get_doc('Appointment Booking Settings') - holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) - # Format datetimes - format_string = '%Y-%m-%d %H:%M:%S' - start_time = datetime.datetime.strptime(date+' 00:00:00', format_string) - end_time = datetime.datetime.strptime(date+' 23:59:59', format_string) - # Convert to ist - start_time = _convert_to_ist(start_time, tz) - end_time = _convert_to_ist(end_time, tz) - timeslots = get_available_slots_between(start_time, end_time, settings) - converted_timeslots = [] - print('Appointments') - print(frappe.get_list('Appointment',fields=['from_time'])) - for timeslot in timeslots: - if timeslot > end_time or timeslot < start_time: - pass - else: - if frappe.db.count('Appointment',{'from_time':start_time.time()}) < settings.number_of_agents: - converted_timeslots.append(dict(time=_convert_to_tz(timeslot, tz), unavailable=False)) - else: - converted_timeslots.append(dict(time=_convert_to_tz(timeslot, tz),unavailable=True)) - - context.timeslots = converted_timeslots - context.date = date - return context - -def _is_holiday(date, holiday_list): - for holiday in holiday_list.holidays: - if holiday.holiday_date.isoformat() == date: - return True - return False - -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 - -def get_available_slots_between(start_time_parameter, end_time_parameter, settings): - records = get_records(start_time_parameter, end_time_parameter, settings) - timeslots = [] - appointment_duration = datetime.timedelta( - minutes=settings.appointment_duration) - for record in records: - if record.day_of_week == weekdays[start_time_parameter.weekday()]: - current_time = _deltatime_to_datetime( - start_time_parameter, record.from_time) - end_time = _deltatime_to_datetime( - start_time_parameter, record.to_time) - elif record.day_of_week == weekdays[end_time_parameter.weekday()]: - current_time = _deltatime_to_datetime( - end_time_parameter, record.from_time) - end_time = _deltatime_to_datetime( - end_time_parameter, record.to_time) - while current_time + appointment_duration <= end_time: - timeslots.append(current_time) - current_time += appointment_duration - return timeslots - - -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) - - -weekdays = ["Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", "Sunday"] diff --git a/erpnext/www/book-appointment/3.html b/erpnext/www/book-appointment/3.html deleted file mode 100644 index b627a0c9cf7..00000000000 --- a/erpnext/www/book-appointment/3.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %}{{ _("Book Appointment") }}{% endblock %} - -{% block page_content %} -
- -
-

Add details

-

Selected date is {{ date }} at {{ time }}

-
-
-
- - - - - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/erpnext/www/book-appointment/3.js b/erpnext/www/book-appointment/3.js deleted file mode 100644 index 23c55a3fce2..00000000000 --- a/erpnext/www/book-appointment/3.js +++ /dev/null @@ -1,11 +0,0 @@ -function submit(){ - let params = new URLSearchParams(window.location.search); - const date = params.get('date'); - const time = params.get('time'); - const tz = params.get('tz'); - const customer_name = document.getElementById('customer_name').value; - const customer_number = document.getElementById('customer_number').value; - const customer_skype = document.getElementById('customer_skype').value; - const customer_notes = document.getElementById('customer_notes').value; - console.log({date,time,tz,customer_name,customer_number,customer_skype,customer_notes}); -} \ No newline at end of file diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css new file mode 100644 index 00000000000..3ffe996238e --- /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 00000000000..b705f9e82d4 --- /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 %} +
+ +
+
+

Book an appointment

+

Select the date and your timezone

+
+
+
+
+ + +
+ +
+
+
+ + +
+
+

Pick A Time Slot

+

Selected date is Date Span

+
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+

Add details

+

Selected date is Date Span at time

+
+
+
+ + + + + +
+
+
+
+ +{% 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 00000000000..1482c511d43 --- /dev/null +++ b/erpnext/www/book-appointment/index.js @@ -0,0 +1,146 @@ + +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 +} + +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 }); +} diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py new file mode 100644 index 00000000000..15d5f9a49d1 --- /dev/null +++ b/erpnext/www/book-appointment/index.py @@ -0,0 +1,123 @@ +import frappe +import datetime + +@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.frappe.get_doc('Appointment') + appointment.scheduled_time = date + +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})