fix: exchange gain loss not set for advances linked with invoices (#26436)

diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 0c21aae..ff00fde 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -183,6 +183,13 @@
 					d.reference_name, self.party_account_currency)
 
 				for field, value in iteritems(ref_details):
+					if d.exchange_gain_loss:
+						# for cases where gain/loss is booked into invoice
+						# exchange_gain_loss is calculated from invoice & populated 
+						# and row.exchange_rate is already set to payment entry's exchange rate
+						# refer -> `update_reference_in_payment_entry()` in utils.py
+						continue
+
 					if field == 'exchange_rate' or not d.get(field) or force:
 						d.db_set(field, value)
 
@@ -664,8 +671,8 @@
 				gl_entries.append(gle)
 
 			if self.unallocated_amount:
-				base_unallocated_amount = self.unallocated_amount * \
-					(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
+				exchange_rate = self.get_exchange_rate()
+				base_unallocated_amount = (self.unallocated_amount * exchange_rate)
 
 				gle = party_gl_dict.copy()
 
@@ -806,10 +813,17 @@
 
 		if account_details:
 			row.update(account_details)
+		
+		if not row.get('amount'):
+			# if no difference amount
+			return
 
 		self.append('deductions', row)
 		self.set_unallocated_amount()
 
+	def get_exchange_rate(self):
+		return self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate
+
 	def initialize_taxes(self):
 		for tax in self.get("taxes"):
 			validate_taxes_and_charges(tax)
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 912ad09..43eb0b6 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -14,7 +14,8 @@
   "total_amount",
   "outstanding_amount",
   "allocated_amount",
-  "exchange_rate"
+  "exchange_rate",
+  "exchange_gain_loss"
  ],
  "fields": [
   {
@@ -90,12 +91,19 @@
    "fieldtype": "Link",
    "label": "Payment Term",
    "options": "Payment Term"
+  },
+  {
+   "fieldname": "exchange_gain_loss",
+   "fieldtype": "Currency",
+   "label": "Exchange Gain/Loss",
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-10 11:25:47.144392",
+ "modified": "2021-04-21 13:30:11.605388",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 45d89ad..f799279 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -451,6 +451,7 @@
 			self.get_asset_gl_entry(gl_entries)
 
 		self.make_tax_gl_entries(gl_entries)
+		self.make_exchange_gain_loss_gl_entries(gl_entries)
 		self.make_internal_transfer_gl_entries(gl_entries)
 
 		self.allocate_advance_taxes(gl_entries)
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 311745d..c9384be 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -953,6 +953,109 @@
 		acc_settings.submit_journal_entriessubmit_journal_entries = 0
 		acc_settings.save()
 
+	def test_gain_loss_with_advance_entry(self):
+		unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice")
+		frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1)
+		pay = frappe.get_doc({
+			'doctype': 'Payment Entry',
+			'company': '_Test Company',
+			'payment_type': 'Pay',
+			'party_type': 'Supplier',
+			'party': '_Test Supplier USD',
+			'paid_to': '_Test Payable USD - _TC',
+			'paid_from': 'Cash - _TC',
+			'paid_amount': 70000,
+			'target_exchange_rate': 70,
+			'received_amount': 1000,
+		})
+		pay.insert()
+		pay.submit()
+
+		pi = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
+			conversion_rate=75, rate=500, do_not_save=1, qty=1)
+		pi.cost_center = "_Test Cost Center - _TC"
+		pi.advances = []
+		pi.append("advances", {
+			"reference_type": "Payment Entry",
+			"reference_name": pay.name,
+			"advance_amount": 1000,
+			"remarks": pay.remarks,
+			"allocated_amount": 500,
+			"ref_exchange_rate": 70
+		})
+		pi.save()
+		pi.submit()
+
+		expected_gle = [
+			["_Test Account Cost for Goods Sold - _TC", 37500.0],
+			["_Test Payable USD - _TC", -40000.0],
+			["Exchange Gain/Loss - _TC", 2500.0]
+		]
+
+		gl_entries = frappe.db.sql("""
+			select account, sum(debit - credit) as balance from `tabGL Entry`
+			where voucher_no=%s
+			group by account order by account asc""", (pi.name), as_dict=1)
+		
+		for i, gle in enumerate(gl_entries):
+			self.assertEqual(expected_gle[i][0], gle.account)
+			self.assertEqual(expected_gle[i][1], gle.balance)
+
+		pi_2 = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
+			conversion_rate=73, rate=500, do_not_save=1, qty=1)
+		pi_2.cost_center = "_Test Cost Center - _TC"
+		pi_2.advances = []
+		pi_2.append("advances", {
+			"reference_type": "Payment Entry",
+			"reference_name": pay.name,
+			"advance_amount": 500,
+			"remarks": pay.remarks,
+			"allocated_amount": 500,
+			"ref_exchange_rate": 70
+		})
+		pi_2.save()
+		pi_2.submit()
+
+		expected_gle = [
+			["_Test Account Cost for Goods Sold - _TC", 36500.0],
+			["_Test Payable USD - _TC", -38000.0],
+			["Exchange Gain/Loss - _TC", 1500.0]
+		]
+
+		gl_entries = frappe.db.sql("""
+			select account, sum(debit - credit) as balance from `tabGL Entry`
+			where voucher_no=%s
+			group by account order by account asc""", (pi_2.name), as_dict=1)
+
+		for i, gle in enumerate(gl_entries):
+			self.assertEqual(expected_gle[i][0], gle.account)
+			self.assertEqual(expected_gle[i][1], gle.balance)
+
+		expected_gle = [
+			["_Test Payable USD - _TC", 70000.0],
+			["Cash - _TC", -70000.0]
+		]
+
+		gl_entries = frappe.db.sql("""
+			select account, sum(debit - credit) as balance from `tabGL Entry`
+			where voucher_no=%s and is_cancelled=0
+			group by account order by account asc""", (pay.name), as_dict=1)
+
+		for i, gle in enumerate(gl_entries):
+			self.assertEqual(expected_gle[i][0], gle.account)
+			self.assertEqual(expected_gle[i][1], gle.balance)
+
+		pi.reload()
+		pi.cancel()
+
+		pi_2.reload()
+		pi_2.cancel()
+
+		pay.reload()
+		pay.cancel()
+
+		frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
+
 	def test_purchase_invoice_advance_taxes(self):
 		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
 		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
index 5801b17..63dfff8 100644
--- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
+++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
@@ -1,235 +1,127 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2013-03-08 15:36:46", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Document", 
- "editable_grid": 1, 
+ "actions": [],
+ "creation": "2013-03-08 15:36:46",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "reference_type",
+  "reference_name",
+  "remarks",
+  "reference_row",
+  "col_break1",
+  "advance_amount",
+  "allocated_amount",
+  "exchange_gain_loss",
+  "ref_exchange_rate"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "reference_type", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Reference Type", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "journal_voucher", 
-   "oldfieldtype": "Link", 
-   "options": "DocType", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "180px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "fieldname": "reference_type",
+   "fieldtype": "Link",
+   "label": "Reference Type",
+   "no_copy": 1,
+   "oldfieldname": "journal_voucher",
+   "oldfieldtype": "Link",
+   "options": "DocType",
+   "print_width": "180px",
+   "read_only": 1,
    "width": "180px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 3, 
-   "fieldname": "reference_name", 
-   "fieldtype": "Dynamic Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Reference Name", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "reference_type", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "columns": 3,
+   "fieldname": "reference_name",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Reference Name",
+   "no_copy": 1,
+   "options": "reference_type",
+   "read_only": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 3, 
-   "fieldname": "remarks", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Remarks", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "remarks", 
-   "oldfieldtype": "Small Text", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "150px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "columns": 3,
+   "fieldname": "remarks",
+   "fieldtype": "Text",
+   "in_list_view": 1,
+   "label": "Remarks",
+   "no_copy": 1,
+   "oldfieldname": "remarks",
+   "oldfieldtype": "Small Text",
+   "print_width": "150px",
+   "read_only": 1,
    "width": "150px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "reference_row", 
-   "fieldtype": "Data", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Reference Row", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "jv_detail_no", 
-   "oldfieldtype": "Date", 
-   "permlevel": 0, 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "80px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "fieldname": "reference_row",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Reference Row",
+   "no_copy": 1,
+   "oldfieldname": "jv_detail_no",
+   "oldfieldtype": "Date",
+   "print_hide": 1,
+   "print_width": "80px",
+   "read_only": 1,
    "width": "80px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "col_break1", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "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
-  }, 
+   "fieldname": "col_break1",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "fieldname": "advance_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Advance Amount", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "advance_amount", 
-   "oldfieldtype": "Currency", 
-   "options": "party_account_currency", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "100px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "columns": 2,
+   "fieldname": "advance_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Advance Amount",
+   "no_copy": 1,
+   "oldfieldname": "advance_amount",
+   "oldfieldtype": "Currency",
+   "options": "party_account_currency",
+   "print_width": "100px",
+   "read_only": 1,
    "width": "100px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "fieldname": "allocated_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Allocated Amount", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "allocated_amount", 
-   "oldfieldtype": "Currency", 
-   "options": "party_account_currency", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "100px", 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "columns": 2,
+   "fieldname": "allocated_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Allocated Amount",
+   "no_copy": 1,
+   "oldfieldname": "allocated_amount",
+   "oldfieldtype": "Currency",
+   "options": "party_account_currency",
+   "print_width": "100px",
    "width": "100px"
+  },
+  {
+   "fieldname": "exchange_gain_loss",
+   "fieldtype": "Currency",
+   "label": "Exchange Gain/Loss",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "ref_exchange_rate",
+   "fieldtype": "Float",
+   "label": "Reference Exchange Rate",
+   "non_negative": 1,
+   "read_only": 1
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "menu_index": 0, 
- "modified": "2016-08-26 02:30:54.407138", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Purchase Invoice Advance", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_order": "DESC", 
- "track_seen": 0
+ ],
+ "idx": 1,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-20 16:26:53.820530",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Purchase Invoice Advance",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ 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 55a5b99..6d1f624 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -840,6 +840,7 @@
 		self.make_customer_gl_entry(gl_entries)
 
 		self.make_tax_gl_entries(gl_entries)
+		self.make_exchange_gain_loss_gl_entries(gl_entries)
 		self.make_internal_transfer_gl_entries(gl_entries)
 
 		self.allocate_advance_taxes(gl_entries)
diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
index 14bf4d8..29422d6 100644
--- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
+++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
@@ -1,235 +1,128 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2013-02-22 01:27:41", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Document", 
- "editable_grid": 1, 
+ "actions": [],
+ "creation": "2013-02-22 01:27:41",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "reference_type",
+  "reference_name",
+  "remarks",
+  "reference_row",
+  "col_break1",
+  "advance_amount",
+  "allocated_amount",
+  "exchange_gain_loss",
+  "ref_exchange_rate"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "reference_type", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Reference Type", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "journal_voucher", 
-   "oldfieldtype": "Link", 
-   "options": "DocType", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "250px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "fieldname": "reference_type",
+   "fieldtype": "Link",
+   "label": "Reference Type",
+   "no_copy": 1,
+   "oldfieldname": "journal_voucher",
+   "oldfieldtype": "Link",
+   "options": "DocType",
+   "print_width": "250px",
+   "read_only": 1,
    "width": "250px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 3, 
-   "fieldname": "reference_name", 
-   "fieldtype": "Dynamic Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Reference Name", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "reference_type", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "columns": 3,
+   "fieldname": "reference_name",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Reference Name",
+   "no_copy": 1,
+   "options": "reference_type",
+   "print_hide": 1,
+   "read_only": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 3, 
-   "fieldname": "remarks", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Remarks", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "remarks", 
-   "oldfieldtype": "Small Text", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "150px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "columns": 3,
+   "fieldname": "remarks",
+   "fieldtype": "Text",
+   "in_list_view": 1,
+   "label": "Remarks",
+   "no_copy": 1,
+   "oldfieldname": "remarks",
+   "oldfieldtype": "Small Text",
+   "print_width": "150px",
+   "read_only": 1,
    "width": "150px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "reference_row", 
-   "fieldtype": "Data", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Reference Row", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "jv_detail_no", 
-   "oldfieldtype": "Data", 
-   "permlevel": 0, 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "120px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "fieldname": "reference_row",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Reference Row",
+   "no_copy": 1,
+   "oldfieldname": "jv_detail_no",
+   "oldfieldtype": "Data",
+   "print_hide": 1,
+   "print_width": "120px",
+   "read_only": 1,
    "width": "120px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "col_break1", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "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
-  }, 
+   "fieldname": "col_break1",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "fieldname": "advance_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Advance amount", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "advance_amount", 
-   "oldfieldtype": "Currency", 
-   "options": "party_account_currency", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "120px", 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "columns": 2,
+   "fieldname": "advance_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Advance amount",
+   "no_copy": 1,
+   "oldfieldname": "advance_amount",
+   "oldfieldtype": "Currency",
+   "options": "party_account_currency",
+   "print_width": "120px",
+   "read_only": 1,
    "width": "120px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "fieldname": "allocated_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Allocated amount", 
-   "length": 0, 
-   "no_copy": 1, 
-   "oldfieldname": "allocated_amount", 
-   "oldfieldtype": "Currency", 
-   "options": "party_account_currency", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "120px", 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "columns": 2,
+   "fieldname": "allocated_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Allocated amount",
+   "no_copy": 1,
+   "oldfieldname": "allocated_amount",
+   "oldfieldtype": "Currency",
+   "options": "party_account_currency",
+   "print_width": "120px",
    "width": "120px"
+  },
+  {
+   "fieldname": "exchange_gain_loss",
+   "fieldtype": "Currency",
+   "label": "Exchange Gain/Loss",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "ref_exchange_rate",
+   "fieldtype": "Float",
+   "label": "Reference Exchange Rate",
+   "non_negative": 1,
+   "read_only": 1
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "menu_index": 0, 
- "modified": "2016-08-26 02:36:10.718057", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Sales Invoice Advance", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_order": "DESC", 
- "track_seen": 0
+ ],
+ "idx": 1,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-06-04 20:25:49.832052",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Invoice Advance",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index ed6e28d..1cdbd8d 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -472,7 +472,8 @@
 		"total_amount": d.grand_total,
 		"outstanding_amount": d.outstanding_amount,
 		"allocated_amount": d.allocated_amount,
-		"exchange_rate": d.exchange_rate
+		"exchange_rate": d.exchange_rate if not d.exchange_gain_loss else payment_entry.get_exchange_rate(),
+		"exchange_gain_loss": d.exchange_gain_loss # only populated from invoice in case of advance allocation
 	}
 
 	if d.voucher_detail_no:
@@ -498,12 +499,15 @@
 	payment_entry.set_amounts()
 
 	if d.difference_amount and d.difference_account:
-		payment_entry.set_gain_or_loss(account_details={
+		account_details = {
 			'account': d.difference_account,
 			'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
-				payment_entry.company, "cost_center"),
-			'amount': d.difference_amount
-		})
+				payment_entry.company, "cost_center")
+		}
+		if d.difference_amount:
+			account_details['amount'] = d.difference_amount
+
+		payment_entry.set_gain_or_loss(account_details=account_details)
 
 	if not do_not_save:
 		payment_entry.save(ignore_permissions=True)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1c086e9..a9860ed 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -124,6 +124,8 @@
 			if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
 				self.set_advances()
 
+			self.set_advance_gain_or_loss()
+
 			if self.is_return:
 				self.validate_qty()
 			else:
@@ -584,15 +586,18 @@
 				allocated_amount = min(amount - advance_allocated, d.amount)
 			advance_allocated += flt(allocated_amount)
 
-			self.append("advances", {
+			advance_row = {
 				"doctype": self.doctype + " Advance",
 				"reference_type": d.reference_type,
 				"reference_name": d.reference_name,
 				"reference_row": d.reference_row,
 				"remarks": d.remarks,
 				"advance_amount": flt(d.amount),
-				"allocated_amount": allocated_amount
-			})
+				"allocated_amount": allocated_amount,
+				"ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry
+			}
+
+			self.append("advances", advance_row)
 
 	def get_advance_entries(self, include_unallocated=True):
 		if self.doctype == "Sales Invoice":
@@ -650,6 +655,66 @@
 						"Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.")
 							.format(d.reference_name, d.against_order))
 
+	def set_advance_gain_or_loss(self):
+		if not self.get("advances"):
+			return
+
+		for d in self.get("advances"):
+			advance_exchange_rate = d.ref_exchange_rate
+			if (d.allocated_amount and self.conversion_rate != 1
+				and self.conversion_rate != advance_exchange_rate):
+
+				base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
+				base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
+				difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate
+
+				d.exchange_gain_loss = difference
+
+	def make_exchange_gain_loss_gl_entries(self, gl_entries):
+		if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
+			for d in self.get("advances"):
+				if d.exchange_gain_loss:
+					party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
+					party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
+					party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
+
+					gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
+					account_currency = get_account_currency(gain_loss_account)
+					if account_currency != self.company_currency:
+						frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
+
+					# for purchase
+					dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
+					# just reverse for sales?
+					dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
+
+					gl_entries.append(
+						self.get_gl_dict({
+							"account": gain_loss_account,
+							"account_currency": account_currency,
+							"against": party,
+							dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
+							dr_or_cr: abs(d.exchange_gain_loss),
+							"cost_center": self.cost_center,
+							"project": self.project
+						}, item=d)
+					)
+
+					dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
+
+					gl_entries.append(
+						self.get_gl_dict({
+							"account": party_account,
+							"party_type": party_type,
+							"party": party,
+							"against": gain_loss_account,
+							dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate),
+							dr_or_cr: abs(d.exchange_gain_loss),
+							"cost_center": self.cost_center,
+							"project": self.project
+						}, self.party_account_currency, item=self)
+					)
+
 	def update_against_document_in_jv(self):
 		"""
 			Links invoice and advance voucher:
@@ -690,7 +755,9 @@
 						if self.party_account_currency != self.company_currency else 1),
 					'grand_total': (self.base_grand_total
 						if self.party_account_currency == self.company_currency else self.grand_total),
-					'outstanding_amount': self.outstanding_amount
+					'outstanding_amount': self.outstanding_amount,
+					'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'),
+					'exchange_gain_loss': flt(d.get('exchange_gain_loss'))
 				})
 				lst.append(args)
 
@@ -1289,6 +1356,8 @@
 	party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
 	currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
 	payment_type = "Receive" if party_type == "Customer" else "Pay"
+	exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
+
 	payment_entries_against_order, unallocated_payment_entries = [], []
 	limit_cond = "limit %s" % limit if limit else ""
 
@@ -1305,27 +1374,28 @@
 				"Payment Entry" as reference_type, t1.name as reference_name,
 				t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
 				t2.reference_name as against_order, t1.posting_date,
-				t1.{0} as currency
+				t1.{0} as currency, t1.{4} as exchange_rate
 			from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
 			where
 				t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
 				and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
 				and t2.reference_doctype = %s {2}
 			order by t1.posting_date {3}
-		""".format(currency_field, party_account_field, reference_condition, limit_cond),
+		""".format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field),
 													  [party_account, payment_type, party_type, party,
 													   order_doctype] + order_list, as_dict=1)
 
 	if include_unallocated:
 		unallocated_payment_entries = frappe.db.sql("""
 				select "Payment Entry" as reference_type, name as reference_name,
-				remarks, unallocated_amount as amount
+				remarks, unallocated_amount as amount, {2} as exchange_rate
 				from `tabPayment Entry`
 				where
 					{0} = %s and party_type = %s and party = %s and payment_type = %s
 					and docstatus = 1 and unallocated_amount > 0
 				order by posting_date {1}
-			""".format(party_account_field, limit_cond), (party_account, party_type, party, payment_type), as_dict=1)
+			""".format(party_account_field, limit_cond, exchange_rate_field),
+			(party_account, party_type, party, payment_type), as_dict=1)
 
 	return list(payment_entries_against_order) + list(unallocated_payment_entries)