mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 11:09:17 +00:00
Merge pull request #45413 from frappe/mergify/bp/version-15/pr-44790
refactor: configurable posting date for Exc Gain / Loss journal (backport #44790)
This commit is contained in:
@@ -47,6 +47,7 @@
|
|||||||
"auto_reconciliation_job_trigger",
|
"auto_reconciliation_job_trigger",
|
||||||
"reconciliation_queue_size",
|
"reconciliation_queue_size",
|
||||||
"column_break_resa",
|
"column_break_resa",
|
||||||
|
"exchange_gain_loss_posting_date",
|
||||||
"invoicing_settings_tab",
|
"invoicing_settings_tab",
|
||||||
"accounts_transactions_settings_section",
|
"accounts_transactions_settings_section",
|
||||||
"over_billing_allowance",
|
"over_billing_allowance",
|
||||||
@@ -389,7 +390,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_jpd0",
|
"fieldname": "section_break_jpd0",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Payment Reconciliations"
|
"label": "Payment Reconciliation Settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -523,6 +524,14 @@
|
|||||||
"fieldname": "ignore_is_opening_check_for_reporting",
|
"fieldname": "ignore_is_opening_check_for_reporting",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Is Opening check for reporting"
|
"label": "Ignore Is Opening check for reporting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Payment",
|
||||||
|
"description": "Only applies for Normal Payments",
|
||||||
|
"fieldname": "exchange_gain_loss_posting_date",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Posting Date Inheritance for Exchange Gain / Loss",
|
||||||
|
"options": "Invoice\nPayment\nReconciliation Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -530,7 +539,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-18 21:24:19.840745",
|
"modified": "2025-01-23 13:15:44.077853",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class AccountsSettings(Document):
|
|||||||
enable_fuzzy_matching: DF.Check
|
enable_fuzzy_matching: DF.Check
|
||||||
enable_immutable_ledger: DF.Check
|
enable_immutable_ledger: DF.Check
|
||||||
enable_party_matching: DF.Check
|
enable_party_matching: DF.Check
|
||||||
|
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
|
||||||
frozen_accounts_modifier: DF.Link | None
|
frozen_accounts_modifier: DF.Link | None
|
||||||
general_ledger_remarks_length: DF.Int
|
general_ledger_remarks_length: DF.Int
|
||||||
ignore_account_closing_balance: DF.Check
|
ignore_account_closing_balance: DF.Check
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ class PaymentReconciliation(Document):
|
|||||||
for payment in non_reconciled_payments:
|
for payment in non_reconciled_payments:
|
||||||
row = self.append("payments", {})
|
row = self.append("payments", {})
|
||||||
row.update(payment)
|
row.update(payment)
|
||||||
|
row.is_advance = payment.book_advance_payments_in_separate_party_account
|
||||||
|
|
||||||
def get_invoice_entries(self):
|
def get_invoice_entries(self):
|
||||||
# Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
|
# Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
|
||||||
@@ -424,6 +425,9 @@ class PaymentReconciliation(Document):
|
|||||||
def allocate_entries(self, args):
|
def allocate_entries(self, args):
|
||||||
self.validate_entries()
|
self.validate_entries()
|
||||||
|
|
||||||
|
exc_gain_loss_posting_date = frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "exchange_gain_loss_posting_date", cache=True
|
||||||
|
)
|
||||||
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
|
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
|
||||||
default_exchange_gain_loss_account = frappe.get_cached_value(
|
default_exchange_gain_loss_account = frappe.get_cached_value(
|
||||||
"Company", self.company, "exchange_gain_loss_account"
|
"Company", self.company, "exchange_gain_loss_account"
|
||||||
@@ -450,6 +454,11 @@ class PaymentReconciliation(Document):
|
|||||||
res.difference_account = default_exchange_gain_loss_account
|
res.difference_account = default_exchange_gain_loss_account
|
||||||
res.exchange_rate = inv.get("exchange_rate")
|
res.exchange_rate = inv.get("exchange_rate")
|
||||||
res.update({"gain_loss_posting_date": pay.get("posting_date")})
|
res.update({"gain_loss_posting_date": pay.get("posting_date")})
|
||||||
|
if not pay.get("is_advance"):
|
||||||
|
if exc_gain_loss_posting_date == "Invoice":
|
||||||
|
res.update({"gain_loss_posting_date": inv.get("invoice_date")})
|
||||||
|
elif exc_gain_loss_posting_date == "Reconciliation Date":
|
||||||
|
res.update({"gain_loss_posting_date": nowdate()})
|
||||||
|
|
||||||
if pay.get("amount") == 0:
|
if pay.get("amount") == 0:
|
||||||
entries.append(res)
|
entries.append(res)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"is_advance",
|
"is_advance",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"difference_amount",
|
"difference_amount",
|
||||||
|
"gain_loss_posting_date",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
"difference_account",
|
"difference_account",
|
||||||
"exchange_rate",
|
"exchange_rate",
|
||||||
@@ -153,11 +154,16 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Reconciled"
|
"label": "Reconciled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "gain_loss_posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Difference Posting Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-20 21:05:43.121945",
|
"modified": "2025-01-23 16:09:01.058574",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Payment Reconciliation Log Allocations",
|
"name": "Process Payment Reconciliation Log Allocations",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class ProcessPaymentReconciliationLogAllocations(Document):
|
|||||||
difference_account: DF.Link | None
|
difference_account: DF.Link | None
|
||||||
difference_amount: DF.Currency
|
difference_amount: DF.Currency
|
||||||
exchange_rate: DF.Float
|
exchange_rate: DF.Float
|
||||||
|
gain_loss_posting_date: DF.Date | None
|
||||||
invoice_number: DF.DynamicLink
|
invoice_number: DF.DynamicLink
|
||||||
invoice_type: DF.Link
|
invoice_type: DF.Link
|
||||||
is_advance: DF.Data | None
|
is_advance: DF.Data | None
|
||||||
|
|||||||
@@ -2969,6 +2969,7 @@ def get_advance_payment_entries(
|
|||||||
(payment_ref.allocated_amount).as_("amount"),
|
(payment_ref.allocated_amount).as_("amount"),
|
||||||
(payment_ref.name).as_("reference_row"),
|
(payment_ref.name).as_("reference_row"),
|
||||||
(payment_ref.reference_name).as_("against_order"),
|
(payment_ref.reference_name).as_("against_order"),
|
||||||
|
(payment_entry.book_advance_payments_in_separate_party_account),
|
||||||
)
|
)
|
||||||
|
|
||||||
q = q.where(payment_ref.reference_doctype == order_doctype)
|
q = q.where(payment_ref.reference_doctype == order_doctype)
|
||||||
@@ -3013,6 +3014,7 @@ def get_common_query(
|
|||||||
(payment_entry.name).as_("reference_name"),
|
(payment_entry.name).as_("reference_name"),
|
||||||
payment_entry.posting_date,
|
payment_entry.posting_date,
|
||||||
(payment_entry.remarks).as_("remarks"),
|
(payment_entry.remarks).as_("remarks"),
|
||||||
|
(payment_entry.book_advance_payments_in_separate_party_account),
|
||||||
)
|
)
|
||||||
.where(payment_entry.payment_type == payment_type)
|
.where(payment_entry.payment_type == payment_type)
|
||||||
.where(payment_entry.party_type == party_type)
|
.where(payment_entry.party_type == party_type)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from frappe import qb
|
|||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, getdate, nowdate
|
from frappe.utils import add_days, getdate, nowdate
|
||||||
|
from frappe.utils.data import getdate as convert_to_date
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||||
@@ -868,6 +869,67 @@ class TestAccountsController(FrappeTestCase):
|
|||||||
self.assertEqual(pi.items[0].rate, arms_length_price)
|
self.assertEqual(pi.items[0].rate, arms_length_price)
|
||||||
self.assertEqual(pi.items[0].valuation_rate, 100)
|
self.assertEqual(pi.items[0].valuation_rate, 100)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"})
|
||||||
|
def test_17_gain_loss_posting_date_for_normal_payment(self):
|
||||||
|
# Sales Invoice in Foreign Currency
|
||||||
|
rate = 80
|
||||||
|
rate_in_account_currency = 1
|
||||||
|
|
||||||
|
adv_date = convert_to_date(add_days(nowdate(), -2))
|
||||||
|
inv_date = convert_to_date(add_days(nowdate(), -1))
|
||||||
|
|
||||||
|
si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=rate_in_account_currency)
|
||||||
|
|
||||||
|
# Test payments with different exchange rates
|
||||||
|
pe = self.create_payment_entry(posting_date=adv_date, amount=1, source_exc_rate=75.1).save().submit()
|
||||||
|
|
||||||
|
pr = self.create_payment_reconciliation()
|
||||||
|
pr.from_invoice_date = add_days(nowdate(), -1)
|
||||||
|
pr.to_invoice_date = nowdate()
|
||||||
|
pr.from_payment_date = add_days(nowdate(), -2)
|
||||||
|
pr.to_payment_date = nowdate()
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
self.assertEqual(len(pr.invoices), 1)
|
||||||
|
self.assertEqual(len(pr.payments), 1)
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices]
|
||||||
|
payments = [x.as_dict() for x in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
self.assertEqual(len(pr.invoices), 0)
|
||||||
|
self.assertEqual(len(pr.payments), 0)
|
||||||
|
|
||||||
|
# Outstanding in both currencies should be '0'
|
||||||
|
si.reload()
|
||||||
|
self.assertEqual(si.outstanding_amount, 0)
|
||||||
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
||||||
|
|
||||||
|
# Exchange Gain/Loss Journal should've been created.
|
||||||
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||||
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
||||||
|
self.assertNotEqual(exc_je_for_si, [])
|
||||||
|
self.assertEqual(len(exc_je_for_si), 1)
|
||||||
|
self.assertEqual(len(exc_je_for_pe), 1)
|
||||||
|
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
getdate(nowdate()), frappe.db.get_value("Journal Entry", exc_je_for_pe[0].parent, "posting_date")
|
||||||
|
)
|
||||||
|
# Cancel Payment
|
||||||
|
pe.reload()
|
||||||
|
pe.cancel()
|
||||||
|
|
||||||
|
# outstanding should be same as grand total
|
||||||
|
si.reload()
|
||||||
|
self.assertEqual(si.outstanding_amount, rate_in_account_currency)
|
||||||
|
self.assert_ledger_outstanding(si.doctype, si.name, rate, rate_in_account_currency)
|
||||||
|
|
||||||
|
# Exchange Gain/Loss Journal should've been cancelled
|
||||||
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||||
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
||||||
|
self.assertEqual(exc_je_for_si, [])
|
||||||
|
self.assertEqual(exc_je_for_pe, [])
|
||||||
|
|
||||||
def test_20_journal_against_sales_invoice(self):
|
def test_20_journal_against_sales_invoice(self):
|
||||||
# Invoice in Foreign Currency
|
# Invoice in Foreign Currency
|
||||||
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
||||||
|
|||||||
@@ -390,3 +390,4 @@ erpnext.patches.v15_0.update_asset_status_to_work_in_progress
|
|||||||
erpnext.patches.v15_0.rename_manufacturing_settings_field
|
erpnext.patches.v15_0.rename_manufacturing_settings_field
|
||||||
erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
|
erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
|
||||||
erpnext.patches.v15_0.sync_auto_reconcile_config
|
erpnext.patches.v15_0.sync_auto_reconcile_config
|
||||||
|
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
|
||||||
|
|||||||
Reference in New Issue
Block a user