mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
Merge branch 'develop' into accounting_dimension_consistency
This commit is contained in:
@@ -99,7 +99,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
|||||||
if doctype == "Budget":
|
if doctype == "Budget":
|
||||||
add_dimension_to_budget_doctype(df.copy(), doc)
|
add_dimension_to_budget_doctype(df.copy(), doc)
|
||||||
else:
|
else:
|
||||||
create_custom_field(doctype, df)
|
create_custom_field(doctype, df, ignore_validate=True)
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ def add_dimension_to_budget_doctype(df, doc):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
create_custom_field("Budget", df)
|
create_custom_field("Budget", df, ignore_validate=True)
|
||||||
|
|
||||||
property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options")
|
property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options")
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"automatically_fetch_payment_terms",
|
"automatically_fetch_payment_terms",
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"enable_common_party_accounting",
|
"enable_common_party_accounting",
|
||||||
"enable_discount_accounting",
|
|
||||||
"report_setting_section",
|
"report_setting_section",
|
||||||
"use_custom_cash_flow",
|
"use_custom_cash_flow",
|
||||||
"deferred_accounting_settings_section",
|
"deferred_accounting_settings_section",
|
||||||
@@ -272,13 +271,6 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Create Ledger Entries for Change Amount"
|
"label": "Create Ledger Entries for Change Amount"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
|
||||||
"fieldname": "enable_discount_accounting",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Enable Discount Accounting"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
|
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
|
||||||
@@ -354,7 +346,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-02-04 12:32:36.805652",
|
"modified": "2022-04-08 14:45:06.796418",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class AccountsSettings(Document):
|
|||||||
|
|
||||||
self.validate_stale_days()
|
self.validate_stale_days()
|
||||||
self.enable_payment_schedule_in_print()
|
self.enable_payment_schedule_in_print()
|
||||||
self.toggle_discount_accounting_fields()
|
|
||||||
self.validate_pending_reposts()
|
self.validate_pending_reposts()
|
||||||
|
|
||||||
def validate_stale_days(self):
|
def validate_stale_days(self):
|
||||||
@@ -52,74 +51,6 @@ class AccountsSettings(Document):
|
|||||||
validate_fields_for_doctype=False,
|
validate_fields_for_doctype=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def toggle_discount_accounting_fields(self):
|
|
||||||
enable_discount_accounting = cint(self.enable_discount_accounting)
|
|
||||||
|
|
||||||
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
|
|
||||||
make_property_setter(
|
|
||||||
doctype,
|
|
||||||
"discount_account",
|
|
||||||
"hidden",
|
|
||||||
not (enable_discount_accounting),
|
|
||||||
"Check",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
if enable_discount_accounting:
|
|
||||||
make_property_setter(
|
|
||||||
doctype,
|
|
||||||
"discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"eval: doc.discount_amount",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
make_property_setter(
|
|
||||||
doctype,
|
|
||||||
"discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
|
||||||
make_property_setter(
|
|
||||||
doctype,
|
|
||||||
"additional_discount_account",
|
|
||||||
"hidden",
|
|
||||||
not (enable_discount_accounting),
|
|
||||||
"Check",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
if enable_discount_accounting:
|
|
||||||
make_property_setter(
|
|
||||||
doctype,
|
|
||||||
"additional_discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"eval: doc.discount_amount",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
make_property_setter(
|
|
||||||
doctype,
|
|
||||||
"additional_discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
make_property_setter(
|
|
||||||
"Item",
|
|
||||||
"default_discount_account",
|
|
||||||
"hidden",
|
|
||||||
not (enable_discount_accounting),
|
|
||||||
"Check",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_pending_reposts(self):
|
def validate_pending_reposts(self):
|
||||||
if self.acc_frozen_upto:
|
if self.acc_frozen_upto:
|
||||||
check_pending_reposting(self.acc_frozen_upto)
|
check_pending_reposting(self.acc_frozen_upto)
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import flt, fmt_money, getdate, nowdate
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
|
from frappe.utils import flt, fmt_money, getdate
|
||||||
|
|
||||||
|
import erpnext
|
||||||
|
|
||||||
form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
|
form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
|
||||||
|
|
||||||
@@ -76,6 +79,52 @@ class BankClearance(Document):
|
|||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
|
||||||
|
|
||||||
|
loan_disbursements = (
|
||||||
|
frappe.qb.from_(loan_disbursement)
|
||||||
|
.select(
|
||||||
|
ConstantColumn("Loan Disbursement").as_("payment_document"),
|
||||||
|
loan_disbursement.name.as_("payment_entry"),
|
||||||
|
loan_disbursement.disbursed_amount.as_("credit"),
|
||||||
|
ConstantColumn(0).as_("debit"),
|
||||||
|
loan_disbursement.reference_number.as_("cheque_number"),
|
||||||
|
loan_disbursement.reference_date.as_("cheque_date"),
|
||||||
|
loan_disbursement.disbursement_date.as_("posting_date"),
|
||||||
|
loan_disbursement.applicant.as_("against_account"),
|
||||||
|
)
|
||||||
|
.where(loan_disbursement.docstatus == 1)
|
||||||
|
.where(loan_disbursement.disbursement_date >= self.from_date)
|
||||||
|
.where(loan_disbursement.disbursement_date <= self.to_date)
|
||||||
|
.where(loan_disbursement.clearance_date.isnull())
|
||||||
|
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
|
||||||
|
.orderby(loan_disbursement.disbursement_date)
|
||||||
|
.orderby(loan_disbursement.name, frappe.qb.desc)
|
||||||
|
).run(as_dict=1)
|
||||||
|
|
||||||
|
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
||||||
|
|
||||||
|
loan_repayments = (
|
||||||
|
frappe.qb.from_(loan_repayment)
|
||||||
|
.select(
|
||||||
|
ConstantColumn("Loan Repayment").as_("payment_document"),
|
||||||
|
loan_repayment.name.as_("payment_entry"),
|
||||||
|
loan_repayment.amount_paid.as_("debit"),
|
||||||
|
ConstantColumn(0).as_("credit"),
|
||||||
|
loan_repayment.reference_number.as_("cheque_number"),
|
||||||
|
loan_repayment.reference_date.as_("cheque_date"),
|
||||||
|
loan_repayment.applicant.as_("against_account"),
|
||||||
|
loan_repayment.posting_date,
|
||||||
|
)
|
||||||
|
.where(loan_repayment.docstatus == 1)
|
||||||
|
.where(loan_repayment.clearance_date.isnull())
|
||||||
|
.where(loan_repayment.posting_date >= self.from_date)
|
||||||
|
.where(loan_repayment.posting_date <= self.to_date)
|
||||||
|
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
|
||||||
|
.orderby(loan_repayment.posting_date)
|
||||||
|
.orderby(loan_repayment.name, frappe.qb.desc)
|
||||||
|
).run(as_dict=1)
|
||||||
|
|
||||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||||
if self.include_pos_transactions:
|
if self.include_pos_transactions:
|
||||||
pos_sales_invoices = frappe.db.sql(
|
pos_sales_invoices = frappe.db.sql(
|
||||||
@@ -114,20 +163,29 @@ class BankClearance(Document):
|
|||||||
|
|
||||||
entries = sorted(
|
entries = sorted(
|
||||||
list(payment_entries)
|
list(payment_entries)
|
||||||
+ list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
|
+ list(journal_entries)
|
||||||
key=lambda k: k["posting_date"] or getdate(nowdate()),
|
+ list(pos_sales_invoices)
|
||||||
|
+ list(pos_purchase_invoices)
|
||||||
|
+ list(loan_disbursements)
|
||||||
|
+ list(loan_repayments),
|
||||||
|
key=lambda k: getdate(k["posting_date"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.set("payment_entries", [])
|
self.set("payment_entries", [])
|
||||||
self.total_amount = 0.0
|
self.total_amount = 0.0
|
||||||
|
default_currency = erpnext.get_default_currency()
|
||||||
|
|
||||||
for d in entries:
|
for d in entries:
|
||||||
row = self.append("payment_entries", {})
|
row = self.append("payment_entries", {})
|
||||||
|
|
||||||
amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
|
amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
|
||||||
|
|
||||||
|
if not d.get("account_currency"):
|
||||||
|
d.account_currency = default_currency
|
||||||
|
|
||||||
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
|
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
|
||||||
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
|
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
|
||||||
|
d.posting_date = getdate(d.posting_date)
|
||||||
|
|
||||||
d.pop("credit")
|
d.pop("credit")
|
||||||
d.pop("debit")
|
d.pop("debit")
|
||||||
|
|||||||
@@ -1,9 +1,96 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import add_months, getdate
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
from erpnext.loan_management.doctype.loan.test_loan import (
|
||||||
|
create_loan,
|
||||||
|
create_loan_accounts,
|
||||||
|
create_loan_type,
|
||||||
|
create_repayment_entry,
|
||||||
|
make_loan_disbursement_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestBankClearance(unittest.TestCase):
|
class TestBankClearance(unittest.TestCase):
|
||||||
pass
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
make_bank_account()
|
||||||
|
create_loan_accounts()
|
||||||
|
create_loan_masters()
|
||||||
|
add_transactions()
|
||||||
|
|
||||||
|
# Basic test case to test if bank clearance tool doesn't break
|
||||||
|
# Detailed test can be added later
|
||||||
|
def test_bank_clearance(self):
|
||||||
|
bank_clearance = frappe.get_doc("Bank Clearance")
|
||||||
|
bank_clearance.account = "_Test Bank Clearance - _TC"
|
||||||
|
bank_clearance.from_date = add_months(getdate(), -1)
|
||||||
|
bank_clearance.to_date = getdate()
|
||||||
|
bank_clearance.get_payment_entries()
|
||||||
|
self.assertEqual(len(bank_clearance.payment_entries), 3)
|
||||||
|
|
||||||
|
|
||||||
|
def make_bank_account():
|
||||||
|
if not frappe.db.get_value("Account", "_Test Bank Clearance - _TC"):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_type": "Bank",
|
||||||
|
"account_name": "_Test Bank Clearance",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"parent_account": "Bank Accounts - _TC",
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def create_loan_masters():
|
||||||
|
create_loan_type(
|
||||||
|
"Clearance Loan",
|
||||||
|
2000000,
|
||||||
|
13.5,
|
||||||
|
25,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
"Cash",
|
||||||
|
"_Test Bank Clearance - _TC",
|
||||||
|
"_Test Bank Clearance - _TC",
|
||||||
|
"Loan Account - _TC",
|
||||||
|
"Interest Income Account - _TC",
|
||||||
|
"Penalty Income Account - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_transactions():
|
||||||
|
make_payment_entry()
|
||||||
|
make_loan()
|
||||||
|
|
||||||
|
|
||||||
|
def make_loan():
|
||||||
|
loan = create_loan(
|
||||||
|
"_Test Customer",
|
||||||
|
"Clearance Loan",
|
||||||
|
280000,
|
||||||
|
"Repay Over Number of Periods",
|
||||||
|
20,
|
||||||
|
applicant_type="Customer",
|
||||||
|
)
|
||||||
|
loan.submit()
|
||||||
|
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
|
||||||
|
repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
|
||||||
|
repayment_entry.save()
|
||||||
|
repayment_entry.submit()
|
||||||
|
|
||||||
|
|
||||||
|
def make_payment_entry():
|
||||||
|
pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
|
||||||
|
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
|
||||||
|
pe.reference_no = "Conrad Oct 18"
|
||||||
|
pe.reference_date = "2018-10-24"
|
||||||
|
pe.insert()
|
||||||
|
pe.submit()
|
||||||
|
|||||||
@@ -669,7 +669,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||||
|
|
||||||
enable_discount_accounting = cint(
|
enable_discount_accounting = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||||
)
|
)
|
||||||
provisional_accounting_for_non_stock_items = cint(
|
provisional_accounting_for_non_stock_items = cint(
|
||||||
frappe.db.get_value(
|
frappe.db.get_value(
|
||||||
@@ -1159,7 +1159,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# tax table gl entries
|
# tax table gl entries
|
||||||
valuation_tax = {}
|
valuation_tax = {}
|
||||||
enable_discount_accounting = cint(
|
enable_discount_accounting = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||||
)
|
)
|
||||||
|
|
||||||
for tax in self.get("taxes"):
|
for tax in self.get("taxes"):
|
||||||
@@ -1252,7 +1252,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
def enable_discount_accounting(self):
|
def enable_discount_accounting(self):
|
||||||
if not hasattr(self, "_enable_discount_accounting"):
|
if not hasattr(self, "_enable_discount_accounting"):
|
||||||
self._enable_discount_accounting = cint(
|
self._enable_discount_accounting = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._enable_discount_accounting
|
return self._enable_discount_accounting
|
||||||
@@ -1369,7 +1369,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if (
|
if (
|
||||||
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
|
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
|
||||||
):
|
):
|
||||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
|
self.company, "Purchase Invoice", self.name
|
||||||
|
)
|
||||||
|
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import change_settings
|
||||||
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -336,8 +337,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
||||||
|
|
||||||
|
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||||
def test_purchase_invoice_with_discount_accounting_enabled(self):
|
def test_purchase_invoice_with_discount_accounting_enabled(self):
|
||||||
enable_discount_accounting()
|
|
||||||
|
|
||||||
discount_account = create_account(
|
discount_account = create_account(
|
||||||
account_name="Discount Account",
|
account_name="Discount Account",
|
||||||
@@ -353,10 +354,10 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||||
enable_discount_accounting(enable=0)
|
|
||||||
|
|
||||||
|
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||||
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
||||||
enable_discount_accounting()
|
|
||||||
additional_discount_account = create_account(
|
additional_discount_account = create_account(
|
||||||
account_name="Discount Account",
|
account_name="Discount Account",
|
||||||
parent_account="Indirect Expenses - _TC",
|
parent_account="Indirect Expenses - _TC",
|
||||||
@@ -1588,12 +1589,6 @@ def unlink_payment_on_cancel_of_invoice(enable=1):
|
|||||||
accounts_settings.save()
|
accounts_settings.save()
|
||||||
|
|
||||||
|
|
||||||
def enable_discount_accounting(enable=1):
|
|
||||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
|
||||||
accounts_settings.enable_discount_accounting = enable
|
|
||||||
accounts_settings.save()
|
|
||||||
|
|
||||||
|
|
||||||
def make_purchase_invoice(**args):
|
def make_purchase_invoice(**args):
|
||||||
pi = frappe.new_doc("Purchase Invoice")
|
pi = frappe.new_doc("Purchase Invoice")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -1051,7 +1051,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
enable_discount_accounting = cint(
|
enable_discount_accounting = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
)
|
)
|
||||||
|
|
||||||
for tax in self.get("taxes"):
|
for tax in self.get("taxes"):
|
||||||
@@ -1097,7 +1097,7 @@ class SalesInvoice(SellingController):
|
|||||||
def make_item_gl_entries(self, gl_entries):
|
def make_item_gl_entries(self, gl_entries):
|
||||||
# income account gl entries
|
# income account gl entries
|
||||||
enable_discount_accounting = cint(
|
enable_discount_accounting = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
@@ -1276,7 +1276,7 @@ class SalesInvoice(SellingController):
|
|||||||
def enable_discount_accounting(self):
|
def enable_discount_accounting(self):
|
||||||
if not hasattr(self, "_enable_discount_accounting"):
|
if not hasattr(self, "_enable_discount_accounting"):
|
||||||
self._enable_discount_accounting = cint(
|
self._enable_discount_accounting = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._enable_discount_accounting
|
return self._enable_discount_accounting
|
||||||
@@ -1466,7 +1466,9 @@ class SalesInvoice(SellingController):
|
|||||||
and self.base_rounding_adjustment
|
and self.base_rounding_adjustment
|
||||||
and not self.is_internal_transfer()
|
and not self.is_internal_transfer()
|
||||||
):
|
):
|
||||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
|
self.company, "Sales Invoice", self.name
|
||||||
|
)
|
||||||
|
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
|
from frappe.tests.utils import change_settings
|
||||||
from frappe.utils import add_days, flt, getdate, nowdate
|
from frappe.utils import add_days, flt, getdate, nowdate
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -1977,6 +1978,13 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||||
|
|
||||||
def test_rounding_adjustment_3(self):
|
def test_rounding_adjustment_3(self):
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
||||||
|
create_dimension,
|
||||||
|
disable_dimension,
|
||||||
|
)
|
||||||
|
|
||||||
|
create_dimension()
|
||||||
|
|
||||||
si = create_sales_invoice(do_not_save=True)
|
si = create_sales_invoice(do_not_save=True)
|
||||||
si.items = []
|
si.items = []
|
||||||
for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
|
for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
|
||||||
@@ -2004,6 +2012,10 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"included_in_print_rate": 1,
|
"included_in_print_rate": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
si.cost_center = "_Test Cost Center 2 - _TC"
|
||||||
|
si.location = "Block 1"
|
||||||
|
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
self.assertEqual(si.net_total, 4007.16)
|
self.assertEqual(si.net_total, 4007.16)
|
||||||
@@ -2039,6 +2051,18 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(debit_credit_diff, 0)
|
self.assertEqual(debit_credit_diff, 0)
|
||||||
|
|
||||||
|
round_off_gle = frappe.db.get_value(
|
||||||
|
"GL Entry",
|
||||||
|
{"voucher_type": "Sales Invoice", "voucher_no": si.name, "account": "Round Off - _TC"},
|
||||||
|
["cost_center", "location"],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC")
|
||||||
|
self.assertEqual(round_off_gle.location, "Block 1")
|
||||||
|
|
||||||
|
disable_dimension()
|
||||||
|
|
||||||
def test_sales_invoice_with_shipping_rule(self):
|
def test_sales_invoice_with_shipping_rule(self):
|
||||||
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
|
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
|
||||||
|
|
||||||
@@ -2684,12 +2708,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC"
|
sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||||
def test_sales_invoice_with_discount_accounting_enabled(self):
|
def test_sales_invoice_with_discount_accounting_enabled(self):
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
|
||||||
enable_discount_accounting,
|
|
||||||
)
|
|
||||||
|
|
||||||
enable_discount_accounting()
|
|
||||||
|
|
||||||
discount_account = create_account(
|
discount_account = create_account(
|
||||||
account_name="Discount Account",
|
account_name="Discount Account",
|
||||||
@@ -2705,14 +2725,10 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||||
enable_discount_accounting(enable=0)
|
|
||||||
|
|
||||||
|
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||||
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
|
||||||
enable_discount_accounting,
|
|
||||||
)
|
|
||||||
|
|
||||||
enable_discount_accounting()
|
|
||||||
additional_discount_account = create_account(
|
additional_discount_account = create_account(
|
||||||
account_name="Discount Account",
|
account_name="Discount Account",
|
||||||
parent_account="Indirect Expenses - _TC",
|
parent_account="Indirect Expenses - _TC",
|
||||||
@@ -2743,7 +2759,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||||
enable_discount_accounting(enable=0)
|
|
||||||
|
|
||||||
def test_asset_depreciation_on_sale_with_pro_rata(self):
|
def test_asset_depreciation_on_sale_with_pro_rata(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -163,10 +163,15 @@ def get_party_details(party, party_type, args=None):
|
|||||||
def get_tax_template(posting_date, args):
|
def get_tax_template(posting_date, args):
|
||||||
"""Get matching tax rule"""
|
"""Get matching tax rule"""
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
from_date = to_date = posting_date
|
||||||
|
if not posting_date:
|
||||||
|
from_date = "1900-01-01"
|
||||||
|
to_date = "4000-01-01"
|
||||||
|
|
||||||
conditions = [
|
conditions = [
|
||||||
"""(from_date is null or from_date <= '{0}')
|
"""(from_date is null or from_date <= '{0}')
|
||||||
and (to_date is null or to_date >= '{0}')""".format(
|
and (to_date is null or to_date >= '{1}')""".format(
|
||||||
posting_date
|
from_date, to_date
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_
|
|||||||
|
|
||||||
def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
||||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
gl_map[0].company
|
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
|
||||||
)
|
)
|
||||||
round_off_account_exists = False
|
round_off_account_exists = False
|
||||||
round_off_gle = frappe._dict()
|
round_off_gle = frappe._dict()
|
||||||
@@ -392,14 +392,43 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
update_accounting_dimensions(round_off_gle)
|
||||||
|
|
||||||
if not round_off_account_exists:
|
if not round_off_account_exists:
|
||||||
gl_map.append(round_off_gle)
|
gl_map.append(round_off_gle)
|
||||||
|
|
||||||
|
|
||||||
def get_round_off_account_and_cost_center(company):
|
def update_accounting_dimensions(round_off_gle):
|
||||||
|
dimensions = get_accounting_dimensions()
|
||||||
|
meta = frappe.get_meta(round_off_gle["voucher_type"])
|
||||||
|
has_all_dimensions = True
|
||||||
|
|
||||||
|
for dimension in dimensions:
|
||||||
|
if not meta.has_field(dimension):
|
||||||
|
has_all_dimensions = False
|
||||||
|
|
||||||
|
if dimensions and has_all_dimensions:
|
||||||
|
dimension_values = frappe.db.get_value(
|
||||||
|
round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
|
||||||
|
)
|
||||||
|
|
||||||
|
for dimension in dimensions:
|
||||||
|
round_off_gle[dimension] = dimension_values.get(dimension)
|
||||||
|
|
||||||
|
|
||||||
|
def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
|
||||||
round_off_account, round_off_cost_center = frappe.get_cached_value(
|
round_off_account, round_off_cost_center = frappe.get_cached_value(
|
||||||
"Company", company, ["round_off_account", "round_off_cost_center"]
|
"Company", company, ["round_off_account", "round_off_cost_center"]
|
||||||
) or [None, None]
|
) or [None, None]
|
||||||
|
|
||||||
|
meta = frappe.get_meta(voucher_type)
|
||||||
|
|
||||||
|
# Give first preference to parent cost center for round off GLE
|
||||||
|
if meta.has_field("cost_center"):
|
||||||
|
parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
|
||||||
|
if parent_cost_center:
|
||||||
|
round_off_cost_center = parent_cost_center
|
||||||
|
|
||||||
if not round_off_account:
|
if not round_off_account:
|
||||||
frappe.throw(_("Please mention Round Off Account in Company"))
|
frappe.throw(_("Please mention Round Off Account in Company"))
|
||||||
|
|
||||||
|
|||||||
@@ -831,9 +831,9 @@ def get_party_shipping_address(doctype, name):
|
|||||||
"where "
|
"where "
|
||||||
"dl.link_doctype=%s "
|
"dl.link_doctype=%s "
|
||||||
"and dl.link_name=%s "
|
"and dl.link_name=%s "
|
||||||
'and dl.parenttype="Address" '
|
"and dl.parenttype='Address' "
|
||||||
"and ifnull(ta.disabled, 0) = 0 and"
|
"and ifnull(ta.disabled, 0) = 0 and"
|
||||||
'(ta.address_type="Shipping" or ta.is_shipping_address=1) '
|
"(ta.address_type='Shipping' or ta.is_shipping_address=1) "
|
||||||
"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
|
"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
|
||||||
(doctype, name),
|
(doctype, name),
|
||||||
)
|
)
|
||||||
@@ -881,11 +881,11 @@ def get_default_contact(doctype, name):
|
|||||||
"""
|
"""
|
||||||
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
|
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
|
||||||
FROM `tabDynamic Link` dl
|
FROM `tabDynamic Link` dl
|
||||||
INNER JOIN tabContact c ON c.name = dl.parent
|
INNER JOIN `tabContact` c ON c.name = dl.parent
|
||||||
WHERE
|
WHERE
|
||||||
dl.link_doctype=%s AND
|
dl.link_doctype=%s AND
|
||||||
dl.link_name=%s AND
|
dl.link_name=%s AND
|
||||||
dl.parenttype = "Contact"
|
dl.parenttype = 'Contact'
|
||||||
ORDER BY is_primary_contact DESC, is_billing_contact DESC
|
ORDER BY is_primary_contact DESC, is_billing_contact DESC
|
||||||
""",
|
""",
|
||||||
(doctype, name),
|
(doctype, name),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"maintain_same_rate",
|
"maintain_same_rate",
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
|
"enable_discount_accounting",
|
||||||
"subcontract",
|
"subcontract",
|
||||||
"backflush_raw_materials_of_subcontract_based_on",
|
"backflush_raw_materials_of_subcontract_based_on",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
@@ -133,6 +134,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
||||||
|
"fieldname": "enable_discount_accounting",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Discount Accounting for Buying"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@@ -140,7 +148,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-01-27 17:57:58.367048",
|
"modified": "2022-04-14 15:56:42.340223",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
|||||||
@@ -5,10 +5,15 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import cint
|
||||||
|
|
||||||
|
|
||||||
class BuyingSettings(Document):
|
class BuyingSettings(Document):
|
||||||
|
def on_update(self):
|
||||||
|
self.toggle_discount_accounting_fields()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
|
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
|
||||||
frappe.db.set_default(key, self.get(key, ""))
|
frappe.db.set_default(key, self.get(key, ""))
|
||||||
@@ -21,3 +26,60 @@ class BuyingSettings(Document):
|
|||||||
self.get("supp_master_name") == "Naming Series",
|
self.get("supp_master_name") == "Naming Series",
|
||||||
hide_name_field=False,
|
hide_name_field=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def toggle_discount_accounting_fields(self):
|
||||||
|
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||||
|
|
||||||
|
make_property_setter(
|
||||||
|
"Purchase Invoice Item",
|
||||||
|
"discount_account",
|
||||||
|
"hidden",
|
||||||
|
not (enable_discount_accounting),
|
||||||
|
"Check",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
if enable_discount_accounting:
|
||||||
|
make_property_setter(
|
||||||
|
"Purchase Invoice Item",
|
||||||
|
"discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"eval: doc.discount_amount",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
make_property_setter(
|
||||||
|
"Purchase Invoice Item",
|
||||||
|
"discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
make_property_setter(
|
||||||
|
"Purchase Invoice",
|
||||||
|
"additional_discount_account",
|
||||||
|
"hidden",
|
||||||
|
not (enable_discount_accounting),
|
||||||
|
"Check",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
if enable_discount_accounting:
|
||||||
|
make_property_setter(
|
||||||
|
"Purchase Invoice",
|
||||||
|
"additional_discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"eval: doc.discount_amount",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
make_property_setter(
|
||||||
|
"Purchase Invoice",
|
||||||
|
"additional_discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1079,9 +1079,14 @@ class AccountsController(TransactionBase):
|
|||||||
return amount, base_amount
|
return amount, base_amount
|
||||||
|
|
||||||
def make_discount_gl_entries(self, gl_entries):
|
def make_discount_gl_entries(self, gl_entries):
|
||||||
enable_discount_accounting = cint(
|
if self.doctype == "Purchase Invoice":
|
||||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
enable_discount_accounting = cint(
|
||||||
)
|
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||||
|
)
|
||||||
|
elif self.doctype == "Sales Invoice":
|
||||||
|
enable_discount_accounting = cint(
|
||||||
|
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
|
)
|
||||||
|
|
||||||
if enable_discount_accounting:
|
if enable_discount_accounting:
|
||||||
if self.doctype == "Purchase Invoice":
|
if self.doctype == "Purchase Invoice":
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ class Opportunity(TransactionBase):
|
|||||||
self.calculate_totals()
|
self.calculate_totals()
|
||||||
|
|
||||||
def map_fields(self):
|
def map_fields(self):
|
||||||
for field in self.meta.fields:
|
for field in self.meta.get_valid_columns():
|
||||||
if not self.get(field.fieldname):
|
if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field):
|
||||||
try:
|
try:
|
||||||
value = frappe.db.get_value(self.opportunity_from, self.party_name, field.fieldname)
|
value = frappe.db.get_value(self.opportunity_from, self.party_name, field)
|
||||||
frappe.db.set(self, field.fieldname, value)
|
frappe.db.set(self, field, value)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -753,7 +753,7 @@ class BOM(WebsiteGenerator):
|
|||||||
bom_item.include_item_in_manufacturing,
|
bom_item.include_item_in_manufacturing,
|
||||||
bom_item.sourced_by_supplier,
|
bom_item.sourced_by_supplier,
|
||||||
bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit
|
bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit
|
||||||
FROM `tabBOM Explosion Item` bom_item, tabBOM bom
|
FROM `tabBOM Explosion Item` bom_item, `tabBOM` bom
|
||||||
WHERE
|
WHERE
|
||||||
bom_item.parent = bom.name
|
bom_item.parent = bom.name
|
||||||
AND bom.name = %s
|
AND bom.name = %s
|
||||||
|
|||||||
@@ -365,5 +365,6 @@ erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
|
|||||||
erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
|
erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
|
||||||
erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
|
erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
|
||||||
erpnext.patches.v13_0.copy_custom_field_filters_to_website_item
|
erpnext.patches.v13_0.copy_custom_field_filters_to_website_item
|
||||||
|
erpnext.patches.v14_0.discount_accounting_separation
|
||||||
erpnext.patches.v14_0.delete_employee_transfer_property_doctype
|
erpnext.patches.v14_0.delete_employee_transfer_property_doctype
|
||||||
erpnext.patches.v13_0.create_accounting_dimensions_in_orders
|
erpnext.patches.v13_0.create_accounting_dimensions_in_orders
|
||||||
9
erpnext/patches/v14_0/discount_accounting_separation.py
Normal file
9
erpnext/patches/v14_0/discount_accounting_separation.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
doc = frappe.get_doc("Accounts Settings")
|
||||||
|
discount_account = doc.enable_discount_accounting
|
||||||
|
if discount_account:
|
||||||
|
for doctype in ["Buying Settings", "Selling Settings"]:
|
||||||
|
frappe.db.set_value(doctype, doctype, "enable_discount_accounting", 1, update_modified=False)
|
||||||
@@ -553,6 +553,7 @@ def validate_totals(einvoice):
|
|||||||
+ flt(value_details["CgstVal"])
|
+ flt(value_details["CgstVal"])
|
||||||
+ flt(value_details["SgstVal"])
|
+ flt(value_details["SgstVal"])
|
||||||
+ flt(value_details["IgstVal"])
|
+ flt(value_details["IgstVal"])
|
||||||
|
+ flt(value_details["CesVal"])
|
||||||
+ flt(value_details["OthChrg"])
|
+ flt(value_details["OthChrg"])
|
||||||
+ flt(value_details["RndOffAmt"])
|
+ flt(value_details["RndOffAmt"])
|
||||||
- flt(value_details["Discount"])
|
- flt(value_details["Discount"])
|
||||||
|
|||||||
@@ -1219,7 +1219,7 @@ def make_fixtures(company=None):
|
|||||||
try:
|
try:
|
||||||
doc = frappe.get_doc(d)
|
doc = frappe.get_doc(d)
|
||||||
doc.flags.ignore_permissions = True
|
doc.flags.ignore_permissions = True
|
||||||
doc.insert()
|
doc.insert(ignore_if_duplicate=True)
|
||||||
except frappe.NameError:
|
except frappe.NameError:
|
||||||
frappe.clear_messages()
|
frappe.clear_messages()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
"column_break_5",
|
"column_break_5",
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
"allow_against_multiple_purchase_orders",
|
"allow_against_multiple_purchase_orders",
|
||||||
"hide_tax_id"
|
"hide_tax_id",
|
||||||
|
"enable_discount_accounting"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -164,6 +165,13 @@
|
|||||||
"fieldname": "editable_bundle_item_rates",
|
"fieldname": "editable_bundle_item_rates",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Calculate Product Bundle Price based on Child Items' Rates"
|
"label": "Calculate Product Bundle Price based on Child Items' Rates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
||||||
|
"fieldname": "enable_discount_accounting",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Discount Accounting for Selling"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@@ -171,7 +179,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-02-04 15:41:59.939261",
|
"modified": "2022-04-14 16:01:29.405642",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class SellingSettings(Document):
|
|||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.toggle_hide_tax_id()
|
self.toggle_hide_tax_id()
|
||||||
self.toggle_editable_rate_for_bundle_items()
|
self.toggle_editable_rate_for_bundle_items()
|
||||||
|
self.toggle_discount_accounting_fields()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
for key in [
|
for key in [
|
||||||
@@ -58,3 +59,60 @@ class SellingSettings(Document):
|
|||||||
"Check",
|
"Check",
|
||||||
validate_fields_for_doctype=False,
|
validate_fields_for_doctype=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def toggle_discount_accounting_fields(self):
|
||||||
|
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||||
|
|
||||||
|
make_property_setter(
|
||||||
|
"Sales Invoice Item",
|
||||||
|
"discount_account",
|
||||||
|
"hidden",
|
||||||
|
not (enable_discount_accounting),
|
||||||
|
"Check",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
if enable_discount_accounting:
|
||||||
|
make_property_setter(
|
||||||
|
"Sales Invoice Item",
|
||||||
|
"discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"eval: doc.discount_amount",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
make_property_setter(
|
||||||
|
"Sales Invoice Item",
|
||||||
|
"discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
make_property_setter(
|
||||||
|
"Sales Invoice",
|
||||||
|
"additional_discount_account",
|
||||||
|
"hidden",
|
||||||
|
not (enable_discount_accounting),
|
||||||
|
"Check",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
if enable_discount_accounting:
|
||||||
|
make_property_setter(
|
||||||
|
"Sales Invoice",
|
||||||
|
"additional_discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"eval: doc.discount_amount",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
make_property_setter(
|
||||||
|
"Sales Invoice",
|
||||||
|
"additional_discount_account",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
"",
|
||||||
|
"Code",
|
||||||
|
validate_fields_for_doctype=False,
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
from frappe.query_builder.functions import Cast_
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
|
|
||||||
@@ -48,35 +50,57 @@ class ItemPrice(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def check_duplicates(self):
|
def check_duplicates(self):
|
||||||
conditions = (
|
|
||||||
"""where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
|
|
||||||
)
|
|
||||||
|
|
||||||
for field in [
|
item_price = frappe.qb.DocType("Item Price")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(item_price)
|
||||||
|
.select(item_price.price_list_rate)
|
||||||
|
.where(
|
||||||
|
(item_price.item_code == self.item_code)
|
||||||
|
& (item_price.price_list == self.price_list)
|
||||||
|
& (item_price.name != self.name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data_fields = (
|
||||||
"uom",
|
"uom",
|
||||||
"valid_from",
|
"valid_from",
|
||||||
"valid_upto",
|
"valid_upto",
|
||||||
"packing_unit",
|
|
||||||
"customer",
|
"customer",
|
||||||
"supplier",
|
"supplier",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
]:
|
|
||||||
if self.get(field):
|
|
||||||
conditions += " and {0} = %({0})s ".format(field)
|
|
||||||
else:
|
|
||||||
conditions += "and (isnull({0}) or {0} = '')".format(field)
|
|
||||||
|
|
||||||
price_list_rate = frappe.db.sql(
|
|
||||||
"""
|
|
||||||
select price_list_rate
|
|
||||||
from `tabItem Price`
|
|
||||||
{conditions}
|
|
||||||
""".format(
|
|
||||||
conditions=conditions
|
|
||||||
),
|
|
||||||
self.as_dict(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
number_fields = ["packing_unit"]
|
||||||
|
|
||||||
|
for field in data_fields:
|
||||||
|
if self.get(field):
|
||||||
|
query = query.where(item_price[field] == self.get(field))
|
||||||
|
else:
|
||||||
|
query = query.where(
|
||||||
|
Criterion.any(
|
||||||
|
[
|
||||||
|
item_price[field].isnull(),
|
||||||
|
Cast_(item_price[field], "varchar") == "",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for field in number_fields:
|
||||||
|
if self.get(field):
|
||||||
|
query = query.where(item_price[field] == self.get(field))
|
||||||
|
else:
|
||||||
|
query = query.where(
|
||||||
|
Criterion.any(
|
||||||
|
[
|
||||||
|
item_price[field].isnull(),
|
||||||
|
item_price[field] == 0,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
price_list_rate = query.run(as_dict=True)
|
||||||
|
|
||||||
if price_list_rate:
|
if price_list_rate:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
|
|||||||
@@ -1167,7 +1167,7 @@ class StockEntry(StockController):
|
|||||||
from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s
|
from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s
|
||||||
where i.name=%s
|
where i.name=%s
|
||||||
and i.disabled=0
|
and i.disabled=0
|
||||||
and (i.end_of_life is null or i.end_of_life='0000-00-00' or i.end_of_life > %s)""",
|
and (i.end_of_life is null or i.end_of_life<'1900-01-01' or i.end_of_life > %s)""",
|
||||||
(self.company, args.get("item_code"), nowdate()),
|
(self.company, args.get("item_code"), nowdate()),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.core.page.permission_manager.permission_manager import reset
|
from frappe.core.page.permission_manager.permission_manager import reset
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
|
from frappe.query_builder.functions import CombineDatetime
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, today
|
from frappe.utils import add_days, today
|
||||||
from frappe.utils.data import add_to_date
|
from frappe.utils.data import add_to_date
|
||||||
@@ -1126,6 +1126,63 @@ class TestStockLedgerEntry(FrappeTestCase):
|
|||||||
# original amount
|
# original amount
|
||||||
self.assertEqual(50, _get_stock_credit(final_consumption))
|
self.assertEqual(50, _get_stock_credit(final_consumption))
|
||||||
|
|
||||||
|
def test_tie_breaking(self):
|
||||||
|
frappe.flags.dont_execute_stock_reposts = True
|
||||||
|
self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
|
||||||
|
|
||||||
|
item = make_item().name
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
posting_date = "2022-01-01"
|
||||||
|
posting_time = "00:00:01"
|
||||||
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
|
def ordered_qty_after_transaction():
|
||||||
|
return (
|
||||||
|
frappe.qb.from_(sle)
|
||||||
|
.select("qty_after_transaction")
|
||||||
|
.where((sle.item_code == item) & (sle.warehouse == warehouse) & (sle.is_cancelled == 0))
|
||||||
|
.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
|
||||||
|
.orderby(sle.creation)
|
||||||
|
).run(pluck=True)
|
||||||
|
|
||||||
|
first = make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
to_warehouse=warehouse,
|
||||||
|
qty=10,
|
||||||
|
posting_time=posting_time,
|
||||||
|
posting_date=posting_date,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
second = make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
to_warehouse=warehouse,
|
||||||
|
qty=1,
|
||||||
|
posting_date=posting_date,
|
||||||
|
posting_time=posting_time,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
first.submit()
|
||||||
|
second.submit()
|
||||||
|
|
||||||
|
self.assertEqual([10, 11], ordered_qty_after_transaction())
|
||||||
|
|
||||||
|
first.cancel()
|
||||||
|
self.assertEqual([1], ordered_qty_after_transaction())
|
||||||
|
|
||||||
|
backdated = make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
to_warehouse=warehouse,
|
||||||
|
qty=1,
|
||||||
|
posting_date="2021-01-01",
|
||||||
|
posting_time=posting_time,
|
||||||
|
)
|
||||||
|
self.assertEqual([1, 2], ordered_qty_after_transaction())
|
||||||
|
|
||||||
|
backdated.cancel()
|
||||||
|
self.assertEqual([1], ordered_qty_after_transaction())
|
||||||
|
|
||||||
|
|
||||||
def create_repack_entry(**args):
|
def create_repack_entry(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ from typing import Optional, Set, Tuple
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now, nowdate
|
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now, nowdate
|
||||||
from pypika import CustomFunction
|
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
|
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
|
||||||
@@ -1158,16 +1157,15 @@ def get_batch_incoming_rate(
|
|||||||
item_code, warehouse, batch_no, posting_date, posting_time, creation=None
|
item_code, warehouse, batch_no, posting_date, posting_time, creation=None
|
||||||
):
|
):
|
||||||
|
|
||||||
Timestamp = CustomFunction("timestamp", ["date", "time"])
|
|
||||||
|
|
||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
timestamp_condition = Timestamp(sle.posting_date, sle.posting_time) < Timestamp(
|
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
|
||||||
posting_date, posting_time
|
posting_date, posting_time
|
||||||
)
|
)
|
||||||
if creation:
|
if creation:
|
||||||
timestamp_condition |= (
|
timestamp_condition |= (
|
||||||
Timestamp(sle.posting_date, sle.posting_time) == Timestamp(posting_date, posting_time)
|
CombineDatetime(sle.posting_date, sle.posting_time)
|
||||||
|
== CombineDatetime(posting_date, posting_time)
|
||||||
) & (sle.creation < creation)
|
) & (sle.creation < creation)
|
||||||
|
|
||||||
batch_details = (
|
batch_details = (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from typing import Dict, Optional
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import CombineDatetime
|
||||||
from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
|
from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -143,12 +144,10 @@ def get_stock_balance(
|
|||||||
|
|
||||||
|
|
||||||
def get_serial_nos_data_after_transactions(args):
|
def get_serial_nos_data_after_transactions(args):
|
||||||
from pypika import CustomFunction
|
|
||||||
|
|
||||||
serial_nos = set()
|
serial_nos = set()
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
Timestamp = CustomFunction("timestamp", ["date", "time"])
|
|
||||||
|
|
||||||
stock_ledger_entries = (
|
stock_ledger_entries = (
|
||||||
frappe.qb.from_(sle)
|
frappe.qb.from_(sle)
|
||||||
@@ -157,7 +156,8 @@ def get_serial_nos_data_after_transactions(args):
|
|||||||
(sle.item_code == args.item_code)
|
(sle.item_code == args.item_code)
|
||||||
& (sle.warehouse == args.warehouse)
|
& (sle.warehouse == args.warehouse)
|
||||||
& (
|
& (
|
||||||
Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)
|
CombineDatetime(sle.posting_date, sle.posting_time)
|
||||||
|
< CombineDatetime(args.posting_date, args.posting_time)
|
||||||
)
|
)
|
||||||
& (sle.is_cancelled == 0)
|
& (sle.is_cancelled == 0)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user