Merge branch 'defer-stop-date' of https://github.com/Zlash65/erpnext into Zlash65-defer-stop-date
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 5fefd6e..619776c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -740,6 +740,39 @@
 	}
 })
 
+frappe.ui.form.on('Sales Invoice Item', {
+	service_stop_date: function(frm, cdt, cdn) {
+		var child = locals[cdt][cdn];
+
+		if(child.service_stop_date) {
+			let start_date = Date.parse(child.service_start_date);
+			let end_date = Date.parse(child.service_end_date);
+			let stop_date = Date.parse(child.service_stop_date);
+
+			if(stop_date < start_date) {
+				frappe.model.set_value(cdt, cdn, "service_stop_date", "");
+				frappe.throw(__("Service Stop Date cannot be before Service Start Date"));
+			} else if (stop_date > end_date) {
+				frappe.model.set_value(cdt, cdn, "service_stop_date", "");
+				frappe.throw(__("Service Stop Date cannot be after Service End Date"));
+			}
+		}
+	},
+	service_start_date: function(frm, cdt, cdn) {
+		var child = locals[cdt][cdn];
+
+		if(child.service_start_date) {
+			frappe.call({
+				"method": "erpnext.stock.get_item_details.calculate_service_end_date",
+				args: {"args": child},
+				callback: function(r) {
+					frappe.model.set_value(cdt, cdn, "service_end_date", r.message.service_end_date);
+				}
+			})
+		}
+	}
+})
+
 var calculate_total_billing_amount =  function(frm) {
 	var doc = frm.doc;
 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index d741215..2907149 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -89,6 +89,9 @@
 			self.update_current_stock()
 			self.validate_delivery_note()
 
+		# validate service stop date to lie in between start and end date
+		self.validate_service_stop_date()
+
 		if not self.is_opening:
 			self.is_opening = 'No'
 
@@ -535,6 +538,23 @@
 				if frappe.db.get_value("Sales Order Item", item.so_detail, "delivered_by_supplier"):
 					frappe.throw(_("Could not update stock, invoice contains drop shipping item."))
 
+	def validate_service_stop_date(self):
+		old_doc = frappe.db.get_all("Sales Invoice Item", {"parent": self.name}, ["name", "service_stop_date"])
+		old_stop_dates = {}
+		for d in old_doc:
+			old_stop_dates[d.name] = d.service_stop_date or ""
+
+		for item in self.items:
+			if item.enable_deferred_revenue:
+				if date_diff(item.service_stop_date, item.service_start_date) < 0:
+					frappe.throw(_("Service Stop Date cannot be before Service Start Date"))
+
+				if date_diff(item.service_stop_date, item.service_end_date) > 0:
+					frappe.throw(_("Service Stop Date cannot be after Service End Date"))
+
+				if old_stop_dates and old_stop_dates[item.name] and item.service_stop_date!=old_stop_dates[item.name]:
+					frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx)))
+
 	def update_current_stock(self):
 		for d in self.get('items'):
 			if d.item_code and d.warehouse:
@@ -1071,7 +1091,7 @@
 			if points_to_redeem < 1: # since points_to_redeem is integer
 				break
 
-	def book_income_for_deferred_revenue(self):
+	def book_income_for_deferred_revenue(self, start_date=None, end_date=None):
 		# book the income on the last day, but it will be trigger on the 1st of month at 12:00 AM
 		# start_date: 1st of the last month or the start date
 		# end_date: end_date or today-1
@@ -1080,13 +1100,30 @@
 		for item in self.get('items'):
 			last_gl_entry = False
 
-			booking_start_date = getdate(add_months(today(), -1))
+			booking_start_date = getdate(add_months(today(), -1)) if not start_date else start_date
 			booking_start_date = booking_start_date if booking_start_date>item.service_start_date else item.service_start_date
 
-			booking_end_date = getdate(add_days(today(), -1))
-			if booking_end_date>=item.service_end_date:
+			if item.service_start_date < booking_start_date:
+				prev_gl_entry = frappe.db.sql('''
+					select name, posting_date from `tabGL Entry` where company=%s and account=%s and
+					voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+					order by posting_date desc limit 1
+				''', (self.company, item.deferred_revenue_account, "Sales Invoice", self.name, item.name), as_dict=True)[0]
+
+				if not prev_gl_entry:
+					booking_start_date = item.service_start_date
+				else:
+					booking_start_date = getdate(add_days(prev_gl_entry.posting_date, 1))
+
+			booking_end_date = getdate(add_days(today(), -1)) if not end_date else end_date
+			if item.service_stop_date and booking_end_date.month > item.service_stop_date.month:
+				continue
+			elif booking_end_date>=item.service_end_date:
 				last_gl_entry = True
 				booking_end_date = item.service_end_date
+			elif item.service_stop_date and item.service_stop_date<=booking_end_date:
+				last_gl_entry = True
+				booking_end_date = item.service_stop_date
 
 			total_days = date_diff(item.service_end_date, item.service_start_date)
 			total_booking_days = date_diff(booking_end_date, booking_start_date) + 1
@@ -1139,17 +1176,17 @@
 			make_gl_entries(gl_entries, cancel=(self.docstatus == 2), merge_entries=True)
 
 
-def booked_deferred_revenue():
+def booked_deferred_revenue(start_date=None, end_date=None):
 	# check for the sales invoice for which GL entries has to be done
 	invoices = frappe.db.sql_list('''
 		select parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s
 		and enable_deferred_revenue = 1 and docstatus = 1
-	''', (today(), add_months(today(), -1)))
+	''', (end_date or today(), start_date or add_months(today(), -1)))
 
 	# ToDo also find the list on the basic of the GL entry, and make another list
 	for invoice in invoices:
 		doc = frappe.get_doc("Sales Invoice", invoice)
-		doc.book_income_for_deferred_revenue()
+		doc.book_income_for_deferred_revenue(start_date, end_date)
 
 
 def validate_inter_company_party(doctype, party, company, inter_company_invoice_reference):
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 70429a4..44d5e77 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -1566,9 +1566,42 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "default": "0", 
-   "fieldname": "enable_deferred_revenue", 
-   "fieldtype": "Check", 
+   "depends_on": "enable_deferred_revenue",
+   "fieldname": "deferred_revenue_account",
+   "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": "Deferred Revenue Account",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Account",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 1,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "depends_on": "enable_deferred_revenue",
+   "fieldname": "service_stop_date",
+   "fieldtype": "Date",
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
@@ -1576,7 +1609,7 @@
    "in_global_search": 0, 
    "in_list_view": 0, 
    "in_standard_filter": 0, 
-   "label": "Enable Deferred Revenue", 
+   "label": "Service Stop Date",
    "length": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
@@ -1598,9 +1631,9 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "depends_on": "enable_deferred_revenue", 
-   "fieldname": "deferred_revenue_account", 
-   "fieldtype": "Link", 
+   "default": "0",
+   "fieldname": "enable_deferred_revenue",
+   "fieldtype": "Check",
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
@@ -1608,10 +1641,9 @@
    "in_global_search": 0, 
    "in_list_view": 0, 
    "in_standard_filter": 0, 
-   "label": "Deferred Revenue Account", 
+   "label": "Enable Deferred Revenue",
    "length": 0, 
    "no_copy": 0, 
-   "options": "Account", 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
@@ -2648,7 +2680,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-08-06 05:18:07.578350", 
+ "modified": "2018-08-29 15:22:58.455304",
  "modified_by": "Administrator", 
  "module": "Accounts", 
  "name": "Sales Invoice Item", 
@@ -2661,5 +2693,6 @@
  "sort_field": "modified", 
  "sort_order": "DESC", 
  "track_changes": 0, 
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
 }
\ No newline at end of file
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 30d9d70..a04d81d 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -274,13 +274,7 @@
 	})
 
 	if item.enable_deferred_revenue:
-		service_end_date = add_months(args.transaction_date, item.no_of_months)
-		out.update({
-			"enable_deferred_revenue": item.enable_deferred_revenue,
-			"deferred_revenue_account": get_default_deferred_revenue_account(args, item),
-			"service_start_date": args.transaction_date,
-			"service_end_date": service_end_date
-		})
+		out.update(calculate_service_end_date(args, item))
 
 	# calculate conversion factor
 	if item.stock_uom == args.uom:
@@ -310,6 +304,22 @@
 
 	return out
 
+@frappe.whitelist()
+def calculate_service_end_date(args, item=None):
+	args = process_args(args)
+	if not item:
+		item = frappe.get_cached_doc("Item", args.item_code)
+
+	service_start_date = args.service_start_date if args.service_start_date else args.transaction_date
+	service_end_date = add_months(service_start_date, item.no_of_months)
+	deferred_detail = {
+		"enable_deferred_revenue": item.enable_deferred_revenue,
+		"deferred_revenue_account": get_default_deferred_revenue_account(args, item),
+		"service_start_date": service_start_date,
+		"service_end_date": service_end_date
+	}
+
+	return deferred_detail
 
 def get_default_income_account(args, item, item_group):
 	return (item.get("income_account")