[ Major ] Loyalty Program Fixes (#14863)

* fix checking of customer_group & territory of customer in loyalty program

* fetch and set applicable loyalty program
- in customer.py if found 1 program, set it or show a message to set it manually
- in sales invoice, if found 1 program for selected customer with no program set, set it else open a dialog with applicable options

* removed disabled field, added from_date & to_date

* loyalty program section made collapsible, added redeem check in it

* setting loyalty program improvised, manual selection if multiple found

* get_query added, amount calculation updated

* args passed rectified for expiry_date

* get loyalty_points logic improv, redemption_details logic added

* improv based on from/to date and other rectification

* code rectified based on different scenarios
- is_return, cancel, make loyalty points entry improv
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
index 37fce0b..f0dd887 100644
--- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
@@ -13,15 +13,22 @@
 	pass
 
 
-def get_loyalty_point_entries(customer, loyalty_program, expiry_date=None, company=None):
+def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None):
 	if not expiry_date:
 		date = today()
-	args_list = [customer, loyalty_program, expiry_date]
-	condition = ''
-	if company:
-		condition = " and company=%s "
-		args_list.append(company)
-	loyalty_point_details = frappe.db.sql('''select name, loyalty_points, expiry_date, loyalty_program_tier
-		from `tabLoyalty Point Entry` where customer=%s and loyalty_program=%s and expiry_date>=%s and loyalty_points>0
-		{condition} order by expiry_date'''.format(condition=condition), tuple(args_list), as_dict=1)
-	return loyalty_point_details
+
+	return frappe.db.sql('''
+		select name, loyalty_points, expiry_date, loyalty_program_tier
+		from `tabLoyalty Point Entry`
+		where customer=%s and loyalty_program=%s
+			and expiry_date>=%s and loyalty_points>0 and company=%s
+		order by expiry_date
+	''', (customer, loyalty_program, expiry_date, company), as_dict=1)
+
+def get_redemption_details(customer, loyalty_program, company):
+	return frappe._dict(frappe.db.sql('''
+		select redeem_against, sum(loyalty_points)
+		from `tabLoyalty Point Entry`
+		where customer=%s and loyalty_program=%s and loyalty_points<0 and company=%s
+		group by redeem_against
+	''', (customer, loyalty_program, company)))
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.json b/erpnext/accounts/doctype/loyalty_program/loyalty_program.json
index 4536a7a..a553faa 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.json
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.json
@@ -15,128 +15,7 @@
  "fields": [
   {
    "allow_bulk_edit": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "disabled",
-   "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": "Disabled",
-   "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
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "column_break_2",
-   "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
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "auto_opt_in",
-   "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": "Auto Opt In (For all customers)",
-   "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
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "section_break_2",
-   "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,
-   "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
-  },
-  {
-   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -164,10 +43,11 @@
    "search_index": 0,
    "set_only_once": 0,
    "translatable": 0,
-   "unique": 0
+   "unique": 1
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -200,6 +80,72 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "description": "",
+   "fieldname": "from_date",
+   "fieldtype": "Date",
+   "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 Date",
+   "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": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "to_date",
+   "fieldtype": "Date",
+   "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": "To Date",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -230,6 +176,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -262,6 +209,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -294,6 +242,39 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "auto_opt_in",
+   "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": "Auto Opt In (For all customers)",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -325,6 +306,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -357,6 +339,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -388,6 +371,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -420,6 +404,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -451,6 +436,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -481,6 +467,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -513,6 +500,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -545,6 +533,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -577,6 +566,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -608,6 +598,7 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
@@ -648,7 +639,7 @@
  "issingle": 0,
  "istable": 0,
  "max_attachments": 0,
- "modified": "2018-03-21 10:20:25.468206",
+ "modified": "2018-07-10 19:15:24.994385",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Loyalty Program",
@@ -657,7 +648,6 @@
  "permissions": [
   {
    "amend": 0,
-   "apply_user_permissions": 0,
    "cancel": 0,
    "create": 1,
    "delete": 1,
@@ -683,5 +673,6 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
index 593e426..96eda7f 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
@@ -16,14 +16,17 @@
 def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=None):
 	if not expiry_date:
 		expiry_date = today()
-	args_list = [customer, loyalty_program, expiry_date]
+
+	args_list = [customer, loyalty_program, expiry_date, expiry_date]
 	condition = ''
 	if company:
 		condition = " and company=%s "
 		args_list.append(company)
 	loyalty_point_details = frappe.db.sql('''select sum(loyalty_points) as loyalty_points,
 		sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
-		where customer=%s and loyalty_program=%s and (expiry_date>=%s) {condition}
+		where customer=%s and loyalty_program=%s
+		and expiry_date>=%s and posting_date <= %s
+		{condition}
 		group by customer'''.format(condition=condition), tuple(args_list), as_dict=1)
 	if loyalty_point_details:
 		return loyalty_point_details[0]
@@ -33,35 +36,28 @@
 @frappe.whitelist()
 def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None, company=None, silent=False):
 	lp_details = frappe._dict()
-	customer_loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")
-
-	if not (customer_loyalty_program or silent):
-		frappe.throw(_("Customer isn't enrolled in any Loyalty Program"))
-	elif silent and not customer_loyalty_program:
-		return frappe._dict({"loyalty_program": None})
-
-	if loyalty_program and loyalty_program != customer_loyalty_program:
-		frappe.throw(_("Customer isn't enrolled in this Loyalty Program"))
 
 	if not loyalty_program:
-		loyalty_program = customer_loyalty_program
+		loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")
+
+		if not (loyalty_program or silent):
+			frappe.throw(_("Customer isn't enrolled in any Loyalty Program"))
+		elif silent and not loyalty_program:
+			return frappe._dict({"loyalty_program": None})
+
 	if not company:
 		company = frappe.db.get_default("company") or frappe.get_all("Company")[0].name
 
-	lp_details.update(get_loyalty_details(customer, loyalty_program, expiry_date, company))
+	loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
+	lp_details.update({"loyalty_program": loyalty_program.name})
+	lp_details.update(loyalty_program.as_dict())
 
-	lp_details.update({"loyalty_program": loyalty_program})
-	loyalty_program = frappe.get_doc("Loyalty Program", lp_details.loyalty_program)
+	lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company))
 
-	lp_details.expiry_duration = loyalty_program.expiry_duration
-	lp_details.conversion_factor = loyalty_program.conversion_factor
-	lp_details.expense_account = loyalty_program.expense_account
-	lp_details.cost_center = loyalty_program.cost_center
-	lp_details.company = loyalty_program.company
-
-	tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules], key=lambda rule:rule.min_spent, reverse=True)
+	tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
+		key=lambda rule:rule.min_spent, reverse=True)
 	for i, d in enumerate(tier_spent_level):
-		if i==0 or lp_details.total_spent < d.min_spent:
+		if i == 0 or lp_details.total_spent < d.min_spent:
 			lp_details.tier_name = d.tier_name
 			lp_details.collection_factor = d.collection_factor
 		else:
@@ -122,4 +118,4 @@
 				ref_doc.loyalty_redemption_cost_center = loyalty_program_details.cost_center
 
 		elif ref_doc.doctype == "Sales Order":
-			return loyalty_amount
\ No newline at end of file
+			return loyalty_amount
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 5f04f8b..d3efb70 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -230,6 +230,20 @@
 			}, function() {
 				me.apply_pricing_rule();
 			});
+
+		if(this.frm.doc.customer) {
+			frappe.call({
+				"method": "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_loyalty_programs",
+				"args": {
+					"customer": this.frm.doc.customer
+				},
+				callback: function(r) {
+					if(r.message) {
+						select_loyalty_program(me.frm, r.message);
+					}
+				}
+			});
+		}
 	},
 
 	make_inter_company_invoice: function() {
@@ -530,10 +544,6 @@
 });
 
 frappe.ui.form.on('Sales Invoice', {
-	refresh: function(frm) {
-		frm.add_fetch('customer', 'loyalty_program', 'loyalty_program');
-	},
-
 	setup: function(frm){
 		
 		frm.custom_make_buttons = {
@@ -598,6 +608,24 @@
 				}
 			};
 		});
+
+		// set get_query for loyalty redemption account
+		frm.fields_dict["loyalty_redemption_account"].get_query = function() {
+			return {
+				filters:{
+					"company": frm.doc.company
+				}
+			}
+		};
+
+		// set get_query for loyalty redemption cost center
+		frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() {
+			return {
+				filters:{
+					"company": frm.doc.company
+				}
+			}
+		};
 	},
 	//When multiple companies are set up. in case company name is changed set default company address
 	company:function(frm){
@@ -661,18 +689,15 @@
 				method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
 				args: {
 					"customer": frm.doc.customer,
-					"till_date": frm.doc.posting_date,
+					"loyalty_program": frm.doc.loyalty_program,
+					"expiry_date": frm.doc.posting_date,
 					"company": frm.doc.company
 				},
 				callback: function(r) {
 					if (r) {
-						frm.set_value("loyalty_program", r.message.loyalty_program);
 						frm.set_value("loyalty_redemption_account", r.message.expense_account);
 						frm.set_value("loyalty_redemption_cost_center", r.message.cost_center);
 						frm.redemption_conversion_factor = r.message.conversion_factor;
-						// let max_loyalty_points = parseInt((frm.doc.grand_total-frm.doc.total_advance)/r.message.conversion_factor);
-						// let redeemable_points = max_loyalty_points > r.message.loyalty_points ? r.message.loyalty_points : max_loyalty_points;
-						// frm.set_value("loyalty_points", redeemable_points);
 					}
 				}
 			});
@@ -682,10 +707,10 @@
 	set_loyalty_points: function(frm) {
 		if (frm.redemption_conversion_factor) {
 			let loyalty_amount = flt(frm.redemption_conversion_factor*flt(frm.doc.loyalty_points), precision("loyalty_amount"));
-			var remaining_amount = flt(frm.doc.grand_total - frm.doc.total_advance)
+			var remaining_amount = flt(frm.doc.grand_total) - flt(frm.doc.total_advance) - flt(frm.doc.write_off_amount);
 			if (frm.doc.grand_total && (remaining_amount < loyalty_amount)) {
-				let redeemable_amount = parseInt(remaining_amount/frm.redemption_conversion_factor);
-				frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_amount]));
+				let redeemable_points = parseInt(remaining_amount/frm.redemption_conversion_factor);
+				frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_points]));
 			}
 			frm.set_value("loyalty_amount", loyalty_amount);
 		}
@@ -729,3 +754,34 @@
 
 	refresh_field('total_billing_amount')
 }
+
+var select_loyalty_program = function(frm, loyalty_programs) {
+	var dialog = new frappe.ui.Dialog({
+		title: __("Select Loyalty Program"),
+		fields: [
+			{
+				"label": __("Loyalty Program"),
+				"fieldname": "loyalty_program",
+				"fieldtype": "Select",
+				"options": loyalty_programs,
+				"default": loyalty_programs[0]
+			}
+		]
+	});
+
+	dialog.set_primary_action(__("Set"), function() {
+		dialog.hide();
+		return frappe.call({
+			method: "frappe.client.set_value",
+			args: {
+				doctype: "Customer",
+				name: frm.doc.customer,
+				fieldname: "loyalty_program",
+				value: dialog.get_value("loyalty_program"),
+			},
+			callback: function(r) { }
+		});
+	});
+
+	dialog.show();
+}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 55921c3..7a9e7d0 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -388,38 +388,6 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "redeem_loyalty_points", 
-   "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": "Redeem Loyalty Points", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
    "fieldname": "column_break1", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -2460,10 +2428,10 @@
    "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
-   "collapsible": 0, 
+   "collapsible": 1,
    "collapsible_depends_on": "", 
    "columns": 0, 
-   "depends_on": "redeem_loyalty_points", 
+   "depends_on": "",
    "fieldname": "loyalty_points_redemption", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -2496,6 +2464,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "redeem_loyalty_points",
    "fieldname": "loyalty_points", 
    "fieldtype": "Int", 
    "hidden": 0, 
@@ -2528,6 +2497,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "redeem_loyalty_points",
    "fieldname": "loyalty_amount", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -2560,6 +2530,38 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "redeem_loyalty_points",
+   "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": "Redeem Loyalty Points",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
    "fieldname": "column_break_77", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -2591,6 +2593,8 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "",
+   "fetch_from": "customer.loyalty_program",
    "fieldname": "loyalty_program", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -2624,6 +2628,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "redeem_loyalty_points",
    "fieldname": "loyalty_redemption_account", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -2657,6 +2662,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "redeem_loyalty_points",
    "fieldname": "loyalty_redemption_cost_center", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -5376,7 +5382,7 @@
  "istable": 0, 
  "max_attachments": 0, 
  "menu_index": 0, 
- "modified": "2018-07-06 18:15:09.600814", 
+ "modified": "2018-07-10 19:28:04.351108",
  "modified_by": "Administrator", 
  "module": "Accounts", 
  "name": "Sales Invoice", 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 92c6ec4..f645983 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -163,6 +163,7 @@
 		elif self.is_return and self.return_against and self.loyalty_program:
 			against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
 			against_si_doc.delete_loyalty_point_entry()
+			against_si_doc.make_loyalty_point_entry()
 		if self.redeem_loyalty_points and self.loyalty_points:
 			self.apply_loyalty_points()
 
@@ -209,6 +210,7 @@
 			self.delete_loyalty_point_entry()
 		elif self.is_return and self.return_against and self.loyalty_program:
 			against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
+			against_si_doc.delete_loyalty_point_entry()
 			against_si_doc.make_loyalty_point_entry()
 
 		unlink_inter_company_invoice(self.doctype, self.name, self.inter_company_invoice_reference)
@@ -967,24 +969,28 @@
 
 	# collection of the loyalty points, create the ledger entry for that.
 	def make_loyalty_point_entry(self):
-		loyalty_program_details = get_loyalty_program_details(self.customer, company=self.company)
-		if loyalty_program_details:
-			points_earned = int(self.grand_total/loyalty_program_details.collection_factor)
+		lp_details = get_loyalty_program_details(self.customer, company=self.company,
+			loyalty_program=self.loyalty_program, expiry_date=self.posting_date)
+		if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
+			(not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
+			returned_amount = self.get_returned_amount()
+			eligible_amount = flt(self.grand_total) - cint(self.loyalty_amount) - returned_amount
+			points_earned = cint(eligible_amount/lp_details.collection_factor)
 			doc = frappe.get_doc({
 				"doctype": "Loyalty Point Entry",
 				"company": self.company,
-				"loyalty_program": loyalty_program_details.loyalty_program,
-				"loyalty_program_tier": loyalty_program_details.tier_name,
+				"loyalty_program": lp_details.loyalty_program,
+				"loyalty_program_tier": lp_details.tier_name,
 				"customer": self.customer,
 				"sales_invoice": self.name,
 				"loyalty_points": points_earned,
-				"purchase_amount": self.grand_total,
-				"expiry_date": add_days(self.posting_date, loyalty_program_details.expiry_duration),
+				"purchase_amount": eligible_amount,
+				"expiry_date": add_days(self.posting_date, lp_details.expiry_duration),
 				"posting_date": self.posting_date
 			})
 			doc.flags.ignore_permissions = 1
 			doc.save()
-			# frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", loyalty_program_details.tier_name)
+			frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
 
 	# valdite the redemption and then delete the loyalty points earned on cancel of the invoice
 	def delete_loyalty_point_entry(self):
@@ -998,19 +1004,33 @@
 				First cancel the Sales Invoice No {0}''').format(invoice_list))
 		else:
 			frappe.db.sql('''delete from `tabLoyalty Point Entry` where sales_invoice=%s''', (self.name))
+			# Set loyalty program
+			lp_details = get_loyalty_program_details(self.customer, company=self.company,
+				loyalty_program=self.loyalty_program, expiry_date=self.posting_date)
+			frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
+
+	def get_returned_amount(self):
+		returned_amount = frappe.db.sql("""
+			select sum(grand_total)
+			from `tabSales Invoice`
+			where docstatus=1 and is_return=1 and ifnull(return_against, '')=%s
+		""", self.name)
+		return flt(returned_amount[0][0]) if returned_amount else 0
 
 	# redeem the loyalty points.
 	def apply_loyalty_points(self):
 		from erpnext.accounts.doctype.loyalty_point_entry.loyalty_point_entry \
-			import get_loyalty_point_entries
-		loyalty_point_entries = get_loyalty_point_entries(self.customer, self.loyalty_program, self.posting_date, self.company)
+			import get_loyalty_point_entries, get_redemption_details
+		loyalty_point_entries = get_loyalty_point_entries(self.customer, self.loyalty_program, self.company, self.posting_date)
+		redemption_details = get_redemption_details(self.customer, self.loyalty_program, self.company)
 
-		points_to_redeem = self.loyalty_amount
+		points_to_redeem = self.loyalty_points
 		for lp_entry in loyalty_point_entries:
-			if lp_entry.loyalty_points > points_to_redeem:
+			available_points = lp_entry.loyalty_points - redemption_details.get(lp_entry.name)
+			if available_points > points_to_redeem:
 				redeemed_points = points_to_redeem
 			else:
-				redeemed_points = lp_entry.loyalty_points
+				redeemed_points = available_points
 			doc = frappe.get_doc({
 				"doctype": "Loyalty Point Entry",
 				"company": self.company,
@@ -1019,7 +1039,7 @@
 				"customer": self.customer,
 				"sales_invoice": self.name,
 				"redeem_against": lp_entry.name,
-				"loyalty_points": -(redeemed_points),
+				"loyalty_points": -1*redeemed_points,
 				"purchase_amount": self.grand_total,
 				"expiry_date": lp_entry.expiry_date,
 				"posting_date": self.posting_date
@@ -1312,3 +1332,19 @@
 	}, target_doc, set_missing_values)
 
 	return doclist
+
+@frappe.whitelist()
+def get_loyalty_programs(customer):
+	''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
+	from erpnext.selling.doctype.customer.customer import get_loyalty_programs
+
+	customer = frappe.get_doc('Customer', customer)
+	if customer.loyalty_program: return
+
+	lp_details = get_loyalty_programs(customer)
+
+	if len(lp_details) == 1:
+		frappe.db.set(customer, 'loyalty_program', lp_details[0])
+		return []
+	else:
+		return lp_details
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 1e63e59..595180b 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -6,7 +6,7 @@
 from frappe.model.naming import set_name_by_naming_series
 from frappe import _, msgprint, throw
 import frappe.defaults
-from frappe.utils import flt, cint, cstr
+from frappe.utils import flt, cint, cstr, today
 from frappe.desk.reportview import build_match_conditions, get_filters_cond
 from erpnext.utilities.transaction_base import TransactionBase
 from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
@@ -188,16 +188,33 @@
 			frappe.db.set(self, "customer_name", newdn)
 
 	def set_loyalty_program(self):
-		if not self.loyalty_program:
-			loyalty_programs = frappe.get_all("Loyalty Program", fields=["name", "customer_group",
-				"customer_territory"], filters={"auto_opt_in": 1, "disabled": 0})
-			from frappe.desk.treeview import get_children
-			for loyalty_program in loyalty_programs:
-				customer_groups = get_children("Customer Group", loyalty_program.customer_group, )
-				if self.customer_group in customer_groups and\
-					self.territory in get_children("Territory", loyalty_program.customer_territory):
-					self.loyalty_program = loyalty_program.name
+		if self.loyalty_program: return
+		loyalty_program = get_loyalty_programs(self)
+		if not loyalty_program: return
+		if len(loyalty_program) == 1:
+			self.loyalty_program = loyalty_program[0]
+		else:
+			frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
 
+@frappe.whitelist()
+def get_loyalty_programs(doc):
+	''' returns applicable loyalty programs for a customer '''
+	from frappe.desk.treeview import get_children
+
+	lp_details = []
+	loyalty_programs = frappe.get_all("Loyalty Program",
+		fields=["name", "customer_group", "customer_territory"],
+		filters={"auto_opt_in": 1, "from_date": ["<=", today()],
+			"ifnull(to_date, '2500-01-01')": [">=", today()]})
+
+	for loyalty_program in loyalty_programs:
+		customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)]
+		customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)]
+		if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
+			and (not loyalty_program.customer_territory or doc.territory in customer_territories):
+			lp_details.append(loyalty_program.name)
+
+	return lp_details
 
 def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
 	if frappe.db.get_default("cust_master_name") == "Customer Name":
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index 294b2ba..d75bf61 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -129,7 +129,7 @@
 							method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
 							args: {
 								"customer": me.frm.doc.customer,
-								"till_date": me.frm.doc.posting_date,
+								"expiry_date": me.frm.doc.posting_date,
 								"company": me.frm.doc.company,
 								"silent": true
 							},