refactor: migrated calculation and validation logic in js to py
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
index 3c05526..79167ae 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
@@ -73,31 +73,11 @@
 			}
 			if (flag) {
 				this.frm.add_custom_button(__('Create Maintenance Visit'), function () {
-					let items = me.frm.doc.items;
-					let s = me.frm.doc.schedules;
 					let options = "";
-					let dates = "";
-					for (let i in items) {
-						for (let d in s) {
-							if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") {
-								options = options + '\n' + items[i].item_name;
-								break;
-							}
-						}
-					}
-					function formatDate(date) {
-						var d = new Date(date),
-							month = '' + (d.getMonth() + 1),
-							day = '' + d.getDate(),
-							year = d.getFullYear();
-
-						if (month.length < 2)
-							month = '0' + month;
-						if (day.length < 2)
-							day = '0' + day;
-
-						return [day, month, year].join('-');
-					}
+					
+					me.frm.call('get_pending_data',{data_type:"items"}).then(r =>{
+						options = r.message
+						
 					var schedule_id = "";
 					var d = new frappe.ui.Dialog({
 						title: __("Enter Visit Details"),
@@ -109,30 +89,23 @@
 							reqd: 1,
 							onchange: function () {
 								let field = d.get_field("scheduled_date");
-								dates = "";
-								for (let i in s) {
-									if (s[i].item_name == this.value && s[i].completion_status == "Pending") {
-										dates = dates + '\n' + formatDate(s[i].scheduled_date);
-									}
-
-								}
-								field.df.options = dates;
-								field.refresh();
+								me.frm.call('get_pending_data',{item_name:this.value,data_type:"date"}).then(r =>{
+									field.df.options = r.message;
+									field.refresh();
+								})
 							}
 						},
 						{
 							label: __('Scheduled Date'),
 							fieldname: 'scheduled_date',
 							fieldtype: 'Select',
-							options: dates,
+							options: "",
 							reqd: 1,
 							onchange: function () {
 								let field = d.get_field('item_name');
-								for (let i in s) {
-									if (s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value) {
-										schedule_id = s[i].name;
-									}
-								}
+								me.frm.call('get_pending_data',{item_name:field.value,s_date:this.value,data_type:"id"}).then(r =>{
+									schedule_id = r.message;
+								})
 							}
 						},
 						],
@@ -159,7 +132,7 @@
 						}
 					});
 					d.show();
-
+				});
 				}, __('Create'));
 			}
 		}
@@ -185,35 +158,8 @@
 		var item = frappe.get_doc(cdt, cdn);
 
 		if (item.start_date && item.periodicity) {
-
-			var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1;
-
-			var days_in_period = {
-				"Weekly": 7,
-				"Monthly": 30,
-				"Quarterly": 91,
-				"Half Yearly": 182,
-				"Yearly": 365
-			}
-
-			var no_of_visits = cint(date_diff / days_in_period[item.periodicity]);
-			if (no_of_visits == 0 || !no_of_visits) {
-
-				let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]);
-				frappe.model.set_value(item.doctype, item.name, "end_date", end_date);
-				date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1;
-				no_of_visits = cint(date_diff / days_in_period[item.periodicity]);
-				frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits);
-
-			} else if (item.no_of_visits > no_of_visits) {
-				let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]);
-				frappe.model.set_value(item.doctype, item.name, "end_date", end_date);
-
-			} else if (item.no_of_visits < no_of_visits) {
-				let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]);
-				frappe.model.set_value(item.doctype, item.name, "end_date", end_date);
-
-			}
+			me.frm.call('validate_end_date_visits')
+			
 		}
 	},
 });
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index b89d540..d11bf7e 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -4,7 +4,7 @@
 from __future__ import unicode_literals
 import frappe
 
-from frappe.utils import add_days, getdate, cint, cstr
+from frappe.utils import add_days, getdate, cint, cstr, date_diff, formatdate
 
 from frappe import throw, _
 from erpnext.utilities.transaction_base import TransactionBase, delete_events
@@ -36,6 +36,39 @@
 				child.completion_status = "Pending"
 				child.item_ref = d.name
 
+	@frappe.whitelist()
+	def validate_end_date_visits(self):
+		days_in_period = {
+			"Weekly": 7,
+			"Monthly": 30,
+			"Quarterly": 91,
+			"Half Yearly": 182,
+			"Yearly": 365
+		}
+		for i in self.items:
+			
+			if i.periodicity and i.start_date:
+				if not i.end_date:
+					if i.no_of_visits:
+						i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity])
+					else:
+						i.end_date = add_days(i.start_date, days_in_period[i.periodicity])
+						
+				diff = date_diff(i.end_date, i.start_date) + 1
+				no_of_visits = cint(diff / days_in_period[i.periodicity])
+				
+				if not i.no_of_visits or i.no_of_visits == 0:
+					i.end_date = add_days(i.start_date, days_in_period[i.periodicity])
+					diff = date_diff(i.end_date, i.start_date ) + 1
+					i.no_of_visits = cint(diff / days_in_period[i.periodicity])
+					
+				elif i.no_of_visits > no_of_visits:
+					i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity])
+
+				elif i.no_of_visits < no_of_visits:
+					i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity])
+
+
 	def on_submit(self):
 		if not self.get('schedules'):
 			throw(_("Please click on 'Generate Schedule' to get schedule"))
@@ -166,6 +199,7 @@
 					throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order))
 
 	def validate(self):
+		self.validate_end_date_visits()
 		self.validate_maintenance_detail()
 		self.validate_dates_with_periodicity()
 		self.validate_sales_order()
@@ -246,6 +280,30 @@
 	def on_trash(self):
 		delete_events(self.doctype, self.name)
 
+	
+	@frappe.whitelist()
+	def get_pending_data(self,data_type,s_date = None, item_name = None):
+		if data_type == "date":
+			dates = ""
+			for i in self.schedules:
+				if i.item_name == item_name and i.completion_status == "Pending":
+					dates = dates + "\n" + formatdate(i.scheduled_date, "dd-MM-yyyy")
+			return dates
+		elif data_type == "items":
+			items = ""
+			for i in self.items:
+				for s in self.schedules:
+					if i.item_name == s.item_name and s.completion_status == "Pending":
+						items = items + "\n" + i.item_name
+						break
+			return items
+		elif data_type == "id":
+			for s in self.schedules:
+				if s.item_name == item_name and s_date == formatdate(s.scheduled_date,"dd-mm-yyyy"):
+					return s.name
+				
+					
+
 @frappe.whitelist()
 def update_serial_nos(s_id):
 	serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no')