mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 16:34:46 +00:00
Merge pull request #40742 from frappe/mergify/bp/version-15-hotfix/pr-37630
refactor: configurable exchange gain or loss posting date (#37630)
This commit is contained in:
@@ -254,6 +254,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
this.data = [];
|
this.data = [];
|
||||||
const dialog = new frappe.ui.Dialog({
|
const dialog = new frappe.ui.Dialog({
|
||||||
title: __("Select Difference Account"),
|
title: __("Select Difference Account"),
|
||||||
|
size: "extra-large",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: "allocation",
|
fieldname: "allocation",
|
||||||
@@ -279,6 +280,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
read_only: 1,
|
read_only: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Date",
|
||||||
|
fieldname: "gain_loss_posting_date",
|
||||||
|
label: __("Posting Date"),
|
||||||
|
in_list_view: 1,
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Account",
|
options: "Account",
|
||||||
@@ -319,6 +327,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
"difference_account",
|
"difference_account",
|
||||||
d.difference_account
|
d.difference_account
|
||||||
);
|
);
|
||||||
|
frappe.model.set_value(
|
||||||
|
"Payment Reconciliation Allocation",
|
||||||
|
d.docname,
|
||||||
|
"gain_loss_posting_date",
|
||||||
|
d.gain_loss_posting_date
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.reconcile_payment_entries();
|
this.reconcile_payment_entries();
|
||||||
@@ -334,6 +348,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
reference_name: d.reference_name,
|
reference_name: d.reference_name,
|
||||||
difference_amount: d.difference_amount,
|
difference_amount: d.difference_amount,
|
||||||
difference_account: d.difference_account,
|
difference_account: d.difference_account,
|
||||||
|
gain_loss_posting_date: d.gain_loss_posting_date,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -446,6 +446,7 @@ class PaymentReconciliation(Document):
|
|||||||
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
||||||
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")})
|
||||||
|
|
||||||
if pay.get("amount") == 0:
|
if pay.get("amount") == 0:
|
||||||
entries.append(res)
|
entries.append(res)
|
||||||
@@ -562,6 +563,7 @@ class PaymentReconciliation(Document):
|
|||||||
"allocated_amount": flt(row.get("allocated_amount")),
|
"allocated_amount": flt(row.get("allocated_amount")),
|
||||||
"difference_amount": flt(row.get("difference_amount")),
|
"difference_amount": flt(row.get("difference_amount")),
|
||||||
"difference_account": row.get("difference_account"),
|
"difference_account": row.get("difference_account"),
|
||||||
|
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||||
"cost_center": row.get("cost_center"),
|
"cost_center": row.get("cost_center"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,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",
|
||||||
|
|||||||
@@ -1360,7 +1360,9 @@ class AccountsController(TransactionBase):
|
|||||||
self.name,
|
self.name,
|
||||||
arg.get("referenced_row"),
|
arg.get("referenced_row"),
|
||||||
):
|
):
|
||||||
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date")
|
posting_date = arg.get("difference_posting_date") or frappe.db.get_value(
|
||||||
|
arg.voucher_type, arg.voucher_no, "posting_date"
|
||||||
|
)
|
||||||
je = create_gain_loss_journal(
|
je = create_gain_loss_journal(
|
||||||
self.company,
|
self.company,
|
||||||
posting_date,
|
posting_date,
|
||||||
@@ -1444,7 +1446,7 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
je = create_gain_loss_journal(
|
je = create_gain_loss_journal(
|
||||||
self.company,
|
self.company,
|
||||||
self.posting_date,
|
args.get("difference_posting_date") if args else self.posting_date,
|
||||||
self.party_type,
|
self.party_type,
|
||||||
self.party,
|
self.party,
|
||||||
party_account,
|
party_account,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import frappe
|
|||||||
from frappe import qb
|
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, flt, nowdate
|
from frappe.utils import add_days, flt, getdate, nowdate
|
||||||
|
|
||||||
from erpnext import get_default_cost_center
|
from erpnext import get_default_cost_center
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
@@ -616,6 +616,73 @@ class TestAccountsController(FrappeTestCase):
|
|||||||
self.assertEqual(exc_je_for_si, [])
|
self.assertEqual(exc_je_for_si, [])
|
||||||
self.assertEqual(exc_je_for_pe, [])
|
self.assertEqual(exc_je_for_pe, [])
|
||||||
|
|
||||||
|
def test_15_gain_loss_on_different_posting_date(self):
|
||||||
|
# Invoice in Foreign Currency
|
||||||
|
si = self.create_sales_invoice(
|
||||||
|
posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1
|
||||||
|
)
|
||||||
|
# Payment
|
||||||
|
pe = (
|
||||||
|
self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75)
|
||||||
|
.save()
|
||||||
|
.submit()
|
||||||
|
)
|
||||||
|
|
||||||
|
# There should be outstanding in both currencies
|
||||||
|
si.reload()
|
||||||
|
self.assertEqual(si.outstanding_amount, 2)
|
||||||
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
||||||
|
|
||||||
|
# Reconcile the remaining amount
|
||||||
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
|
pr.company = self.company
|
||||||
|
pr.party_type = "Customer"
|
||||||
|
pr.party = self.customer
|
||||||
|
pr.receivable_payable_account = self.debit_usd
|
||||||
|
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.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1)
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
# 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(
|
||||||
|
frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"),
|
||||||
|
getdate(add_days(nowdate(), 1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(pr.invoices), 0)
|
||||||
|
self.assertEqual(len(pr.payments), 0)
|
||||||
|
|
||||||
|
# There should be no outstanding
|
||||||
|
si.reload()
|
||||||
|
self.assertEqual(si.outstanding_amount, 0)
|
||||||
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
||||||
|
|
||||||
|
# Cancel Payment
|
||||||
|
pe.reload()
|
||||||
|
pe.cancel()
|
||||||
|
|
||||||
|
si.reload()
|
||||||
|
self.assertEqual(si.outstanding_amount, 2)
|
||||||
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|||||||
Reference in New Issue
Block a user