Merge pull request #6657 from rohitwaghchaure/invoice_unlink_feature

[Feature] Provision to allow unlink the payment against the invoice
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index c5b3e5b..c389b5c 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -10,6 +10,7 @@
  "doctype": "DocType", 
  "document_type": "Other", 
  "editable_grid": 1, 
+ "engine": "InnoDB", 
  "fields": [
   {
    "allow_on_submit": 0, 
@@ -194,6 +195,33 @@
    "search_index": 0, 
    "set_only_once": 0, 
    "unique": 0
+  }, 
+  {
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "default": "1", 
+   "fieldname": "unlink_payment_on_cancellation_of_invoice", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_list_view": 0, 
+   "label": "Unlink Payment on Cancellation of Invoice", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
   }
  ], 
  "hide_heading": 0, 
@@ -207,8 +235,8 @@
  "issingle": 1, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2016-10-05 16:13:10.978208", 
- "modified_by": "rohitw1991@gmail.com", 
+ "modified": "2016-10-20 16:12:38.595075", 
+ "modified_by": "Administrator", 
  "module": "Accounts", 
  "name": "Accounts Settings", 
  "owner": "Administrator", 
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 042af0b..1c9bcbc 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -578,7 +578,8 @@
 
 		if not self.is_return:
 			from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
-			unlink_ref_doc_from_payment_entries(self.doctype, self.name)
+			if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
+				unlink_ref_doc_from_payment_entries(self.doctype, self.name)
 
 			self.update_prevdoc_status()
 			self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 4ea18ac..b4b8444 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -6,7 +6,7 @@
 import unittest
 import frappe
 import frappe.model
-from frappe.utils import cint, flt, today
+from frappe.utils import cint, flt, today, nowdate
 import frappe.defaults
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
 	test_records as pr_test_records
@@ -17,6 +17,12 @@
 test_ignore = ["Serial No"]
 
 class TestPurchaseInvoice(unittest.TestCase):
+	def setUp(self):
+		unlink_payment_on_cancel_of_invoice()
+
+	def tearDown(self):
+		unlink_payment_on_cancel_of_invoice(0)
+
 	def test_gl_entries_without_auto_accounting_for_stock(self):
 		set_perpetual_inventory(0)
 		self.assertTrue(not cint(frappe.defaults.get_global_default("auto_accounting_for_stock")))
@@ -55,6 +61,27 @@
 
 		set_perpetual_inventory(0)
 
+	def test_payment_entry_unlink_against_purchase_invoice(self):
+		from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+		unlink_payment_on_cancel_of_invoice(0)
+
+		pi_doc = make_purchase_invoice()
+
+		pe = get_payment_entry("Purchase Invoice", pi_doc.name, bank_account="_Test Bank - _TC")
+		pe.reference_no = "1"
+		pe.reference_date = nowdate()
+		pe.paid_from_account_currency = pi_doc.currency
+		pe.paid_to_account_currency = pi_doc.currency
+		pe.source_exchange_rate = 1
+		pe.target_exchange_rate = 1
+		pe.paid_amount = pi_doc.grand_total
+		pe.save(ignore_permissions=True)
+		pe.submit()
+
+		pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
+
+		self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
+
 	def test_gl_entries_with_auto_accounting_for_stock_against_pr(self):
 		set_perpetual_inventory(1)
 		self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1)
@@ -411,6 +438,11 @@
 		self.assertEquals(frappe.db.get_value("Serial No", pi.get("items")[0].rejected_serial_no,
 			"warehouse"), pi.get("items")[0].rejected_warehouse)
 
+def unlink_payment_on_cancel_of_invoice(enable=1):
+	accounts_settings = frappe.get_doc("Accounts Settings")
+	accounts_settings.unlink_payment_on_cancellation_of_invoice = enable
+	accounts_settings.save()
+
 def make_purchase_invoice(**args):
 	pi = frappe.new_doc("Purchase Invoice")
 	args = frappe._dict(args)
@@ -455,4 +487,4 @@
 			pi.submit()
 	return pi
 
-test_records = frappe.get_test_records('Purchase Invoice')
+test_records = frappe.get_test_records('Purchase Invoice')
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 3c46a16..521b0eb 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -136,7 +136,8 @@
 		self.check_close_sales_order("sales_order")
 
 		from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
-		unlink_ref_doc_from_payment_entries(self.doctype, self.name)
+		if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
+			unlink_ref_doc_from_payment_entries(self.doctype, self.name)
 
 		if self.is_return:
 			# NOTE status updating bypassed for is_return
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 1bb7b1c..83fc83c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -4,8 +4,9 @@
 
 import frappe
 import unittest, copy
-from frappe.utils import nowdate, add_days, flt
+from frappe.utils import nowdate, add_days, flt, nowdate
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
 from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
@@ -19,6 +20,12 @@
 		w.submit()
 		return w
 
+	def setUp(self):
+		unlink_payment_on_cancel_of_invoice()
+
+	def tearDown(self):
+		unlink_payment_on_cancel_of_invoice(0)
+
 	def test_timestamp_change(self):
 		w = frappe.copy_doc(test_records[0])
 		w.docstatus = 0
@@ -78,6 +85,28 @@
 		self.assertEquals(si.base_grand_total, 1627.05)
 		self.assertEquals(si.grand_total, 1627.05)
 
+	def test_payment_entry_unlink_against_invoice(self):
+		from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+		si = frappe.copy_doc(test_records[0])
+		si.is_pos = 0
+		si.insert()
+		si.submit()
+
+		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
+		pe.reference_no = "1"
+		pe.reference_date = nowdate()
+		pe.paid_from_account_currency = si.currency
+		pe.paid_to_account_currency = si.currency
+		pe.source_exchange_rate = 1
+		pe.target_exchange_rate = 1
+		pe.paid_amount = si.grand_total
+		pe.insert()
+		pe.submit()
+
+		unlink_payment_on_cancel_of_invoice(0)
+		si = frappe.get_doc('Sales Invoice', si.name)
+		self.assertRaises(frappe.LinkExistsError, si.cancel)
+
 	def test_sales_invoice_calculation_export_currency(self):
 		si = frappe.copy_doc(test_records[2])
 		si.currency = "USD"
diff --git a/erpnext/docs/user/manual/en/accounts/setup/accounts-settings.md b/erpnext/docs/user/manual/en/accounts/setup/accounts-settings.md
index 5242b39..e920b65 100644
--- a/erpnext/docs/user/manual/en/accounts/setup/accounts-settings.md
+++ b/erpnext/docs/user/manual/en/accounts/setup/accounts-settings.md
@@ -7,4 +7,8 @@
 
 * Credit Controller: Role that is allowed to submit transactions that exceed credit limits set.
 
+* Make Payment via Journal Entry: If checked, on invoice if uer has clicked on payment system open the journal entry else payment entry
+
+* Unlink Payment on Cancellation of Invoice: If checked system inlink the payment against the invoice else shows the link error.
+
 {next}
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 84312b8..7d599d7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -339,4 +339,5 @@
 erpnext.patches.v7_0.update_status_of_zero_amount_sales_order
 erpnext.patches.v7_1.add_field_for_task_dependent
 erpnext.patches.v7_0.repost_bin_qty_and_item_projected_qty
-erpnext.patches.v7_1.set_prefered_contact_email
\ No newline at end of file
+erpnext.patches.v7_1.set_prefered_contact_email
+execute:frappe.db.sql("update `tabSingles` set value = 1 where field = 'unlink_payment_on_cancellation_of_invoice' and doctype = 'Accounts Settings'")
\ No newline at end of file