mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 03:09:09 +00:00
Converted to an SPA
This commit is contained in:
@@ -1,38 +0,0 @@
|
|||||||
{% extends "templates/web.html" %}
|
|
||||||
|
|
||||||
{% block title %}{{ _("Book Appointment") }}{% endblock %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
|
||||||
<div class="container">
|
|
||||||
<!-- title: Book an appointment -->
|
|
||||||
<div class="text-center mb-5">
|
|
||||||
<h3>Book an appointment</h3>
|
|
||||||
<h4>Select the date and your timezone</h4>
|
|
||||||
</div>
|
|
||||||
<div class="row justify-content-center mt-3">
|
|
||||||
<div class="col-md-4 align-self-center ">
|
|
||||||
<form name="myform">
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
onchange="ondatechange()"
|
|
||||||
name="appointment-date"
|
|
||||||
id="appointment-date"
|
|
||||||
class="form-control mt-3"
|
|
||||||
min="{{ from_date }}"
|
|
||||||
max="{{ to_date }}">
|
|
||||||
<select name="appointment-timezone" id="appointment-timezone" class="form-control mt-3">
|
|
||||||
{% if timezones %}
|
|
||||||
{% for timezone in timezones%}
|
|
||||||
<option value="{{timezone.offset}}">{{timezone.timezone_name}}</option>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
</form>
|
|
||||||
<button class="form-control mt-3 btn btn-dark" id="next-button" onclick="next()">
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
{% extends "templates/web.html" %}
|
|
||||||
|
|
||||||
{% block title %}{{ _("Book Appointment") }}{% endblock %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
|
||||||
<style>
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="container">
|
|
||||||
<div class="text-center mb-5">
|
|
||||||
{% if is_holiday %}
|
|
||||||
<h3> This day is a holiday</h3>
|
|
||||||
{% else %}
|
|
||||||
<h3>Pick A Time Slot</h3>
|
|
||||||
<h4>Selected date is {{ date }}</h4>
|
|
||||||
</div>
|
|
||||||
<!-- Start of main content-->
|
|
||||||
|
|
||||||
<div class="mt-3 justify-content-center">
|
|
||||||
<div class="row">
|
|
||||||
{% for timeslot in timeslots %}
|
|
||||||
<div class="col-md time-slot {% if timeslot.unavailable %}unavailable{% endif %}" id="{{ timeslot.time.time() }}">{{ timeslot.time.time().strftime('%H : %M') }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-4 align-self-center">
|
|
||||||
<button class="form-control mt-5 btn btn-dark" onclick="next()">
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End of main content -->
|
|
||||||
{% endblock %}
|
|
||||||
@@ -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}`;
|
|
||||||
}
|
|
||||||
@@ -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"]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{% extends "templates/web.html" %}
|
|
||||||
|
|
||||||
{% block title %}{{ _("Book Appointment") }}{% endblock %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<div class="text-center mb-5">
|
|
||||||
<h3>Add details</h3>
|
|
||||||
<h4>Selected date is {{ date }} at {{ time }}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="row justify-content-center mt-3">
|
|
||||||
<div class="col-md-4 align-items-center">
|
|
||||||
<input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name" required>
|
|
||||||
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number" placeholder="Contact Number" required>
|
|
||||||
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype" required>
|
|
||||||
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10" placeholder="Notes"></textarea>
|
|
||||||
<button class="btn btn-primary form-control mt-3" onclick="submit()">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -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});
|
|
||||||
}
|
|
||||||
25
erpnext/www/book-appointment/index.css
Normal file
25
erpnext/www/book-appointment/index.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
70
erpnext/www/book-appointment/index.html
Normal file
70
erpnext/www/book-appointment/index.html
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{% extends "templates/web.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ _("Book Appointment") }}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="container">
|
||||||
|
<!-- title: Book an appointment -->
|
||||||
|
<div id="select-date">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h3>Book an appointment</h3>
|
||||||
|
<p class="lead">Select the date and your timezone</p>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center mt-3">
|
||||||
|
<div class="col-md-4 align-self-center ">
|
||||||
|
<form name="myform">
|
||||||
|
<input type="date" onchange="validate_date()" name="appointment-date" id="appointment-date"
|
||||||
|
class="form-control mt-3">
|
||||||
|
<select name="appointment-timezone" id="appointment-timezone" class="form-control mt-3">
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
<button class="form-control mt-3 btn btn-dark" id="next-button" onclick="navigate_to_time_select()">
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--Select Time Slot-->
|
||||||
|
<div id="select-time">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h3>Pick A Time Slot</h3>
|
||||||
|
<p class="lead">Selected date is <span class="date-span">Date Span</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 justify-content-center">
|
||||||
|
<div class="row" id="timeslot-container">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-4 align-self-center">
|
||||||
|
<button class="form-control mt-5 btn btn-dark" onclick="initialise_enter_details()">
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--Enter Details-->
|
||||||
|
<div id="enter-details">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h3>Add details</h3>
|
||||||
|
<p class="lead">Selected date is <span class="date-span">Date Span</span> at <span class="time-span"> time </span></p>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center mt-3">
|
||||||
|
<div class="col-md-4 align-items-center">
|
||||||
|
<input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name"
|
||||||
|
required>
|
||||||
|
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number"
|
||||||
|
placeholder="Contact Number" required>
|
||||||
|
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype"
|
||||||
|
required>
|
||||||
|
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
|
||||||
|
placeholder="Notes"></textarea>
|
||||||
|
<button class="btn btn-primary form-control mt-3" onclick="submit()">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
146
erpnext/www/book-appointment/index.js
Normal file
146
erpnext/www/book-appointment/index.js
Normal file
@@ -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 });
|
||||||
|
}
|
||||||
123
erpnext/www/book-appointment/index.py
Normal file
123
erpnext/www/book-appointment/index.py
Normal file
@@ -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})<settings.number_of_agents
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _is_holiday(date, holiday_list):
|
||||||
|
for holiday in holiday_list.holidays:
|
||||||
|
if holiday.holiday_date == date:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_records(start_time, end_time, settings):
|
||||||
|
records = []
|
||||||
|
for record in settings.availability_of_slots:
|
||||||
|
if record.day_of_week == WEEKDAYS[start_time.weekday()] or record.day_of_week == WEEKDAYS[end_time.weekday()]:
|
||||||
|
records.append(record)
|
||||||
|
return records
|
||||||
|
|
||||||
|
def _deltatime_to_datetime(date, deltatime):
|
||||||
|
time = (datetime.datetime.min + deltatime).time()
|
||||||
|
return datetime.datetime.combine(date.date(), time)
|
||||||
|
|
||||||
|
def _datetime_to_deltatime(date_time):
|
||||||
|
midnight = datetime.datetime.combine(date_time.date(),datetime.time.min)
|
||||||
|
return (date_time-midnight)
|
||||||
|
|
||||||
|
def _convert_to_ist(datetime_object, timezone):
|
||||||
|
offset = datetime.timedelta(minutes=timezone)
|
||||||
|
datetime_object = datetime_object + offset
|
||||||
|
offset = datetime.timedelta(minutes=-330)
|
||||||
|
datetime_object = datetime_object - offset
|
||||||
|
return datetime_object
|
||||||
|
|
||||||
|
def _convert_to_tz(datetime_object, timezone):
|
||||||
|
offset = datetime.timedelta(minutes=timezone)
|
||||||
|
datetime_object = datetime_object - offset
|
||||||
|
offset = datetime.timedelta(minutes=-330)
|
||||||
|
datetime_object = datetime_object + offset
|
||||||
|
return datetime_object
|
||||||
|
|
||||||
|
WEEKDAYS = ["Monday", "Tuesday", "Wednesday",
|
||||||
|
"Thursday", "Friday", "Saturday", "Sunday"]
|
||||||
Reference in New Issue
Block a user