Merge pull request #31516 from deepeshgarg007/previsional_accounting_report
fix: Incorrect provisional expense booking while reposting
diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict
index a79137d..198ec7b 100644
--- a/.github/helper/.flake8_strict
+++ b/.github/helper/.flake8_strict
@@ -66,6 +66,7 @@
F841,
E713,
E712,
+ B023
max-line-length = 200
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 5b2b526..5ed34d3 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -162,7 +162,7 @@
{
"reference_type": inv.voucher_type,
"reference_name": inv.voucher_no,
- "amount": -(inv.outstanding),
+ "amount": -(inv.outstanding_in_account_currency),
"posting_date": inv.posting_date,
"currency": inv.currency,
}
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 575ac74..325346d 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -19,6 +19,7 @@
self.create_company()
self.create_item()
self.create_customer()
+ self.create_account()
self.clear_old_entries()
def tearDown(self):
@@ -89,6 +90,38 @@
customer.save()
self.customer2 = customer.name
+ if frappe.db.exists("Customer", "_Test PR Customer 3"):
+ self.customer3 = "_Test PR Customer 3"
+ else:
+ customer = frappe.new_doc("Customer")
+ customer.customer_name = "_Test PR Customer 3"
+ customer.type = "Individual"
+ customer.default_currency = "EUR"
+ customer.save()
+ self.customer3 = customer.name
+
+ def create_account(self):
+ account_name = "Debtors EUR"
+ if not frappe.db.get_value(
+ "Account", filters={"account_name": account_name, "company": self.company}
+ ):
+ acc = frappe.new_doc("Account")
+ acc.account_name = account_name
+ acc.parent_account = "Accounts Receivable - _PR"
+ acc.company = self.company
+ acc.account_currency = "EUR"
+ acc.account_type = "Receivable"
+ acc.insert()
+ else:
+ name = frappe.db.get_value(
+ "Account",
+ filters={"account_name": account_name, "company": self.company},
+ fieldname="name",
+ pluck=True,
+ )
+ acc = frappe.get_doc("Account", name)
+ self.debtors_eur = acc.name
+
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
@@ -454,3 +487,56 @@
self.assertEqual(len(pr.get("payments")), 1)
self.assertEqual(pr.get("invoices")[0].outstanding_amount, 20)
self.assertEqual(pr.get("payments")[0].amount, 20)
+
+ def test_pr_output_foreign_currency_and_amount(self):
+ # test for currency and amount invoices and payments
+ transaction_date = nowdate()
+ # In EUR
+ amount = 100
+ exchange_rate = 80
+
+ si = self.create_sales_invoice(
+ qty=1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
+ )
+ si.customer = self.customer3
+ si.currency = "EUR"
+ si.conversion_rate = exchange_rate
+ si.debit_to = self.debtors_eur
+ si = si.save().submit()
+
+ cr_note = self.create_sales_invoice(
+ qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
+ )
+ cr_note.customer = self.customer3
+ cr_note.is_return = 1
+ cr_note.currency = "EUR"
+ cr_note.conversion_rate = exchange_rate
+ cr_note.debit_to = self.debtors_eur
+ cr_note = cr_note.save().submit()
+
+ pr = self.create_payment_reconciliation()
+ pr.party = self.customer3
+ pr.receivable_payable_account = self.debtors_eur
+ pr.get_unreconciled_entries()
+
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+
+ self.assertEqual(pr.invoices[0].amount, amount)
+ self.assertEqual(pr.invoices[0].currency, "EUR")
+ self.assertEqual(pr.payments[0].amount, amount)
+ self.assertEqual(pr.payments[0].currency, "EUR")
+
+ cr_note.cancel()
+
+ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+
+ pay = get_payment_entry(si.doctype, si.name)
+ pay.references.clear()
+ pay = pay.save().submit()
+
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+ self.assertEqual(pr.payments[0].amount, amount)
+ self.assertEqual(pr.payments[0].currency, "EUR")
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index aefa9a5..cdb187f 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -476,6 +476,13 @@
this.frm.trigger("calculate_timesheet_totals");
}
}
+
+ is_cash_or_non_trade_discount() {
+ this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
+ if (!this.frm.doc.is_cash_or_non_trade_discount) {
+ this.frm.set_value("additional_discount_account", "");
+ }
+ }
};
// for backward compatibility: combine new and previous states
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 327545a..499377d 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -106,6 +106,7 @@
"loyalty_redemption_cost_center",
"section_break_49",
"apply_discount_on",
+ "is_cash_or_non_trade_discount",
"base_discount_amount",
"additional_discount_account",
"column_break_51",
@@ -1790,8 +1791,6 @@
"width": "50%"
},
{
- "fetch_from": "sales_partner.commission_rate",
- "fetch_if_empty": 1,
"fieldname": "commission_rate",
"fieldtype": "Float",
"hide_days": 1,
@@ -1990,7 +1989,7 @@
{
"fieldname": "additional_discount_account",
"fieldtype": "Link",
- "label": "Additional Discount Account",
+ "label": "Discount Account",
"options": "Account"
},
{
@@ -2028,6 +2027,13 @@
"fieldtype": "Currency",
"label": "Amount Eligible for Commission",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval: doc.apply_discount_on == \"Grand Total\"",
+ "fieldname": "is_cash_or_non_trade_discount",
+ "fieldtype": "Check",
+ "label": "Is Cash or Non Trade Discount"
}
],
"icon": "fa fa-file-text",
@@ -2040,7 +2046,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2022-06-10 03:52:51.409913",
+ "modified": "2022-06-16 16:22:44.870575",
"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 657cd99..6530e5c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1030,7 +1030,7 @@
)
if grand_total and not self.is_internal_transfer():
- # Didnot use base_grand_total to book rounding loss gle
+ # Did not use base_grand_total to book rounding loss gle
gl_entries.append(
self.get_gl_dict(
{
@@ -1055,6 +1055,22 @@
)
)
+ if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": self.additional_discount_account,
+ "against": self.debit_to,
+ "debit": self.base_discount_amount,
+ "debit_in_account_currency": self.discount_amount,
+ "cost_center": self.cost_center,
+ "project": self.project,
+ },
+ self.currency,
+ item=self,
+ )
+ )
+
def make_tax_gl_entries(self, gl_entries):
enable_discount_accounting = cint(
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 448ec54..c2e82fb 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2731,6 +2731,63 @@
einvoice = make_einvoice(si)
validate_totals(einvoice)
+ def test_einvoice_discounts(self):
+ from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
+
+ # Normal Itemized Discount
+ si = get_sales_invoice_for_e_invoice()
+ si.apply_discount_on = ""
+ si.items[0].discount_amount = 4000
+ si.items[1].discount_amount = 300
+ si.save()
+
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
+ self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000)
+ self.assertEqual(einvoice["ItemList"][1]["Discount"], 300)
+ self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
+
+ # Invoice Discount on net total
+ si = get_sales_invoice_for_e_invoice()
+ si.apply_discount_on = "Net Total"
+ si.discount_amount = 400
+ si.save()
+
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
+ self.assertEqual(einvoice["ItemList"][0]["Discount"], 316.83)
+ self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17)
+ self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
+
+ # Invoice Discount on grand total (Itemized Discount)
+ si = get_sales_invoice_for_e_invoice()
+ si.apply_discount_on = "Grand Total"
+ si.discount_amount = 400
+ si.save()
+
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
+ self.assertEqual(einvoice["ItemList"][0]["Discount"], 268.5)
+ self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48)
+ self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
+
+ # Invoice Discount on grand total (Cash/Non-Trade Discount)
+ si = get_sales_invoice_for_e_invoice()
+ si.apply_discount_on = "Grand Total"
+ si.is_cash_or_non_trade_discount = 1
+ si.discount_amount = 400
+ si.save()
+
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
+ self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
+ self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
+ self.assertEqual(einvoice["ValDtls"]["Discount"], 400)
+
def test_item_tax_net_range(self):
item = create_item("T Shirt")
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 8daff9d..6be8fd7 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -855,8 +855,8 @@
)
for d in invoice_list:
- payment_amount = d.invoice_amount - d.outstanding
- outstanding_amount = d.outstanding
+ payment_amount = d.invoice_amount_in_account_currency - d.outstanding_in_account_currency
+ outstanding_amount = d.outstanding_in_account_currency
if outstanding_amount > 0.5 / (10**precision):
if (
min_outstanding
@@ -872,7 +872,7 @@
"voucher_no": d.voucher_no,
"voucher_type": d.voucher_type,
"posting_date": d.posting_date,
- "invoice_amount": flt(d.invoice_amount),
+ "invoice_amount": flt(d.invoice_amount_in_account_currency),
"payment_amount": payment_amount,
"outstanding_amount": outstanding_amount,
"due_date": d.due_date,
@@ -1412,7 +1412,7 @@
if gle.against_voucher_type
else gle.voucher_type,
"against_voucher_no": gle.against_voucher if gle.against_voucher else gle.voucher_no,
- "currency": gle.currency,
+ "account_currency": gle.account_currency,
"amount": dr_or_cr,
"amount_in_account_currency": dr_or_cr_account_currency,
"delinked": True if cancel else False,
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 2144055..0d8cffe 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -500,6 +500,9 @@
else:
self.doc.grand_total = flt(self.doc.net_total)
+ if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
+ self.doc.grand_total -= self.doc.discount_amount
+
if self.doc.get("taxes"):
self.doc.total_taxes_and_charges = flt(
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
@@ -594,6 +597,12 @@
if not self.doc.apply_discount_on:
frappe.throw(_("Please select Apply Discount On"))
+ if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
+ "is_cash_or_non_trade_discount"
+ ):
+ self.discount_amount_applied = True
+ return
+
self.doc.base_discount_amount = flt(
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
)
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 5eb14a5..fb2779b 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -271,14 +271,14 @@
item.description = sanitize_for_json(d.item_name)
item.qty = abs(item.qty)
- if flt(item.qty) != 0.0:
- item.unit_rate = abs(item.taxable_value / item.qty)
- else:
- item.unit_rate = abs(item.taxable_value)
- item.gross_amount = abs(item.taxable_value)
- item.taxable_value = abs(item.taxable_value)
- item.discount_amount = 0
+ if invoice.get("apply_discount_on"):
+ item.discount_amount = item.base_amount - item.base_net_amount
+
+ item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty
+
+ item.gross_amount = abs(item.taxable_value) + item.discount_amount
+ item.taxable_value = abs(item.taxable_value)
item.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N"
item.serial_no = ""
@@ -352,7 +352,14 @@
def get_invoice_value_details(invoice):
invoice_value_details = frappe._dict(dict())
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get("items")]))
- invoice_value_details.invoice_discount_amt = 0
+ if (
+ invoice.apply_discount_on == "Grand Total"
+ and invoice.discount_amount
+ and invoice.get("is_cash_or_non_trade_discount")
+ ):
+ invoice_value_details.invoice_discount_amt = invoice.discount_amount
+ else:
+ invoice_value_details.invoice_discount_amt = 0
invoice_value_details.round_off = invoice.base_rounding_adjustment
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 0262469..f1586fc 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -1060,8 +1060,16 @@
considered_rows.append(prev_row_id)
for item in doc.get("items"):
- proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
- total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
+ if (
+ doc.apply_discount_on == "Grand Total"
+ and doc.discount_amount
+ and doc.get("is_cash_or_non_trade_discount")
+ ):
+ proportionate_value = item.base_amount if doc.base_total else item.qty
+ total_value = doc.base_total if doc.base_total else doc.total_qty
+ else:
+ proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
+ total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
applicable_charges = flt(
flt(