From 7b9e30914fdd97fa3894c09e04be74aa11ff37af Mon Sep 17 00:00:00 2001 From: pranav nachnekar Date: Wed, 28 Aug 2019 16:57:37 +0530 Subject: [PATCH 001/157] Added doctypes and portal pages --- erpnext/crm/doctype/appointment/__init__.py | 0 .../crm/doctype/appointment/appointment.js | 8 ++ .../crm/doctype/appointment/appointment.json | 91 +++++++++++++++++++ .../crm/doctype/appointment/appointment.py | 10 ++ .../doctype/appointment/test_appointment.py | 10 ++ .../appointment_booking_settings/__init__.py | 0 .../appointment_booking_settings.js | 18 ++++ .../appointment_booking_settings.json | 72 +++++++++++++++ .../appointment_booking_settings.py | 10 ++ .../test_appointment_booking_settings.py | 10 ++ .../doctype/availability_of_slots/__init__.py | 0 .../availability_of_slots.json | 46 ++++++++++ .../availability_of_slots.py | 10 ++ erpnext/crm/doctype/timezone/__init__.py | 0 erpnext/crm/doctype/timezone/test_timezone.py | 10 ++ erpnext/crm/doctype/timezone/timezone.js | 8 ++ erpnext/crm/doctype/timezone/timezone.json | 52 +++++++++++ erpnext/crm/doctype/timezone/timezone.py | 10 ++ erpnext/www/book-appointment/1.html | 31 +++++++ erpnext/www/book-appointment/1.js | 14 +++ erpnext/www/book-appointment/1.py | 17 ++++ erpnext/www/book-appointment/2.html | 85 +++++++++++++++++ erpnext/www/book-appointment/2.js | 27 ++++++ erpnext/www/book-appointment/2.py | 28 ++++++ erpnext/www/book-appointment/3.html | 22 +++++ erpnext/www/book-appointment/3.js | 11 +++ 26 files changed, 600 insertions(+) create mode 100644 erpnext/crm/doctype/appointment/__init__.py create mode 100644 erpnext/crm/doctype/appointment/appointment.js create mode 100644 erpnext/crm/doctype/appointment/appointment.json create mode 100644 erpnext/crm/doctype/appointment/appointment.py create mode 100644 erpnext/crm/doctype/appointment/test_appointment.py create mode 100644 erpnext/crm/doctype/appointment_booking_settings/__init__.py create mode 100644 erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js create mode 100644 erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json create mode 100644 erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py create mode 100644 erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py create mode 100644 erpnext/crm/doctype/availability_of_slots/__init__.py create mode 100644 erpnext/crm/doctype/availability_of_slots/availability_of_slots.json create mode 100644 erpnext/crm/doctype/availability_of_slots/availability_of_slots.py create mode 100644 erpnext/crm/doctype/timezone/__init__.py create mode 100644 erpnext/crm/doctype/timezone/test_timezone.py create mode 100644 erpnext/crm/doctype/timezone/timezone.js create mode 100644 erpnext/crm/doctype/timezone/timezone.json create mode 100644 erpnext/crm/doctype/timezone/timezone.py create mode 100644 erpnext/www/book-appointment/1.html create mode 100644 erpnext/www/book-appointment/1.js create mode 100644 erpnext/www/book-appointment/1.py create mode 100644 erpnext/www/book-appointment/2.html create mode 100644 erpnext/www/book-appointment/2.js create mode 100644 erpnext/www/book-appointment/2.py create mode 100644 erpnext/www/book-appointment/3.html create mode 100644 erpnext/www/book-appointment/3.js diff --git a/erpnext/crm/doctype/appointment/__init__.py b/erpnext/crm/doctype/appointment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/appointment/appointment.js b/erpnext/crm/doctype/appointment/appointment.js new file mode 100644 index 0000000000..4e41047fa1 --- /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 0000000000..24cbd92bc7 --- /dev/null +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -0,0 +1,91 @@ +{ + "autoname": "format:APMT-{appointment_date}-{####}", + "creation": "2019-08-27 10:48:27.926283", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "from_time", + "to_time", + "appointment_date", + "customer_details_section", + "customer_name", + "customer_phone_number", + "customer_skype", + "customer_details" + ], + "fields": [ + { + "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 Tme", + "reqd": 1 + }, + { + "fieldname": "appointment_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date ", + "reqd": 1 + }, + { + "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" + } + ], + "modified": "2019-08-27 12:43:30.143937", + "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 + } + ], + "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 0000000000..204b066031 --- /dev/null +++ b/erpnext/crm/doctype/appointment/appointment.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 Appointment(Document): + pass diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py new file mode 100644 index 0000000000..702ac7176f --- /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 0000000000..e69de29bb2 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 0000000000..465df2c3a6 --- /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 0000000000..ed6150a210 --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -0,0 +1,72 @@ +{ + "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-08-27 17:32:46.208951", + "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 + } + ], + "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 0000000000..33076366c1 --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.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 AppointmentBookingSettings(Document): + pass 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 0000000000..3dc3c39971 --- /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 0000000000..e69de29bb2 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 0000000000..d26f7ced35 --- /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 0000000000..8258471eed --- /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 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/timezone/test_timezone.py b/erpnext/crm/doctype/timezone/test_timezone.py new file mode 100644 index 0000000000..92a8889cce --- /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 0000000000..4dc57db2ed --- /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 0000000000..9eb8ed9012 --- /dev/null +++ b/erpnext/crm/doctype/timezone/timezone.json @@ -0,0 +1,52 @@ +{ + "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-08-27 11:39:30.328670", + "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 + } + ], + "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 0000000000..20e7d378f7 --- /dev/null +++ b/erpnext/crm/doctype/timezone/timezone.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 Timezone(Document): + pass diff --git a/erpnext/www/book-appointment/1.html b/erpnext/www/book-appointment/1.html new file mode 100644 index 0000000000..db4ef26651 --- /dev/null +++ b/erpnext/www/book-appointment/1.html @@ -0,0 +1,31 @@ +{% 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 new file mode 100644 index 0000000000..d05c2535c1 --- /dev/null +++ b/erpnext/www/book-appointment/1.js @@ -0,0 +1,14 @@ + +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") + } + let tz = document.getElementsByName('appointment-timezone')[0].value; + window.location = `/book-appointment/2?date=${date}&tz=${tz}`; +} \ No newline at end of file diff --git a/erpnext/www/book-appointment/1.py b/erpnext/www/book-appointment/1.py new file mode 100644 index 0000000000..95169b9bf2 --- /dev/null +++ b/erpnext/www/book-appointment/1.py @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000000..198b12d67c --- /dev/null +++ b/erpnext/www/book-appointment/2.html @@ -0,0 +1,85 @@ +{% 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 }}

+
+ + +
+
+
12 pm to 1 am
+
1 am to 2 am
+
2 am to 3 am
+
3 am to 4 am
+
4 am to 5 am
+
5 am to 6 am
+
6 am to 7 am
+
7 am to 8 am
+
+
+
8 am to 9 am
+
9 am to 10 am
+
10 am to 11 am
+
11 am to 12 am
+
12 am to 1 pm
+
1 pm to 2 pm
+
2 pm to 3 pm
+
3 pm to 4pm
+
+
+
4pm to 5pm
+
5 pm to 6 pm
+
6 pm to 7 pm
+
7 pm to 8 pm
+
8 pm to 9 pm
+
9 pm to 10 pm
+
10 pm to 11 pm
+
11 pm to 12 pm
+
+
+
+ +
+
+ {% endif %} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/erpnext/www/book-appointment/2.js b/erpnext/www/book-appointment/2.js new file mode 100644 index 0000000000..bdcabdc5ef --- /dev/null +++ b/erpnext/www/book-appointment/2.js @@ -0,0 +1,27 @@ +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) + var selected = document.getElementsByClassName('selected')[0]; + selected.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 new file mode 100644 index 0000000000..688545a77d --- /dev/null +++ b/erpnext/www/book-appointment/2.py @@ -0,0 +1,28 @@ +import frappe +import datetime + + +def get_context(context): + context.date = frappe.form_dict['date'] + settings = frappe.get_doc('Appointment Booking Settings') + holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) + if(is_holiday(context.date,holiday_list)): + context.is_holiday = True + return context + get_time_slots(context.date,settings) + # time_slots = get_time_slots(date) + return context + +def is_holiday(date,holiday_list): + for holiday in holiday_list.holidays: + if holiday.holiday_date.isoformat() == date: + print('matched') + return True + return False + + + +def _deltatime_to_time(deltatime): + return (datetime.datetime.min + deltatime).time() + +weekdays = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"] \ No newline at end of file diff --git a/erpnext/www/book-appointment/3.html b/erpnext/www/book-appointment/3.html new file mode 100644 index 0000000000..b627a0c9cf --- /dev/null +++ b/erpnext/www/book-appointment/3.html @@ -0,0 +1,22 @@ +{% 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 new file mode 100644 index 0000000000..23c55a3fce --- /dev/null +++ b/erpnext/www/book-appointment/3.js @@ -0,0 +1,11 @@ +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 From dbd72ea89d0a985dea8ef661d36eb23f2f2abcde Mon Sep 17 00:00:00 2001 From: pranav nachnekar Date: Thu, 29 Aug 2019 16:56:19 +0530 Subject: [PATCH 002/157] Added time generation --- erpnext/www/book-appointment/2.html | 31 +--------- erpnext/www/book-appointment/2.js | 8 ++- erpnext/www/book-appointment/2.py | 87 +++++++++++++++++++++++++---- 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/erpnext/www/book-appointment/2.html b/erpnext/www/book-appointment/2.html index 198b12d67c..2a8c5c916c 100644 --- a/erpnext/www/book-appointment/2.html +++ b/erpnext/www/book-appointment/2.html @@ -42,34 +42,9 @@
-
12 pm to 1 am
-
1 am to 2 am
-
2 am to 3 am
-
3 am to 4 am
-
4 am to 5 am
-
5 am to 6 am
-
6 am to 7 am
-
7 am to 8 am
-
-
-
8 am to 9 am
-
9 am to 10 am
-
10 am to 11 am
-
11 am to 12 am
-
12 am to 1 pm
-
1 pm to 2 pm
-
2 pm to 3 pm
-
3 pm to 4pm
-
-
-
4pm to 5pm
-
5 pm to 6 pm
-
6 pm to 7 pm
-
7 pm to 8 pm
-
8 pm to 9 pm
-
9 pm to 10 pm
-
10 pm to 11 pm
-
11 pm to 12 pm
+ {% for timeslot in timeslots %} +
{{ timeslot.time.time().strftime('%H : %M') }}
+ {% endfor %}
diff --git a/erpnext/www/book-appointment/2.js b/erpnext/www/book-appointment/2.js index bdcabdc5ef..113564a722 100644 --- a/erpnext/www/book-appointment/2.js +++ b/erpnext/www/book-appointment/2.js @@ -9,8 +9,12 @@ function select_time() { return } console.log(this.id) - var selected = document.getElementsByClassName('selected')[0]; - selected.classList.remove('selected'); + try{ + selected_element = document.getElementsByClassName('selected')[0] + }catch(e){ + this.classList.add('selected') + } + selected_element.classList.remove('selected'); this.classList.add('selected'); } diff --git a/erpnext/www/book-appointment/2.py b/erpnext/www/book-appointment/2.py index 688545a77d..fa8aafac0b 100644 --- a/erpnext/www/book-appointment/2.py +++ b/erpnext/www/book-appointment/2.py @@ -3,26 +3,91 @@ import datetime def get_context(context): - context.date = frappe.form_dict['date'] + # 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) - if(is_holiday(context.date,holiday_list)): - context.is_holiday = True - return context - get_time_slots(context.date,settings) - # time_slots = get_time_slots(date) + # 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): +def _is_holiday(date, holiday_list): for holiday in holiday_list.holidays: if holiday.holiday_date.isoformat() == date: - print('matched') 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 _deltatime_to_time(deltatime): - return (datetime.datetime.min + deltatime).time() +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 -weekdays = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"] \ No newline at end of file + +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"] From 17906d5599d33c57033f68c060d24afb7eca505c Mon Sep 17 00:00:00 2001 From: pranav nachnekar Date: Fri, 30 Aug 2019 10:49:07 +0530 Subject: [PATCH 003/157] Added polyfill for datepicker for Safari and IE support --- erpnext/public/js/date_polyfill.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 erpnext/public/js/date_polyfill.js diff --git a/erpnext/public/js/date_polyfill.js b/erpnext/public/js/date_polyfill.js new file mode 100644 index 0000000000..6899d82291 --- /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=0,d=this.input.localeText.days.length;b'+this.input.localeText.days[b]+'');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')+'\n \n '),j+1<=d){h.push('');continue}var i=j+1-d,k=g&&f.getDate()===i;h.push('\n '+i+'\n ')}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 Date: Fri, 30 Aug 2019 10:49:43 +0530 Subject: [PATCH 004/157] Better date validation --- erpnext/www/book-appointment/1.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/www/book-appointment/1.js b/erpnext/www/book-appointment/1.js index d05c2535c1..9f6c487241 100644 --- a/erpnext/www/book-appointment/1.js +++ b/erpnext/www/book-appointment/1.js @@ -1,4 +1,5 @@ +{% include 'erpnext/public/js/date_polyfill.js' %} let holidays = []; {% if holidays %} holidays = {{holidays}} @@ -9,6 +10,16 @@ function next() { 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 From 828fea6d66fc15eec19c27ed4bcc65f24154f913 Mon Sep 17 00:00:00 2001 From: pranav nachnekar Date: Fri, 30 Aug 2019 10:49:57 +0530 Subject: [PATCH 005/157] formatting and date validation --- erpnext/www/book-appointment/1.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/1.html b/erpnext/www/book-appointment/1.html index db4ef26651..da4fb25919 100644 --- a/erpnext/www/book-appointment/1.html +++ b/erpnext/www/book-appointment/1.html @@ -12,7 +12,14 @@
- + - -
- -
-
-
- -{% 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 9f6c487241..0000000000 --- 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 95169b9bf2..0000000000 --- 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 2a8c5c916c..0000000000 --- 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 113564a722..0000000000 --- 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 fa8aafac0b..0000000000 --- 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 b627a0c9cf..0000000000 --- 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 23c55a3fce..0000000000 --- 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 0000000000..3ffe996238 --- /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 0000000000..b705f9e82d --- /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 0000000000..1482c511d4 --- /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 0000000000..15d5f9a49d --- /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}) Date: Tue, 3 Sep 2019 12:58:12 +0530 Subject: [PATCH 010/157] A --- .../crm/doctype/appointment/appointment.py | 9 +++++-- .../appointment_booking_settings.json | 2 +- .../appointment_booking_settings.py | 24 +++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 204b066031..cce6a1d684 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -3,8 +3,13 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document class Appointment(Document): - pass + 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_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index d54b568c34..cf27f770c2 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -48,7 +48,7 @@ } ], "issingle": 1, - "modified": "2019-09-01 10:20:06.935115", + "modified": "2019-09-03 12:27:09.763730", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index 33076366c1..8f1fb14f5b 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -3,8 +3,28 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ +import datetime from frappe.model.document import Document class AppointmentBookingSettings(Document): - pass + 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')) + From c5b2a5866904c8426e6e5eb314b1033a9a94e86d Mon Sep 17 00:00:00 2001 From: pranav nachnekar Date: Tue, 3 Sep 2019 14:16:47 +0530 Subject: [PATCH 011/157] Added submit fucntionality --- erpnext/www/book-appointment/index.js | 13 +++++++++++-- erpnext/www/book-appointment/index.py | 17 ++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 1482c511d4..e1a2338bfd 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -80,7 +80,7 @@ async function navigate_to_time_select() { 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); } @@ -134,7 +134,7 @@ function initialise_enter_details() { time_span.innerHTML = time_div.id } -function submit() { +async function submit() { var date = document.getElementById('appointment-date').value; var time = document.getElementsByClassName('selected')[0].id; contact = {}; @@ -143,4 +143,13 @@ function submit() { 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 index 15d5f9a49d..340f3adb67 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -1,5 +1,6 @@ import frappe import datetime +import json @frappe.whitelist(allow_guest=True) def get_appointment_settings(): @@ -68,10 +69,18 @@ def get_available_slots_between(query_start_time, query_end_time, settings): @frappe.whitelist(allow_guest=True) def create_appointment(date,time,contact): - - appointment = frappe.frappe.get_doc('Appointment') - appointment.scheduled_time = date + 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: @@ -82,8 +91,6 @@ def filter_timeslots(date,timeslots): def check_availabilty(timeslot,settings): return frappe.db.count('Appointment',{'scheduled_time':timeslot}) Date: Tue, 3 Sep 2019 14:16:56 +0530 Subject: [PATCH 012/157] changed Autoname --- erpnext/crm/doctype/appointment/appointment.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 435607f99a..aee16f799f 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -1,5 +1,5 @@ { - "autoname": "format:APMT-{appointment_date}-{####}", + "autoname": "format:APMT-{scheduled_time}-{####}", "creation": "2019-08-27 10:48:27.926283", "doctype": "DocType", "editable_grid": 1, @@ -48,7 +48,7 @@ "reqd": 1 } ], - "modified": "2019-09-01 10:19:50.711989", + "modified": "2019-09-03 14:07:16.837591", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", From 217aadba7e40169ac27e72ac38eb811d1df0e1d5 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 14:43:41 +0530 Subject: [PATCH 013/157] Better autoname --- erpnext/crm/doctype/appointment/appointment.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index aee16f799f..ec63420e98 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -1,5 +1,5 @@ { - "autoname": "format:APMT-{scheduled_time}-{####}", + "autoname": "format:APMT-{customer_name}-{####}", "creation": "2019-08-27 10:48:27.926283", "doctype": "DocType", "editable_grid": 1, @@ -48,7 +48,7 @@ "reqd": 1 } ], - "modified": "2019-09-03 14:07:16.837591", + "modified": "2019-09-09 12:23:33.611408", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", From 48e43e2421dda3ae29d115af6b2d326062730f99 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 14:43:55 +0530 Subject: [PATCH 014/157] build fix --- .../doctype/availabilty_of_slots/__init__.py | 0 .../availability_of_slots.json | 46 +++++++++++++++++++ .../availabilty_of_slots.py | 10 ++++ 3 files changed, 56 insertions(+) create mode 100644 erpnext/crm/doctype/availabilty_of_slots/__init__.py create mode 100644 erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json create mode 100644 erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py diff --git a/erpnext/crm/doctype/availabilty_of_slots/__init__.py b/erpnext/crm/doctype/availabilty_of_slots/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json b/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json new file mode 100644 index 0000000000..d26f7ced35 --- /dev/null +++ b/erpnext/crm/doctype/availabilty_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/availabilty_of_slots/availabilty_of_slots.py b/erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py new file mode 100644 index 0000000000..62436b8da7 --- /dev/null +++ b/erpnext/crm/doctype/availabilty_of_slots/availabilty_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 AvailabiltyOfSlots(Document): + pass \ No newline at end of file From 63dbacd7c034e9b8bc94d283e4509cdfdea054fe Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 15:19:57 +0530 Subject: [PATCH 015/157] Disabled caching --- erpnext/www/book-appointment/index.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 340f3adb67..e853a35fff 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -2,6 +2,8 @@ import frappe import datetime import json +no_cache = 1 + @frappe.whitelist(allow_guest=True) def get_appointment_settings(): settings = frappe.get_doc('Appointment Booking Settings') From 10711dd09daeddee84375b8d7663943daba73271 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 15:41:20 +0530 Subject: [PATCH 016/157] Refactor UI --- .../crm/doctype/appointment/appointment.json | 8 +- erpnext/www/book-appointment/index.css | 23 ++- erpnext/www/book-appointment/index.html | 85 ++++---- erpnext/www/book-appointment/index.js | 181 +++++++++++------- 4 files changed, 171 insertions(+), 126 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index ec63420e98..8392549fd3 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -28,12 +28,14 @@ { "fieldname": "customer_phone_number", "fieldtype": "Data", - "label": "Phone Number" + "label": "Phone Number", + "reqd": 1 }, { "fieldname": "customer_skype", "fieldtype": "Data", - "label": "Skype ID" + "label": "Skype ID", + "reqd": 1 }, { "fieldname": "customer_details", @@ -48,7 +50,7 @@ "reqd": 1 } ], - "modified": "2019-09-09 12:23:33.611408", + "modified": "2019-09-09 15:40:21.881421", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css index 3ffe996238..a6e6313f79 100644 --- a/erpnext/www/book-appointment/index.css +++ b/erpnext/www/book-appointment/index.css @@ -1,7 +1,12 @@ .time-slot { - margin: 0 0; + margin-bottom: 2em; + margin-left: 0.5em; + margin-right: 0.5em; + border-radius: 0.4em; + cursor: pointer; border: 0.5px solid #cccccc; - min-height: 100px; + min-height: 75px; + padding: 0.5em 1em; } .time-slot:hover { @@ -9,9 +14,13 @@ } .time-slot.unavailable { - background: #bbb; + background: #CBD5E0; + cursor: not-allowed; + color: #718096 +} - color: #777777 +.time-slot.unavailable .text-muted { + color: #718096 } input[type="radio"] { @@ -22,4 +31,8 @@ input[type="radio"] { .time-slot.selected { color: white; background: #5e64ff; -} \ No newline at end of file +} + +.time-slot.selected .text-muted { + color: #EDF2F7 !important; +} diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index b705f9e82d..b915484f54 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -2,69 +2,60 @@ {% block title %}{{ _("Book Appointment") }}{% endblock %} +{% block script %} + + +{% endblock %} + {% block page_content %}
-
+

Book an appointment

-

Select the date and your timezone

+

Select the date and your timezone

-
-
- - + -
- -
-
-
- - -
-
-

Pick A Time Slot

-

Selected date is Date Span

-
-
-
- -
-
-
-
-
- - -
-
-

Add details

-

Selected date is Date Span at time

+
+
-
- - - - - +
+
+ +
+
+

Add details

+

Selected date is at +

+
+
+
+ + + + + +
+
+
+
{% endblock %} \ No newline at end of file diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index e1a2338bfd..bb21ddf273 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -2,47 +2,35 @@ frappe.ready(() => { initialise_select_date() }) -var holiday_list = []; +window.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' +async function initialise_select_date() { + document.getElementById('enter-details').style.display = 'none'; + await get_global_variables(); + setup_date_picker(); + setup_timezone_selector(); + hide_next_button(); } -// Page 1 -async function initialise_select_date() { - navigator(1); - let timezones, settings; - settings = (await frappe.call({ +async function get_global_variables() { + window.appointment_settings = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_appointment_settings' })).message - timezones = (await frappe.call({ + window.timezones = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_timezones' })).message; - holiday_list = (await frappe.call({ + window.holiday_list = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_holiday_list', args: { - 'holiday_list_name': settings.holiday_list + 'holiday_list_name': window.appointment_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); +} + +function setup_timezone_selector() { let timezones_element = document.getElementById('appointment-timezone'); var offset = new Date().getTimezoneOffset(); - timezones.forEach(timezone => { + window.timezones.forEach(timezone => { var opt = document.createElement('option'); opt.value = timezone.offset; opt.innerHTML = timezone.timezone_name; @@ -51,56 +39,90 @@ async function initialise_select_date() { }); } -function validate_date() { +function setup_date_picker() { let date_picker = document.getElementById('appointment-date'); - if (date_picker.value === '') { - frappe.throw('Please select a date') - } + let today = new Date(); + date_picker.min = today.toISOString().substr(0, 10); + date_picker.max = window.holiday_list.to_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({ +function hide_next_button(){ + let next_button = document.getElementById('next-button'); + next_button.disabled = true; + next_button.onclick = ()=>{frappe.msgprint("Please select a date and time")}; +} + +function show_next_button(){ + let next_button = document.getElementById('next-button'); + next_button.disabled = false; + next_button.onclick = setup_details_page; +} + +function on_date_or_timezone_select() { + let date_picker = document.getElementById('appointment-date'); + let timezone = document.getElementById('appointment-timezone'); + if (date_picker.value === '') { + clear_time_slots(); + hide_next_button(); + frappe.throw('Please select a date'); + } + window.selected_date = date_picker.value; + window.selected_timezone = timezone.value; + update_time_slots(date_picker.value, timezone.value); +} + +async function get_time_slots(date, timezone) { + debugger + let 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'); + return slots; +} +async function update_time_slots(selected_date, selected_timezone) { + let timeslot_container = document.getElementById('timeslot-container'); + window.slots = await get_time_slots(selected_date, selected_timezone); + clear_time_slots(); + if (window.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); + return } - for (let i = 0; i < slots.length; i++) { - const slot = slots[i]; + window.slots.forEach(slot => { + let start_time = new Date(slot.time) 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.innerHTML = get_slot_layout(start_time); timeslot_div.id = slot.time.substr(11, 20); + timeslot_div.addEventListener('click', select_time); timeslot_container.appendChild(timeslot_div); + }); + set_default_timeslot(); + show_next_button(); +} + +function clear_time_slots() { + let timeslot_container = document.getElementById('timeslot-container'); + while (timeslot_container.firstChild) { + timeslot_container.removeChild(timeslot_container.firstChild) } - 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 get_slot_layout(time) { + time = new Date(time) + let start_time_string = moment(time).format("LT"); + let end_time = moment(time).add('1','hours'); + let end_time_string = end_time.format("LT"); + return `${start_time_string}
to ${end_time_string}`; } function select_time() { @@ -110,8 +132,10 @@ function select_time() { try { selected_element = document.getElementsByClassName('selected')[0] } catch (e) { + debugger this.classList.add("selected") } + window.selected_time = this.id selected_element.classList.remove("selected"); this.classList.add("selected"); } @@ -127,23 +151,23 @@ function set_default_timeslot() { } } -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 setup_details_page(){ + let page1 = document.getElementById('select-date-time'); + let page2 = document.getElementById('enter-details'); + page1.style.display = 'none'; + page2.style.display = 'block'; + + let date_container = document.getElementsByClassName('date-span')[0]; + let time_container = document.getElementsByClassName('time-span')[0]; + + date_container.innerHTML = new Date(window.selected_date).toLocaleDateString(); + time_container.innerHTML = moment(window.selected_time,"HH:mm:ss").format("LT"); } 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({ + // form validation here + form_validation(); + let appointment = (await frappe.call({ method: 'erpnext.www.book-appointment.index.create_appointment', args: { 'date': date, @@ -151,5 +175,20 @@ async function submit() { 'contact': contact } })).message; - console.log(abc) + frappe.msgprint(__('Appointment Created Successfully')); + let button = document.getElementById('submit-button'); + button.disabled = true; + button.onclick = () => { console.log('This should never have happened') } } + +function form_validation(){ + var date = window.selected_date; + 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; + window.contact = contact + console.log({ date, time, contact }); +} From 5945144c08e00966a4cdbeb662e29be9d0952c0b Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 16:35:48 +0530 Subject: [PATCH 017/157] Added tests --- .../doctype/appointment/test_appointment.py | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 702ac7176f..e446712d01 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -3,8 +3,45 @@ # See license.txt from __future__ import unicode_literals -# import frappe +import frappe import unittest +import datetime + + +def create_appointments(number): + for i in range(1, number): + frappe.get_doc({ + 'doctype': 'Appointment', + 'scheduled_time': datetime.datetime.min, + 'customer_name': 'Test Customer'+str(i), + 'customer_phone_number': '8088', + 'customer_skype': 'test'+str(i), + }) + class TestAppointment(unittest.TestCase): - pass + def setUp(self): + settings = frappe.get_doc('Appointment Booking Settings') + create_appointments(settings.number_of_agents) + frappe.get_doc({ + 'doctype': 'Appointment', + 'scheduled_time': datetime.datetime.min, + 'customer_name': 'Extra Customer', + 'customer_phone_number': '8088', + 'customer_skype': 'extra_customer', + }) + + def tearDown(self): + delete_appointments() + + def delete_appointments(self): + doc_list = frappe.get_list('Appointment',filters={'scheduled_time':datetime.datetime.min,'customer_phone_number':'8088'}) + for doc in doc_list: + doc.delete() + + def test_number_of_appointments(self): + settings = frappe.get_doc('Appointment Booking Settings') + self.assertLessEqual(frappe.db.count('Apoointment', + filters={'scheduled_time': datetime.datetime.min, 'customer_name':}), + settings.number_of_agents, + "Number of appointments exceed number of agents") From 20c7c290fa0c5564d5c4b226203152948db3b458 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 16:36:01 +0530 Subject: [PATCH 018/157] Formatting --- erpnext/www/book-appointment/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index bb21ddf273..5bc8af0bde 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -156,10 +156,8 @@ function setup_details_page(){ let page2 = document.getElementById('enter-details'); page1.style.display = 'none'; page2.style.display = 'block'; - let date_container = document.getElementsByClassName('date-span')[0]; let time_container = document.getElementsByClassName('time-span')[0]; - date_container.innerHTML = new Date(window.selected_date).toLocaleDateString(); time_container.innerHTML = moment(window.selected_time,"HH:mm:ss").format("LT"); } From db21f86b260f7ef68a06adfa736eee522f734431 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 17:01:40 +0530 Subject: [PATCH 019/157] Removed unneccessary doctype --- .../doctype/availabilty_of_slots/__init__.py | 0 .../availability_of_slots.json | 46 ------------------- .../availabilty_of_slots.py | 10 ---- erpnext/www/book-appointment/index.js | 9 ++-- erpnext/www/book-appointment/index.py | 2 +- 5 files changed, 6 insertions(+), 61 deletions(-) delete mode 100644 erpnext/crm/doctype/availabilty_of_slots/__init__.py delete mode 100644 erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json delete mode 100644 erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py diff --git a/erpnext/crm/doctype/availabilty_of_slots/__init__.py b/erpnext/crm/doctype/availabilty_of_slots/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json b/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json deleted file mode 100644 index d26f7ced35..0000000000 --- a/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "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/availabilty_of_slots/availabilty_of_slots.py b/erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py deleted file mode 100644 index 62436b8da7..0000000000 --- a/erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 AvailabiltyOfSlots(Document): - pass \ No newline at end of file diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 5bc8af0bde..b2df3b4382 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -165,12 +165,13 @@ function setup_details_page(){ async function submit() { // form validation here form_validation(); + debugger; let appointment = (await frappe.call({ method: 'erpnext.www.book-appointment.index.create_appointment', args: { - 'date': date, - 'time': time, - 'contact': contact + 'date': window.selected_date, + 'time': window.selected_time, + 'contact': window.contact } })).message; frappe.msgprint(__('Appointment Created Successfully')); @@ -181,7 +182,7 @@ async function submit() { function form_validation(){ var date = window.selected_date; - var time = document.getElementsByClassName('selected')[0].id; + var time = window.selected_time; contact = {}; contact.name = document.getElementById('customer_name').value; contact.number = document.getElementById('customer_number').value; diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index e853a35fff..9c37fb0c99 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -76,7 +76,7 @@ def create_appointment(date,time,contact): 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_phone_number = contact['number'] appointment.customer_skype = contact['skype'] appointment.customer_details = contact['notes'] appointment.insert() From 110f4ea0c9b3b9121697f008026c6da668230311 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 9 Sep 2019 17:04:25 +0530 Subject: [PATCH 020/157] Formatting --- .../availability_of_slots.py | 3 +- erpnext/www/book-appointment/index.html | 15 ++-- erpnext/www/book-appointment/index.py | 72 ++++++++++++------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py index 8258471eed..94fb0c94d6 100644 --- a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py +++ b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py @@ -6,5 +6,6 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document + class AvailabilityOfSlots(Document): - pass + pass diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index b915484f54..f4074270e0 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -3,8 +3,8 @@ {% block title %}{{ _("Book Appointment") }}{% endblock %} {% block script %} - - + + {% endblock %} {% block page_content %} @@ -18,15 +18,16 @@
- - +
-
- +
+
diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 9c37fb0c99..f4e96b47d6 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -4,50 +4,62 @@ import json no_cache = 1 + @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) + 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='*') + timezones = frappe.get_list('Timezone', fields='*') return timezones + @frappe.whitelist(allow_guest=True) -def get_appointment_slots(date,timezone): +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) + 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) - + 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)) + 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)) + 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) + 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 = [] @@ -59,7 +71,7 @@ def get_available_slots_between(query_start_time, query_end_time, settings): query_start_time, record.from_time) end_time = _deltatime_to_datetime( query_start_time, record.to_time) - else : + else: current_time = _deltatime_to_datetime( query_end_time, record.from_time) end_time = _deltatime_to_datetime( @@ -69,11 +81,13 @@ def get_available_slots_between(query_start_time, query_end_time, settings): current_time += appointment_duration return timeslots -@frappe.whitelist(allow_guest=True) -def create_appointment(date,time,contact): + +@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) + appointment.scheduled_time = datetime.datetime.strptime( + date+" "+time, format_string) contact = json.loads(contact) appointment.customer_name = contact['name'] appointment.customer_phone_number = contact['number'] @@ -83,15 +97,17 @@ def create_appointment(date,time,contact): # Helper Functions -def filter_timeslots(date,timeslots): +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}) Date: Mon, 9 Sep 2019 17:09:03 +0530 Subject: [PATCH 021/157] added doctype --- .../doctype/availabilty_of_slots/__init__.py | 0 .../availability_of_slots.json | 46 +++++++++++++++++++ .../availabilty_of_slots.py | 11 +++++ 3 files changed, 57 insertions(+) create mode 100644 erpnext/crm/doctype/availabilty_of_slots/__init__.py create mode 100644 erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json create mode 100644 erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py diff --git a/erpnext/crm/doctype/availabilty_of_slots/__init__.py b/erpnext/crm/doctype/availabilty_of_slots/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json b/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json new file mode 100644 index 0000000000..d26f7ced35 --- /dev/null +++ b/erpnext/crm/doctype/availabilty_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/availabilty_of_slots/availabilty_of_slots.py b/erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py new file mode 100644 index 0000000000..bd764806ba --- /dev/null +++ b/erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py @@ -0,0 +1,11 @@ +# -*- 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 AvailabiltyOfSlots(Document): + pass From 5c211d8abfb764ad52f53accad8ffe15b0a4893d Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 10 Sep 2019 11:52:55 +0530 Subject: [PATCH 022/157] fixed codacy --- erpnext/crm/doctype/appointment/appointment.json | 10 +++++++++- .../appointment_booking_settings.js | 13 ++++--------- erpnext/public/js/date_polyfill.js | 1 - 3 files changed, 13 insertions(+), 11 deletions(-) delete mode 100644 erpnext/public/js/date_polyfill.js diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 8392549fd3..356cbea2cc 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "scheduled_time", + "status", "customer_details_section", "customer_name", "customer_phone_number", @@ -48,9 +49,16 @@ "in_list_view": 1, "label": "Scheduled Time", "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Open\nClosed", + "reqd": 1 } ], - "modified": "2019-09-09 15:40:21.881421", + "modified": "2019-09-10 11:17:20.200603", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js index 465df2c3a6..2642e6eb26 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js @@ -1,18 +1,13 @@ // 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) +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"`)) + 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 + }); +} \ No newline at end of file diff --git a/erpnext/public/js/date_polyfill.js b/erpnext/public/js/date_polyfill.js deleted file mode 100644 index 6899d82291..0000000000 --- a/erpnext/public/js/date_polyfill.js +++ /dev/null @@ -1 +0,0 @@ -(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=0,d=this.input.localeText.days.length;b'+this.input.localeText.days[b]+'');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')+'\n \n '),j+1<=d){h.push('');continue}var i=j+1-d,k=g&&f.getDate()===i;h.push('\n '+i+'\n ')}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 Date: Tue, 10 Sep 2019 13:12:07 +0530 Subject: [PATCH 023/157] UI Fixes Only 8 time slots will appear in a row Date is more readable on the contact details page --- erpnext/www/book-appointment/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index b2df3b4382..61ea8e40d7 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -93,7 +93,13 @@ async function update_time_slots(selected_date, selected_timezone) { timeslot_container.appendChild(message_div); return } - window.slots.forEach(slot => { + window.slots.forEach((slot,index) => { + debugger + if(index%8==0){ + let break_element = document.createElement('div'); + break_element.classList.add('w-100'); + timeslot_container.appendChild(break_element); + } let start_time = new Date(slot.time) var timeslot_div = document.createElement('div'); timeslot_div.classList.add('time-slot'); @@ -120,7 +126,7 @@ function clear_time_slots() { function get_slot_layout(time) { time = new Date(time) let start_time_string = moment(time).format("LT"); - let end_time = moment(time).add('1','hours'); + let end_time = moment(time).add(window.appointment_settings.appointment_duration,'minutes'); let end_time_string = end_time.format("LT"); return `${start_time_string}
to ${end_time_string}`; } @@ -158,7 +164,7 @@ function setup_details_page(){ page2.style.display = 'block'; let date_container = document.getElementsByClassName('date-span')[0]; let time_container = document.getElementsByClassName('time-span')[0]; - date_container.innerHTML = new Date(window.selected_date).toLocaleDateString(); + date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY"); time_container.innerHTML = moment(window.selected_time,"HH:mm:ss").format("LT"); } From 6f486f371919962958644b892bfaffe614cf407e Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 10 Sep 2019 13:12:28 +0530 Subject: [PATCH 024/157] Addded status to appointment creation --- erpnext/www/book-appointment/index.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index f4e96b47d6..1b87b86a40 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -93,6 +93,7 @@ def create_appointment(date, time, contact): appointment.customer_phone_number = contact['number'] appointment.customer_skype = contact['skype'] appointment.customer_details = contact['notes'] + appointment.status = 'Open' appointment.insert() From c4950a028136e8e68661488250ce41c8f3a73305 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 10 Sep 2019 15:10:51 +0530 Subject: [PATCH 025/157] Added doctype availabitlity of slots added --- erpnext/crm/doctype/appointment/test_appointment.py | 12 ++++++------ .../appointment_booking_settings.json | 2 +- .../availability_of_slots/availability_of_slots.json | 8 ++++---- .../availability_of_slots/availability_of_slots.py | 3 +-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index e446712d01..96c4e4fc05 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -34,14 +34,14 @@ class TestAppointment(unittest.TestCase): def tearDown(self): delete_appointments() - def delete_appointments(self): - doc_list = frappe.get_list('Appointment',filters={'scheduled_time':datetime.datetime.min,'customer_phone_number':'8088'}) - for doc in doc_list: - doc.delete() + def delete_appointments(self): + doc_list = frappe.get_list('Appointment',filters={'scheduled_time':datetime.datetime.min,'customer_phone_number':'8088'}) + for doc in doc_list: + doc.delete() def test_number_of_appointments(self): settings = frappe.get_doc('Appointment Booking Settings') - self.assertLessEqual(frappe.db.count('Apoointment', - filters={'scheduled_time': datetime.datetime.min, 'customer_name':}), + self.assertFalse(frappe.db.exists('Apoointment', + filters={'scheduled_time': datetime.datetime.min, 'customer_name':'Extra Customer'}), settings.number_of_agents, "Number of appointments exceed number of agents") diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index cf27f770c2..11820b965a 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -48,7 +48,7 @@ } ], "issingle": 1, - "modified": "2019-09-03 12:27:09.763730", + "modified": "2019-09-10 15:02:39.969131", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json index d26f7ced35..b54af8dba4 100644 --- a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json +++ b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json @@ -1,5 +1,5 @@ { - "creation": "2019-08-27 10:52:54.204677", + "creation": "2019-09-10 15:02:05.779434", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -21,7 +21,7 @@ "fieldname": "from_time", "fieldtype": "Time", "in_list_view": 1, - "label": "From Time ", + "label": "From Time", "reqd": 1 }, { @@ -33,10 +33,10 @@ } ], "istable": 1, - "modified": "2019-08-27 10:52:54.204677", + "modified": "2019-09-10 15:05:20.406855", "modified_by": "Administrator", "module": "CRM", - "name": "Availabilty Of Slots", + "name": "Availability Of Slots", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py index 94fb0c94d6..8258471eed 100644 --- a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py +++ b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py @@ -6,6 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document - class AvailabilityOfSlots(Document): - pass + pass From 2d7370a525622ee02345b65f15a312555d2fe0ab Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 10 Sep 2019 16:46:17 +0530 Subject: [PATCH 026/157] Moved delete_appointment --- erpnext/crm/doctype/appointment/test_appointment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 96c4e4fc05..8487b258f2 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -18,6 +18,11 @@ def create_appointments(number): 'customer_skype': 'test'+str(i), }) +def delete_appointments(): + doc_list = frappe.get_list('Appointment',filters={'scheduled_time':datetime.datetime.min,'customer_phone_number':'8088'}) + for doc in doc_list: + doc.delete() + class TestAppointment(unittest.TestCase): def setUp(self): @@ -34,14 +39,9 @@ class TestAppointment(unittest.TestCase): def tearDown(self): delete_appointments() - def delete_appointments(self): - doc_list = frappe.get_list('Appointment',filters={'scheduled_time':datetime.datetime.min,'customer_phone_number':'8088'}) - for doc in doc_list: - doc.delete() - def test_number_of_appointments(self): settings = frappe.get_doc('Appointment Booking Settings') self.assertFalse(frappe.db.exists('Apoointment', - filters={'scheduled_time': datetime.datetime.min, 'customer_name':'Extra Customer'}), + filters={'scheduled_time': datetime.datetime.min, 'customer_name':'Extra Cu'}), settings.number_of_agents, "Number of appointments exceed number of agents") From 5038d6a6db28f9112f0ee743ccf0c44ec394ff57 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 11 Sep 2019 10:31:04 +0530 Subject: [PATCH 027/157] Removed appointment tests TODO: Write better tests after adding lead and calender event generation --- .../doctype/appointment/test_appointment.py | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 8487b258f2..c1a1c4ff46 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -25,23 +25,4 @@ def delete_appointments(): class TestAppointment(unittest.TestCase): - def setUp(self): - settings = frappe.get_doc('Appointment Booking Settings') - create_appointments(settings.number_of_agents) - frappe.get_doc({ - 'doctype': 'Appointment', - 'scheduled_time': datetime.datetime.min, - 'customer_name': 'Extra Customer', - 'customer_phone_number': '8088', - 'customer_skype': 'extra_customer', - }) - - def tearDown(self): - delete_appointments() - - def test_number_of_appointments(self): - settings = frappe.get_doc('Appointment Booking Settings') - self.assertFalse(frappe.db.exists('Apoointment', - filters={'scheduled_time': datetime.datetime.min, 'customer_name':'Extra Cu'}), - settings.number_of_agents, - "Number of appointments exceed number of agents") + pass From 0cc837eac5f66be7042a18241de58c1747d50867 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 11 Sep 2019 14:12:30 +0530 Subject: [PATCH 028/157] Create event for the appointment TODO: Add lead and employee to this --- erpnext/crm/doctype/appointment/appointment.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index cce6a1d684..30d10194b2 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -3,6 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals +from datetime import timedelta import frappe from frappe.model.document import Document @@ -12,4 +13,14 @@ class Appointment(Document): 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') + + def after_insert(self): + appointment_event = frappe.new_doc('Event') + appointment_event.subject = 'Appointment with ' + self.customer_name + appointment_event.starts_on = self.scheduled_time + appointment_event.status = 'Open' + appointment_event.type = 'Private' + settings = frappe.get_doc('Appointment Booking Settings') + appointment_event.ends_on = self.scheduled_time + timedelta(minutes=settings.appointment_duration) + appointment_event.insert() From a322b159ab9da2e60dbf260b8bd578c20fdd3612 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 11 Sep 2019 14:25:26 +0530 Subject: [PATCH 029/157] Added back button from details page --- erpnext/www/book-appointment/index.html | 7 +++++-- erpnext/www/book-appointment/index.js | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index f4074270e0..43275eb243 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -13,7 +13,7 @@

Book an appointment

-

Select the date and your timezone

+

Select the date and your timezone

@@ -53,7 +53,10 @@ required> - +
+
+
+
diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 61ea8e40d7..90572fb891 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -69,6 +69,8 @@ function on_date_or_timezone_select() { window.selected_date = date_picker.value; window.selected_timezone = timezone.value; update_time_slots(date_picker.value, timezone.value); + let lead_text = document.getElementById('lead-text'); + lead_text.innerHTML = "Select Time" } async function get_time_slots(date, timezone) { From e543fc483fea6b09ae72a1f84cdaf783122cc721 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 11 Sep 2019 14:59:13 +0530 Subject: [PATCH 030/157] Removed email reminders As it will be handled by calender event in the future --- .../appointment_booking_settings.json | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 11820b965a..0150309ad0 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -7,7 +7,6 @@ "availability_of_slots", "number_of_agents", "holiday_list", - "email_reminders", "appointment_duration" ], "fields": [ @@ -19,6 +18,7 @@ "reqd": 1 }, { + "default": "1", "fieldname": "number_of_agents", "fieldtype": "Int", "in_list_view": 1, @@ -33,22 +33,16 @@ "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", + "label": "Appointment Duration (In Minutes)", "reqd": 1 } ], "issingle": 1, - "modified": "2019-09-10 15:02:39.969131", + "modified": "2019-09-11 14:44:33.471834", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", From 249cdd92e0d430984a63e54504bfa6f21f0d87f5 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 11 Sep 2019 14:59:25 +0530 Subject: [PATCH 031/157] Added uniqueness check for offset --- erpnext/crm/doctype/timezone/timezone.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/crm/doctype/timezone/timezone.py b/erpnext/crm/doctype/timezone/timezone.py index f0da6e3d9a..2c77023b39 100644 --- a/erpnext/crm/doctype/timezone/timezone.py +++ b/erpnext/crm/doctype/timezone/timezone.py @@ -12,3 +12,6 @@ class Timezone(Document): if self.offset > 720 or self.offset < -720: frappe.throw( 'Timezone offsets must be between -720 and +720 minutes') + if frappe.db.exists({'doctype':'Timezone','offset':self.offset}): + frappe.throw( + 'Timezone offsets need to be unique') \ No newline at end of file From 8051ca1859f247aaaeba758a038997fb858cf538 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 12 Sep 2019 10:47:45 +0530 Subject: [PATCH 032/157] Limit advance booking of appointments --- .../appointment_booking_settings.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 0150309ad0..2386ed76e9 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -7,7 +7,9 @@ "availability_of_slots", "number_of_agents", "holiday_list", - "appointment_duration" + "appointment_duration", + "email_reminders", + "advance_booking_days" ], "fields": [ { @@ -39,10 +41,23 @@ "fieldtype": "Int", "label": "Appointment Duration (In Minutes)", "reqd": 1 + }, + { + "default": "0", + "fieldname": "email_reminders", + "fieldtype": "Check", + "label": "Email Reminders" + }, + { + "default": "7", + "fieldname": "advance_booking_days", + "fieldtype": "Int", + "label": "Number of days appointments can be booked in advance", + "reqd": 1 } ], "issingle": 1, - "modified": "2019-09-11 14:44:33.471834", + "modified": "2019-09-12 10:47:20.274330", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", From a2dbd391b3bffad6b0c87b82565231051ee93f96 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 12 Sep 2019 10:48:26 +0530 Subject: [PATCH 033/157] Add lead and calender event to appointments --- .../crm/doctype/appointment/appointment.json | 20 +++++++++++++-- .../crm/doctype/appointment/appointment.py | 3 ++- erpnext/www/book-appointment/index.html | 4 ++- erpnext/www/book-appointment/index.js | 25 +++++++++++++++---- erpnext/www/book-appointment/index.py | 8 ++++++ 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 356cbea2cc..b2fe7b9db2 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -11,7 +11,9 @@ "customer_name", "customer_phone_number", "customer_skype", - "customer_details" + "customer_details", + "lead", + "calender_event" ], "fields": [ { @@ -56,9 +58,23 @@ "label": "Status", "options": "Open\nClosed", "reqd": 1 + }, + { + "fieldname": "lead", + "fieldtype": "Link", + "label": "Lead", + "options": "Lead", + "reqd": 1 + }, + { + "fieldname": "calender_event", + "fieldtype": "Link", + "label": "Calender Event", + "options": "Event", + "reqd": 1 } ], - "modified": "2019-09-10 11:17:20.200603", + "modified": "2019-09-12 10:42:47.841841", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 30d10194b2..4c95c6e5c3 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -14,7 +14,7 @@ class Appointment(Document): if(number_of_appointments_in_same_slot>=settings.number_of_agents): frappe.throw('Time slot is not available') - def after_insert(self): + def before_insert(self): appointment_event = frappe.new_doc('Event') appointment_event.subject = 'Appointment with ' + self.customer_name appointment_event.starts_on = self.scheduled_time @@ -23,4 +23,5 @@ class Appointment(Document): settings = frappe.get_doc('Appointment Booking Settings') appointment_event.ends_on = self.scheduled_time + timedelta(minutes=settings.appointment_duration) appointment_event.insert() + self.calender_event = appointment_event.name diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index 43275eb243..2e0321394e 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -51,10 +51,12 @@ placeholder="Contact Number" required> +
-
+
diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 90572fb891..f9d9b6e845 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -5,7 +5,7 @@ frappe.ready(() => { window.holiday_list = []; async function initialise_select_date() { - document.getElementById('enter-details').style.display = 'none'; + navigate_to_page(1); await get_global_variables(); setup_date_picker(); setup_timezone_selector(); @@ -115,7 +115,6 @@ async function update_time_slots(selected_date, selected_timezone) { timeslot_container.appendChild(timeslot_div); }); set_default_timeslot(); - show_next_button(); } function clear_time_slots() { @@ -146,6 +145,7 @@ function select_time() { window.selected_time = this.id selected_element.classList.remove("selected"); this.classList.add("selected"); + show_next_button(); } function set_default_timeslot() { @@ -159,11 +159,25 @@ function set_default_timeslot() { } } -function setup_details_page(){ +function navigate_to_page(page_number){ let page1 = document.getElementById('select-date-time'); let page2 = document.getElementById('enter-details'); - page1.style.display = 'none'; - page2.style.display = 'block'; + switch(page_number){ + case 1: + page1.style.display = 'block'; + page2.style.display = 'none'; + break; + case 2: + page1.style.display = 'none'; + page2.style.display = 'block'; + break; + default: + console.log("That's not a valid page") + } +} + +function setup_details_page(){ + navigate_to_page(2) let date_container = document.getElementsByClassName('date-span')[0]; let time_container = document.getElementsByClassName('time-span')[0]; date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY"); @@ -196,6 +210,7 @@ function form_validation(){ contact.number = document.getElementById('customer_number').value; contact.skype = document.getElementById('customer_skype').value; contact.notes = document.getElementById('customer_notes').value; + contact.email = document.getElementById('customer_email').value; window.contact = contact console.log({ date, time, contact }); } diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 1b87b86a40..530445ff91 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -94,8 +94,16 @@ def create_appointment(date, time, contact): appointment.customer_skype = contact['skype'] appointment.customer_details = contact['notes'] appointment.status = 'Open' + appointment.lead = find_lead_by_email(contact['email']).name appointment.insert() +def find_lead_by_email(email): + if frappe.db.exists({ + 'doctype':'Lead', + 'email_id':email + }): + return frappe.get_list('Lead',filters={'email_id':email})[0] + frappe.throw('Email ID not associated with any Lead. Please make sure to use the email address you got this mail on') # Helper Functions def filter_timeslots(date, timeslots): From 469247bf73c94458ef730d68b6f13fff961bd253 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 12 Sep 2019 11:15:42 +0530 Subject: [PATCH 034/157] Change max date of datepicker to number of days in future as specified by the settings --- .../appointment_booking_settings.json | 2 +- erpnext/www/book-appointment/index.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 2386ed76e9..6ef00703d1 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -57,7 +57,7 @@ } ], "issingle": 1, - "modified": "2019-09-12 10:47:20.274330", + "modified": "2019-09-12 10:52:25.931931", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index f9d9b6e845..96ad66ace3 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -43,7 +43,8 @@ function setup_date_picker() { let date_picker = document.getElementById('appointment-date'); let today = new Date(); date_picker.min = today.toISOString().substr(0, 10); - date_picker.max = window.holiday_list.to_date; + today.setDate(today.getDate() + window.appointment_settings.advance_booking_days); + date_picker.max = today.toISOString().substr(0,10); } function hide_next_button(){ From 1564f1476c75323c84ec19e5d52b27f99220d923 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 12 Sep 2019 14:24:28 +0530 Subject: [PATCH 035/157] Added customer to calender event --- erpnext/crm/doctype/appointment/appointment.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 4c95c6e5c3..13904116a7 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -22,6 +22,12 @@ class Appointment(Document): appointment_event.type = 'Private' settings = frappe.get_doc('Appointment Booking Settings') appointment_event.ends_on = self.scheduled_time + timedelta(minutes=settings.appointment_duration) + event_participants = [] + event_participant_customer = frappe.new_doc('Event Participants') + event_participant_customer.reference_doctype = 'Lead' + event_participant_customer.reference_docname = self.lead + event_participants.append(event_participant_customer) + appointment_event.event_participants = event_participants appointment_event.insert() self.calender_event = appointment_event.name From a3b8c77af133f137fae0df50663f7b515947a07b Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 12 Sep 2019 15:19:22 +0530 Subject: [PATCH 036/157] Fixed leads --- .../crm/doctype/appointment/appointment.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 13904116a7..1b6ef94bfc 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -16,18 +16,15 @@ class Appointment(Document): def before_insert(self): appointment_event = frappe.new_doc('Event') - appointment_event.subject = 'Appointment with ' + self.customer_name - appointment_event.starts_on = self.scheduled_time - appointment_event.status = 'Open' - appointment_event.type = 'Private' - settings = frappe.get_doc('Appointment Booking Settings') - appointment_event.ends_on = self.scheduled_time + timedelta(minutes=settings.appointment_duration) - event_participants = [] - event_participant_customer = frappe.new_doc('Event Participants') - event_participant_customer.reference_doctype = 'Lead' - event_participant_customer.reference_docname = self.lead - event_participants.append(event_participant_customer) - appointment_event.event_participants = event_participants - appointment_event.insert() + appointment_event = frappe.get_doc({ + "doctype": "Event", + "subject": ' '.join(['Appointment with', self.customer_name]), + "starts_on": self.scheduled_time, + "status": "Open", + "type": "Private", + "event_participants": [dict(reference_doctype="Lead", reference_docname=self.lead)] + }) + + appointment_event.insert(ignore_permissions=True) self.calender_event = appointment_event.name From cf045d86b07dae9dcd23ab53327574038b5e84fa Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 13 Sep 2019 15:55:54 +0530 Subject: [PATCH 037/157] fixed typo --- erpnext/crm/doctype/appointment/appointment.json | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index b2fe7b9db2..2d695f3199 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -13,7 +13,7 @@ "customer_skype", "customer_details", "lead", - "calender_event" + "calendar_event" ], "fields": [ { @@ -67,14 +67,13 @@ "reqd": 1 }, { - "fieldname": "calender_event", + "fieldname": "calendar_event", "fieldtype": "Link", - "label": "Calender Event", - "options": "Event", - "reqd": 1 + "label": "Calendar Event", + "options": "Event" } ], - "modified": "2019-09-12 10:42:47.841841", + "modified": "2019-09-13 15:25:49.362246", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", From d88f850d0fa0100eae8ce5ca6cbb8740aed153b2 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 13 Sep 2019 15:56:47 +0530 Subject: [PATCH 038/157] removed debugger --- erpnext/www/book-appointment/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 96ad66ace3..5302d1b626 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -75,7 +75,6 @@ function on_date_or_timezone_select() { } async function get_time_slots(date, timezone) { - debugger let slots = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_appointment_slots', args: { @@ -97,7 +96,6 @@ async function update_time_slots(selected_date, selected_timezone) { return } window.slots.forEach((slot,index) => { - debugger if(index%8==0){ let break_element = document.createElement('div'); break_element.classList.add('w-100'); @@ -140,7 +138,6 @@ function select_time() { try { selected_element = document.getElementsByClassName('selected')[0] } catch (e) { - debugger this.classList.add("selected") } window.selected_time = this.id @@ -188,7 +185,6 @@ function setup_details_page(){ async function submit() { // form validation here form_validation(); - debugger; let appointment = (await frappe.call({ method: 'erpnext.www.book-appointment.index.create_appointment', args: { From 1cd762e9d0587cab4649ab1c55d7b9ad4b24a67a Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 13 Sep 2019 15:56:54 +0530 Subject: [PATCH 039/157] Added ajuto assignment --- .../crm/doctype/appointment/appointment.py | 67 ++++++++++++++++--- .../appointment_booking_settings.json | 12 +++- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 1b6ef94bfc..4dea04b39c 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -3,28 +3,75 @@ # For license information, please see license.txt from __future__ import unicode_literals +from collections import Counter from datetime import timedelta import frappe from frappe.model.document import Document +from frappe.desk.form.assign_to import add as add_assignemnt + + +def _get_agents_sorted_by_asc_workload(): + appointments = frappe.db.get_list('Appointment', fields='*') + # Handle case where no appointments are created + appointment_counter = Counter() + if not appointments: + return frappe.get_doc('Appointment Booking Settings').agent_list + for appointment in appointments: + if appointment._assign == '[]' or not appointment._assign: + continue + appointment_counter[appointment._assign] += 1 + sorted_agent_list = appointment_counter.most_common() + sorted_agent_list.reverse() + return sorted_agent_list + +def _check_agent_availability(agent_email,scheduled_time): + appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters={'scheduled_time':scheduled_time}) + for appointment in appointemnts_at_scheduled_time: + if appointment._assign == agent_email: + return False + return True + +def _get_employee_from_user(user): + return frappe.get_list('Employee', fields='*',filters={'user_id':user}) 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): + if(number_of_appointments_in_same_slot >= settings.number_of_agents): frappe.throw('Time slot is not available') - + def before_insert(self): appointment_event = frappe.new_doc('Event') appointment_event = frappe.get_doc({ - "doctype": "Event", - "subject": ' '.join(['Appointment with', self.customer_name]), - "starts_on": self.scheduled_time, - "status": "Open", - "type": "Private", - "event_participants": [dict(reference_doctype="Lead", reference_docname=self.lead)] + 'doctype': 'Event', + 'subject': ' '.join(['Appointment with', self.customer_name]), + 'starts_on': self.scheduled_time, + 'status': 'Open', + 'type': 'Private', + 'event_participants': [dict(reference_doctype="Lead", reference_docname=self.lead)] }) - appointment_event.insert(ignore_permissions=True) - self.calender_event = appointment_event.name + self.calendar_event = appointment_event.name + def after_insert(self): + available_agents = _get_agents_sorted_by_asc_workload() + for agent in available_agents: + if(_check_agent_availability(agent, self.scheduled_time)): + agent = agent[0] + agent = frappe.json.loads(agent)[0] + add_assignemnt({ + 'doctype':self.doctype, + 'name':self.name, + 'assign_to':agent + }) + employee = _get_employee_from_user(agent) + if employee: + print(employee) + calendar_event = frappe.get_doc('Event', self.calendar_event) + calendar_event.append('event_participants', dict( + reference_doctype='Employee', + reference_docname=employee[0].name)) + print(calendar_event) + calendar_event.save() + break \ 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 index 6ef00703d1..c59a2e466f 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -9,7 +9,8 @@ "holiday_list", "appointment_duration", "email_reminders", - "advance_booking_days" + "advance_booking_days", + "agent_list" ], "fields": [ { @@ -54,10 +55,17 @@ "fieldtype": "Int", "label": "Number of days appointments can be booked in advance", "reqd": 1 + }, + { + "fieldname": "agent_list", + "fieldtype": "Table MultiSelect", + "label": "Agents", + "options": "Assignment Rule User", + "reqd": 1 } ], "issingle": 1, - "modified": "2019-09-12 10:52:25.931931", + "modified": "2019-09-13 11:31:26.654516", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", From 018f0d3bbd044cb61cd6cc5805ef0169d070b42b Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 13 Sep 2019 16:25:26 +0530 Subject: [PATCH 040/157] Fixed issue: agents weren't looked up in settings --- .../crm/doctype/appointment/appointment.py | 73 ++++++++++++------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 4dea04b39c..3a588fbcd8 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -3,37 +3,15 @@ # For license information, please see license.txt from __future__ import unicode_literals + from collections import Counter from datetime import timedelta + import frappe from frappe.model.document import Document from frappe.desk.form.assign_to import add as add_assignemnt -def _get_agents_sorted_by_asc_workload(): - appointments = frappe.db.get_list('Appointment', fields='*') - # Handle case where no appointments are created - appointment_counter = Counter() - if not appointments: - return frappe.get_doc('Appointment Booking Settings').agent_list - for appointment in appointments: - if appointment._assign == '[]' or not appointment._assign: - continue - appointment_counter[appointment._assign] += 1 - sorted_agent_list = appointment_counter.most_common() - sorted_agent_list.reverse() - return sorted_agent_list - -def _check_agent_availability(agent_email,scheduled_time): - appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters={'scheduled_time':scheduled_time}) - for appointment in appointemnts_at_scheduled_time: - if appointment._assign == agent_email: - return False - return True - -def _get_employee_from_user(user): - return frappe.get_list('Employee', fields='*',filters={'user_id':user}) - class Appointment(Document): def validate(self): number_of_appointments_in_same_slot = frappe.db.count('Appointment',filters={'scheduled_time':self.scheduled_time}) @@ -74,4 +52,49 @@ class Appointment(Document): reference_docname=employee[0].name)) print(calendar_event) calendar_event.save() - break \ No newline at end of file + break + + +def _get_agents_sorted_by_asc_workload(): + appointments = frappe.db.get_list('Appointment', fields='*') + agent_list = _get_agent_list_as_strings() + + if not appointments: + return agent_list + + appointment_counter = Counter(agent_list) + + for appointment in appointments: + assigned_to = frappe.parse_json(appointment._assign) + print(assigned_to) + if appointment._assign == '[]' or not appointment._assign: + continue + if assigned_to[0] in agent_list: + appointment_counter[assigned_to[0]] += 1 + + sorted_agent_list = appointment_counter.most_common() + sorted_agent_list.reverse() + + return sorted_agent_list + + +def _get_agent_list_as_strings(): + agent_list_as_strings = [] + agent_list = frappe.get_doc('Appointment Booking Settings').agent_list + + for agent in agent_list: + agent_list_as_strings.append(agent.user) + + return agent_list_as_strings + + +def _check_agent_availability(agent_email,scheduled_time): + appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters={'scheduled_time':scheduled_time}) + for appointment in appointemnts_at_scheduled_time: + if appointment._assign == agent_email: + return False + return True + + +def _get_employee_from_user(user): + return frappe.get_list('Employee', fields='*',filters={'user_id':user}) \ No newline at end of file From a8752db012ffa9222b6e9a18c152bcc776a5be10 Mon Sep 17 00:00:00 2001 From: Pranav Nachnekar Date: Mon, 16 Sep 2019 20:02:20 +0530 Subject: [PATCH 041/157] Typo and styling fixes Co-Authored-By: Shivam Mishra --- erpnext/crm/doctype/appointment/appointment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 3a588fbcd8..614a43c590 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -89,7 +89,7 @@ def _get_agent_list_as_strings(): def _check_agent_availability(agent_email,scheduled_time): - appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters={'scheduled_time':scheduled_time}) + appointments_at_scheduled_time = frappe.get_list('Appointment', filters={'scheduled_time': scheduled_time}) for appointment in appointemnts_at_scheduled_time: if appointment._assign == agent_email: return False @@ -97,4 +97,4 @@ def _check_agent_availability(agent_email,scheduled_time): def _get_employee_from_user(user): - return frappe.get_list('Employee', fields='*',filters={'user_id':user}) \ No newline at end of file + return frappe.get_list('Employee', fields='*',filters={'user_id':user}) From 91a564989f883293aeeca7479d9f5eaa0a02bc65 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 17 Sep 2019 16:58:41 +0530 Subject: [PATCH 042/157] Styling and PR review changes --- .../crm/doctype/appointment/appointment.py | 33 +++---- .../doctype/appointment/test_appointment.py | 14 +-- .../appointment_booking_settings.py | 13 ++- erpnext/crm/doctype/timezone/timezone.py | 6 +- erpnext/www/book-appointment/index.js | 87 ++++++++++--------- erpnext/www/book-appointment/index.py | 18 ++-- 6 files changed, 79 insertions(+), 92 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 3a588fbcd8..5408b4d91a 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -14,22 +14,21 @@ from frappe.desk.form.assign_to import add as add_assignemnt class Appointment(Document): def validate(self): - number_of_appointments_in_same_slot = frappe.db.count('Appointment',filters={'scheduled_time':self.scheduled_time}) + 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') def before_insert(self): - appointment_event = frappe.new_doc('Event') appointment_event = frappe.get_doc({ 'doctype': 'Event', 'subject': ' '.join(['Appointment with', self.customer_name]), 'starts_on': self.scheduled_time, 'status': 'Open', 'type': 'Private', - 'event_participants': [dict(reference_doctype="Lead", reference_docname=self.lead)] + 'event_participants': [dict(reference_doctype = "Lead", reference_docname = self.lead)] }) - appointment_event.insert(ignore_permissions=True) + appointment_event.insert(ignore_permissions = True) self.calendar_event = appointment_event.name def after_insert(self): @@ -37,7 +36,6 @@ class Appointment(Document): for agent in available_agents: if(_check_agent_availability(agent, self.scheduled_time)): agent = agent[0] - agent = frappe.json.loads(agent)[0] add_assignemnt({ 'doctype':self.doctype, 'name':self.name, @@ -45,33 +43,25 @@ class Appointment(Document): }) employee = _get_employee_from_user(agent) if employee: - print(employee) calendar_event = frappe.get_doc('Event', self.calendar_event) calendar_event.append('event_participants', dict( - reference_doctype='Employee', - reference_docname=employee[0].name)) - print(calendar_event) + reference_doctype= 'Employee', + reference_docname= employee.name)) calendar_event.save() break - def _get_agents_sorted_by_asc_workload(): appointments = frappe.db.get_list('Appointment', fields='*') - agent_list = _get_agent_list_as_strings() - + agent_list = _get_agent_list_as_strings() if not appointments: return agent_list - appointment_counter = Counter(agent_list) - for appointment in appointments: assigned_to = frappe.parse_json(appointment._assign) - print(assigned_to) - if appointment._assign == '[]' or not appointment._assign: + if not assigned_to: continue if assigned_to[0] in agent_list: appointment_counter[assigned_to[0]] += 1 - sorted_agent_list = appointment_counter.most_common() sorted_agent_list.reverse() @@ -81,15 +71,13 @@ def _get_agents_sorted_by_asc_workload(): def _get_agent_list_as_strings(): agent_list_as_strings = [] agent_list = frappe.get_doc('Appointment Booking Settings').agent_list - for agent in agent_list: agent_list_as_strings.append(agent.user) - return agent_list_as_strings def _check_agent_availability(agent_email,scheduled_time): - appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters={'scheduled_time':scheduled_time}) + appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters = {'scheduled_time':scheduled_time}) for appointment in appointemnts_at_scheduled_time: if appointment._assign == agent_email: return False @@ -97,4 +85,7 @@ def _check_agent_availability(agent_email,scheduled_time): def _get_employee_from_user(user): - return frappe.get_list('Employee', fields='*',filters={'user_id':user}) \ No newline at end of file + employee_docname = frappe.db.exists({'doctype':'Employee','user_id':user}) + if employee_docname: + return frappe.get_doc('Employee',employee_docname[0][0]) # frappe.db.exists returns a tuple of a tuple + return None \ No newline at end of file diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index c1a1c4ff46..3c977505b5 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -8,20 +8,8 @@ import unittest import datetime -def create_appointments(number): - for i in range(1, number): - frappe.get_doc({ - 'doctype': 'Appointment', - 'scheduled_time': datetime.datetime.min, - 'customer_name': 'Test Customer'+str(i), - 'customer_phone_number': '8088', - 'customer_skype': 'test'+str(i), - }) - def delete_appointments(): - doc_list = frappe.get_list('Appointment',filters={'scheduled_time':datetime.datetime.min,'customer_phone_number':'8088'}) - for doc in doc_list: - doc.delete() + pass class TestAppointment(unittest.TestCase): diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index 8f1fb14f5b..da181ae119 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -14,17 +14,22 @@ class AppointmentBookingSettings(Document): 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) + 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): + + 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): + + 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/timezone/timezone.py b/erpnext/crm/doctype/timezone/timezone.py index 2c77023b39..539ffa2547 100644 --- a/erpnext/crm/doctype/timezone/timezone.py +++ b/erpnext/crm/doctype/timezone/timezone.py @@ -10,8 +10,6 @@ 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') + frappe.throw('Timezone offsets must be between -720 and +720 minutes') if frappe.db.exists({'doctype':'Timezone','offset':self.offset}): - frappe.throw( - 'Timezone offsets need to be unique') \ No newline at end of file + frappe.throw('Timezone offsets need to be unique') \ No newline at end of file diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 5302d1b626..8fc5e31708 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -13,6 +13,7 @@ async function initialise_select_date() { } async function get_global_variables() { + // Using await window.appointment_settings = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_appointment_settings' })).message @@ -29,9 +30,9 @@ async function get_global_variables() { function setup_timezone_selector() { let timezones_element = document.getElementById('appointment-timezone'); - var offset = new Date().getTimezoneOffset(); + let offset = new Date().getTimezoneOffset(); window.timezones.forEach(timezone => { - var opt = document.createElement('option'); + let opt = document.createElement('option'); opt.value = timezone.offset; opt.innerHTML = timezone.timezone_name; opt.defaultSelected = (offset == timezone.offset) @@ -44,16 +45,16 @@ function setup_date_picker() { let today = new Date(); date_picker.min = today.toISOString().substr(0, 10); today.setDate(today.getDate() + window.appointment_settings.advance_booking_days); - date_picker.max = today.toISOString().substr(0,10); + date_picker.max = today.toISOString().substr(0, 10); } -function hide_next_button(){ +function hide_next_button() { let next_button = document.getElementById('next-button'); next_button.disabled = true; - next_button.onclick = ()=>{frappe.msgprint("Please select a date and time")}; + next_button.onclick = () => frappe.msgprint("Please select a date and time"); } -function show_next_button(){ +function show_next_button() { let next_button = document.getElementById('next-button'); next_button.disabled = false; next_button.onclick = setup_details_page; @@ -95,28 +96,36 @@ async function update_time_slots(selected_date, selected_timezone) { timeslot_container.appendChild(message_div); return } - window.slots.forEach((slot,index) => { - if(index%8==0){ + window.slots.forEach((slot, index) => { + // Add a break after each 8 elements + if (index % 8 == 0) { let break_element = document.createElement('div'); break_element.classList.add('w-100'); timeslot_container.appendChild(break_element); } - let start_time = new Date(slot.time) - 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 = get_slot_layout(start_time); - timeslot_div.id = slot.time.substr(11, 20); - timeslot_div.addEventListener('click', select_time); + // Get and append timeslot div + let timeslot_div = get_timeslot_div_layout(slot) timeslot_container.appendChild(timeslot_div); }); set_default_timeslot(); } +function get_timeslot_div_layout(timeslot) { + let start_time = new Date(timeslot.time) + let timeslot_div = document.createElement('div'); + timeslot_div.classList.add('time-slot'); + timeslot_div.classList.add('col-md'); + if (!timeslot.availability) { + timeslot_div.classList.add('unavailable') + } + timeslot_div.innerHTML = get_slot_layout(start_time); + timeslot_div.id = timeslot.time.substr(11, 20); + timeslot_div.addEventListener('click', select_time); + return timeslot_div +} + function clear_time_slots() { + // Clear any existing divs in timeslot container let timeslot_container = document.getElementById('timeslot-container'); while (timeslot_container.firstChild) { timeslot_container.removeChild(timeslot_container.firstChild) @@ -126,23 +135,24 @@ function clear_time_slots() { function get_slot_layout(time) { time = new Date(time) let start_time_string = moment(time).format("LT"); - let end_time = moment(time).add(window.appointment_settings.appointment_duration,'minutes'); + let end_time = moment(time).add(window.appointment_settings.appointment_duration, 'minutes'); let end_time_string = end_time.format("LT"); return `${start_time_string}
to ${end_time_string}`; } function select_time() { - if (this.classList.contains("unavailable")) { + if (this.classList.contains('unavailable')) { return } - try { - selected_element = document.getElementsByClassName('selected')[0] - } catch (e) { - this.classList.add("selected") + let selected_element = document.getElementsByClassName('selected'); + if (!(selected_element.length > 0)){ + this.classList.add('selected') + return } + selected_element = selected_element[0] window.selected_time = this.id - selected_element.classList.remove("selected"); - this.classList.add("selected"); + selected_element.classList.remove('selected'); + this.classList.add('selected'); show_next_button(); } @@ -151,17 +161,17 @@ function set_default_timeslot() { for (let i = 0; i < timeslots.length; i++) { const timeslot = timeslots[i]; if (!timeslot.classList.contains('unavailable')) { - timeslot.classList.add("selected"); + timeslot.classList.add('selected'); break; } } } -function navigate_to_page(page_number){ +function navigate_to_page(page_number) { let page1 = document.getElementById('select-date-time'); let page2 = document.getElementById('enter-details'); - switch(page_number){ - case 1: + switch (page_number) { + case 1: page1.style.display = 'block'; page2.style.display = 'none'; break; @@ -170,21 +180,21 @@ function navigate_to_page(page_number){ page2.style.display = 'block'; break; default: - console.log("That's not a valid page") + break; } } -function setup_details_page(){ +function setup_details_page() { navigate_to_page(2) let date_container = document.getElementsByClassName('date-span')[0]; let time_container = document.getElementsByClassName('time-span')[0]; date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY"); - time_container.innerHTML = moment(window.selected_time,"HH:mm:ss").format("LT"); + time_container.innerHTML = moment(window.selected_time, "HH:mm:ss").format("LT"); } async function submit() { // form validation here - form_validation(); + get_form_data(); let appointment = (await frappe.call({ method: 'erpnext.www.book-appointment.index.create_appointment', args: { @@ -196,12 +206,10 @@ async function submit() { frappe.msgprint(__('Appointment Created Successfully')); let button = document.getElementById('submit-button'); button.disabled = true; - button.onclick = () => { console.log('This should never have happened') } -} + button.onclick = null +} -function form_validation(){ - var date = window.selected_date; - var time = window.selected_time; +function get_form_data() { contact = {}; contact.name = document.getElementById('customer_name').value; contact.number = document.getElementById('customer_number').value; @@ -209,5 +217,4 @@ function form_validation(){ contact.notes = document.getElementById('customer_notes').value; contact.email = document.getElementById('customer_email').value; window.contact = contact - console.log({ date, time, contact }); } diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 530445ff91..6f6d4ac45c 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -2,6 +2,10 @@ import frappe import datetime import json + +WEEKDAYS = ["Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", "Sunday"] + no_cache = 1 @@ -98,11 +102,9 @@ def create_appointment(date, time, contact): appointment.insert() def find_lead_by_email(email): - if frappe.db.exists({ - 'doctype':'Lead', - 'email_id':email - }): - return frappe.get_list('Lead',filters={'email_id':email})[0] + lead_list = frappe.get_list('Lead',filters={'email_id':email})[0] + if lead_list: + return lead_list frappe.throw('Email ID not associated with any Lead. Please make sure to use the email address you got this mail on') # Helper Functions @@ -156,8 +158,4 @@ def _convert_to_tz(datetime_object, 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"] + return datetime_object \ No newline at end of file From 7323bfdad7bd02b42400ce8c9b924f0c244685c8 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 18 Sep 2019 14:33:10 +0530 Subject: [PATCH 043/157] Styling and bug fixes --- erpnext/www/book-appointment/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 8fc5e31708..345e614154 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -128,12 +128,12 @@ function clear_time_slots() { // Clear any existing divs in timeslot container let timeslot_container = document.getElementById('timeslot-container'); while (timeslot_container.firstChild) { - timeslot_container.removeChild(timeslot_container.firstChild) + timeslot_container.removeChild(timeslot_container.firstChild); } } function get_slot_layout(time) { - time = new Date(time) + time = new Date(time); let start_time_string = moment(time).format("LT"); let end_time = moment(time).add(window.appointment_settings.appointment_duration, 'minutes'); let end_time_string = end_time.format("LT"); @@ -142,15 +142,16 @@ function get_slot_layout(time) { function select_time() { if (this.classList.contains('unavailable')) { - return + return; } let selected_element = document.getElementsByClassName('selected'); if (!(selected_element.length > 0)){ - this.classList.add('selected') - return + this.classList.add('selected'); + show_next_button(); + return; } selected_element = selected_element[0] - window.selected_time = this.id + window.selected_time = this.id; selected_element.classList.remove('selected'); this.classList.add('selected'); show_next_button(); @@ -158,6 +159,7 @@ function select_time() { function set_default_timeslot() { let timeslots = document.getElementsByClassName('time-slot') + // Can't use a forEach here since, we need to break the loop after a timeslot is selected for (let i = 0; i < timeslots.length; i++) { const timeslot = timeslots[i]; if (!timeslot.classList.contains('unavailable')) { From 81449ece54aacd7cbe1d62c9a37f6719b6ac3b28 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 18 Sep 2019 14:33:40 +0530 Subject: [PATCH 044/157] fix:Linking lead --- erpnext/www/book-appointment/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 6f6d4ac45c..e238bd5205 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -102,9 +102,9 @@ def create_appointment(date, time, contact): appointment.insert() def find_lead_by_email(email): - lead_list = frappe.get_list('Lead',filters={'email_id':email})[0] + lead_list = frappe.get_list('Lead',filters={'email_id':email},ignore_permissions=True) if lead_list: - return lead_list + return lead_list[0] frappe.throw('Email ID not associated with any Lead. Please make sure to use the email address you got this mail on') # Helper Functions From 7d476a3e353dcb1ca711208ee111dbe5b80b00b2 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 18 Sep 2019 15:33:31 +0530 Subject: [PATCH 045/157] Moved lead assignment to the controller --- erpnext/crm/doctype/appointment/appointment.py | 7 +++++++ erpnext/www/book-appointment/index.py | 7 +------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 8a6d0635bc..1ffd58fa04 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -20,6 +20,7 @@ class Appointment(Document): frappe.throw('Time slot is not available') def before_insert(self): + self.lead = _find_lead_by_email(self.lead).name appointment_event = frappe.get_doc({ 'doctype': 'Event', 'subject': ' '.join(['Appointment with', self.customer_name]), @@ -67,6 +68,12 @@ def _get_agents_sorted_by_asc_workload(): return sorted_agent_list +def _find_lead_by_email(email): + lead_list = frappe.get_list('Lead',filters={'email_id':email},ignore_permissions=True) + if lead_list: + return lead_list[0] + frappe.throw('Email ID not associated with any Lead. Please make sure to use the email address you got this mail on') + def _get_agent_list_as_strings(): agent_list_as_strings = [] diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index e238bd5205..3370f2429e 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -97,15 +97,10 @@ def create_appointment(date, time, contact): appointment.customer_phone_number = contact['number'] appointment.customer_skype = contact['skype'] appointment.customer_details = contact['notes'] + appointment.lead = contact['email'] appointment.status = 'Open' - appointment.lead = find_lead_by_email(contact['email']).name appointment.insert() -def find_lead_by_email(email): - lead_list = frappe.get_list('Lead',filters={'email_id':email},ignore_permissions=True) - if lead_list: - return lead_list[0] - frappe.throw('Email ID not associated with any Lead. Please make sure to use the email address you got this mail on') # Helper Functions def filter_timeslots(date, timeslots): From ec1dae023cf9de6513452f70712b7393d7348a79 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 18 Sep 2019 16:13:29 +0530 Subject: [PATCH 046/157] styling --- erpnext/crm/doctype/appointment/appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 1ffd58fa04..ac2e0a8c74 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -29,7 +29,7 @@ class Appointment(Document): 'type': 'Private', 'event_participants': [dict(reference_doctype = "Lead", reference_docname = self.lead)] }) - appointment_event.insert(ignore_permissions = True) + appointment_event.insert(ignore_permissions=True) self.calendar_event = appointment_event.name def after_insert(self): From ba99945359a41130744e9dbd36824897654a8918 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 19 Sep 2019 11:21:05 +0530 Subject: [PATCH 047/157] Prevent booking of appointments for past times --- erpnext/www/book-appointment/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 3370f2429e..d5111c8d1b 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -37,6 +37,7 @@ def get_appointment_slots(date, timezone): 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) + now = datetime.datetime.now() # Database queries settings = frappe.get_doc('Appointment Booking Settings') holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) @@ -52,7 +53,7 @@ def get_appointment_slots(date, timezone): dict(time=_convert_to_tz(timeslot, timezone), availability=False)) continue # Check availability - if check_availabilty(timeslot, settings): + if check_availabilty(timeslot, settings) and timeslot >= now: converted_timeslots.append( dict(time=_convert_to_tz(timeslot, timezone), availability=True)) else: From 5bf52ebed66e5d95dc401df324d027d806280904 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 19 Sep 2019 11:47:54 +0530 Subject: [PATCH 048/157] limit assigment load to appointment day --- erpnext/crm/doctype/appointment/appointment.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index ac2e0a8c74..6d23f2a767 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -21,6 +21,9 @@ class Appointment(Document): def before_insert(self): self.lead = _find_lead_by_email(self.lead).name + + + def after_insert(self): appointment_event = frappe.get_doc({ 'doctype': 'Event', 'subject': ' '.join(['Appointment with', self.customer_name]), @@ -31,9 +34,7 @@ class Appointment(Document): }) appointment_event.insert(ignore_permissions=True) self.calendar_event = appointment_event.name - - def after_insert(self): - available_agents = _get_agents_sorted_by_asc_workload() + available_agents = _get_agents_sorted_by_asc_workload(self.scheduled_time.date()) for agent in available_agents: if(_check_agent_availability(agent, self.scheduled_time)): agent = agent[0] @@ -51,7 +52,7 @@ class Appointment(Document): calendar_event.save() break -def _get_agents_sorted_by_asc_workload(): +def _get_agents_sorted_by_asc_workload(date): appointments = frappe.db.get_list('Appointment', fields='*') agent_list = _get_agent_list_as_strings() if not appointments: @@ -61,7 +62,7 @@ def _get_agents_sorted_by_asc_workload(): assigned_to = frappe.parse_json(appointment._assign) if not assigned_to: continue - if assigned_to[0] in agent_list: + if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date: appointment_counter[assigned_to[0]] += 1 sorted_agent_list = appointment_counter.most_common() sorted_agent_list.reverse() @@ -69,7 +70,7 @@ def _get_agents_sorted_by_asc_workload(): return sorted_agent_list def _find_lead_by_email(email): - lead_list = frappe.get_list('Lead',filters={'email_id':email},ignore_permissions=True) + lead_list = frappe.get_list('Lead', filters={'email_id':email}, ignore_permissions=True) if lead_list: return lead_list[0] frappe.throw('Email ID not associated with any Lead. Please make sure to use the email address you got this mail on') @@ -92,7 +93,7 @@ def _check_agent_availability(agent_email,scheduled_time): def _get_employee_from_user(user): - employee_docname = frappe.db.exists({'doctype':'Employee','user_id':user}) + employee_docname = frappe.db.exists({'doctype':'Employee', 'user_id':user}) if employee_docname: - return frappe.get_doc('Employee',employee_docname[0][0]) # frappe.db.exists returns a tuple of a tuple + return frappe.get_doc('Employee', employee_docname[0][0]) # frappe.db.exists returns a tuple of a tuple return None From 4109f88c04f2ac1240d381da248c3735ff96fd14 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 19 Sep 2019 12:08:10 +0530 Subject: [PATCH 049/157] Linked send_reminder in calendar event to Appointment Booking Settings --- erpnext/crm/doctype/appointment/appointment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 6d23f2a767..9365301e8f 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -30,6 +30,7 @@ class Appointment(Document): 'starts_on': self.scheduled_time, 'status': 'Open', 'type': 'Private', + 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings','email_reminders'), 'event_participants': [dict(reference_doctype = "Lead", reference_docname = self.lead)] }) appointment_event.insert(ignore_permissions=True) From ca2509423ab809127441b6efb3a66bbde7d41837 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 19 Sep 2019 12:36:51 +0530 Subject: [PATCH 050/157] Added permissions for HR manager --- .../appointment_booking_settings.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index c59a2e466f..d72f577656 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -65,7 +65,7 @@ } ], "issingle": 1, - "modified": "2019-09-13 11:31:26.654516", + "modified": "2019-09-19 12:36:34.011724", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", @@ -87,6 +87,15 @@ "read": 1, "role": "Guest", "share": 1 + }, + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "HR Manager", + "share": 1, + "write": 1 } ], "quick_entry": 1, From 5324234bd00357e4f0f0be8016f4f0dc9ae708a7 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 20 Sep 2019 10:08:26 +0530 Subject: [PATCH 051/157] Removed required lead --- .../crm/doctype/appointment/appointment.json | 5 +- erpnext/crm/doctype/lead/lead.json | 1319 ++--------------- 2 files changed, 100 insertions(+), 1224 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 2d695f3199..5ea234437d 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -63,8 +63,7 @@ "fieldname": "lead", "fieldtype": "Link", "label": "Lead", - "options": "Lead", - "reqd": 1 + "options": "Lead" }, { "fieldname": "calendar_event", @@ -73,7 +72,7 @@ "options": "Event" } ], - "modified": "2019-09-13 15:25:49.362246", + "modified": "2019-09-19 16:00:54.390581", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 3c22dc7199..eb68c679ba 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -1,1436 +1,372 @@ { - "allow_copy": 0, "allow_events_in_timeline": 1, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2013-04-10 11:45:37", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Document", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "organization_lead", + "lead_details", + "naming_series", + "lead_name", + "company_name", + "email_id", + "col_break123", + "lead_owner", + "status", + "gender", + "source", + "customer", + "campaign_name", + "image", + "section_break_12", + "contact_by", + "column_break_14", + "contact_date", + "ends_on", + "notes_section", + "notes", + "contact_info", + "address_desc", + "address_html", + "column_break2", + "contact_html", + "phone", + "salutation", + "mobile_no", + "fax", + "website", + "territory", + "more_info", + "type", + "market_segment", + "industry", + "request_type", + "column_break3", + "company", + "unsubscribed", + "blog_subscriber" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, + "default": "0", "fieldname": "organization_lead", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Lead is an Organization", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "lead_details", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "options": "fa fa-user", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-user" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, "fieldname": "naming_series", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Series", - "length": 0, "no_copy": 1, "oldfieldname": "naming_series", "oldfieldtype": "Select", "options": "CRM-LEAD-.YYYY.-", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "lead_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Person Name", - "length": 0, - "no_copy": 0, "oldfieldname": "lead_name", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "company_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Organization Name", - "length": 0, - "no_copy": 0, "oldfieldname": "company_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "email_id", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Email Address", - "length": 0, - "no_copy": 0, "oldfieldname": "email_id", "oldfieldtype": "Data", "options": "Email", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "col_break123", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "__user", - "fetch_if_empty": 0, "fieldname": "lead_owner", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Lead Owner", - "length": 0, - "no_copy": 0, "oldfieldname": "lead_owner", "oldfieldtype": "Link", "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Lead", - "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "length": 0, "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", "options": "Lead\nOpen\nReplied\nOpportunity\nQuotation\nLost Quotation\nInterested\nConverted\nDo Not Contact", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "gender", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Gender" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "source", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Source", - "length": 0, - "no_copy": 0, "oldfieldname": "source", "oldfieldtype": "Select", - "options": "Lead Source", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Lead Source" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.source == 'Existing Customer'", - "fetch_if_empty": 0, "fieldname": "customer", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "From Customer", - "length": 0, "no_copy": 1, "oldfieldname": "customer", "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Customer" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval: doc.source==\"Campaign\"", - "description": "", - "fetch_if_empty": 0, "fieldname": "campaign_name", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Campaign Name", - "length": 0, - "no_copy": 0, "oldfieldname": "campaign_name", "oldfieldtype": "Link", - "options": "Campaign", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Campaign" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "image", "fieldtype": "Attach Image", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_12", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Follow Up", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Follow Up" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "contact_by", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Next Contact By", - "length": 0, - "no_copy": 0, "oldfieldname": "contact_by", "oldfieldtype": "Link", "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, "fieldname": "contact_date", "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Next Contact Date", - "length": 0, "no_copy": 1, "oldfieldname": "contact_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "ends_on", "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Ends On", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "no_copy": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "notes_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Notes" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "notes", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Notes" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "contact_info", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address & Contact", - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "options": "fa fa-map-marker", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-map-marker" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.__islocal", - "fetch_if_empty": 0, "fieldname": "address_desc", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address Desc", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "address_html", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "contact_html", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Contact HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "phone", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Phone", - "length": 0, - "no_copy": 0, "oldfieldname": "contact_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "salutation", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Salutation", - "length": 0, - "no_copy": 0, - "options": "Salutation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Salutation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "mobile_no", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Mobile No.", - "length": 0, - "no_copy": 0, "oldfieldname": "mobile_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "fax", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Fax", - "length": 0, - "no_copy": 0, "oldfieldname": "fax", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "website", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Website", - "length": 0, - "no_copy": 0, "oldfieldname": "website", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, "fieldname": "territory", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Territory", - "length": 0, - "no_copy": 0, "oldfieldname": "territory", "oldfieldtype": "Link", "options": "Territory", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "more_info", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "More Information", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-file-text" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Lead Type", - "length": 0, - "no_copy": 0, "oldfieldname": "type", "oldfieldtype": "Select", - "options": "\nClient\nChannel Partner\nConsultant", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nClient\nChannel Partner\nConsultant" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "market_segment", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Market Segment", - "length": 0, - "no_copy": 0, "oldfieldname": "market_segment", "oldfieldtype": "Select", - "options": "Market Segment", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Market Segment" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "industry", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Industry", - "length": 0, - "no_copy": 0, "oldfieldname": "industry", "oldfieldtype": "Link", - "options": "Industry Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Industry Type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "request_type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Request Type", - "length": 0, - "no_copy": 0, "oldfieldname": "request_type", "oldfieldtype": "Select", - "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break3", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "oldfieldname": "company", "oldfieldtype": "Link", "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "remember_last_selected_value": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "unsubscribed", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unsubscribed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Unsubscribed" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "blog_subscriber", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blog Subscriber", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Blog Subscriber" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-user", "idx": 5, "image_field": "image", - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-06-18 03:22:57.283628", + "modified": "2019-09-19 12:49:02.536647", "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -1438,128 +374,69 @@ "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "All" }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales Manager" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Guest", + "share": 1 } ], - "quick_entry": 0, - "read_only": 0, "search_fields": "lead_name,lead_owner,status", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "lead_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "title_field": "lead_name" } \ No newline at end of file From df1a5a9633646945351cac034283f6409c9d4958 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 20 Sep 2019 10:08:48 +0530 Subject: [PATCH 052/157] Added flow for verifying emails --- .../crm/doctype/appointment/appointment.py | 95 +++++++++++++------ erpnext/www/book-appointment/index.py | 7 -- .../www/book-appointment/verify/index.html | 18 ++++ erpnext/www/book-appointment/verify/index.py | 14 +++ 4 files changed, 96 insertions(+), 38 deletions(-) create mode 100644 erpnext/www/book-appointment/verify/index.html create mode 100644 erpnext/www/book-appointment/verify/index.py diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 9365301e8f..52711fee84 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -13,28 +13,56 @@ from frappe.desk.form.assign_to import add as add_assignemnt class Appointment(Document): - def validate(self): + email='' + + def find_lead_by_email(self,email): + lead_list = frappe.get_list('Lead', filters = {'email_id':email}, ignore_permissions = True) + if lead_list: + return lead_list[0].name + self.email = email + return None + + def before_insert(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') - - def before_insert(self): - self.lead = _find_lead_by_email(self.lead).name - + # Link lead + self.lead = self.find_lead_by_email(self.lead) def after_insert(self): - appointment_event = frappe.get_doc({ - 'doctype': 'Event', - 'subject': ' '.join(['Appointment with', self.customer_name]), - 'starts_on': self.scheduled_time, - 'status': 'Open', - 'type': 'Private', - 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings','email_reminders'), - 'event_participants': [dict(reference_doctype = "Lead", reference_docname = self.lead)] + # Auto assign + self.auto_assign() + # Check if lead was found + if(self.lead): + # Create Calendar event + self.create_calendar_event() + else: + # Send email to confirm + # frappe.sendmail(recipients=[self.email],message='https:/',subject="") + frappe.msgprint("Please check your email to confirm the appointment") + + def set_verified(self,email): + # Create new lead + self.create_lead(email) + # Create calender event + self.create_calendar_event() + self.save( ignore_permissions=True ) + frappe.db.commit() + + def create_lead(self,email): + lead = frappe.get_doc({ + 'doctype':'Lead', + 'lead_name':self.customer_name, + 'email_id':email, + 'notes':self.customer_details, + 'phone':self.customer_phone_number, }) - appointment_event.insert(ignore_permissions=True) - self.calendar_event = appointment_event.name + print(lead.insert( ignore_permissions=True )) + # Link lead + self.lead = lead.name + + def auto_assign(self): available_agents = _get_agents_sorted_by_asc_workload(self.scheduled_time.date()) for agent in available_agents: if(_check_agent_availability(agent, self.scheduled_time)): @@ -44,14 +72,26 @@ class Appointment(Document): 'name':self.name, 'assign_to':agent }) - employee = _get_employee_from_user(agent) - if employee: - calendar_event = frappe.get_doc('Event', self.calendar_event) - calendar_event.append('event_participants', dict( - reference_doctype= 'Employee', - reference_docname= employee.name)) - calendar_event.save() - break + break + + def create_calendar_event(self): + appointment_event = frappe.get_doc({ + 'doctype': 'Event', + 'subject': ' '.join(['Appointment with', self.customer_name]), + 'starts_on': self.scheduled_time, + 'status': 'Open', + 'type': 'Public', + 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings','email_reminders'), + 'event_participants': [dict(reference_doctype = "Lead", reference_docname = self.lead)] + }) + employee = _get_employee_from_user(self._assign) + if employee: + appointment_event.append('event_participants', dict( + reference_doctype = 'Employee', + reference_docname = employee.name)) + appointment_event.insert(ignore_permissions=True) + self.calendar_event = appointment_event.name + self.save(ignore_permissions=True) def _get_agents_sorted_by_asc_workload(date): appointments = frappe.db.get_list('Appointment', fields='*') @@ -70,13 +110,6 @@ def _get_agents_sorted_by_asc_workload(date): return sorted_agent_list -def _find_lead_by_email(email): - lead_list = frappe.get_list('Lead', filters={'email_id':email}, ignore_permissions=True) - if lead_list: - return lead_list[0] - frappe.throw('Email ID not associated with any Lead. Please make sure to use the email address you got this mail on') - - def _get_agent_list_as_strings(): agent_list_as_strings = [] agent_list = frappe.get_doc('Appointment Booking Settings').agent_list @@ -97,4 +130,4 @@ def _get_employee_from_user(user): employee_docname = frappe.db.exists({'doctype':'Employee', 'user_id':user}) if employee_docname: return frappe.get_doc('Employee', employee_docname[0][0]) # frappe.db.exists returns a tuple of a tuple - return None + return None \ No newline at end of file diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index d5111c8d1b..c1585aaf2f 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -111,18 +111,15 @@ def filter_timeslots(date, timeslots): 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: @@ -130,17 +127,14 @@ def _get_records(start_time, end_time, settings): 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 @@ -148,7 +142,6 @@ def _convert_to_ist(datetime_object, timezone): 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 diff --git a/erpnext/www/book-appointment/verify/index.html b/erpnext/www/book-appointment/verify/index.html new file mode 100644 index 0000000000..ebb65b1f24 --- /dev/null +++ b/erpnext/www/book-appointment/verify/index.html @@ -0,0 +1,18 @@ +{% extends "templates/web.html" %} + +{% block title %} +{{ _("Verify Email") }} +{% endblock%} + +{% block page_content %} + + {% if success==True %} +
+ Your email has been verified and your appointment has been scheduled +
+ {% else %} +
+ Verification failed please check the link +
+ {% endif %} +{% endblock%} \ No newline at end of file diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book-appointment/verify/index.py new file mode 100644 index 0000000000..d25b50565a --- /dev/null +++ b/erpnext/www/book-appointment/verify/index.py @@ -0,0 +1,14 @@ +import frappe +@frappe.whitelist(allow_guest=True) +def get_context(context): + email = frappe.form_dict['email'] + appointment_name = frappe.form_dict['appointment'] + if email and appointment_name: + appointment = frappe.get_doc('Appointment',appointment_name) + appointment.set_verified(email) + context.success = True + return context + else: + print('Something not found') + context.success = False + return context \ No newline at end of file From fa4a2a53e8029f35b0194d6f5186478090796607 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 20 Sep 2019 10:41:59 +0530 Subject: [PATCH 053/157] Added email --- erpnext/crm/doctype/appointment/appointment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 52711fee84..dee7c7c32c 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -39,7 +39,8 @@ class Appointment(Document): self.create_calendar_event() else: # Send email to confirm - # frappe.sendmail(recipients=[self.email],message='https:/',subject="") + verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.email,"&appoitnment=",self.name])) + frappe.sendmail(recipients=[self.email],message=verify_url',self.email,"&appoitnment=",self.name),subject="") frappe.msgprint("Please check your email to confirm the appointment") def set_verified(self,email): From 73420e462f821eeacb33423017a8f8715439788a Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 20 Sep 2019 10:41:59 +0530 Subject: [PATCH 054/157] Added email --- erpnext/crm/doctype/appointment/appointment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 52711fee84..af2878ec67 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -39,7 +39,8 @@ class Appointment(Document): self.create_calendar_event() else: # Send email to confirm - # frappe.sendmail(recipients=[self.email],message='https:/',subject="") + verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.email,"&appoitnment=",self.name]) + frappe.sendmail(recipients=[self.email],message=verify_url',self.email,"&appoitnment=",self.name),subject="") frappe.msgprint("Please check your email to confirm the appointment") def set_verified(self,email): From 9c0f46233639768a5fd393943ff4ab8540c72692 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 20 Sep 2019 10:51:56 +0530 Subject: [PATCH 055/157] Fixed Syntax errors --- erpnext/crm/doctype/appointment/appointment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 1ca706c19b..5d8a30fd2f 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -40,7 +40,9 @@ class Appointment(Document): else: # Send email to confirm verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.email,"&appoitnment=",self.name]) - frappe.sendmail(recipients=[self.email],message=verify_url,self.email,"&appoitnment=",self.name),subject="") + frappe.sendmail(recipients=[self.email], + message=verify_url, + subject="") frappe.msgprint("Please check your email to confirm the appointment") def set_verified(self,email): From 6b0fea16b64806bdfcc1e5f391ce8fd0a5d82fab Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 11:26:18 +0530 Subject: [PATCH 056/157] Added buttons to linked docs --- erpnext/crm/doctype/appointment/appointment.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.js b/erpnext/crm/doctype/appointment/appointment.js index 4e41047fa1..975abfcd93 100644 --- a/erpnext/crm/doctype/appointment/appointment.js +++ b/erpnext/crm/doctype/appointment/appointment.js @@ -2,7 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on('Appointment', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + if(frm.doc.lead){ + frm.add_custom_button(__(frm.doc.lead),()=>{ + frappe.set_route("Form","Lead",frm.doc.lead) + }) + } + if(frm.doc.calendar_event){ + frm.add_custom_button(__(frm.doc.calendar_event),()=>{ + frappe.set_route("Form","Event",frm.doc.calendar_event) + }) + } + } }); From 0800031c0d712d9d24e34703beedc8defe8660db Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 11:26:46 +0530 Subject: [PATCH 057/157] Addee email to appointment doctyoe and asthetic changes --- .../crm/doctype/appointment/appointment.json | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 5ea234437d..22df5c6aa8 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -11,8 +11,12 @@ "customer_name", "customer_phone_number", "customer_skype", + "customer_email", + "col_br_2", "customer_details", + "linked_docs_section", "lead", + "col_br_3", "calendar_event" ], "fields": [ @@ -56,7 +60,7 @@ "fieldname": "status", "fieldtype": "Select", "label": "Status", - "options": "Open\nClosed", + "options": "Open\nUnverified\nClosed", "reqd": 1 }, { @@ -70,9 +74,28 @@ "fieldtype": "Link", "label": "Calendar Event", "options": "Event" + }, + { + "fieldname": "col_br_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "customer_email", + "fieldtype": "Data", + "label": "Email", + "reqd": 1 + }, + { + "fieldname": "linked_docs_section", + "fieldtype": "Section Break", + "label": "Linked Docs" + }, + { + "fieldname": "col_br_3", + "fieldtype": "Column Break" } ], - "modified": "2019-09-19 16:00:54.390581", + "modified": "2019-09-23 10:57:04.876506", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", From f8cc86bfedb9a0e8ec1945e56c320e44d27f4cbb Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 11:28:05 +0530 Subject: [PATCH 058/157] Moved email from class variable to doctype Formatting Made methods which link other doctypes idempotent --- .../crm/doctype/appointment/appointment.py | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 5d8a30fd2f..5e0648659b 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -13,10 +13,9 @@ from frappe.desk.form.assign_to import add as add_assignemnt class Appointment(Document): - email='' - def find_lead_by_email(self,email): - lead_list = frappe.get_list('Lead', filters = {'email_id':email}, ignore_permissions = True) + def find_lead_by_email(self): + lead_list = frappe.get_list('Lead', filters = {'email_id':self.email}, ignore_permissions = True) if lead_list: return lead_list[0].name self.email = email @@ -28,7 +27,7 @@ class Appointment(Document): if(number_of_appointments_in_same_slot >= settings.number_of_agents): frappe.throw('Time slot is not available') # Link lead - self.lead = self.find_lead_by_email(self.lead) + self.lead = self.find_lead_by_email() def after_insert(self): # Auto assign @@ -38,22 +37,35 @@ class Appointment(Document): # Create Calendar event self.create_calendar_event() else: + # Set status to unverified + self.status = 'Unverified' # Send email to confirm - verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.email,"&appoitnment=",self.name]) + verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.email,'&appoitnment=',self.name]) + message = ''.join(['Please click the following link to confirm your appointment:']+verify_url) frappe.sendmail(recipients=[self.email], - message=verify_url, - subject="") - frappe.msgprint("Please check your email to confirm the appointment") + message=message, + subject=_('Appointment Confirmation')) + frappe.msgprint('Please check your email to confirm the appointment') + + def on_update(): + # Sync Calednar + cal_event = frappe.get_doc('Event,self.calendar_event def set_verified(self,email): + if not email == self.email: + frappe.throw('Email verification failed.') # Create new lead - self.create_lead(email) + self.create_lead() # Create calender event + self.status = 'Open' self.create_calendar_event() - self.save( ignore_permissions=True ) + self.save(ignore_permissions=True) frappe.db.commit() def create_lead(self,email): + # Return if already linked + if self.lead: + return lead = frappe.get_doc({ 'doctype':'Lead', 'lead_name':self.customer_name, @@ -61,11 +73,13 @@ class Appointment(Document): 'notes':self.customer_details, 'phone':self.customer_phone_number, }) - print(lead.insert( ignore_permissions=True )) + lead.insert(ignore_permissions=True) # Link lead self.lead = lead.name def auto_assign(self): + if self._assign: + return available_agents = _get_agents_sorted_by_asc_workload(self.scheduled_time.date()) for agent in available_agents: if(_check_agent_availability(agent, self.scheduled_time)): @@ -78,6 +92,8 @@ class Appointment(Document): break def create_calendar_event(self): + if self.appointment: + return appointment_event = frappe.get_doc({ 'doctype': 'Event', 'subject': ' '.join(['Appointment with', self.customer_name]), @@ -85,7 +101,7 @@ class Appointment(Document): 'status': 'Open', 'type': 'Public', 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings','email_reminders'), - 'event_participants': [dict(reference_doctype = "Lead", reference_docname = self.lead)] + 'event_participants': [dict(reference_doctype = 'Lead', reference_docname = self.lead)] }) employee = _get_employee_from_user(self._assign) if employee: @@ -110,7 +126,6 @@ def _get_agents_sorted_by_asc_workload(date): appointment_counter[assigned_to[0]] += 1 sorted_agent_list = appointment_counter.most_common() sorted_agent_list.reverse() - return sorted_agent_list def _get_agent_list_as_strings(): @@ -120,7 +135,6 @@ def _get_agent_list_as_strings(): agent_list_as_strings.append(agent.user) return agent_list_as_strings - def _check_agent_availability(agent_email,scheduled_time): appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters = {'scheduled_time':scheduled_time}) for appointment in appointemnts_at_scheduled_time: @@ -128,7 +142,6 @@ def _check_agent_availability(agent_email,scheduled_time): return False return True - def _get_employee_from_user(user): employee_docname = frappe.db.exists({'doctype':'Employee', 'user_id':user}) if employee_docname: From d9ab09ab2b169873f17b49ddd8994462bf39f990 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 11:28:17 +0530 Subject: [PATCH 059/157] Moved email to appoitnmetn doctype --- erpnext/www/book-appointment/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index c1585aaf2f..67619fc5d5 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -98,7 +98,7 @@ def create_appointment(date, time, contact): appointment.customer_phone_number = contact['number'] appointment.customer_skype = contact['skype'] appointment.customer_details = contact['notes'] - appointment.lead = contact['email'] + appointment.email = contact['email'] appointment.status = 'Open' appointment.insert() From dcfc849946f5b1f0700b13f40bee008e333d0ba4 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 11:28:17 +0530 Subject: [PATCH 060/157] Moved email to appoitnmetn doctype --- erpnext/crm/doctype/appointment/appointment.py | 4 +++- erpnext/www/book-appointment/index.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 5e0648659b..1095b56ae9 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -49,7 +49,9 @@ class Appointment(Document): def on_update(): # Sync Calednar - cal_event = frappe.get_doc('Event,self.calendar_event + cal_event = frappe.get_doc('Event',self.calendar_event) + cal_event.starts_on = self.scheduled_time + cal_event.save() def set_verified(self,email): if not email == self.email: diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index c1585aaf2f..67619fc5d5 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -98,7 +98,7 @@ def create_appointment(date, time, contact): appointment.customer_phone_number = contact['number'] appointment.customer_skype = contact['skype'] appointment.customer_details = contact['notes'] - appointment.lead = contact['email'] + appointment.email = contact['email'] appointment.status = 'Open' appointment.insert() From 7b7962d28c7f53731f0aa7c0e21c9f4b23aa59e3 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 13:05:18 +0530 Subject: [PATCH 061/157] Added test cases --- .../doctype/appointment/test_appointment.py | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 3c977505b5..d529d37aad 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -7,10 +7,46 @@ import frappe import unittest import datetime +def create_test_lead(): + if frappe.db.exists('Lead',filters={'lead_name':'Test Lead'}): + return + test_lead = frappe.get_doc({ + 'doctype':'Lead', + 'lead_name':'Test Lead', + 'email_id':'test@example.com' + }) + test_lead.insert(ignore_permissions=True) + return test_lead -def delete_appointments(): - pass - +def create_test_appointments(): + if frappe.db.exists('Appointment',filters={'email':'test@example.com'}): + return + test_appointment = frappe.get_doc({ + 'doctype':'Appointment', + 'email':'test@example.com', + 'status':'Open', + 'customer_name':'Test Lead', + 'customer_phone_number':'666', + 'customer_skype':'test', + 'customer_email':'test@example.com', + 'scheduled_time':datetime.datetime.now() + }) + test_appointment.insert() + return test_appointment class TestAppointment(unittest.TestCase): - pass + test_appointment,test_lead = None + def setUp(self): + test_lead = create_test_lead() + test_appointment = test_create_test_appointments() + + def tearDown(self): + pass + + def test_calendar_event_created(self): + cal_event = frappe.get_doc('Event',test_appointment.calendar_event) + self.assertEqual(cal_event.starts_on ,test_appointment.scheduled_time) + + def test_lead_linked(self): + lead = frappe.get_doc('Lead',self.lead) + self.assertIsNotNone(lead) \ No newline at end of file From b6b27d9256be2c3d72522c6baf647fcac1fd0bfd Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 14:16:13 +0530 Subject: [PATCH 062/157] Corrected moving to doctype for email --- erpnext/crm/doctype/appointment/appointment.py | 12 ++++++------ erpnext/www/book-appointment/index.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 1095b56ae9..219f93111a 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -8,6 +8,7 @@ from collections import Counter from datetime import timedelta import frappe +from frappe import _ from frappe.model.document import Document from frappe.desk.form.assign_to import add as add_assignemnt @@ -15,10 +16,9 @@ from frappe.desk.form.assign_to import add as add_assignemnt class Appointment(Document): def find_lead_by_email(self): - lead_list = frappe.get_list('Lead', filters = {'email_id':self.email}, ignore_permissions = True) + lead_list = frappe.get_list('Lead', filters = {'email_id':self.customer_email}, ignore_permissions = True) if lead_list: return lead_list[0].name - self.email = email return None def before_insert(self): @@ -40,9 +40,9 @@ class Appointment(Document): # Set status to unverified self.status = 'Unverified' # Send email to confirm - verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.email,'&appoitnment=',self.name]) - message = ''.join(['Please click the following link to confirm your appointment:']+verify_url) - frappe.sendmail(recipients=[self.email], + verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.customer_email,'&appoitnment=',self.name]) + message = ''.join(['Please click the following link to confirm your appointment:',verify_url]) + frappe.sendmail(recipients=[self.customer_email], message=message, subject=_('Appointment Confirmation')) frappe.msgprint('Please check your email to confirm the appointment') @@ -54,7 +54,7 @@ class Appointment(Document): cal_event.save() def set_verified(self,email): - if not email == self.email: + if not email == self.customer_email: frappe.throw('Email verification failed.') # Create new lead self.create_lead() diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 67619fc5d5..49b3ffc2cf 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -98,7 +98,7 @@ def create_appointment(date, time, contact): appointment.customer_phone_number = contact['number'] appointment.customer_skype = contact['skype'] appointment.customer_details = contact['notes'] - appointment.email = contact['email'] + appointment.customer_email = contact['email'] appointment.status = 'Open' appointment.insert() From e40b1001104614275b6e622cbbee0cbcd753b3d4 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 14:23:04 +0530 Subject: [PATCH 063/157] Fixed update method --- erpnext/crm/doctype/appointment/appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 219f93111a..b259758e02 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -47,7 +47,7 @@ class Appointment(Document): subject=_('Appointment Confirmation')) frappe.msgprint('Please check your email to confirm the appointment') - def on_update(): + def on_update(self): # Sync Calednar cal_event = frappe.get_doc('Event',self.calendar_event) cal_event.starts_on = self.scheduled_time From 3eccb84eaa4240c84049447440041b5c5992bb41 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 14:23:04 +0530 Subject: [PATCH 064/157] Fixed update method --- erpnext/crm/doctype/appointment/appointment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 219f93111a..95c7f35fbb 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -47,8 +47,10 @@ class Appointment(Document): subject=_('Appointment Confirmation')) frappe.msgprint('Please check your email to confirm the appointment') - def on_update(): + def on_update(self): # Sync Calednar + if not self.calendar_event: + return cal_event = frappe.get_doc('Event',self.calendar_event) cal_event.starts_on = self.scheduled_time cal_event.save() From a35e34b5f0f36323fd0941f5eb2070a5c4510622 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 14:38:22 +0530 Subject: [PATCH 065/157] FIxed typos and create_lead method --- erpnext/crm/doctype/appointment/appointment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 95c7f35fbb..260026c495 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -40,7 +40,7 @@ class Appointment(Document): # Set status to unverified self.status = 'Unverified' # Send email to confirm - verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.customer_email,'&appoitnment=',self.name]) + verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.customer_email,'&appointment=',self.name]) message = ''.join(['Please click the following link to confirm your appointment:',verify_url]) frappe.sendmail(recipients=[self.customer_email], message=message, @@ -66,14 +66,14 @@ class Appointment(Document): self.save(ignore_permissions=True) frappe.db.commit() - def create_lead(self,email): + def create_lead(self): # Return if already linked if self.lead: return lead = frappe.get_doc({ 'doctype':'Lead', 'lead_name':self.customer_name, - 'email_id':email, + 'email_id':self.customer_email, 'notes':self.customer_details, 'phone':self.customer_phone_number, }) @@ -96,7 +96,7 @@ class Appointment(Document): break def create_calendar_event(self): - if self.appointment: + if self.calendar_event: return appointment_event = frappe.get_doc({ 'doctype': 'Event', From 8b744b2d03eb88b4674503836c3fa2da66674de6 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 15:55:35 +0530 Subject: [PATCH 066/157] Added request verification and url encoding --- erpnext/crm/doctype/appointment/appointment.py | 18 ++++++++++++++++-- erpnext/www/book-appointment/verify/index.py | 6 ++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 260026c495..a495b910e8 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals +import urllib from collections import Counter from datetime import timedelta @@ -11,6 +12,8 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.desk.form.assign_to import add as add_assignemnt +from frappe.utils import get_url +from frappe.utils.verified_command import verify_request,get_signed_params class Appointment(Document): @@ -40,13 +43,23 @@ class Appointment(Document): # Set status to unverified self.status = 'Unverified' # Send email to confirm - verify_url = ''.join([frappe.utils.get_url(),'/book-appointment/verify?email=',self.customer_email,'&appointment=',self.name]) + verify_url = self.get_verify_url() message = ''.join(['Please click the following link to confirm your appointment:',verify_url]) frappe.sendmail(recipients=[self.customer_email], message=message, subject=_('Appointment Confirmation')) frappe.msgprint('Please check your email to confirm the appointment') + def get_verify_url(self): + verify_route = '/book-appointment/verify' + + params = { + 'email':self.customer_email, + 'appointment':self.name + } + + return get_url(verify_route + '?' + get_signed_params(params)) + def on_update(self): # Sync Calednar if not self.calendar_event: @@ -60,8 +73,9 @@ class Appointment(Document): frappe.throw('Email verification failed.') # Create new lead self.create_lead() - # Create calender event + # Remove unverified status self.status = 'Open' + # Create calender event self.create_calendar_event() self.save(ignore_permissions=True) frappe.db.commit() diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book-appointment/verify/index.py index d25b50565a..86f9515332 100644 --- a/erpnext/www/book-appointment/verify/index.py +++ b/erpnext/www/book-appointment/verify/index.py @@ -1,8 +1,14 @@ import frappe +from frappe.utils.verified_command import verify_request @frappe.whitelist(allow_guest=True) def get_context(context): + if not verify_request(): + context.success = False + return context + email = frappe.form_dict['email'] appointment_name = frappe.form_dict['appointment'] + if email and appointment_name: appointment = frappe.get_doc('Appointment',appointment_name) appointment.set_verified(email) From 8393ebbbca6c262c635a5504c4f52ddea7604ad3 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 23 Sep 2019 17:14:31 +0530 Subject: [PATCH 067/157] Fixed missing permission in update --- erpnext/crm/doctype/appointment/appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index a495b910e8..2f140989a7 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -66,7 +66,7 @@ class Appointment(Document): return cal_event = frappe.get_doc('Event',self.calendar_event) cal_event.starts_on = self.scheduled_time - cal_event.save() + cal_event.save(ignore_permissions=True) def set_verified(self,email): if not email == self.customer_email: From 558d44e519d64b59f341802acd193db447244421 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 24 Sep 2019 11:33:57 +0530 Subject: [PATCH 068/157] Removed auto-assignment for unverified appointments --- erpnext/crm/doctype/appointment/appointment.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2f140989a7..f32699e786 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -33,12 +33,10 @@ class Appointment(Document): self.lead = self.find_lead_by_email() def after_insert(self): - # Auto assign - self.auto_assign() - # Check if lead was found if(self.lead): # Create Calendar event self.create_calendar_event() + self.auto_assign() else: # Set status to unverified self.status = 'Unverified' @@ -77,6 +75,7 @@ class Appointment(Document): self.status = 'Open' # Create calender event self.create_calendar_event() + self.auto_assign() self.save(ignore_permissions=True) frappe.db.commit() From c9cf5aebeaa159580b1ed6e35c1155de9602063d Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 24 Sep 2019 12:08:37 +0530 Subject: [PATCH 069/157] Changed required values, add clientside validation --- erpnext/crm/doctype/appointment/appointment.json | 8 +++----- erpnext/www/book-appointment/index.html | 14 ++++++-------- erpnext/www/book-appointment/index.js | 7 ++++++- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 22df5c6aa8..9dfcc57197 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -35,14 +35,12 @@ { "fieldname": "customer_phone_number", "fieldtype": "Data", - "label": "Phone Number", - "reqd": 1 + "label": "Phone Number" }, { "fieldname": "customer_skype", "fieldtype": "Data", - "label": "Skype ID", - "reqd": 1 + "label": "Skype ID" }, { "fieldname": "customer_details", @@ -95,7 +93,7 @@ "fieldtype": "Column Break" } ], - "modified": "2019-09-23 10:57:04.876506", + "modified": "2019-09-24 11:44:21.228104", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index 2e0321394e..1f6dd2e0e6 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -45,16 +45,14 @@
- - - - +
+ + + + +
diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 345e614154..6034f4eb48 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -195,7 +195,11 @@ function setup_details_page() { } async function submit() { - // form validation here + let form = document.querySelector('#customer-form'); + if(!form.checkValidity()){ + form.reportValidity(); + return; + } get_form_data(); let appointment = (await frappe.call({ method: 'erpnext.www.book-appointment.index.create_appointment', @@ -212,6 +216,7 @@ async function submit() { } function get_form_data() { + contact = {}; contact.name = document.getElementById('customer_name').value; contact.number = document.getElementById('customer_number').value; From d45c12b38265e8b2bb28becfac5c247793713cac Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 24 Sep 2019 16:07:02 +0530 Subject: [PATCH 070/157] Formatting --- erpnext/www/book-appointment/verify/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book-appointment/verify/index.py index 86f9515332..8ea96383a3 100644 --- a/erpnext/www/book-appointment/verify/index.py +++ b/erpnext/www/book-appointment/verify/index.py @@ -5,7 +5,7 @@ def get_context(context): if not verify_request(): context.success = False return context - + email = frappe.form_dict['email'] appointment_name = frappe.form_dict['appointment'] From 9f86022c2b68268a7bd69d7f1c5f3ca099048a32 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Tue, 24 Sep 2019 16:07:41 +0530 Subject: [PATCH 071/157] fix: Error in test setUp --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index d529d37aad..bc7fe72e90 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -35,7 +35,7 @@ def create_test_appointments(): return test_appointment class TestAppointment(unittest.TestCase): - test_appointment,test_lead = None + test_appointment = test_lead = None def setUp(self): test_lead = create_test_lead() test_appointment = test_create_test_appointments() From 291e1617935e4551dcc9f32b9981d8188e50d13f Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 25 Sep 2019 13:11:04 +0530 Subject: [PATCH 072/157] Added permissions for sales user --- .../crm/doctype/appointment/appointment.json | 25 ++++++++++++++++++- .../appointment_booking_settings.json | 12 ++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 9dfcc57197..323e096b40 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -93,7 +93,7 @@ "fieldtype": "Column Break" } ], - "modified": "2019-09-24 11:44:21.228104", + "modified": "2019-09-25 13:08:46.368307", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", @@ -121,6 +121,29 @@ "report": 1, "role": "Guest", "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 } ], "quick_entry": 1, diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index d72f577656..4229e4b788 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -65,7 +65,7 @@ } ], "issingle": 1, - "modified": "2019-09-19 12:36:34.011724", + "modified": "2019-09-25 13:08:28.328561", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", @@ -96,6 +96,16 @@ "role": "HR Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 } ], "quick_entry": 1, From fd46bf261624a8e0ade9a54698ac0924446fe8a4 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 25 Sep 2019 16:01:48 +0530 Subject: [PATCH 073/157] fix codacy --- erpnext/crm/doctype/appointment/appointment.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.js b/erpnext/crm/doctype/appointment/appointment.js index 975abfcd93..485520f562 100644 --- a/erpnext/crm/doctype/appointment/appointment.js +++ b/erpnext/crm/doctype/appointment/appointment.js @@ -5,13 +5,13 @@ frappe.ui.form.on('Appointment', { refresh: function(frm) { if(frm.doc.lead){ frm.add_custom_button(__(frm.doc.lead),()=>{ - frappe.set_route("Form","Lead",frm.doc.lead) - }) + frappe.set_route("Form","Lead",frm.doc.lead); + }); } if(frm.doc.calendar_event){ frm.add_custom_button(__(frm.doc.calendar_event),()=>{ - frappe.set_route("Form","Event",frm.doc.calendar_event) - }) + frappe.set_route("Form","Event",frm.doc.calendar_event); + }); } } }); From 250bae260380e5fba2bce41e0f1664157cdf8d72 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 30 Sep 2019 12:40:25 +0530 Subject: [PATCH 074/157] fix:appointment tests exist check --- erpnext/crm/doctype/appointment/test_appointment.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index bc7fe72e90..d73c6ec035 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -19,7 +19,10 @@ def create_test_lead(): return test_lead def create_test_appointments(): - if frappe.db.exists('Appointment',filters={'email':'test@example.com'}): + if frappe.db.exists({ + 'doctype':'Appointment', + 'email':'test@example.com' + }): return test_appointment = frappe.get_doc({ 'doctype':'Appointment', From 7f4bc64d22a78db2b20d1d945a4555194951c8fa Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 30 Sep 2019 12:40:25 +0530 Subject: [PATCH 075/157] fix:appointment tests exist check --- erpnext/crm/doctype/appointment/test_appointment.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index bc7fe72e90..9b87c792cd 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -8,7 +8,7 @@ import unittest import datetime def create_test_lead(): - if frappe.db.exists('Lead',filters={'lead_name':'Test Lead'}): + if frappe.db.exists({'doctype:''Lead','lead_name':'Test Lead'}): return test_lead = frappe.get_doc({ 'doctype':'Lead', @@ -19,7 +19,10 @@ def create_test_lead(): return test_lead def create_test_appointments(): - if frappe.db.exists('Appointment',filters={'email':'test@example.com'}): + if frappe.db.exists({ + 'doctype':'Appointment', + 'email':'test@example.com' + }): return test_appointment = frappe.get_doc({ 'doctype':'Appointment', From 2ea9b3e6f20a502643e231853350fbd04372cac0 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Mon, 30 Sep 2019 15:35:38 +0530 Subject: [PATCH 076/157] fix:test appointments --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 9b87c792cd..f6385bfba2 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -8,7 +8,7 @@ import unittest import datetime def create_test_lead(): - if frappe.db.exists({'doctype:''Lead','lead_name':'Test Lead'}): + if frappe.db.exists({'doctype':'Lead','lead_name':'Test Lead'}): return test_lead = frappe.get_doc({ 'doctype':'Lead', From c6da5fb38e72a4dc999642e7083c2e26414cb8b1 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 11:56:23 +0530 Subject: [PATCH 077/157] fix:guess timezone using moment --- erpnext/www/book-appointment/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 6034f4eb48..1b7a80154e 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -33,9 +33,11 @@ function setup_timezone_selector() { let offset = new Date().getTimezoneOffset(); window.timezones.forEach(timezone => { let opt = document.createElement('option'); - opt.value = timezone.offset; - opt.innerHTML = timezone.timezone_name; - opt.defaultSelected = (offset == timezone.offset) + opt.value = timezone; + if(timezone == moment.tz.guess()){ + opt.selected = true; + } + opt.innerHTML = timezone; timezones_element.appendChild(opt) }); } From 1dcedb5054203c6e6a81302bfbbaa2758193cefb Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 11:56:54 +0530 Subject: [PATCH 078/157] fix: empty leads and appointment in test --- erpnext/crm/doctype/appointment/test_appointment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index f6385bfba2..7e7f67c700 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -9,7 +9,7 @@ import datetime def create_test_lead(): if frappe.db.exists({'doctype':'Lead','lead_name':'Test Lead'}): - return + return frappe.get_doc('Lead','Test Lead') test_lead = frappe.get_doc({ 'doctype':'Lead', 'lead_name':'Test Lead', @@ -23,7 +23,7 @@ def create_test_appointments(): 'doctype':'Appointment', 'email':'test@example.com' }): - return + return frappe.get_doc('Appointment','Test Appointment') test_appointment = frappe.get_doc({ 'doctype':'Appointment', 'email':'test@example.com', From 93670fedda62a3a91517e5c80deb66f1728bc0b2 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 11:58:02 +0530 Subject: [PATCH 079/157] timezone manipulation using pytz --- erpnext/www/book-appointment/index.py | 41 +++++++++++++-------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 49b3ffc2cf..5f51ced96e 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -1,6 +1,7 @@ import frappe import datetime import json +import pytz WEEKDAYS = ["Monday", "Tuesday", "Wednesday", @@ -24,20 +25,26 @@ def get_holiday_list(holiday_list_name): @frappe.whitelist(allow_guest=True) def get_timezones(): timezones = frappe.get_list('Timezone', fields='*') - return timezones + return pytz.all_timezones @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): - timezone = int(timezone) + import pytz + guest_timezone = pytz.timezone(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) + local_timezone = frappe.utils.get_time_zone() + local_timezone = pytz.timezone(local_timezone) + query_start_time = guest_timezone.localize(query_start_time) + query_end_time = guest_timezone.localize(query_end_time) + query_start_time = query_start_time.astimezone(local_timezone) + query_end_time = query_end_time.astimezone(local_timezone) now = datetime.datetime.now() + # now = local_timezone.localize(now) # Database queries settings = frappe.get_doc('Appointment Booking Settings') holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) @@ -47,18 +54,22 @@ def get_appointment_slots(date, timezone): # Filter timeslots based on date converted_timeslots = [] for timeslot in timeslots: + timeslot = local_timezone.localize(timeslot) + print(timeslot) + timeslot = timeslot.astimezone(guest_timezone) + timeslot = timeslot.replace(tzinfo=None) # Check if holiday if _is_holiday(timeslot.date(), holiday_list): converted_timeslots.append( - dict(time=_convert_to_tz(timeslot, timezone), availability=False)) + dict(time=timeslot, availability=False)) continue # Check availability if check_availabilty(timeslot, settings) and timeslot >= now: converted_timeslots.append( - dict(time=_convert_to_tz(timeslot, timezone), availability=True)) + dict(time=timeslot, availability=True)) else: converted_timeslots.append( - dict(time=_convert_to_tz(timeslot, timezone), availability=False)) + dict(time=timeslot, availability=False)) date_required = datetime.datetime.strptime( date + ' 00:00:00', format_string).date() converted_timeslots = filter_timeslots(date_required, converted_timeslots) @@ -133,18 +144,4 @@ def _deltatime_to_datetime(date, deltatime): 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 \ No newline at end of file + return (date_time-midnight) \ No newline at end of file From c5420bb4535bfc9006f9ed035441bc76b852a2dc Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 12:06:43 +0530 Subject: [PATCH 080/157] fix: remove validation for repeated days --- erpnext/crm/doctype/appointment/appointment.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index f32699e786..4dcb2016ac 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -58,7 +58,7 @@ class Appointment(Document): return get_url(verify_route + '?' + get_signed_params(params)) - def on_update(self): + def on_change(self): # Sync Calednar if not self.calendar_event: return @@ -66,6 +66,12 @@ class Appointment(Document): cal_event.starts_on = self.scheduled_time cal_event.save(ignore_permissions=True) + def on_trash(self): + # Delete calendar event + cal_event = frappe.get_doc('Event',self.calendar_event) + if cal_event: + cal_event.delete() + # Delete task? def set_verified(self,email): if not email == self.customer_email: frappe.throw('Email verification failed.') From 4856645b6d4c38db3c9edd5e6cd15410c43abd5f Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 12:45:42 +0530 Subject: [PATCH 081/157] fix:styling for time-slot --- erpnext/www/book-appointment/index.html | 2 +- erpnext/www/book-appointment/index.js | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index 1f6dd2e0e6..e1355a7765 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -26,7 +26,7 @@
-
+
diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 1b7a80154e..f2496da5a6 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -99,12 +99,6 @@ async function update_time_slots(selected_date, selected_timezone) { return } window.slots.forEach((slot, index) => { - // Add a break after each 8 elements - if (index % 8 == 0) { - let break_element = document.createElement('div'); - break_element.classList.add('w-100'); - timeslot_container.appendChild(break_element); - } // Get and append timeslot div let timeslot_div = get_timeslot_div_layout(slot) timeslot_container.appendChild(timeslot_div); @@ -116,7 +110,6 @@ function get_timeslot_div_layout(timeslot) { let start_time = new Date(timeslot.time) let timeslot_div = document.createElement('div'); timeslot_div.classList.add('time-slot'); - timeslot_div.classList.add('col-md'); if (!timeslot.availability) { timeslot_div.classList.add('unavailable') } From 76cbb9132f6126d22e4d5f9375d2364988395c4f Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 12:50:55 +0530 Subject: [PATCH 082/157] fix: more test errors --- erpnext/crm/doctype/appointment/test_appointment.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 7e7f67c700..ec16d6a582 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -8,8 +8,9 @@ import unittest import datetime def create_test_lead(): - if frappe.db.exists({'doctype':'Lead','lead_name':'Test Lead'}): - return frappe.get_doc('Lead','Test Lead') + test_lead = frappe.db.exists({'doctype':'Lead','lead_name':'Test Lead'}) + if test_lead: + return frappe.get_doc('Lead',test_lead) test_lead = frappe.get_doc({ 'doctype':'Lead', 'lead_name':'Test Lead', @@ -19,11 +20,9 @@ def create_test_lead(): return test_lead def create_test_appointments(): - if frappe.db.exists({ - 'doctype':'Appointment', - 'email':'test@example.com' - }): - return frappe.get_doc('Appointment','Test Appointment') + test_appointment = frappe.db.exists({ 'doctype':'Appointment', 'email':'test@example.com' }) + if test_appointment: + return frappe.get_doc('Appointment',test_appointment) test_appointment = frappe.get_doc({ 'doctype':'Appointment', 'email':'test@example.com', From 59c543570a411b2d647a88f3aceb74a69250ca00 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 13:18:13 +0530 Subject: [PATCH 083/157] feat: made timeslots into flex --- erpnext/www/book-appointment/index.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css index a6e6313f79..d5202065ea 100644 --- a/erpnext/www/book-appointment/index.css +++ b/erpnext/www/book-appointment/index.css @@ -7,6 +7,12 @@ border: 0.5px solid #cccccc; min-height: 75px; padding: 0.5em 1em; + +} + +#timeslot-container{ + display: grid; + grid-template-columns: repeat(6, 1fr); } .time-slot:hover { From 1dccc039b720aa4ff82c0a45794fc695899861b8 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 13:32:46 +0530 Subject: [PATCH 084/157] fix:add tear down to tests --- erpnext/crm/doctype/appointment/test_appointment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index ec16d6a582..5670a7e52e 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -40,10 +40,11 @@ class TestAppointment(unittest.TestCase): test_appointment = test_lead = None def setUp(self): test_lead = create_test_lead() - test_appointment = test_create_test_appointments() + test_appointment = create_test_appointments() def tearDown(self): - pass + test_appointment.delete() + test_lead.delete() def test_calendar_event_created(self): cal_event = frappe.get_doc('Event',test_appointment.calendar_event) From 8640a01f8535df351ba59ffdbf2991cf73daf7dd Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 13:32:57 +0530 Subject: [PATCH 085/157] remove duplicate day validation --- .../appointment_booking_settings.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index da181ae119..6a1cf56cb7 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -26,10 +26,4 @@ class AppointmentBookingSettings(Document): 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')) - + frappe.throw('The difference between from time and To Time must be a multiple of Appointment ') \ No newline at end of file From 42cf5f279f5b03f856b4ec35fbe386dcac9a7938 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 14:26:21 +0530 Subject: [PATCH 086/157] fix:added class variables to test --- erpnext/crm/doctype/appointment/test_appointment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 5670a7e52e..111ab08d23 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -39,12 +39,12 @@ def create_test_appointments(): class TestAppointment(unittest.TestCase): test_appointment = test_lead = None def setUp(self): - test_lead = create_test_lead() - test_appointment = create_test_appointments() + self.test_lead = create_test_lead() + self.test_appointment = create_test_appointments() def tearDown(self): - test_appointment.delete() - test_lead.delete() + self.test_appointment.delete() + self.test_lead.delete() def test_calendar_event_created(self): cal_event = frappe.get_doc('Event',test_appointment.calendar_event) From 43331564b42614e823ad660c198e2b734ad26557 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 15:29:09 +0530 Subject: [PATCH 087/157] fix:class variable in tests --- erpnext/crm/doctype/appointment/test_appointment.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 111ab08d23..f1aa610548 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -44,11 +44,10 @@ class TestAppointment(unittest.TestCase): def tearDown(self): self.test_appointment.delete() - self.test_lead.delete() def test_calendar_event_created(self): - cal_event = frappe.get_doc('Event',test_appointment.calendar_event) - self.assertEqual(cal_event.starts_on ,test_appointment.scheduled_time) + cal_event = frappe.get_doc('Event',self.test_appointment.calendar_event) + self.assertEqual(cal_event.starts_on ,self.test_appointment.scheduled_time) def test_lead_linked(self): lead = frappe.get_doc('Lead',self.lead) From 72aac09d62d92db73a6810be43ac74c99f6ecfa3 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 16:09:50 +0530 Subject: [PATCH 088/157] fix:remove tearDown from test --- erpnext/crm/doctype/appointment/test_appointment.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index f1aa610548..d7731bec87 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -42,9 +42,6 @@ class TestAppointment(unittest.TestCase): self.test_lead = create_test_lead() self.test_appointment = create_test_appointments() - def tearDown(self): - self.test_appointment.delete() - def test_calendar_event_created(self): cal_event = frappe.get_doc('Event',self.test_appointment.calendar_event) self.assertEqual(cal_event.starts_on ,self.test_appointment.scheduled_time) From afe52e8e09008c5a212cd21508c1e9f33ac73bda Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 16:35:08 +0530 Subject: [PATCH 089/157] feat: add check for toggling the route --- .../appointment_booking_settings.json | 10 +++++++++- erpnext/www/book-appointment/index.js | 18 ++++++++++++++---- erpnext/www/book-appointment/index.py | 5 +++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 4229e4b788..90f3ad9410 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -4,6 +4,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "enable_scheduling", "availability_of_slots", "number_of_agents", "holiday_list", @@ -62,10 +63,17 @@ "label": "Agents", "options": "Assignment Rule User", "reqd": 1 + }, + { + "default": "0", + "fieldname": "enable_scheduling", + "fieldtype": "Check", + "label": "Enable Appointment Scheduling", + "reqd": 1 } ], "issingle": 1, - "modified": "2019-09-25 13:08:28.328561", + "modified": "2019-10-03 14:52:33.076253", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index f2496da5a6..07355e16ff 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -1,7 +1,17 @@ - -frappe.ready(() => { - initialise_select_date() +frappe.ready(async () => { + debugger + let isSchedulingEnabled = await frappe.call({ + method:'erpnext.www.book-appointment.index.is_enabled' + }) + isSchedulingEnabled = isSchedulingEnabled.message + if (!isSchedulingEnabled) { + frappe.show_alert("This feature is not enabled"); + window.location.replace('/'); + return; + } + initialise_select_date(); }) + window.holiday_list = []; async function initialise_select_date() { @@ -16,7 +26,7 @@ async function get_global_variables() { // Using await window.appointment_settings = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_appointment_settings' - })).message + })).message; window.timezones = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_timezones' })).message; diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 5f51ced96e..946cc1b192 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -15,6 +15,11 @@ def get_appointment_settings(): settings = frappe.get_doc('Appointment Booking Settings') return settings +@frappe.whitelist(allow_guest=True) +def is_enabled(): + enable_scheduling = frappe.db.get_single_value('Appointment Booking Settings','enable_scheduling') + return enable_scheduling + @frappe.whitelist(allow_guest=True) def get_holiday_list(holiday_list_name): From bec88bc52a0eb410353b68c10c946c58360dabf2 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 16:58:38 +0530 Subject: [PATCH 090/157] fix: exists return tuple not string --- erpnext/crm/doctype/appointment/test_appointment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index d7731bec87..16a370eb7b 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -10,7 +10,7 @@ import datetime def create_test_lead(): test_lead = frappe.db.exists({'doctype':'Lead','lead_name':'Test Lead'}) if test_lead: - return frappe.get_doc('Lead',test_lead) + return frappe.get_doc('Lead',test_lead[0][0]) test_lead = frappe.get_doc({ 'doctype':'Lead', 'lead_name':'Test Lead', @@ -22,7 +22,7 @@ def create_test_lead(): def create_test_appointments(): test_appointment = frappe.db.exists({ 'doctype':'Appointment', 'email':'test@example.com' }) if test_appointment: - return frappe.get_doc('Appointment',test_appointment) + return frappe.get_doc('Appointment',test_appointment[0][0]) test_appointment = frappe.get_doc({ 'doctype':'Appointment', 'email':'test@example.com', From d40c020e0e461ef4a165966ede267e6452c033eb Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 17:43:31 +0530 Subject: [PATCH 091/157] fix:variable names --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 16a370eb7b..852e0f1e06 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -47,5 +47,5 @@ class TestAppointment(unittest.TestCase): self.assertEqual(cal_event.starts_on ,self.test_appointment.scheduled_time) def test_lead_linked(self): - lead = frappe.get_doc('Lead',self.lead) + lead = frappe.get_doc('Lead',self.test_lead) self.assertIsNotNone(lead) \ No newline at end of file From a1d39cab21bef14cd71e316808fabce7a39a234c Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Thu, 3 Oct 2019 18:26:02 +0530 Subject: [PATCH 092/157] fix: travis --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 852e0f1e06..b22e6e0087 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -47,5 +47,5 @@ class TestAppointment(unittest.TestCase): self.assertEqual(cal_event.starts_on ,self.test_appointment.scheduled_time) def test_lead_linked(self): - lead = frappe.get_doc('Lead',self.test_lead) + lead = frappe.get_doc('Lead',self.test_lead.name) self.assertIsNotNone(lead) \ No newline at end of file From 22189ec9e83f35232604a937738af42ef948e27a Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 11:07:04 +0530 Subject: [PATCH 093/157] remove unnecessary doctype 'Timezone' --- erpnext/crm/doctype/timezone/__init__.py | 0 erpnext/crm/doctype/timezone/test_timezone.py | 10 --- erpnext/crm/doctype/timezone/timezone.js | 8 --- erpnext/crm/doctype/timezone/timezone.json | 61 ------------------- erpnext/crm/doctype/timezone/timezone.py | 15 ----- erpnext/www/book-appointment/index.py | 1 - 6 files changed, 95 deletions(-) delete mode 100644 erpnext/crm/doctype/timezone/__init__.py delete mode 100644 erpnext/crm/doctype/timezone/test_timezone.py delete mode 100644 erpnext/crm/doctype/timezone/timezone.js delete mode 100644 erpnext/crm/doctype/timezone/timezone.json delete mode 100644 erpnext/crm/doctype/timezone/timezone.py diff --git a/erpnext/crm/doctype/timezone/__init__.py b/erpnext/crm/doctype/timezone/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/crm/doctype/timezone/test_timezone.py b/erpnext/crm/doctype/timezone/test_timezone.py deleted file mode 100644 index 92a8889cce..0000000000 --- a/erpnext/crm/doctype/timezone/test_timezone.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 deleted file mode 100644 index 4dc57db2ed..0000000000 --- a/erpnext/crm/doctype/timezone/timezone.js +++ /dev/null @@ -1,8 +0,0 @@ -// 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 deleted file mode 100644 index b998e6c21d..0000000000 --- a/erpnext/crm/doctype/timezone/timezone.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "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 deleted file mode 100644 index 539ffa2547..0000000000 --- a/erpnext/crm/doctype/timezone/timezone.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- 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') - if frappe.db.exists({'doctype':'Timezone','offset':self.offset}): - frappe.throw('Timezone offsets need to be unique') \ No newline at end of file diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 946cc1b192..5f03e777e3 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -29,7 +29,6 @@ def get_holiday_list(holiday_list_name): @frappe.whitelist(allow_guest=True) def get_timezones(): - timezones = frappe.get_list('Timezone', fields='*') return pytz.all_timezones From faf39ecef46b9b4764fcee41f86faf01933c29b8 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 11:12:50 +0530 Subject: [PATCH 094/157] fix:removed print statements --- erpnext/www/book-appointment/index.py | 1 - erpnext/www/book-appointment/verify/index.py | 1 - 2 files changed, 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 5f03e777e3..a810a2b323 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -59,7 +59,6 @@ def get_appointment_slots(date, timezone): converted_timeslots = [] for timeslot in timeslots: timeslot = local_timezone.localize(timeslot) - print(timeslot) timeslot = timeslot.astimezone(guest_timezone) timeslot = timeslot.replace(tzinfo=None) # Check if holiday diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book-appointment/verify/index.py index 8ea96383a3..6eda19f925 100644 --- a/erpnext/www/book-appointment/verify/index.py +++ b/erpnext/www/book-appointment/verify/index.py @@ -15,6 +15,5 @@ def get_context(context): context.success = True return context else: - print('Something not found') context.success = False return context \ No newline at end of file From 9e36a9ee043d8c6c24adb2c23d04aed4b8355d7c Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 11:28:29 +0530 Subject: [PATCH 095/157] fix: move enable check to serverside --- erpnext/www/book-appointment/index.js | 10 ---------- erpnext/www/book-appointment/index.py | 6 ++++++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 07355e16ff..cfacc79dc2 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -1,14 +1,4 @@ frappe.ready(async () => { - debugger - let isSchedulingEnabled = await frappe.call({ - method:'erpnext.www.book-appointment.index.is_enabled' - }) - isSchedulingEnabled = isSchedulingEnabled.message - if (!isSchedulingEnabled) { - frappe.show_alert("This feature is not enabled"); - window.location.replace('/'); - return; - } initialise_select_date(); }) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index a810a2b323..1a9afa577e 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -9,6 +9,12 @@ WEEKDAYS = ["Monday", "Tuesday", "Wednesday", no_cache = 1 +def get_context(context): + is_enabled = frappe.db.get_single_value('Appointment Booking Settings','enable_scheduling') + if is_enabled: + return context + else: + raise frappe.DoesNotExistError @frappe.whitelist(allow_guest=True) def get_appointment_settings(): From 25148d0de50937cead2ad27ce53e675335d091cd Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 11:32:39 +0530 Subject: [PATCH 096/157] fix:readability --- .../crm/doctype/appointment/appointment.py | 287 +++++++++--------- .../doctype/appointment/test_appointment.py | 45 +-- erpnext/www/book-appointment/index.js | 7 +- erpnext/www/book-appointment/index.py | 16 +- 4 files changed, 192 insertions(+), 163 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 4dcb2016ac..2da4acc1f5 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -13,160 +13,173 @@ from frappe import _ from frappe.model.document import Document from frappe.desk.form.assign_to import add as add_assignemnt from frappe.utils import get_url -from frappe.utils.verified_command import verify_request,get_signed_params +from frappe.utils.verified_command import verify_request, get_signed_params class Appointment(Document): - def find_lead_by_email(self): - lead_list = frappe.get_list('Lead', filters = {'email_id':self.customer_email}, ignore_permissions = True) - if lead_list: - return lead_list[0].name - return None - - def before_insert(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') - # Link lead - self.lead = self.find_lead_by_email() + def find_lead_by_email(self): + lead_list = frappe.get_list( + 'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True) + if lead_list: + return lead_list[0].name + return None - def after_insert(self): - if(self.lead): - # Create Calendar event - self.create_calendar_event() - self.auto_assign() - else: - # Set status to unverified - self.status = 'Unverified' - # Send email to confirm - verify_url = self.get_verify_url() - message = ''.join(['Please click the following link to confirm your appointment:',verify_url]) - frappe.sendmail(recipients=[self.customer_email], - message=message, - subject=_('Appointment Confirmation')) - frappe.msgprint('Please check your email to confirm the appointment') + def before_insert(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') + # Link lead + self.lead = self.find_lead_by_email() - def get_verify_url(self): - verify_route = '/book-appointment/verify' + def after_insert(self): + if(self.lead): + # Create Calendar event + self.create_calendar_event() + self.auto_assign() + else: + # Set status to unverified + self.status = 'Unverified' + # Send email to confirm + verify_url = self.get_verify_url() + message = ''.join( + ['Please click the following link to confirm your appointment:', verify_url]) + frappe.sendmail(recipients=[self.customer_email], + message=message, + subject=_('Appointment Confirmation')) + frappe.msgprint( + 'Please check your email to confirm the appointment') - params = { - 'email':self.customer_email, - 'appointment':self.name - } + def get_verify_url(self): + verify_route = '/book-appointment/verify' - return get_url(verify_route + '?' + get_signed_params(params)) + params = { + 'email': self.customer_email, + 'appointment': self.name + } - def on_change(self): - # Sync Calednar - if not self.calendar_event: - return - cal_event = frappe.get_doc('Event',self.calendar_event) - cal_event.starts_on = self.scheduled_time - cal_event.save(ignore_permissions=True) + return get_url(verify_route + '?' + get_signed_params(params)) - def on_trash(self): - # Delete calendar event - cal_event = frappe.get_doc('Event',self.calendar_event) - if cal_event: - cal_event.delete() - # Delete task? - def set_verified(self,email): - if not email == self.customer_email: - frappe.throw('Email verification failed.') - # Create new lead - self.create_lead() - # Remove unverified status - self.status = 'Open' - # Create calender event - self.create_calendar_event() - self.auto_assign() - self.save(ignore_permissions=True) - frappe.db.commit() + def on_change(self): + # Sync Calednar + if not self.calendar_event: + return + cal_event = frappe.get_doc('Event', self.calendar_event) + cal_event.starts_on = self.scheduled_time + cal_event.save(ignore_permissions=True) - def create_lead(self): - # Return if already linked - if self.lead: - return - lead = frappe.get_doc({ - 'doctype':'Lead', - 'lead_name':self.customer_name, - 'email_id':self.customer_email, - 'notes':self.customer_details, - 'phone':self.customer_phone_number, - }) - lead.insert(ignore_permissions=True) - # Link lead - self.lead = lead.name + def on_trash(self): + # Delete calendar event + cal_event = frappe.get_doc('Event', self.calendar_event) + if cal_event: + cal_event.delete() + # Delete task? - def auto_assign(self): - if self._assign: - return - available_agents = _get_agents_sorted_by_asc_workload(self.scheduled_time.date()) - for agent in available_agents: - if(_check_agent_availability(agent, self.scheduled_time)): - agent = agent[0] - add_assignemnt({ - 'doctype':self.doctype, - 'name':self.name, - 'assign_to':agent - }) - break + def set_verified(self, email): + if not email == self.customer_email: + frappe.throw('Email verification failed.') + # Create new lead + self.create_lead() + # Remove unverified status + self.status = 'Open' + # Create calender event + self.create_calendar_event() + self.auto_assign() + self.save(ignore_permissions=True) + frappe.db.commit() + + def create_lead(self): + # Return if already linked + if self.lead: + return + lead = frappe.get_doc({ + 'doctype': 'Lead', + 'lead_name': self.customer_name, + 'email_id': self.customer_email, + 'notes': self.customer_details, + 'phone': self.customer_phone_number, + }) + lead.insert(ignore_permissions=True) + # Link lead + self.lead = lead.name + + def auto_assign(self): + if self._assign: + return + available_agents = _get_agents_sorted_by_asc_workload( + self.scheduled_time.date()) + for agent in available_agents: + if(_check_agent_availability(agent, self.scheduled_time)): + agent = agent[0] + add_assignemnt({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': agent + }) + break + + def create_calendar_event(self): + if self.calendar_event: + return + appointment_event = frappe.get_doc({ + 'doctype': 'Event', + 'subject': ' '.join(['Appointment with', self.customer_name]), + 'starts_on': self.scheduled_time, + 'status': 'Open', + 'type': 'Public', + 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'), + 'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)] + }) + employee = _get_employee_from_user(self._assign) + if employee: + appointment_event.append('event_participants', dict( + reference_doctype='Employee', + reference_docname=employee.name)) + appointment_event.insert(ignore_permissions=True) + self.calendar_event = appointment_event.name + self.save(ignore_permissions=True) - def create_calendar_event(self): - if self.calendar_event: - return - appointment_event = frappe.get_doc({ - 'doctype': 'Event', - 'subject': ' '.join(['Appointment with', self.customer_name]), - 'starts_on': self.scheduled_time, - 'status': 'Open', - 'type': 'Public', - 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings','email_reminders'), - 'event_participants': [dict(reference_doctype = 'Lead', reference_docname = self.lead)] - }) - employee = _get_employee_from_user(self._assign) - if employee: - appointment_event.append('event_participants', dict( - reference_doctype = 'Employee', - reference_docname = employee.name)) - appointment_event.insert(ignore_permissions=True) - self.calendar_event = appointment_event.name - self.save(ignore_permissions=True) def _get_agents_sorted_by_asc_workload(date): - appointments = frappe.db.get_list('Appointment', fields='*') - agent_list = _get_agent_list_as_strings() - if not appointments: - return agent_list - appointment_counter = Counter(agent_list) - for appointment in appointments: - assigned_to = frappe.parse_json(appointment._assign) - if not assigned_to: - continue - if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date: - appointment_counter[assigned_to[0]] += 1 - sorted_agent_list = appointment_counter.most_common() - sorted_agent_list.reverse() - return sorted_agent_list + appointments = frappe.db.get_list('Appointment', fields='*') + agent_list = _get_agent_list_as_strings() + if not appointments: + return agent_list + appointment_counter = Counter(agent_list) + for appointment in appointments: + assigned_to = frappe.parse_json(appointment._assign) + if not assigned_to: + continue + if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date: + appointment_counter[assigned_to[0]] += 1 + sorted_agent_list = appointment_counter.most_common() + sorted_agent_list.reverse() + return sorted_agent_list + def _get_agent_list_as_strings(): - agent_list_as_strings = [] - agent_list = frappe.get_doc('Appointment Booking Settings').agent_list - for agent in agent_list: - agent_list_as_strings.append(agent.user) - return agent_list_as_strings + agent_list_as_strings = [] + agent_list = frappe.get_doc('Appointment Booking Settings').agent_list + for agent in agent_list: + agent_list_as_strings.append(agent.user) + return agent_list_as_strings + + +def _check_agent_availability(agent_email, scheduled_time): + appointemnts_at_scheduled_time = frappe.get_list( + 'Appointment', filters={'scheduled_time': scheduled_time}) + for appointment in appointemnts_at_scheduled_time: + if appointment._assign == agent_email: + return False + return True -def _check_agent_availability(agent_email,scheduled_time): - appointemnts_at_scheduled_time = frappe.get_list('Appointment', filters = {'scheduled_time':scheduled_time}) - for appointment in appointemnts_at_scheduled_time: - if appointment._assign == agent_email: - return False - return True def _get_employee_from_user(user): - employee_docname = frappe.db.exists({'doctype':'Employee', 'user_id':user}) - if employee_docname: - return frappe.get_doc('Employee', employee_docname[0][0]) # frappe.db.exists returns a tuple of a tuple - return None \ No newline at end of file + employee_docname = frappe.db.exists( + {'doctype': 'Employee', 'user_id': user}) + if employee_docname: + # frappe.db.exists returns a tuple of a tuple + return frappe.get_doc('Employee', employee_docname[0][0]) + return None diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index b22e6e0087..72c2ae5ee7 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -7,45 +7,52 @@ import frappe import unittest import datetime + def create_test_lead(): - test_lead = frappe.db.exists({'doctype':'Lead','lead_name':'Test Lead'}) + test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'}) if test_lead: - return frappe.get_doc('Lead',test_lead[0][0]) + return frappe.get_doc('Lead', test_lead[0][0]) test_lead = frappe.get_doc({ - 'doctype':'Lead', - 'lead_name':'Test Lead', - 'email_id':'test@example.com' + 'doctype': 'Lead', + 'lead_name': 'Test Lead', + 'email_id': 'test@example.com' }) test_lead.insert(ignore_permissions=True) return test_lead + def create_test_appointments(): - test_appointment = frappe.db.exists({ 'doctype':'Appointment', 'email':'test@example.com' }) + test_appointment = frappe.db.exists( + {'doctype': 'Appointment', 'email': 'test@example.com'}) if test_appointment: - return frappe.get_doc('Appointment',test_appointment[0][0]) + return frappe.get_doc('Appointment', test_appointment[0][0]) test_appointment = frappe.get_doc({ - 'doctype':'Appointment', - 'email':'test@example.com', - 'status':'Open', - 'customer_name':'Test Lead', - 'customer_phone_number':'666', - 'customer_skype':'test', - 'customer_email':'test@example.com', - 'scheduled_time':datetime.datetime.now() + 'doctype': 'Appointment', + 'email': 'test@example.com', + 'status': 'Open', + 'customer_name': 'Test Lead', + 'customer_phone_number': '666', + 'customer_skype': 'test', + 'customer_email': 'test@example.com', + 'scheduled_time': datetime.datetime.now() }) test_appointment.insert() return test_appointment + class TestAppointment(unittest.TestCase): test_appointment = test_lead = None + def setUp(self): self.test_lead = create_test_lead() self.test_appointment = create_test_appointments() def test_calendar_event_created(self): - cal_event = frappe.get_doc('Event',self.test_appointment.calendar_event) - self.assertEqual(cal_event.starts_on ,self.test_appointment.scheduled_time) + cal_event = frappe.get_doc( + 'Event', self.test_appointment.calendar_event) + self.assertEqual(cal_event.starts_on, + self.test_appointment.scheduled_time) def test_lead_linked(self): - lead = frappe.get_doc('Lead',self.test_lead.name) - self.assertIsNotNone(lead) \ No newline at end of file + lead = frappe.get_doc('Lead', self.test_lead.name) + self.assertIsNotNone(lead) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index cfacc79dc2..f0cf1d7664 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -34,7 +34,7 @@ function setup_timezone_selector() { window.timezones.forEach(timezone => { let opt = document.createElement('option'); opt.value = timezone; - if(timezone == moment.tz.guess()){ + if (timezone == moment.tz.guess()) { opt.selected = true; } opt.innerHTML = timezone; @@ -140,7 +140,7 @@ function select_time() { return; } let selected_element = document.getElementsByClassName('selected'); - if (!(selected_element.length > 0)){ + if (!(selected_element.length > 0)) { this.classList.add('selected'); show_next_button(); return; @@ -191,7 +191,7 @@ function setup_details_page() { async function submit() { let form = document.querySelector('#customer-form'); - if(!form.checkValidity()){ + if (!form.checkValidity()) { form.reportValidity(); return; } @@ -211,7 +211,6 @@ async function submit() { } function get_form_data() { - contact = {}; contact.name = document.getElementById('customer_name').value; contact.number = document.getElementById('customer_number').value; diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 1a9afa577e..e279a4717b 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -9,21 +9,26 @@ WEEKDAYS = ["Monday", "Tuesday", "Wednesday", no_cache = 1 + def get_context(context): - is_enabled = frappe.db.get_single_value('Appointment Booking Settings','enable_scheduling') + is_enabled = frappe.db.get_single_value( + 'Appointment Booking Settings', 'enable_scheduling') if is_enabled: return context else: raise frappe.DoesNotExistError + @frappe.whitelist(allow_guest=True) def get_appointment_settings(): settings = frappe.get_doc('Appointment Booking Settings') return settings + @frappe.whitelist(allow_guest=True) def is_enabled(): - enable_scheduling = frappe.db.get_single_value('Appointment Booking Settings','enable_scheduling') + enable_scheduling = frappe.db.get_single_value( + 'Appointment Booking Settings', 'enable_scheduling') return enable_scheduling @@ -131,15 +136,18 @@ def filter_timeslots(date, timeslots): 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: @@ -147,10 +155,12 @@ def _get_records(start_time, end_time, settings): 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) \ No newline at end of file + return (date_time-midnight) From c1bc0f9dfb8ed4e83b16d9e6b8cde29e53146796 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 11:36:53 +0530 Subject: [PATCH 097/157] fix: added sections for settings --- .../appointment_booking_settings.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 90f3ad9410..25a7c69268 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -5,13 +5,15 @@ "engine": "InnoDB", "field_order": [ "enable_scheduling", + "agent_detail_section", "availability_of_slots", "number_of_agents", + "agent_list", "holiday_list", + "appointment_details_section", "appointment_duration", "email_reminders", - "advance_booking_days", - "agent_list" + "advance_booking_days" ], "fields": [ { @@ -70,10 +72,20 @@ "fieldtype": "Check", "label": "Enable Appointment Scheduling", "reqd": 1 + }, + { + "fieldname": "agent_detail_section", + "fieldtype": "Section Break", + "label": "Agent Details" + }, + { + "fieldname": "appointment_details_section", + "fieldtype": "Section Break", + "label": "Appointment Details" } ], "issingle": 1, - "modified": "2019-10-03 14:52:33.076253", + "modified": "2019-10-04 11:36:20.839075", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", From bfe18d6085d4b64e66e003ad7837c1c1341fdc08 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 14:41:54 +0530 Subject: [PATCH 098/157] feat:assign appointments from opportunity --- .../crm/doctype/appointment/appointment.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2da4acc1f5..5615ae1969 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -106,6 +106,16 @@ class Appointment(Document): self.lead = lead.name def auto_assign(self): + # If the latest opportunity is assigned to someone + # Assign the appointment to the same + existing_assignee = self.get_assignee_from_latest_opportunity() + if existing_assignee: + add_assignemnt({ + 'doctype':self.doctype + 'name':self.name + 'assign_to':existing_assignee + }) + return if self._assign: return available_agents = _get_agents_sorted_by_asc_workload( @@ -120,6 +130,25 @@ class Appointment(Document): }) break + def get_assignee_from_latest_opportunity(self): + if not self.lead: + return None + if not frappe.db.exists('Lead', self.lead): + return None + opporutnities = frappe.get_list( + 'Opportunity', + filters={ + 'party_name': self.lead, + }, + order_by='creation desc') + latest_opportunity = frappe.get_doc( + 'Opportunity', opporutnities[0].name) + assignee = latest_opportunity._assign + if not assignee: + return None + assignee = frappe.parse_json(assignee)[0] + return assignee + def create_calendar_event(self): if self.calendar_event: return From 0082b780759460c0ae635d95fec28e043e8ed316 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 15:36:36 +0530 Subject: [PATCH 099/157] fix:incosistent tabs and spaces --- .../crm/doctype/appointment/appointment.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 5615ae1969..942960e81e 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -89,6 +89,7 @@ class Appointment(Document): self.auto_assign() self.save(ignore_permissions=True) frappe.db.commit() + this.wrapper.find('.filter-edit-area').after(this.get_clear_button()) def create_lead(self): # Return if already linked @@ -106,16 +107,16 @@ class Appointment(Document): self.lead = lead.name def auto_assign(self): - # If the latest opportunity is assigned to someone - # Assign the appointment to the same - existing_assignee = self.get_assignee_from_latest_opportunity() - if existing_assignee: - add_assignemnt({ - 'doctype':self.doctype - 'name':self.name - 'assign_to':existing_assignee - }) - return + # If the latest opportunity is assigned to someone + # Assign the appointment to the same + existing_assignee = self.get_assignee_from_latest_opportunity() + if existing_assignee: + add_assignemnt({ + 'doctype': self.doctype + 'name': self.name + 'assign_to': existing_assignee + }) + return if self._assign: return available_agents = _get_agents_sorted_by_asc_workload( From 911e034d1c3ee22c3999e7dd8d8badb2267c565c Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 15:50:02 +0530 Subject: [PATCH 100/157] fix: syntax error --- erpnext/crm/doctype/appointment/appointment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 942960e81e..714e88ded3 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -112,8 +112,8 @@ class Appointment(Document): existing_assignee = self.get_assignee_from_latest_opportunity() if existing_assignee: add_assignemnt({ - 'doctype': self.doctype - 'name': self.name + 'doctype': self.doctype, + 'name': self.name, 'assign_to': existing_assignee }) return From e18388ade3973c8a5ecae6f48470ff5cc830c116 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Fri, 4 Oct 2019 16:32:32 +0530 Subject: [PATCH 101/157] fix:add exception for no opportunity --- erpnext/crm/doctype/appointment/appointment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 714e88ded3..2b9d31da50 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -142,6 +142,8 @@ class Appointment(Document): 'party_name': self.lead, }, order_by='creation desc') + if not opporutnities: + return None latest_opportunity = frappe.get_doc( 'Opportunity', opporutnities[0].name) assignee = latest_opportunity._assign From 5e4ec8557443027268414c8bb4b5906304613e4a Mon Sep 17 00:00:00 2001 From: Pranav Nachnekar Date: Wed, 9 Oct 2019 08:23:54 +0000 Subject: [PATCH 102/157] remove:unnecessary translation Co-Authored-By: Shivam Mishra --- erpnext/crm/doctype/appointment/appointment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.js b/erpnext/crm/doctype/appointment/appointment.js index 485520f562..fb78d1af94 100644 --- a/erpnext/crm/doctype/appointment/appointment.js +++ b/erpnext/crm/doctype/appointment/appointment.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Appointment', { refresh: function(frm) { if(frm.doc.lead){ - frm.add_custom_button(__(frm.doc.lead),()=>{ + frm.add_custom_button(frm.doc.lead,()=>{ frappe.set_route("Form","Lead",frm.doc.lead); }); } From 96930e25f3b768b3926eb6f4cf0c8950b395a8ed Mon Sep 17 00:00:00 2001 From: Pranav Nachnekar Date: Wed, 9 Oct 2019 08:31:37 +0000 Subject: [PATCH 103/157] fix: readability Co-Authored-By: Shivam Mishra --- erpnext/crm/doctype/appointment/appointment.js | 4 ++-- erpnext/crm/doctype/appointment/appointment.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.js b/erpnext/crm/doctype/appointment/appointment.js index fb78d1af94..8888b569c4 100644 --- a/erpnext/crm/doctype/appointment/appointment.js +++ b/erpnext/crm/doctype/appointment/appointment.js @@ -5,12 +5,12 @@ frappe.ui.form.on('Appointment', { refresh: function(frm) { if(frm.doc.lead){ frm.add_custom_button(frm.doc.lead,()=>{ - frappe.set_route("Form","Lead",frm.doc.lead); + frappe.set_route("Form", "Lead", frm.doc.lead); }); } if(frm.doc.calendar_event){ frm.add_custom_button(__(frm.doc.calendar_event),()=>{ - frappe.set_route("Form","Event",frm.doc.calendar_event); + frappe.set_route("Form", "Event", frm.doc.calendar_event); }); } } diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2b9d31da50..5d1e301b6e 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -35,7 +35,7 @@ class Appointment(Document): self.lead = self.find_lead_by_email() def after_insert(self): - if(self.lead): + if self.lead: # Create Calendar event self.create_calendar_event() self.auto_assign() @@ -63,7 +63,7 @@ class Appointment(Document): return get_url(verify_route + '?' + get_signed_params(params)) def on_change(self): - # Sync Calednar + # Sync Calendar if not self.calendar_event: return cal_event = frappe.get_doc('Event', self.calendar_event) From e434e8e2e24205ff83ce4888b3b9f3f426fad54a Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 9 Oct 2019 14:08:01 +0530 Subject: [PATCH 104/157] fix: formatting --- erpnext/www/book-appointment/index.py | 1 - erpnext/www/book-appointment/verify/index.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index e279a4717b..6f416d1b2b 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -59,7 +59,6 @@ def get_appointment_slots(date, timezone): query_start_time = query_start_time.astimezone(local_timezone) query_end_time = query_end_time.astimezone(local_timezone) now = datetime.datetime.now() - # now = local_timezone.localize(now) # Database queries settings = frappe.get_doc('Appointment Booking Settings') holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book-appointment/verify/index.py index 6eda19f925..e8ccecd8b6 100644 --- a/erpnext/www/book-appointment/verify/index.py +++ b/erpnext/www/book-appointment/verify/index.py @@ -1,4 +1,5 @@ import frappe + from frappe.utils.verified_command import verify_request @frappe.whitelist(allow_guest=True) def get_context(context): From 604febb398f6f27c4caf6202d9ea557f9582ac54 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 9 Oct 2019 14:09:47 +0530 Subject: [PATCH 105/157] fix: set_verified method contained js --- erpnext/crm/doctype/appointment/appointment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 5d1e301b6e..b39de13abb 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -89,7 +89,6 @@ class Appointment(Document): self.auto_assign() self.save(ignore_permissions=True) frappe.db.commit() - this.wrapper.find('.filter-edit-area').after(this.get_clear_button()) def create_lead(self): # Return if already linked From 50e66d81de0dff46d4cd1baf9317df76b2ae5f63 Mon Sep 17 00:00:00 2001 From: Pranav Nachnekar Date: Wed, 9 Oct 2019 08:43:18 +0000 Subject: [PATCH 106/157] fix: use get_single_value Co-Authored-By: Shivam Mishra --- erpnext/crm/doctype/appointment/appointment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 5d1e301b6e..607584f685 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -28,8 +28,8 @@ class Appointment(Document): def before_insert(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): + number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') + if(number_of_appointments_in_same_slot >= number_of_agents): frappe.throw('Time slot is not available') # Link lead self.lead = self.find_lead_by_email() From 2c9959468821add2b895a197e971f98f4213a3f9 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 9 Oct 2019 15:22:57 +0530 Subject: [PATCH 107/157] remove: styles for non existant radio --- erpnext/www/book-appointment/index.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css index d5202065ea..9398b30371 100644 --- a/erpnext/www/book-appointment/index.css +++ b/erpnext/www/book-appointment/index.css @@ -7,7 +7,6 @@ border: 0.5px solid #cccccc; min-height: 75px; padding: 0.5em 1em; - } #timeslot-container{ @@ -29,11 +28,6 @@ color: #718096 } -input[type="radio"] { - visibility: hidden; - display: none; -} - .time-slot.selected { color: white; background: #5e64ff; From aa918e852832efcdda3bd124779bbc8a14b42247 Mon Sep 17 00:00:00 2001 From: Pranav Nachanekar Date: Wed, 9 Oct 2019 15:49:48 +0530 Subject: [PATCH 108/157] moved validations to sepeate functions --- .../appointment_booking_settings.py | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index 6a1cf56cb7..ef762ff025 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -8,22 +8,31 @@ 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" + min_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 + def validate(self): + self.validate_availability_of_slots() - 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 ') \ No newline at end of file + def validate_availability_of_slots(self): + for record in self.availability_of_slots: + from_time = datetime.datetime.strptime( + min_date+record.from_time, format_string) + to_time = datetime.datetime.strptime( + min_date+record.to_time, format_string) + timedelta = to_time-from_time + self.from_time_is_later_than_to_time(from_time, to_time) + self.duration_is_divisible(from_time, to_time) + + def from_time_is_later_than_to_time(self, from_time, to_time): + if from_time > to_time: + err_msg = 'From Time cannot be later than To Time for '+record.day_of_week + frappe.throw(_(err_msg)) + + def duration_is_divisible(self, from_time, to_time): + timedelta = to_time - from_time + if timedelta.total_seconds() % (self.appointment_duration * 60): + frappe.throw( + _('The difference between from time and To Time must be a multiple of Appointmen')) From 29c7d5fc63ec0cd5552fcb793c1f636a3dd98f30 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 15 Oct 2019 16:43:18 +0530 Subject: [PATCH 109/157] fix:margins --- erpnext/www/book-appointment/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index e1355a7765..43a3f1026d 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -11,7 +11,7 @@
-
+

Book an appointment

Select the date and your timezone

From 3d73a4f944d519c7c2470152a20b2bbd326fd178 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 15 Oct 2019 16:43:40 +0530 Subject: [PATCH 110/157] fix:readability for user --- erpnext/crm/doctype/appointment/appointment.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 323e096b40..32df8ec429 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -86,14 +86,14 @@ { "fieldname": "linked_docs_section", "fieldtype": "Section Break", - "label": "Linked Docs" + "label": "Linked Documents" }, { "fieldname": "col_br_3", "fieldtype": "Column Break" } ], - "modified": "2019-09-25 13:08:46.368307", + "modified": "2019-10-14 15:23:54.630731", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", From 2f9ef85614322565f65335cee9fa4b7a66f505e1 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 15 Oct 2019 16:44:28 +0530 Subject: [PATCH 111/157] fix:typo --- .../appointment_booking_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index ef762ff025..2aa51caefd 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -19,9 +19,9 @@ class AppointmentBookingSettings(Document): def validate_availability_of_slots(self): for record in self.availability_of_slots: from_time = datetime.datetime.strptime( - min_date+record.from_time, format_string) + self.min_date+record.from_time, self.format_string) to_time = datetime.datetime.strptime( - min_date+record.to_time, format_string) + self.min_date+record.to_time, self.format_string) timedelta = to_time-from_time self.from_time_is_later_than_to_time(from_time, to_time) self.duration_is_divisible(from_time, to_time) @@ -35,4 +35,4 @@ class AppointmentBookingSettings(Document): timedelta = to_time - from_time if timedelta.total_seconds() % (self.appointment_duration * 60): frappe.throw( - _('The difference between from time and To Time must be a multiple of Appointmen')) + _('The difference between from time and To Time must be a multiple of Appointment')) From 7c27436d210fba9e9dc2c48fdca3e6004132024a Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 15 Oct 2019 16:45:24 +0530 Subject: [PATCH 112/157] fix:visibilty for forms --- erpnext/www/book-appointment/index.css | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css index 9398b30371..30ce957e2c 100644 --- a/erpnext/www/book-appointment/index.css +++ b/erpnext/www/book-appointment/index.css @@ -1,4 +1,6 @@ .time-slot { + flex-grow: 1; + flex : 0 0 calc(16.66% - 20px); margin-bottom: 2em; margin-left: 0.5em; margin-right: 0.5em; @@ -9,9 +11,16 @@ padding: 0.5em 1em; } +#customer-form{ + border-color: black; +} +#customer-form ::placeholder{ + color: #ddd; +} #timeslot-container{ - display: grid; - grid-template-columns: repeat(6, 1fr); + display: flex; + flex-wrap: wrap; + justify-content: center; } .time-slot:hover { From ad013264eb92704e96dad3de71e80c818654a729 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 15 Oct 2019 16:45:37 +0530 Subject: [PATCH 113/157] fix:margins --- erpnext/www/book-appointment/index.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index 43a3f1026d..10fe09ab3c 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -37,8 +37,8 @@
-
-
+
+

Add details

Selected date is at

@@ -46,10 +46,11 @@
- + - + +
From d1c530c564e639bc8bac58f170c54e72a73381aa Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 31 Oct 2019 15:36:33 +0530 Subject: [PATCH 114/157] fix: merge settings into one call --- erpnext/www/book-appointment/index.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index f0cf1d7664..19fc704501 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -20,12 +20,7 @@ async function get_global_variables() { window.timezones = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_timezones' })).message; - window.holiday_list = (await frappe.call({ - method: 'erpnext.www.book-appointment.index.get_holiday_list', - args: { - 'holiday_list_name': window.appointment_settings.holiday_list - } - })).message; + window.holiday_list = window.appointment_settings.holiday_list; } function setup_timezone_selector() { @@ -201,7 +196,8 @@ async function submit() { args: { 'date': window.selected_date, 'time': window.selected_time, - 'contact': window.contact + 'contact': window.contact, + 'tz':window.selected_timezone } })).message; frappe.msgprint(__('Appointment Created Successfully')); From 60093d98b07a465ccd29352895f13dd0eec8e07e Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 31 Oct 2019 15:37:57 +0530 Subject: [PATCH 115/157] auto assign before creating event --- erpnext/crm/doctype/appointment/appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index f575f52909..18f47c9be0 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -85,8 +85,8 @@ class Appointment(Document): # Remove unverified status self.status = 'Open' # Create calender event - self.create_calendar_event() self.auto_assign() + self.create_calendar_event() self.save(ignore_permissions=True) frappe.db.commit() From e494144c965a314b77cbdd1a67e7436daa16a4f4 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 31 Oct 2019 15:38:39 +0530 Subject: [PATCH 116/157] merge settings fetch, add helpers --- erpnext/www/book-appointment/index.py | 66 ++++++++++++++------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 6f416d1b2b..eb7d5b918b 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -18,47 +18,31 @@ def get_context(context): else: raise frappe.DoesNotExistError - @frappe.whitelist(allow_guest=True) def get_appointment_settings(): settings = frappe.get_doc('Appointment Booking Settings') + settings.holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) return settings - -@frappe.whitelist(allow_guest=True) -def is_enabled(): - enable_scheduling = frappe.db.get_single_value( - 'Appointment Booking Settings', 'enable_scheduling') - return enable_scheduling - - -@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(): return pytz.all_timezones - @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): import pytz guest_timezone = pytz.timezone(timezone) + local_timezone = pytz.timezone(frappe.utils.get_time_zone()) 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) - local_timezone = frappe.utils.get_time_zone() - local_timezone = pytz.timezone(local_timezone) - query_start_time = guest_timezone.localize(query_start_time) - query_end_time = guest_timezone.localize(query_end_time) - query_start_time = query_start_time.astimezone(local_timezone) - query_end_time = query_end_time.astimezone(local_timezone) - now = datetime.datetime.now() + + query_start_time = convert_to_system_timzone(timezone,query_start_time) + query_end_time = convert_to_system_timzone(timezone,query_end_time) + now = convert_to_guest_timezone(timezone,datetime.datetime.now()) + # Database queries settings = frappe.get_doc('Appointment Booking Settings') holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) @@ -68,9 +52,9 @@ def get_appointment_slots(date, timezone): # Filter timeslots based on date converted_timeslots = [] for timeslot in timeslots: - timeslot = local_timezone.localize(timeslot) - timeslot = timeslot.astimezone(guest_timezone) - timeslot = timeslot.replace(tzinfo=None) + print("Unconverted Timeslot:{0}".format(timeslot)) + timeslot = convert_to_guest_timezone(timezone,timeslot) + print("Converted Timeslot:{0}".format(timeslot)) # Check if holiday if _is_holiday(timeslot.date(), holiday_list): converted_timeslots.append( @@ -112,11 +96,16 @@ def get_available_slots_between(query_start_time, query_end_time, settings): @frappe.whitelist(allow_guest=True) -def create_appointment(date, time, contact): +def create_appointment(date, time, tz, contact): + import pytz appointment = frappe.new_doc('Appointment') - format_string = '%Y-%m-%d %H:%M:%S' - appointment.scheduled_time = datetime.datetime.strptime( + format_string = '%Y-%m-%d %H:%M:%S%z' + scheduled_time = datetime.datetime.strptime( date+" "+time, format_string) + scheduled_time = scheduled_time.replace(tzinfo=None) + scheduled_time = convert_to_system_timzone(tz,scheduled_time) + scheduled_time= scheduled_time.replace(tzinfo=None) + appointment.scheduled_time = scheduled_time contact = json.loads(contact) appointment.customer_name = contact['name'] appointment.customer_phone_number = contact['number'] @@ -126,7 +115,6 @@ def create_appointment(date, time, contact): appointment.status = 'Open' appointment.insert() - # Helper Functions def filter_timeslots(date, timeslots): filtered_timeslots = [] @@ -135,11 +123,25 @@ def filter_timeslots(date, timeslots): filtered_timeslots.append(timeslot) return filtered_timeslots +def convert_to_guest_timezone(guest_tz,datetimeobject): + import pytz + guest_tz = pytz.timezone(guest_tz) + local_timezone = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = local_timezone.localize(datetimeobject) + datetimeobject = datetimeobject.astimezone(guest_tz) + return datetimeobject + +def convert_to_system_timzone(guest_tz,datetimeobject): + import pytz + guest_tz = pytz.timezone(guest_tz) + datetimeobject = guest_tz.localize(datetimeobject) + system_tz = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = datetimeobject.astimezone(system_tz) + return datetimeobject 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: @@ -162,4 +164,4 @@ def _deltatime_to_datetime(date, deltatime): def _datetime_to_deltatime(date_time): midnight = datetime.datetime.combine(date_time.date(), datetime.time.min) - return (date_time-midnight) + return (date_time-midnight) \ No newline at end of file From 4701bc8bfcf73889a72086ca3b20ad7f89e29afc Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 1 Nov 2019 09:36:29 +0530 Subject: [PATCH 117/157] Add ignore permissions for opportunity --- erpnext/crm/doctype/appointment/appointment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 18f47c9be0..bc2c838930 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -140,11 +140,11 @@ class Appointment(Document): filters={ 'party_name': self.lead, }, + ignore_permissions=True, order_by='creation desc') if not opporutnities: return None - latest_opportunity = frappe.get_doc( - 'Opportunity', opporutnities[0].name) + latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name ) assignee = latest_opportunity._assign if not assignee: return None From 957c9f5ff036d28916522fe30c27942133a509f8 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 1 Nov 2019 09:36:45 +0530 Subject: [PATCH 118/157] fix:comments --- erpnext/www/book-appointment/index.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index eb7d5b918b..b983dde6f2 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -31,14 +31,12 @@ def get_timezones(): @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): import pytz - guest_timezone = pytz.timezone(timezone) - local_timezone = pytz.timezone(frappe.utils.get_time_zone()) + # Convert query to local timezones 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_system_timzone(timezone,query_start_time) query_end_time = convert_to_system_timzone(timezone,query_end_time) now = convert_to_guest_timezone(timezone,datetime.datetime.now()) @@ -49,12 +47,10 @@ def get_appointment_slots(date, timezone): timeslots = get_available_slots_between( query_start_time, query_end_time, settings) - # Filter timeslots based on date + # Filter and convert timeslots converted_timeslots = [] for timeslot in timeslots: - print("Unconverted Timeslot:{0}".format(timeslot)) timeslot = convert_to_guest_timezone(timezone,timeslot) - print("Converted Timeslot:{0}".format(timeslot)) # Check if holiday if _is_holiday(timeslot.date(), holiday_list): converted_timeslots.append( @@ -72,7 +68,6 @@ def get_appointment_slots(date, timezone): 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 = [] From 6de68c8671d4fa691cd17a815ea0ab0f3adb08aa Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 1 Nov 2019 09:51:32 +0530 Subject: [PATCH 119/157] avoid repetition on get_form date --- erpnext/www/book-appointment/index.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 19fc704501..6bd868bbc7 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -208,10 +208,7 @@ async function submit() { function get_form_data() { 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; - contact.email = document.getElementById('customer_email').value; + let inputs = ['name', 'skype', 'number', 'notes', 'email']; + inputs.forEach((id) => contact[id] = document.getElementById(`customer_${id}`).value) window.contact = contact } From 36098727601daa973cb90e3efa8b1fdb39cbbda7 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 1 Nov 2019 12:06:42 +0530 Subject: [PATCH 120/157] rename function --- erpnext/www/book-appointment/index.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index b983dde6f2..9b5ea57a83 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -37,8 +37,8 @@ def get_appointment_slots(date, timezone): date + ' 00:00:00', format_string) query_end_time = datetime.datetime.strptime( date + ' 23:59:59', format_string) - query_start_time = convert_to_system_timzone(timezone,query_start_time) - query_end_time = convert_to_system_timzone(timezone,query_end_time) + query_start_time = convert_to_system_timezone(timezone,query_start_time) + query_end_time = convert_to_system_timezone(timezone,query_end_time) now = convert_to_guest_timezone(timezone,datetime.datetime.now()) # Database queries @@ -50,19 +50,19 @@ def get_appointment_slots(date, timezone): # Filter and convert timeslots converted_timeslots = [] for timeslot in timeslots: - timeslot = convert_to_guest_timezone(timezone,timeslot) + converted_timeslot = convert_to_guest_timezone(timezone,timeslot) # Check if holiday - if _is_holiday(timeslot.date(), holiday_list): + if _is_holiday(converted_timeslot.date(), holiday_list): converted_timeslots.append( - dict(time=timeslot, availability=False)) + dict(time=converted_timeslot, availability=False)) continue # Check availability - if check_availabilty(timeslot, settings) and timeslot >= now: + if check_availabilty(timeslot, settings) and converted_timeslot >= now: converted_timeslots.append( - dict(time=timeslot, availability=True)) + dict(time=converted_timeslot, availability=True)) else: converted_timeslots.append( - dict(time=timeslot, availability=False)) + dict(time=converted_timeslot, availability=False)) date_required = datetime.datetime.strptime( date + ' 00:00:00', format_string).date() converted_timeslots = filter_timeslots(date_required, converted_timeslots) @@ -98,7 +98,7 @@ def create_appointment(date, time, tz, contact): scheduled_time = datetime.datetime.strptime( date+" "+time, format_string) scheduled_time = scheduled_time.replace(tzinfo=None) - scheduled_time = convert_to_system_timzone(tz,scheduled_time) + scheduled_time = convert_to_system_timezone(tz,scheduled_time) scheduled_time= scheduled_time.replace(tzinfo=None) appointment.scheduled_time = scheduled_time contact = json.loads(contact) @@ -126,7 +126,7 @@ def convert_to_guest_timezone(guest_tz,datetimeobject): datetimeobject = datetimeobject.astimezone(guest_tz) return datetimeobject -def convert_to_system_timzone(guest_tz,datetimeobject): +def convert_to_system_timezone(guest_tz,datetimeobject): import pytz guest_tz = pytz.timezone(guest_tz) datetimeobject = guest_tz.localize(datetimeobject) From 54f33f4e5d913a97163a48fb6ffbbef90078dd94 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 1 Nov 2019 12:14:21 +0530 Subject: [PATCH 121/157] move utility functions --- .../crm/doctype/appointment/appointment.py | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index bc2c838930..95a9580dbb 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -11,7 +11,6 @@ from datetime import timedelta import frappe from frappe import _ from frappe.model.document import Document -from frappe.desk.form.assign_to import add as add_assignemnt from frappe.utils import get_url from frappe.utils.verified_command import verify_request, get_signed_params @@ -37,13 +36,13 @@ class Appointment(Document): def after_insert(self): if self.lead: # Create Calendar event - self.create_calendar_event() self.auto_assign() + self.create_calendar_event() else: # Set status to unverified self.status = 'Unverified' # Send email to confirm - verify_url = self.get_verify_url() + verify_url = self._get_verify_url() message = ''.join( ['Please click the following link to confirm your appointment:', verify_url]) frappe.sendmail(recipients=[self.customer_email], @@ -52,15 +51,6 @@ class Appointment(Document): frappe.msgprint( 'Please check your email to confirm the appointment') - def get_verify_url(self): - verify_route = '/book-appointment/verify' - - params = { - 'email': self.customer_email, - 'appointment': self.name - } - - return get_url(verify_route + '?' + get_signed_params(params)) def on_change(self): # Sync Calendar @@ -70,18 +60,12 @@ class Appointment(Document): cal_event.starts_on = self.scheduled_time cal_event.save(ignore_permissions=True) - def on_trash(self): - # Delete calendar event - cal_event = frappe.get_doc('Event', self.calendar_event) - if cal_event: - cal_event.delete() - # Delete task? def set_verified(self, email): if not email == self.customer_email: frappe.throw('Email verification failed.') # Create new lead - self.create_lead() + self.create_lead_and_link() # Remove unverified status self.status = 'Open' # Create calender event @@ -90,7 +74,7 @@ class Appointment(Document): self.save(ignore_permissions=True) frappe.db.commit() - def create_lead(self): + def create_lead_and_link(self): # Return if already linked if self.lead: return @@ -106,10 +90,11 @@ class Appointment(Document): self.lead = lead.name def auto_assign(self): - # If the latest opportunity is assigned to someone - # Assign the appointment to the same + from frappe.desk.form.assign_to import add as add_assignemnt existing_assignee = self.get_assignee_from_latest_opportunity() if existing_assignee: + # If the latest opportunity is assigned to someone + # Assign the appointment to the same add_assignemnt({ 'doctype': self.doctype, 'name': self.name, @@ -171,6 +156,14 @@ class Appointment(Document): appointment_event.insert(ignore_permissions=True) self.calendar_event = appointment_event.name self.save(ignore_permissions=True) + + def _get_verify_url(self): + verify_route = '/book-appointment/verify' + params = { + 'email': self.customer_email, + 'appointment': self.name + } + return get_url(verify_route + '?' + get_signed_params(params)) def _get_agents_sorted_by_asc_workload(date): @@ -214,3 +207,4 @@ def _get_employee_from_user(user): # frappe.db.exists returns a tuple of a tuple return frappe.get_doc('Employee', employee_docname[0][0]) return None + From 97f65762130a5439172dc204ec1ea0027d477d0d Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 1 Nov 2019 12:36:06 +0530 Subject: [PATCH 122/157] prettify confirmation email --- erpnext/crm/doctype/appointment/appointment.py | 9 ++++++++- erpnext/templates/emails/confirm_appointment.html | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 erpnext/templates/emails/confirm_appointment.html diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 95a9580dbb..b3af99da94 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -43,10 +43,17 @@ class Appointment(Document): self.status = 'Unverified' # Send email to confirm verify_url = self._get_verify_url() + template = 'confirm_appointment' + args = { + "link":verify_url, + "site_url":frappe.utils.get_url(), + "full_name":self.customer_name, + } message = ''.join( ['Please click the following link to confirm your appointment:', verify_url]) frappe.sendmail(recipients=[self.customer_email], - message=message, + template=template, + args=args, subject=_('Appointment Confirmation')) frappe.msgprint( 'Please check your email to confirm the appointment') diff --git a/erpnext/templates/emails/confirm_appointment.html b/erpnext/templates/emails/confirm_appointment.html new file mode 100644 index 0000000000..6c9b28bc13 --- /dev/null +++ b/erpnext/templates/emails/confirm_appointment.html @@ -0,0 +1,10 @@ +

{{_("Dear")}} {{ full_name }}{% if last_name %} {{ last_name}}{% endif %},

+

{{_("A new appointment has been created for you with {0}").format(site_url)}}.

+

{{_("Click on the link below to verify your email and confirm the appointment")}}.

+ +

+ {{ _("Verify Email") }} +

+ +
+

{{_("You can also copy-paste this link in your browser")}} {{ link }}

From e573bd90740c93917377f012f51ad2e2e90124ca Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 1 Nov 2019 12:47:11 +0530 Subject: [PATCH 123/157] remove unnecessary variable --- erpnext/crm/doctype/appointment/appointment.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index b3af99da94..fa4b7ec401 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -49,8 +49,6 @@ class Appointment(Document): "site_url":frappe.utils.get_url(), "full_name":self.customer_name, } - message = ''.join( - ['Please click the following link to confirm your appointment:', verify_url]) frappe.sendmail(recipients=[self.customer_email], template=template, args=args, From 4d3dc87a1a2dcf4ad7c71c276c0fc4e8368944dc Mon Sep 17 00:00:00 2001 From: Pranav Nachnekar Date: Tue, 5 Nov 2019 04:32:06 +0000 Subject: [PATCH 124/157] Apply suggestions from code review Co-Authored-By: Shivam Mishra --- .../appointment_booking_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index 2aa51caefd..bb45b7222b 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -23,12 +23,12 @@ class AppointmentBookingSettings(Document): to_time = datetime.datetime.strptime( self.min_date+record.to_time, self.format_string) timedelta = to_time-from_time - self.from_time_is_later_than_to_time(from_time, to_time) + self.validate_from_and_to_time(from_time, to_time) self.duration_is_divisible(from_time, to_time) - def from_time_is_later_than_to_time(self, from_time, to_time): + def validate_from_and_to_time(self, from_time, to_time): if from_time > to_time: - err_msg = 'From Time cannot be later than To Time for '+record.day_of_week + err_msg = _(''From Time cannot be later than To Time for {0}'').format(record.day_of_week) frappe.throw(_(err_msg)) def duration_is_divisible(self, from_time, to_time): From d1ee962d4b7e94518f1dc897660d064c6eac4169 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 5 Nov 2019 14:53:36 +0530 Subject: [PATCH 125/157] seperate function for sending confirmation --- .../crm/doctype/appointment/appointment.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index fa4b7ec401..9e051f607a 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -42,20 +42,21 @@ class Appointment(Document): # Set status to unverified self.status = 'Unverified' # Send email to confirm - verify_url = self._get_verify_url() - template = 'confirm_appointment' - args = { - "link":verify_url, - "site_url":frappe.utils.get_url(), - "full_name":self.customer_name, - } - frappe.sendmail(recipients=[self.customer_email], - template=template, - args=args, - subject=_('Appointment Confirmation')) - frappe.msgprint( - 'Please check your email to confirm the appointment') + def send_confirmation_email() + verify_url = self._get_verify_url() + template = 'confirm_appointment' + args = { + "link":verify_url, + "site_url":frappe.utils.get_url(), + "full_name":self.customer_name, + } + frappe.sendmail(recipients=[self.customer_email], + template=template, + args=args, + subject=_('Appointment Confirmation')) + frappe.msgprint( + 'Please check your email to confirm the appointment') def on_change(self): # Sync Calendar From 6f1d2eeffd81f280fb26a17c34399db84715b3ea Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 6 Nov 2019 11:57:37 +0530 Subject: [PATCH 126/157] changes to suggestions made by shivam --- erpnext/crm/doctype/appointment/appointment.py | 3 ++- .../appointment_booking_settings.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 9e051f607a..5ca124bd85 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -42,8 +42,9 @@ class Appointment(Document): # Set status to unverified self.status = 'Unverified' # Send email to confirm + self.send_confirmation_email() - def send_confirmation_email() + def send_confirmation_email(self): verify_url = self._get_verify_url() template = 'confirm_appointment' args = { diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index bb45b7222b..b8028e3103 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -28,7 +28,7 @@ class AppointmentBookingSettings(Document): def validate_from_and_to_time(self, from_time, to_time): if from_time > to_time: - err_msg = _(''From Time cannot be later than To Time for {0}'').format(record.day_of_week) + err_msg = _('From Time cannot be later than To Time for {0}').format(record.day_of_week) frappe.throw(_(err_msg)) def duration_is_divisible(self, from_time, to_time): From fce8f36bb2ba3541895a67714ec508b45e48d487 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 7 Nov 2019 12:37:28 +0530 Subject: [PATCH 127/157] don't change lead if assigned --- erpnext/crm/doctype/appointment/appointment.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 5ca124bd85..780e04c5ae 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -31,7 +31,8 @@ class Appointment(Document): if(number_of_appointments_in_same_slot >= number_of_agents): frappe.throw('Time slot is not available') # Link lead - self.lead = self.find_lead_by_email() + if not self.lead: + self.lead = self.find_lead_by_email() def after_insert(self): if self.lead: @@ -56,8 +57,9 @@ class Appointment(Document): template=template, args=args, subject=_('Appointment Confirmation')) - frappe.msgprint( - 'Please check your email to confirm the appointment') + if frappe.session.user == "Guest": + frappe.msgprint( + 'Please check your email to confirm the appointment') def on_change(self): # Sync Calendar From 75db6f70735ab930d1dbab03d7b19317028b2653 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 7 Nov 2019 12:47:00 +0530 Subject: [PATCH 128/157] convert indentation to tabs --- .../crm/doctype/appointment/appointment.py | 357 +++++++++--------- .../appointment_booking_settings.py | 44 +-- erpnext/www/book-appointment/index.py | 226 +++++------ erpnext/www/book-appointment/verify/index.py | 26 +- 4 files changed, 328 insertions(+), 325 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 780e04c5ae..91d1c03f7d 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -17,203 +17,206 @@ from frappe.utils.verified_command import verify_request, get_signed_params class Appointment(Document): - def find_lead_by_email(self): - lead_list = frappe.get_list( - 'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True) - if lead_list: - return lead_list[0].name - return None + def find_lead_by_email(self): + lead_list = frappe.get_list( + 'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True) + if lead_list: + return lead_list[0].name + return None - def before_insert(self): - number_of_appointments_in_same_slot = frappe.db.count( - 'Appointment', filters={'scheduled_time': self.scheduled_time}) - number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') - if(number_of_appointments_in_same_slot >= number_of_agents): - frappe.throw('Time slot is not available') - # Link lead - if not self.lead: - self.lead = self.find_lead_by_email() + def before_insert(self): + number_of_appointments_in_same_slot = frappe.db.count( + 'Appointment', filters={'scheduled_time': self.scheduled_time}) + number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') + if(number_of_appointments_in_same_slot >= number_of_agents): + frappe.throw('Time slot is not available') + # Link lead + if not self.lead: + self.lead = self.find_lead_by_email() - def after_insert(self): - if self.lead: - # Create Calendar event - self.auto_assign() - self.create_calendar_event() - else: - # Set status to unverified - self.status = 'Unverified' - # Send email to confirm - self.send_confirmation_email() + def after_insert(self): + if self.lead: + # Create Calendar event + self.auto_assign() + self.create_calendar_event() + else: + # Set status to unverified + self.status = 'Unverified' + # Send email to confirm + self.send_confirmation_email() - def send_confirmation_email(self): - verify_url = self._get_verify_url() - template = 'confirm_appointment' - args = { - "link":verify_url, - "site_url":frappe.utils.get_url(), - "full_name":self.customer_name, - } - frappe.sendmail(recipients=[self.customer_email], - template=template, - args=args, - subject=_('Appointment Confirmation')) - if frappe.session.user == "Guest": - frappe.msgprint( - 'Please check your email to confirm the appointment') + def send_confirmation_email(self): + verify_url = self._get_verify_url() + template = 'confirm_appointment' + args = { + "link":verify_url, + "site_url":frappe.utils.get_url(), + "full_name":self.customer_name, + } + frappe.sendmail(recipients=[self.customer_email], + template=template, + args=args, + subject=_('Appointment Confirmation')) + if frappe.session.user == "Guest": + frappe.msgprint( + 'Please check your email to confirm the appointment') + else : + frappe.msgprint( + 'Appointment was created. But no lead was found. Please check the email to confirm') - def on_change(self): - # Sync Calendar - if not self.calendar_event: - return - cal_event = frappe.get_doc('Event', self.calendar_event) - cal_event.starts_on = self.scheduled_time - cal_event.save(ignore_permissions=True) + def on_change(self): + # Sync Calendar + if not self.calendar_event: + return + cal_event = frappe.get_doc('Event', self.calendar_event) + cal_event.starts_on = self.scheduled_time + cal_event.save(ignore_permissions=True) - def set_verified(self, email): - if not email == self.customer_email: - frappe.throw('Email verification failed.') - # Create new lead - self.create_lead_and_link() - # Remove unverified status - self.status = 'Open' - # Create calender event - self.auto_assign() - self.create_calendar_event() - self.save(ignore_permissions=True) - frappe.db.commit() + def set_verified(self, email): + if not email == self.customer_email: + frappe.throw('Email verification failed.') + # Create new lead + self.create_lead_and_link() + # Remove unverified status + self.status = 'Open' + # Create calender event + self.auto_assign() + self.create_calendar_event() + self.save(ignore_permissions=True) + frappe.db.commit() - def create_lead_and_link(self): - # Return if already linked - if self.lead: - return - lead = frappe.get_doc({ - 'doctype': 'Lead', - 'lead_name': self.customer_name, - 'email_id': self.customer_email, - 'notes': self.customer_details, - 'phone': self.customer_phone_number, - }) - lead.insert(ignore_permissions=True) - # Link lead - self.lead = lead.name + def create_lead_and_link(self): + # Return if already linked + if self.lead: + return + lead = frappe.get_doc({ + 'doctype': 'Lead', + 'lead_name': self.customer_name, + 'email_id': self.customer_email, + 'notes': self.customer_details, + 'phone': self.customer_phone_number, + }) + lead.insert(ignore_permissions=True) + # Link lead + self.lead = lead.name - def auto_assign(self): - from frappe.desk.form.assign_to import add as add_assignemnt - existing_assignee = self.get_assignee_from_latest_opportunity() - if existing_assignee: - # If the latest opportunity is assigned to someone - # Assign the appointment to the same - add_assignemnt({ - 'doctype': self.doctype, - 'name': self.name, - 'assign_to': existing_assignee - }) - return - if self._assign: - return - available_agents = _get_agents_sorted_by_asc_workload( - self.scheduled_time.date()) - for agent in available_agents: - if(_check_agent_availability(agent, self.scheduled_time)): - agent = agent[0] - add_assignemnt({ - 'doctype': self.doctype, - 'name': self.name, - 'assign_to': agent - }) - break + def auto_assign(self): + from frappe.desk.form.assign_to import add as add_assignemnt + existing_assignee = self.get_assignee_from_latest_opportunity() + if existing_assignee: + # If the latest opportunity is assigned to someone + # Assign the appointment to the same + add_assignemnt({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': existing_assignee + }) + return + if self._assign: + return + available_agents = _get_agents_sorted_by_asc_workload( + self.scheduled_time.date()) + for agent in available_agents: + if(_check_agent_availability(agent, self.scheduled_time)): + agent = agent[0] + add_assignemnt({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': agent + }) + break - def get_assignee_from_latest_opportunity(self): - if not self.lead: - return None - if not frappe.db.exists('Lead', self.lead): - return None - opporutnities = frappe.get_list( - 'Opportunity', - filters={ - 'party_name': self.lead, - }, - ignore_permissions=True, - order_by='creation desc') - if not opporutnities: - return None - latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name ) - assignee = latest_opportunity._assign - if not assignee: - return None - assignee = frappe.parse_json(assignee)[0] - return assignee + def get_assignee_from_latest_opportunity(self): + if not self.lead: + return None + if not frappe.db.exists('Lead', self.lead): + return None + opporutnities = frappe.get_list( + 'Opportunity', + filters={ + 'party_name': self.lead, + }, + ignore_permissions=True, + order_by='creation desc') + if not opporutnities: + return None + latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name ) + assignee = latest_opportunity._assign + if not assignee: + return None + assignee = frappe.parse_json(assignee)[0] + return assignee - def create_calendar_event(self): - if self.calendar_event: - return - appointment_event = frappe.get_doc({ - 'doctype': 'Event', - 'subject': ' '.join(['Appointment with', self.customer_name]), - 'starts_on': self.scheduled_time, - 'status': 'Open', - 'type': 'Public', - 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'), - 'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)] - }) - employee = _get_employee_from_user(self._assign) - if employee: - appointment_event.append('event_participants', dict( - reference_doctype='Employee', - reference_docname=employee.name)) - appointment_event.insert(ignore_permissions=True) - self.calendar_event = appointment_event.name - self.save(ignore_permissions=True) - - def _get_verify_url(self): - verify_route = '/book-appointment/verify' - params = { - 'email': self.customer_email, - 'appointment': self.name - } - return get_url(verify_route + '?' + get_signed_params(params)) + def create_calendar_event(self): + if self.calendar_event: + return + appointment_event = frappe.get_doc({ + 'doctype': 'Event', + 'subject': ' '.join(['Appointment with', self.customer_name]), + 'starts_on': self.scheduled_time, + 'status': 'Open', + 'type': 'Public', + 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'), + 'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)] + }) + employee = _get_employee_from_user(self._assign) + if employee: + appointment_event.append('event_participants', dict( + reference_doctype='Employee', + reference_docname=employee.name)) + appointment_event.insert(ignore_permissions=True) + self.calendar_event = appointment_event.name + self.save(ignore_permissions=True) + + def _get_verify_url(self): + verify_route = '/book-appointment/verify' + params = { + 'email': self.customer_email, + 'appointment': self.name + } + return get_url(verify_route + '?' + get_signed_params(params)) def _get_agents_sorted_by_asc_workload(date): - appointments = frappe.db.get_list('Appointment', fields='*') - agent_list = _get_agent_list_as_strings() - if not appointments: - return agent_list - appointment_counter = Counter(agent_list) - for appointment in appointments: - assigned_to = frappe.parse_json(appointment._assign) - if not assigned_to: - continue - if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date: - appointment_counter[assigned_to[0]] += 1 - sorted_agent_list = appointment_counter.most_common() - sorted_agent_list.reverse() - return sorted_agent_list + appointments = frappe.db.get_list('Appointment', fields='*') + agent_list = _get_agent_list_as_strings() + if not appointments: + return agent_list + appointment_counter = Counter(agent_list) + for appointment in appointments: + assigned_to = frappe.parse_json(appointment._assign) + if not assigned_to: + continue + if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date: + appointment_counter[assigned_to[0]] += 1 + sorted_agent_list = appointment_counter.most_common() + sorted_agent_list.reverse() + return sorted_agent_list def _get_agent_list_as_strings(): - agent_list_as_strings = [] - agent_list = frappe.get_doc('Appointment Booking Settings').agent_list - for agent in agent_list: - agent_list_as_strings.append(agent.user) - return agent_list_as_strings + agent_list_as_strings = [] + agent_list = frappe.get_doc('Appointment Booking Settings').agent_list + for agent in agent_list: + agent_list_as_strings.append(agent.user) + return agent_list_as_strings def _check_agent_availability(agent_email, scheduled_time): - appointemnts_at_scheduled_time = frappe.get_list( - 'Appointment', filters={'scheduled_time': scheduled_time}) - for appointment in appointemnts_at_scheduled_time: - if appointment._assign == agent_email: - return False - return True + appointemnts_at_scheduled_time = frappe.get_list( + 'Appointment', filters={'scheduled_time': scheduled_time}) + for appointment in appointemnts_at_scheduled_time: + if appointment._assign == agent_email: + return False + return True def _get_employee_from_user(user): - employee_docname = frappe.db.exists( - {'doctype': 'Employee', 'user_id': user}) - if employee_docname: - # frappe.db.exists returns a tuple of a tuple - return frappe.get_doc('Employee', employee_docname[0][0]) - return None + employee_docname = frappe.db.exists( + {'doctype': 'Employee', 'user_id': user}) + if employee_docname: + # frappe.db.exists returns a tuple of a tuple + return frappe.get_doc('Employee', employee_docname[0][0]) + return None diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index b8028e3103..2874f3fae2 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -10,29 +10,29 @@ from frappe.model.document import Document class AppointmentBookingSettings(Document): - min_date = '01/01/1970 ' - format_string = "%d/%m/%Y %H:%M:%S" + min_date = '01/01/1970 ' + format_string = "%d/%m/%Y %H:%M:%S" - def validate(self): - self.validate_availability_of_slots() + def validate(self): + self.validate_availability_of_slots() - def validate_availability_of_slots(self): - for record in self.availability_of_slots: - from_time = datetime.datetime.strptime( - self.min_date+record.from_time, self.format_string) - to_time = datetime.datetime.strptime( - self.min_date+record.to_time, self.format_string) - timedelta = to_time-from_time - self.validate_from_and_to_time(from_time, to_time) - self.duration_is_divisible(from_time, to_time) + def validate_availability_of_slots(self): + for record in self.availability_of_slots: + from_time = datetime.datetime.strptime( + self.min_date+record.from_time, self.format_string) + to_time = datetime.datetime.strptime( + self.min_date+record.to_time, self.format_string) + timedelta = to_time-from_time + self.validate_from_and_to_time(from_time, to_time) + self.duration_is_divisible(from_time, to_time) - def validate_from_and_to_time(self, from_time, to_time): - if from_time > to_time: - err_msg = _('From Time cannot be later than To Time for {0}').format(record.day_of_week) - frappe.throw(_(err_msg)) + def validate_from_and_to_time(self, from_time, to_time): + if from_time > to_time: + err_msg = _('From Time cannot be later than To Time for {0}').format(record.day_of_week) + frappe.throw(_(err_msg)) - def duration_is_divisible(self, from_time, to_time): - timedelta = to_time - from_time - if timedelta.total_seconds() % (self.appointment_duration * 60): - frappe.throw( - _('The difference between from time and To Time must be a multiple of Appointment')) + def duration_is_divisible(self, from_time, to_time): + timedelta = to_time - from_time + if timedelta.total_seconds() % (self.appointment_duration * 60): + frappe.throw( + _('The difference between from time and To Time must be a multiple of Appointment')) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 9b5ea57a83..11073131b1 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -5,158 +5,158 @@ import pytz WEEKDAYS = ["Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", "Sunday"] + "Thursday", "Friday", "Saturday", "Sunday"] no_cache = 1 def get_context(context): - is_enabled = frappe.db.get_single_value( - 'Appointment Booking Settings', 'enable_scheduling') - if is_enabled: - return context - else: - raise frappe.DoesNotExistError + is_enabled = frappe.db.get_single_value( + 'Appointment Booking Settings', 'enable_scheduling') + if is_enabled: + return context + else: + raise frappe.DoesNotExistError @frappe.whitelist(allow_guest=True) def get_appointment_settings(): - settings = frappe.get_doc('Appointment Booking Settings') - settings.holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) - return settings + settings = frappe.get_doc('Appointment Booking Settings') + settings.holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) + return settings @frappe.whitelist(allow_guest=True) def get_timezones(): - return pytz.all_timezones + return pytz.all_timezones @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): - import pytz - # Convert query to local timezones - 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_system_timezone(timezone,query_start_time) - query_end_time = convert_to_system_timezone(timezone,query_end_time) - now = convert_to_guest_timezone(timezone,datetime.datetime.now()) + import pytz + # Convert query to local timezones + 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_system_timezone(timezone,query_start_time) + query_end_time = convert_to_system_timezone(timezone,query_end_time) + now = convert_to_guest_timezone(timezone,datetime.datetime.now()) - # 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) + # 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 and convert timeslots - converted_timeslots = [] - for timeslot in timeslots: - converted_timeslot = convert_to_guest_timezone(timezone,timeslot) - # Check if holiday - if _is_holiday(converted_timeslot.date(), holiday_list): - converted_timeslots.append( - dict(time=converted_timeslot, availability=False)) - continue - # Check availability - if check_availabilty(timeslot, settings) and converted_timeslot >= now: - converted_timeslots.append( - dict(time=converted_timeslot, availability=True)) - else: - converted_timeslots.append( - dict(time=converted_timeslot, 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 + # Filter and convert timeslots + converted_timeslots = [] + for timeslot in timeslots: + converted_timeslot = convert_to_guest_timezone(timezone,timeslot) + # Check if holiday + if _is_holiday(converted_timeslot.date(), holiday_list): + converted_timeslots.append( + dict(time=converted_timeslot, availability=False)) + continue + # Check availability + if check_availabilty(timeslot, settings) and converted_timeslot >= now: + converted_timeslots.append( + dict(time=converted_timeslot, availability=True)) + else: + converted_timeslots.append( + dict(time=converted_timeslot, 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 + 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, tz, contact): - import pytz - appointment = frappe.new_doc('Appointment') - format_string = '%Y-%m-%d %H:%M:%S%z' - scheduled_time = datetime.datetime.strptime( - date+" "+time, format_string) - scheduled_time = scheduled_time.replace(tzinfo=None) - scheduled_time = convert_to_system_timezone(tz,scheduled_time) - scheduled_time= scheduled_time.replace(tzinfo=None) - appointment.scheduled_time = scheduled_time - contact = json.loads(contact) - appointment.customer_name = contact['name'] - appointment.customer_phone_number = contact['number'] - appointment.customer_skype = contact['skype'] - appointment.customer_details = contact['notes'] - appointment.customer_email = contact['email'] - appointment.status = 'Open' - appointment.insert() + import pytz + appointment = frappe.new_doc('Appointment') + format_string = '%Y-%m-%d %H:%M:%S%z' + scheduled_time = datetime.datetime.strptime( + date+" "+time, format_string) + scheduled_time = scheduled_time.replace(tzinfo=None) + scheduled_time = convert_to_system_timezone(tz,scheduled_time) + scheduled_time= scheduled_time.replace(tzinfo=None) + appointment.scheduled_time = scheduled_time + contact = json.loads(contact) + appointment.customer_name = contact['name'] + appointment.customer_phone_number = contact['number'] + appointment.customer_skype = contact['skype'] + appointment.customer_details = contact['notes'] + appointment.customer_email = contact['email'] + appointment.status = 'Open' + 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 + filtered_timeslots = [] + for timeslot in timeslots: + if(timeslot['time'].date() == date): + filtered_timeslots.append(timeslot) + return filtered_timeslots def convert_to_guest_timezone(guest_tz,datetimeobject): - import pytz - guest_tz = pytz.timezone(guest_tz) - local_timezone = pytz.timezone(frappe.utils.get_time_zone()) - datetimeobject = local_timezone.localize(datetimeobject) - datetimeobject = datetimeobject.astimezone(guest_tz) - return datetimeobject + import pytz + guest_tz = pytz.timezone(guest_tz) + local_timezone = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = local_timezone.localize(datetimeobject) + datetimeobject = datetimeobject.astimezone(guest_tz) + return datetimeobject def convert_to_system_timezone(guest_tz,datetimeobject): - import pytz - guest_tz = pytz.timezone(guest_tz) - datetimeobject = guest_tz.localize(datetimeobject) - system_tz = pytz.timezone(frappe.utils.get_time_zone()) - datetimeobject = datetimeobject.astimezone(system_tz) - return datetimeobject + import pytz + guest_tz = pytz.timezone(guest_tz) + datetimeobject = guest_tz.localize(datetimeobject) + system_tz = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = datetimeobject.astimezone(system_tz) + return datetimeobject def check_availabilty(timeslot, settings): - return frappe.db.count('Appointment', {'scheduled_time': timeslot}) < settings.number_of_agents + 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 + 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 + 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) + 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) \ No newline at end of file + midnight = datetime.datetime.combine(date_time.date(), datetime.time.min) + return (date_time-midnight) \ No newline at end of file diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book-appointment/verify/index.py index e8ccecd8b6..d4478ae34a 100644 --- a/erpnext/www/book-appointment/verify/index.py +++ b/erpnext/www/book-appointment/verify/index.py @@ -3,18 +3,18 @@ import frappe from frappe.utils.verified_command import verify_request @frappe.whitelist(allow_guest=True) def get_context(context): - if not verify_request(): - context.success = False - return context + if not verify_request(): + context.success = False + return context - email = frappe.form_dict['email'] - appointment_name = frappe.form_dict['appointment'] + email = frappe.form_dict['email'] + appointment_name = frappe.form_dict['appointment'] - if email and appointment_name: - appointment = frappe.get_doc('Appointment',appointment_name) - appointment.set_verified(email) - context.success = True - return context - else: - context.success = False - return context \ No newline at end of file + if email and appointment_name: + appointment = frappe.get_doc('Appointment',appointment_name) + appointment.set_verified(email) + context.success = True + return context + else: + context.success = False + return context \ No newline at end of file From 51208b3f0b848f1de06646d2c2647c09e081381f Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 7 Nov 2019 12:54:48 +0530 Subject: [PATCH 129/157] fix:formatting --- erpnext/www/book-appointment/index.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 11073131b1..fe30ef65c5 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -37,9 +37,9 @@ def get_appointment_slots(date, timezone): date + ' 00:00:00', format_string) query_end_time = datetime.datetime.strptime( date + ' 23:59:59', format_string) - query_start_time = convert_to_system_timezone(timezone,query_start_time) - query_end_time = convert_to_system_timezone(timezone,query_end_time) - now = convert_to_guest_timezone(timezone,datetime.datetime.now()) + query_start_time = convert_to_system_timezone(timezone, query_start_time) + query_end_time = convert_to_system_timezone(timezone, query_end_time) + now = convert_to_guest_timezone(timezone, datetime.datetime.now()) # Database queries settings = frappe.get_doc('Appointment Booking Settings') @@ -50,7 +50,7 @@ def get_appointment_slots(date, timezone): # Filter and convert timeslots converted_timeslots = [] for timeslot in timeslots: - converted_timeslot = convert_to_guest_timezone(timezone,timeslot) + converted_timeslot = convert_to_guest_timezone(timezone, timeslot) # Check if holiday if _is_holiday(converted_timeslot.date(), holiday_list): converted_timeslots.append( @@ -98,15 +98,15 @@ def create_appointment(date, time, tz, contact): scheduled_time = datetime.datetime.strptime( date+" "+time, format_string) scheduled_time = scheduled_time.replace(tzinfo=None) - scheduled_time = convert_to_system_timezone(tz,scheduled_time) - scheduled_time= scheduled_time.replace(tzinfo=None) + scheduled_time = convert_to_system_timezone(tz, scheduled_time) + scheduled_time = scheduled_time.replace(tzinfo=None) appointment.scheduled_time = scheduled_time contact = json.loads(contact) - appointment.customer_name = contact['name'] - appointment.customer_phone_number = contact['number'] - appointment.customer_skype = contact['skype'] - appointment.customer_details = contact['notes'] - appointment.customer_email = contact['email'] + appointment.customer_name = contact.get('name',None) + appointment.customer_phone_number = contact.get('number', None) + appointment.customer_skype = contact.get('skype', None) + appointment.customer_details = contact.get('notes', None) + appointment.customer_email = contact.get('email', None) appointment.status = 'Open' appointment.insert() From 151853b887a7ab43075b5ffdd83e139c8bf6228e Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 7 Nov 2019 12:55:43 +0530 Subject: [PATCH 130/157] remove unneccessary imports --- erpnext/www/book-appointment/index.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index fe30ef65c5..213617fedc 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -30,7 +30,6 @@ def get_timezones(): @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): - import pytz # Convert query to local timezones format_string = '%Y-%m-%d %H:%M:%S' query_start_time = datetime.datetime.strptime( @@ -92,7 +91,6 @@ def get_available_slots_between(query_start_time, query_end_time, settings): @frappe.whitelist(allow_guest=True) def create_appointment(date, time, tz, contact): - import pytz appointment = frappe.new_doc('Appointment') format_string = '%Y-%m-%d %H:%M:%S%z' scheduled_time = datetime.datetime.strptime( @@ -119,7 +117,6 @@ def filter_timeslots(date, timeslots): return filtered_timeslots def convert_to_guest_timezone(guest_tz,datetimeobject): - import pytz guest_tz = pytz.timezone(guest_tz) local_timezone = pytz.timezone(frappe.utils.get_time_zone()) datetimeobject = local_timezone.localize(datetimeobject) @@ -127,7 +124,6 @@ def convert_to_guest_timezone(guest_tz,datetimeobject): return datetimeobject def convert_to_system_timezone(guest_tz,datetimeobject): - import pytz guest_tz = pytz.timezone(guest_tz) datetimeobject = guest_tz.localize(datetimeobject) system_tz = pytz.timezone(frappe.utils.get_time_zone()) From 76b20a5fa4927a1821f511d921c6faaff6690eef Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 7 Nov 2019 13:24:59 +0530 Subject: [PATCH 131/157] crack some one liners --- erpnext/www/book-appointment/index.py | 42 +++++++++------------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 213617fedc..366f399bc0 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -4,15 +4,13 @@ import json import pytz -WEEKDAYS = ["Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", "Sunday"] +WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] no_cache = 1 def get_context(context): - is_enabled = frappe.db.get_single_value( - 'Appointment Booking Settings', 'enable_scheduling') + is_enabled = frappe.db.get_single_value('Appointment Booking Settings', 'enable_scheduling') if is_enabled: return context else: @@ -32,10 +30,8 @@ def get_timezones(): def get_appointment_slots(date, timezone): # Convert query to local timezones 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 = 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_system_timezone(timezone, query_start_time) query_end_time = convert_to_system_timezone(timezone, query_end_time) now = convert_to_guest_timezone(timezone, datetime.datetime.now()) @@ -43,8 +39,7 @@ def get_appointment_slots(date, 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) + timeslots = get_available_slots_between(query_start_time, query_end_time, settings) # Filter and convert timeslots converted_timeslots = [] @@ -52,18 +47,14 @@ def get_appointment_slots(date, timezone): converted_timeslot = convert_to_guest_timezone(timezone, timeslot) # Check if holiday if _is_holiday(converted_timeslot.date(), holiday_list): - converted_timeslots.append( - dict(time=converted_timeslot, availability=False)) + converted_timeslots.append(dict(time=converted_timeslot, availability=False)) continue # Check availability if check_availabilty(timeslot, settings) and converted_timeslot >= now: - converted_timeslots.append( - dict(time=converted_timeslot, availability=True)) + converted_timeslots.append(dict(time=converted_timeslot, availability=True)) else: - converted_timeslots.append( - dict(time=converted_timeslot, availability=False)) - date_required = datetime.datetime.strptime( - date + ' 00:00:00', format_string).date() + converted_timeslots.append(dict(time=converted_timeslot, 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 @@ -74,15 +65,11 @@ def get_available_slots_between(query_start_time, query_end_time, settings): 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) + 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) + 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 @@ -93,8 +80,7 @@ def get_available_slots_between(query_start_time, query_end_time, settings): def create_appointment(date, time, tz, contact): appointment = frappe.new_doc('Appointment') format_string = '%Y-%m-%d %H:%M:%S%z' - scheduled_time = datetime.datetime.strptime( - date+" "+time, format_string) + scheduled_time = datetime.datetime.strptime(date+" "+time, format_string) scheduled_time = scheduled_time.replace(tzinfo=None) scheduled_time = convert_to_system_timezone(tz, scheduled_time) scheduled_time = scheduled_time.replace(tzinfo=None) From 0671ea8137f2b8bae1a9f54606635a3f7bd470f5 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 7 Nov 2019 13:31:56 +0530 Subject: [PATCH 132/157] use frappe.Redirect instead of DoesNotExistError --- erpnext/www/book-appointment/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 366f399bc0..9765e5ea4d 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -14,7 +14,8 @@ def get_context(context): if is_enabled: return context else: - raise frappe.DoesNotExistError + frappe.local.flags.redirect_location = '/404' + raise frappe.Redirect @frappe.whitelist(allow_guest=True) def get_appointment_settings(): From 83100c9c847ef000c8e071ccdab4c1cf1ab675bc Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 7 Nov 2019 13:37:11 +0530 Subject: [PATCH 133/157] Add comemnts for tz conversions --- erpnext/www/book-appointment/index.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 9765e5ea4d..707be6775c 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -79,13 +79,15 @@ def get_available_slots_between(query_start_time, query_end_time, settings): @frappe.whitelist(allow_guest=True) def create_appointment(date, time, tz, contact): - appointment = frappe.new_doc('Appointment') format_string = '%Y-%m-%d %H:%M:%S%z' - scheduled_time = datetime.datetime.strptime(date+" "+time, format_string) + scheduled_time = datetime.datetime.strptime(date + " " + time, format_string) + # Strip tzinfo from datetime objects since it's handled by the doctype scheduled_time = scheduled_time.replace(tzinfo=None) scheduled_time = convert_to_system_timezone(tz, scheduled_time) scheduled_time = scheduled_time.replace(tzinfo=None) + # Create a appointment document from form appointment.scheduled_time = scheduled_time + appointment = frappe.new_doc('Appointment') contact = json.loads(contact) appointment.customer_name = contact.get('name',None) appointment.customer_phone_number = contact.get('number', None) From db64c69dace07d36753e31537ec45bb7abb8e668 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 11:12:38 +0530 Subject: [PATCH 134/157] fix: reference before assignement error --- erpnext/www/book-appointment/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 707be6775c..1fe1987453 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -86,8 +86,8 @@ def create_appointment(date, time, tz, contact): scheduled_time = convert_to_system_timezone(tz, scheduled_time) scheduled_time = scheduled_time.replace(tzinfo=None) # Create a appointment document from form - appointment.scheduled_time = scheduled_time appointment = frappe.new_doc('Appointment') + appointment.scheduled_time = scheduled_time contact = json.loads(contact) appointment.customer_name = contact.get('name',None) appointment.customer_phone_number = contact.get('number', None) From cce000a6d09fc860ce6d720e45a84a7325e4e4b8 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 11:48:37 +0530 Subject: [PATCH 135/157] remove: commented code --- .../appointment_booking_settings.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js index 2642e6eb26..4dd07236ca 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js @@ -1,6 +1,3 @@ -// 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) { From f25e2a29f7888d01cc0fefde3c240e74e54094bf Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 12:01:36 +0530 Subject: [PATCH 136/157] fix:formatting --- erpnext/www/book-appointment/index.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 1fe1987453..a8ab22956d 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -82,14 +82,14 @@ def create_appointment(date, time, tz, contact): format_string = '%Y-%m-%d %H:%M:%S%z' scheduled_time = datetime.datetime.strptime(date + " " + time, format_string) # Strip tzinfo from datetime objects since it's handled by the doctype - scheduled_time = scheduled_time.replace(tzinfo=None) + scheduled_time = scheduled_time.replace(tzinfo = None) scheduled_time = convert_to_system_timezone(tz, scheduled_time) - scheduled_time = scheduled_time.replace(tzinfo=None) + scheduled_time = scheduled_time.replace(tzinfo = None) # Create a appointment document from form appointment = frappe.new_doc('Appointment') appointment.scheduled_time = scheduled_time contact = json.loads(contact) - appointment.customer_name = contact.get('name',None) + appointment.customer_name = contact.get('name', None) appointment.customer_phone_number = contact.get('number', None) appointment.customer_skype = contact.get('skype', None) appointment.customer_details = contact.get('notes', None) @@ -105,7 +105,7 @@ def filter_timeslots(date, timeslots): filtered_timeslots.append(timeslot) return filtered_timeslots -def convert_to_guest_timezone(guest_tz,datetimeobject): +def convert_to_guest_timezone(guest_tz, datetimeobject): guest_tz = pytz.timezone(guest_tz) local_timezone = pytz.timezone(frappe.utils.get_time_zone()) datetimeobject = local_timezone.localize(datetimeobject) From a92f060740f5ffaa22347dc54318efb9aa4b43b2 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 12:13:42 +0530 Subject: [PATCH 137/157] multiple fixes in index.js --- erpnext/www/book-appointment/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 6bd868bbc7..70ed4c2ecd 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -185,30 +185,30 @@ function setup_details_page() { } async function submit() { + let button = document.getElementById('submit-button'); + button.disabled = true; let form = document.querySelector('#customer-form'); if (!form.checkValidity()) { form.reportValidity(); + button.disabled = false; return; } - get_form_data(); + let contact = get_form_data(); let appointment = (await frappe.call({ method: 'erpnext.www.book-appointment.index.create_appointment', args: { 'date': window.selected_date, 'time': window.selected_time, - 'contact': window.contact, + 'contact': contact, 'tz':window.selected_timezone } })).message; frappe.msgprint(__('Appointment Created Successfully')); - let button = document.getElementById('submit-button'); - button.disabled = true; - button.onclick = null } function get_form_data() { contact = {}; let inputs = ['name', 'skype', 'number', 'notes', 'email']; inputs.forEach((id) => contact[id] = document.getElementById(`customer_${id}`).value) - window.contact = contact + return contact } From c72e1f812dea12bd25b2a43087ee60796e8dc79b Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 12:59:05 +0530 Subject: [PATCH 138/157] adjust padding for appointment booking --- erpnext/www/book-appointment/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index 10fe09ab3c..9e470dafea 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -21,7 +21,7 @@
@@ -40,7 +40,7 @@

Add details

-

Selected date is at +

Selected date is at

From 67f191df4edecd43de1a7d4904792fe088e8aae2 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 14:14:10 +0530 Subject: [PATCH 139/157] padding fixes for timeslot divs --- erpnext/www/book-appointment/index.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css index 30ce957e2c..0959d5c4cd 100644 --- a/erpnext/www/book-appointment/index.css +++ b/erpnext/www/book-appointment/index.css @@ -1,6 +1,4 @@ .time-slot { - flex-grow: 1; - flex : 0 0 calc(16.66% - 20px); margin-bottom: 2em; margin-left: 0.5em; margin-right: 0.5em; From b1e9fb9e144e91e4f37a4eaff136496ff776d209 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 15:32:56 +0530 Subject: [PATCH 140/157] fix: buttons on page of appointment scheduling --- erpnext/www/book-appointment/index.css | 8 ++++++++ erpnext/www/book-appointment/index.html | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book-appointment/index.css index 0959d5c4cd..6c49fde739 100644 --- a/erpnext/www/book-appointment/index.css +++ b/erpnext/www/book-appointment/index.css @@ -9,6 +9,14 @@ padding: 0.5em 1em; } +@media (max-width: 768px) { + #submit-button-area { + display: grid; + grid-template-areas: + "submit" + "back"; + } +} #customer-form{ border-color: black; } diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index 9e470dafea..8ddfc2928b 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -54,9 +54,9 @@ -
-
-
+
+
+
From 6e6954cab8179af978b0650fd90f1f6cfdd84c7b Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 16:00:59 +0530 Subject: [PATCH 141/157] timezone aware datetime --- erpnext/www/book-appointment/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 70ed4c2ecd..457c6cf1a4 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -123,9 +123,10 @@ function clear_time_slots() { } function get_slot_layout(time) { + let timezone = document.getElementById("appointment-timezone").value; time = new Date(time); - let start_time_string = moment(time).format("LT"); - let end_time = moment(time).add(window.appointment_settings.appointment_duration, 'minutes'); + let start_time_string = moment(time).tz(timezone).format("LT"); + let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes'); let end_time_string = end_time.format("LT"); return `${start_time_string}
to ${end_time_string}`; } From c31808f5b2f87c19bc366a68a8c8b575477773a3 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 13 Nov 2019 16:47:51 +0530 Subject: [PATCH 142/157] fix margins --- erpnext/www/book-appointment/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book-appointment/index.html index 8ddfc2928b..96774d5656 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book-appointment/index.html @@ -30,7 +30,7 @@
-
+
From 793ba8fc06ac5fec09b5c7a52cb73bd44b3f903b Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 14 Nov 2019 11:25:49 +0530 Subject: [PATCH 143/157] pretty timezone names --- erpnext/www/book-appointment/index.js | 13 +++++++++---- erpnext/www/book-appointment/index.py | 13 ++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 457c6cf1a4..b91e3b08eb 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -13,26 +13,31 @@ async function initialise_select_date() { } async function get_global_variables() { - // Using await + // Using await through this file instead of then. window.appointment_settings = (await frappe.call({ method: 'erpnext.www.book-appointment.index.get_appointment_settings' })).message; window.timezones = (await frappe.call({ - method: 'erpnext.www.book-appointment.index.get_timezones' + method:'erpnext.www.book-appointment.index.get_timezones' })).message; window.holiday_list = window.appointment_settings.holiday_list; } function setup_timezone_selector() { + /** + * window.timezones is a dictionary with the following structure + * { IANA name: Pretty name} + * For example : { Asia/Kolkata : "India Time - Asia/Kolkata"} + */ let timezones_element = document.getElementById('appointment-timezone'); let offset = new Date().getTimezoneOffset(); - window.timezones.forEach(timezone => { + Object.keys(window.timezones).forEach((timezone) => { let opt = document.createElement('option'); opt.value = timezone; if (timezone == moment.tz.guess()) { opt.selected = true; } - opt.innerHTML = timezone; + opt.innerHTML = window.timezones[timezone] timezones_element.appendChild(opt) }); } diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index a8ab22956d..163fdc0132 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -25,7 +25,18 @@ def get_appointment_settings(): @frappe.whitelist(allow_guest=True) def get_timezones(): - return pytz.all_timezones + from babel.dates import get_timezone, get_timezone_name, Locale + from frappe.utils.momentjs import get_all_timezones + + translated_dict = {} + locale = Locale.parse(frappe.local.lang, sep="-") + + for tz in get_all_timezones(): + timezone_name = get_timezone_name(get_timezone(tz), locale=locale, width='short') + if timezone_name: + translated_dict[tz] = timezone_name + ' - ' + tz + + return translated_dict @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): From 511780a4d4d6c1a03601f167635ef66ba4cbcb2f Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Thu, 14 Nov 2019 12:47:08 +0530 Subject: [PATCH 144/157] feat: configurable redirect on success --- .../appointment_booking_settings.json | 22 +++++++++++++++---- erpnext/www/book-appointment/index.js | 22 ++++++++++++++++--- erpnext/www/book-appointment/index.py | 1 + 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 25a7c69268..aafdfd960a 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -13,7 +13,9 @@ "appointment_details_section", "appointment_duration", "email_reminders", - "advance_booking_days" + "advance_booking_days", + "success_details", + "success_redirect_url" ], "fields": [ { @@ -28,7 +30,7 @@ "fieldname": "number_of_agents", "fieldtype": "Int", "in_list_view": 1, - "label": "No. Of Agents", + "label": "Number of Concurrent Appointments", "reqd": 1 }, { @@ -48,9 +50,10 @@ }, { "default": "0", + "description": "Notify customer and agent via email on the day of the appointment.", "fieldname": "email_reminders", "fieldtype": "Check", - "label": "Email Reminders" + "label": "Notify Via Email" }, { "default": "7", @@ -82,10 +85,21 @@ "fieldname": "appointment_details_section", "fieldtype": "Section Break", "label": "Appointment Details" + }, + { + "fieldname": "success_details", + "fieldtype": "Section Break", + "label": "Success Settings" + }, + { + "description": "Leave blank for home.\nThis is relative to site URL, for example \"/about\" will redirect to \"https://yoursitename.com/about\"", + "fieldname": "success_redirect_url", + "fieldtype": "Data", + "label": "Success Redirect URL" } ], "issingle": 1, - "modified": "2019-10-04 11:36:20.839075", + "modified": "2019-11-14 12:17:08.721683", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index b91e3b08eb..433b956014 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -200,16 +200,32 @@ async function submit() { return; } let contact = get_form_data(); - let appointment = (await frappe.call({ + let appointment = frappe.call({ method: 'erpnext.www.book-appointment.index.create_appointment', args: { 'date': window.selected_date, 'time': window.selected_time, 'contact': contact, 'tz':window.selected_timezone + }, + callback: (response)=>{ + if (response.message.status == "Unverified") { + frappe.show_alert("Please check your email to confirm the appointment") + } else { + frappe.show_alert("Appointment Created Successfully"); + } + setTimeout(()=>{ + let redirect_url = "/"; + if (window.appointment_settings.success_redirect_url){ + redirect_url += window.appointment_settings.success_redirect_url; + } + window.location.href = redirect_url;},2) + }, + error: (err)=>{ + frappe.show_alert("Something went wrong please try again"); + button.disabled = false; } - })).message; - frappe.msgprint(__('Appointment Created Successfully')); + }); } function get_form_data() { diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book-appointment/index.py index 163fdc0132..5b60dd5e7b 100644 --- a/erpnext/www/book-appointment/index.py +++ b/erpnext/www/book-appointment/index.py @@ -107,6 +107,7 @@ def create_appointment(date, time, tz, contact): appointment.customer_email = contact.get('email', None) appointment.status = 'Open' appointment.insert() + return appointment # Helper Functions def filter_timeslots(date, timeslots): From 18fda5a57173fb4204a0e69f1dc06e90eb7dab6b Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Fri, 15 Nov 2019 11:58:21 +0530 Subject: [PATCH 145/157] add appointment list to module page --- erpnext/config/crm.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/config/crm.py b/erpnext/config/crm.py index eba6c7a02a..8344c66c1f 100644 --- a/erpnext/config/crm.py +++ b/erpnext/config/crm.py @@ -46,6 +46,11 @@ def get_data(): "name": "Contract", "description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"), }, + { + "type": "doctype", + "name": "Appointment", + "description" : _("Helps you manage appointments with your leads"), + }, ] }, { From 539ea2cefbe4e38e073ae4d549611624ed292f70 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 19 Nov 2019 10:56:58 +0530 Subject: [PATCH 146/157] Rename doctype `Appointment Booking Slots` --- .../appointment_booking_settings.js | 2 +- .../appointment_booking_settings.json | 4 ++-- .../__init__.py | 0 .../appointment_booking_slots.json} | 6 +++--- .../appointment_booking_slots.py} | 5 ++--- 5 files changed, 8 insertions(+), 9 deletions(-) rename erpnext/crm/doctype/{availabilty_of_slots => appointment_booking_slots}/__init__.py (100%) rename erpnext/crm/doctype/{availabilty_of_slots/availability_of_slots.json => appointment_booking_slots/appointment_booking_slots.json} (86%) rename erpnext/crm/doctype/{availabilty_of_slots/availabilty_of_slots.py => appointment_booking_slots/appointment_booking_slots.py} (83%) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js index 4dd07236ca..99b82148d2 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js @@ -4,7 +4,7 @@ function check_times(frm) { let from_time = Date.parse('01/01/2019 ' + d.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"`)); + frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`)); } }); } \ 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 index aafdfd960a..2c161ee0c2 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -22,7 +22,7 @@ "fieldname": "availability_of_slots", "fieldtype": "Table", "label": "Availability Of Slots", - "options": "Availability Of Slots", + "options": "Appointment Booking Slots", "reqd": 1 }, { @@ -99,7 +99,7 @@ } ], "issingle": 1, - "modified": "2019-11-14 12:17:08.721683", + "modified": "2019-11-19 10:53:26.935061", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/crm/doctype/availabilty_of_slots/__init__.py b/erpnext/crm/doctype/appointment_booking_slots/__init__.py similarity index 100% rename from erpnext/crm/doctype/availabilty_of_slots/__init__.py rename to erpnext/crm/doctype/appointment_booking_slots/__init__.py diff --git a/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.json similarity index 86% rename from erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json rename to erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.json index d26f7ced35..ddf8738629 100644 --- a/erpnext/crm/doctype/availabilty_of_slots/availability_of_slots.json +++ b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.json @@ -1,5 +1,5 @@ { - "creation": "2019-08-27 10:52:54.204677", + "creation": "2019-11-19 10:49:49.494927", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -33,10 +33,10 @@ } ], "istable": 1, - "modified": "2019-08-27 10:52:54.204677", + "modified": "2019-11-19 10:49:49.494927", "modified_by": "Administrator", "module": "CRM", - "name": "Availabilty Of Slots", + "name": "Appointment Booking Slots", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.py similarity index 83% rename from erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py rename to erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.py index bd764806ba..3cadbc9559 100644 --- a/erpnext/crm/doctype/availabilty_of_slots/availabilty_of_slots.py +++ b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.py @@ -6,6 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document - -class AvailabiltyOfSlots(Document): - pass +class AppointmentBookingSlots(Document): + pass From c8e66a0f7162bed95984804c1c74cc838894ca9c Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 20 Nov 2019 10:27:59 +0530 Subject: [PATCH 147/157] Infer number_of_agents from agent_list in apppointment booking settings --- .../appointment_booking_settings.json | 4 +++- .../appointment_booking_settings.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 2c161ee0c2..92343dbb13 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -29,8 +29,10 @@ "default": "1", "fieldname": "number_of_agents", "fieldtype": "Int", + "hidden": 1, "in_list_view": 1, "label": "Number of Concurrent Appointments", + "read_only": 1, "reqd": 1 }, { @@ -99,7 +101,7 @@ } ], "issingle": 1, - "modified": "2019-11-19 10:53:26.935061", + "modified": "2019-11-20 10:23:37.393363", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index 2874f3fae2..fd20ba0792 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -16,6 +16,12 @@ class AppointmentBookingSettings(Document): def validate(self): self.validate_availability_of_slots() + def save(self): + self.infer_number_of_agents() + + def infer_number_of_agents(): + self.number_of_agents = len(self.agent_list) + def validate_availability_of_slots(self): for record in self.availability_of_slots: from_time = datetime.datetime.strptime( From dbde140e46ecc5aaae611f5355e8482023e3b80c Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 20 Nov 2019 10:30:41 +0530 Subject: [PATCH 148/157] fix: save method of Appointment Booking Setting --- .../appointment_booking_settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index fd20ba0792..484a5729c5 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -18,8 +18,9 @@ class AppointmentBookingSettings(Document): def save(self): self.infer_number_of_agents() + super().save() - def infer_number_of_agents(): + def infer_number_of_agents(self): self.number_of_agents = len(self.agent_list) def validate_availability_of_slots(self): From fe2147a496e6d1117099394fa8e4a73035ae8cab Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 20 Nov 2019 11:37:49 +0530 Subject: [PATCH 149/157] fix travis --- .../appointment_booking_settings.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index 484a5729c5..e817271e2a 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -17,11 +17,8 @@ class AppointmentBookingSettings(Document): self.validate_availability_of_slots() def save(self): - self.infer_number_of_agents() - super().save() - - def infer_number_of_agents(self): self.number_of_agents = len(self.agent_list) + super().save() def validate_availability_of_slots(self): for record in self.availability_of_slots: From 682956543eb12ef8504cea3c9a1fb83c88cab782 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 20 Nov 2019 11:45:14 +0530 Subject: [PATCH 150/157] fix travis --- .../appointment_booking_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 92343dbb13..dbdf432dff 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -101,7 +101,7 @@ } ], "issingle": 1, - "modified": "2019-11-20 10:23:37.393363", + "modified": "2019-11-20 11:44:59.629254", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", From ae90ea9547d934ae6ee596c72bacb5cde41731d0 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 20 Nov 2019 15:24:33 +0530 Subject: [PATCH 151/157] fix:travis errors --- .../appointment_booking_settings.json | 2 +- .../appointment_booking_settings.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index dbdf432dff..17e754b748 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -101,7 +101,7 @@ } ], "issingle": 1, - "modified": "2019-11-20 11:44:59.629254", + "modified": "2019-11-20 15:17:55.617364", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index e817271e2a..82acd93f90 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -10,6 +10,7 @@ from frappe.model.document import Document class AppointmentBookingSettings(Document): + agent_list = [] #Hack min_date = '01/01/1970 ' format_string = "%d/%m/%Y %H:%M:%S" From f2752bf38c20e872b044fbe93d97f09f9fdbed00 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Mon, 25 Nov 2019 14:09:49 +0530 Subject: [PATCH 152/157] fix: tests for python2 --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- .../appointment_booking_settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 72c2ae5ee7..50c98c59de 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -23,7 +23,7 @@ def create_test_lead(): def create_test_appointments(): test_appointment = frappe.db.exists( - {'doctype': 'Appointment', 'email': 'test@example.com'}) + {'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'}) if test_appointment: return frappe.get_doc('Appointment', test_appointment[0][0]) test_appointment = frappe.get_doc({ diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index 82acd93f90..eff8b982c9 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -19,7 +19,7 @@ class AppointmentBookingSettings(Document): def save(self): self.number_of_agents = len(self.agent_list) - super().save() + super(AppointmentBookingSettings,self).save() def validate_availability_of_slots(self): for record in self.availability_of_slots: From 3ec5eabaf64b97f41ba8ebf98e52784c2038f215 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Mon, 25 Nov 2019 14:11:31 +0530 Subject: [PATCH 153/157] formatting --- .../appointment_booking_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py index eff8b982c9..27f14b1dbd 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -19,7 +19,7 @@ class AppointmentBookingSettings(Document): def save(self): self.number_of_agents = len(self.agent_list) - super(AppointmentBookingSettings,self).save() + super(AppointmentBookingSettings, self).save() def validate_availability_of_slots(self): for record in self.availability_of_slots: From f9dec5201fe9fe789dd26ca91498f2b92414967c Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Mon, 25 Nov 2019 16:42:07 +0530 Subject: [PATCH 154/157] fix:tests --- erpnext/crm/doctype/appointment/appointment.py | 2 +- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 91d1c03f7d..b6962d923a 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -28,7 +28,7 @@ class Appointment(Document): number_of_appointments_in_same_slot = frappe.db.count( 'Appointment', filters={'scheduled_time': self.scheduled_time}) number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') - if(number_of_appointments_in_same_slot >= number_of_agents): + if (number_of_appointments_in_same_slot >= number_of_agents): frappe.throw('Time slot is not available') # Link lead if not self.lead: diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 50c98c59de..0dac2bb9ae 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -24,7 +24,7 @@ def create_test_lead(): def create_test_appointments(): test_appointment = frappe.db.exists( {'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'}) - if test_appointment: + if test_appointment[0][0]: return frappe.get_doc('Appointment', test_appointment[0][0]) test_appointment = frappe.get_doc({ 'doctype': 'Appointment', From b84e56ebb55794ce749a362e360e4d338879141f Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Mon, 25 Nov 2019 17:32:02 +0530 Subject: [PATCH 155/157] fix:travis tests --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 0dac2bb9ae..50c98c59de 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -24,7 +24,7 @@ def create_test_lead(): def create_test_appointments(): test_appointment = frappe.db.exists( {'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'}) - if test_appointment[0][0]: + if test_appointment: return frappe.get_doc('Appointment', test_appointment[0][0]) test_appointment = frappe.get_doc({ 'doctype': 'Appointment', From 2515022377ba47109a563b3bb23a8bacbd93f7ce Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 26 Nov 2019 10:55:28 +0530 Subject: [PATCH 156/157] add condition for zero appointment slots --- erpnext/crm/doctype/appointment/appointment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index b6962d923a..2affba2ac4 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -28,8 +28,9 @@ class Appointment(Document): number_of_appointments_in_same_slot = frappe.db.count( 'Appointment', filters={'scheduled_time': self.scheduled_time}) number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') - if (number_of_appointments_in_same_slot >= number_of_agents): - frappe.throw('Time slot is not available') + if not number_of_agents == 0: + if (number_of_appointments_in_same_slot >= number_of_agents): + frappe.throw('Time slot is not available') # Link lead if not self.lead: self.lead = self.find_lead_by_email() From fb1e87710b42821f983abb70659e6ac1a5f79d34 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 26 Nov 2019 12:14:41 +0530 Subject: [PATCH 157/157] Tweaks to success redirect - 5 seconds wait before redirect - Edited description for URL in settings --- .../appointment_booking_settings.json | 4 ++-- erpnext/www/book-appointment/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index 17e754b748..4b26e4901b 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -94,14 +94,14 @@ "label": "Success Settings" }, { - "description": "Leave blank for home.\nThis is relative to site URL, for example \"/about\" will redirect to \"https://yoursitename.com/about\"", + "description": "Leave blank for home.\nThis is relative to site URL, for example \"about\" will redirect to \"https://yoursitename.com/about\"", "fieldname": "success_redirect_url", "fieldtype": "Data", "label": "Success Redirect URL" } ], "issingle": 1, - "modified": "2019-11-20 15:17:55.617364", + "modified": "2019-11-26 12:14:17.669366", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book-appointment/index.js index 433b956014..13c87ddbcf 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book-appointment/index.js @@ -219,7 +219,7 @@ async function submit() { if (window.appointment_settings.success_redirect_url){ redirect_url += window.appointment_settings.success_redirect_url; } - window.location.href = redirect_url;},2) + window.location.href = redirect_url;},5000) }, error: (err)=>{ frappe.show_alert("Something went wrong please try again");