Compare commits

..

1 Commits

Author SHA1 Message Date
rohitwaghchaure
4a748f45c9 fix: linter issue 2024-03-12 15:28:33 +05:30
774 changed files with 9680 additions and 16330 deletions

View File

@@ -28,7 +28,4 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
494bd9ef78313436f0424b918f200dab8fc7c20b
# bulk format python code with black
baec607ff5905b1c67531096a9cf50ec7ff00a5d
# ruff
960ef14b7a68cfec9e309ec12845f521cb6a721c
baec607ff5905b1c67531096a9cf50ec7ff00a5d

View File

@@ -55,18 +55,29 @@ repos:
erpnext/templates/includes/.*
)$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.0
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: ruff
name: "Run ruff import sorter"
args: ["--select=I", "--fix"]
- id: flake8
additional_dependencies: [
'flake8-bugbear',
'flake8-tuple',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- id: ruff
name: "Run ruff linter"
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"
- id: ruff-format
name: "Run ruff formatter"
ci:
autoupdate_schedule: weekly

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "15.26.1"
__version__ = "15.16.2"
def get_default_company(user=None):
@@ -36,8 +36,10 @@ def get_default_cost_center(company):
if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {}
if company not in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value("Company", company, "cost_center")
if not company in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value(
"Company", company, "cost_center"
)
return frappe.flags.company_cost_center[company]
@@ -45,7 +47,7 @@ def get_company_currency(company):
"""Returns the default company currency"""
if not frappe.flags.company_currency:
frappe.flags.company_currency = {}
if company not in frappe.flags.company_currency:
if not company in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value(
"Company", company, "default_currency", cache=True
)
@@ -79,7 +81,7 @@ def is_perpetual_inventory_enabled(company):
if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {}
if company not in frappe.local.enable_perpetual_inventory:
if not company in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = (
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
)
@@ -94,7 +96,7 @@ def get_default_finance_book(company=None):
if not hasattr(frappe.local, "default_finance_book"):
frappe.local.default_finance_book = {}
if company not in frappe.local.default_finance_book:
if not company in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value(
"Company", company, "default_finance_book"
)
@@ -106,7 +108,7 @@ def get_party_account_type(party_type):
if not hasattr(frappe.local, "party_account_types"):
frappe.local.party_account_types = {}
if party_type not in frappe.local.party_account_types:
if not party_type in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = (
frappe.db.get_value("Party Type", party_type, "account_type") or ""
)

View File

@@ -11,14 +11,14 @@ class ERPNextAddress(Address):
def validate(self):
self.validate_reference()
self.update_compnay_address()
super().validate()
super(ERPNextAddress, self).validate()
def link_address(self):
"""Link address based on owner"""
if self.is_your_company_address:
return
return super().link_address()
return super(ERPNextAddress, self).link_address()
def update_compnay_address(self):
for link in self.get("links"):
@@ -26,11 +26,11 @@ class ERPNextAddress(Address):
self.is_your_company_address = 1
def validate_reference(self):
if self.is_your_company_address and not [row for row in self.links if row.link_doctype == "Company"]:
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
frappe.throw(
_(
"Address needs to be linked to a Company. Please add a row for Company in the Links table."
),
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"),
)

View File

@@ -37,7 +37,7 @@ def get(
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
account = filters.get("account")
filters.get("company")
company = filters.get("company")
if not account and chart_name:
frappe.throw(
@@ -83,6 +83,7 @@ def build_result(account, dates, gl_entries):
# get balances in debit
for entry in gl_entries:
# entry date is after the current pointer, so move the pointer forward
while getdate(entry.posting_date) > result[date_index][0]:
date_index += 1
@@ -132,6 +133,8 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
dates = [get_period_ending(from_date, timegrain)]
while getdate(dates[-1]) < getdate(to_date):
date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
date = get_period_ending(
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
)
dates.append(date)
return dates

View File

@@ -24,10 +24,14 @@ from erpnext.accounts.utils import get_account_currency
def validate_service_stop_date(doc):
"""Validates service_stop_date for Purchase Invoice and Sales Invoice"""
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
old_stop_dates = {}
old_doc = frappe.db.get_all(f"{doc.doctype} Item", {"parent": doc.name}, ["name", "service_stop_date"])
old_doc = frappe.db.get_all(
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
)
for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or ""
@@ -58,14 +62,16 @@ def build_conditions(process_type, account, company):
)
if account:
conditions += f"AND {deferred_account}='{account}'"
conditions += "AND %s='%s'" % (deferred_account, account)
elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=""):
def convert_deferred_expense_to_expense(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -75,14 +81,16 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
# check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list(
f"""
"""
select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{conditions}
""",
{0}
""".format(
conditions
),
(end_date, start_date),
) # nosec
@@ -95,7 +103,9 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
send_mail(deferred_process)
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""):
def convert_deferred_revenue_to_income(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -105,14 +115,16 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list(
f"""
"""
select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{conditions}
""",
{0}
""".format(
conditions
),
(end_date, start_date),
) # nosec
@@ -231,7 +243,9 @@ def calculate_monthly_amount(
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
@@ -251,13 +265,17 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount"))
amount = flt(
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
@@ -278,22 +296,26 @@ def get_already_booked_amount(doc, item):
gl_entries_details = frappe.db.sql(
"""
select sum({}) as total_credit, sum({}) as total_credit_in_account_currency, voucher_detail_no
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
group by voucher_detail_no
""".format(total_credit_debit, total_credit_debit_currency),
""".format(
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
journal_entry_details = frappe.db.sql(
"""
SELECT sum(c.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
and p.docstatus < 2 group by reference_detail_no
""".format(total_credit_debit, total_credit_debit_currency),
""".format(
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
@@ -315,7 +337,9 @@ def get_already_booked_amount(doc, item):
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
@@ -360,45 +384,45 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
)
if not amount:
prev_posting_date = end_date
else:
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
return
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:
@@ -416,7 +440,9 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
via_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
)
submit_journal_entry = cint(frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries"))
submit_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
)
book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on"
)
@@ -436,7 +462,9 @@ def process_deferred_accounting(posting_date=None):
posting_date = today()
if not cint(
frappe.db.get_singles_value("Accounts Settings", "automatically_process_deferred_accounting_entry")
frappe.db.get_singles_value(
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
):
return
@@ -559,13 +587,16 @@ def book_revenue_via_journal_entry(
deferred_process=None,
submit="No",
):
if amount == 0:
return
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date
journal_entry.company = doc.company
journal_entry.voucher_type = "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process
debit_entry = {
@@ -614,6 +645,7 @@ def book_revenue_via_journal_entry(
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == "Sales Invoice":
credit_account, debit_account = frappe.db.get_value(
"Sales Invoice Item",

View File

@@ -88,7 +88,7 @@ class Account(NestedSet):
if frappe.local.flags.ignore_update_nsm:
return
else:
super().on_update()
super(Account, self).on_update()
def onload(self):
frozen_accounts_modifier = frappe.db.get_value(
@@ -218,7 +218,9 @@ class Account(NestedSet):
def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies
if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
if (
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
):
return
ancestors = get_root_company(self.company)
if ancestors:
@@ -416,7 +418,7 @@ class Account(NestedSet):
if self.check_gle_exists():
throw(_("Account with existing transaction can not be deleted"))
super().on_trash(True)
super(Account, self).on_trash(True)
@frappe.whitelist()
@@ -424,8 +426,9 @@ class Account(NestedSet):
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = {}
and {} like {} order by name limit {} offset {}""".format("%s", searchfield, "%s", "%s", "%s"),
where is_group = 1 and docstatus != 2 and company = %s
and %s like %s order by name limit %s offset %s"""
% ("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, page_len, start),
as_list=1,
)
@@ -591,5 +594,7 @@ def sync_update_account_number_in_child(
if old_acc_number:
filters["account_number"] = old_acc_number
for d in frappe.db.get_values("Account", filters=filters, fieldname=["company", "name"], as_dict=True):
for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@@ -222,7 +222,7 @@ frappe.treeview_settings["Account"] = {
"General Ledger",
"Balance Sheet",
"Profit and Loss Statement",
"Cash Flow",
"Cash Flow Statement",
"Accounts Payable",
"Accounts Receivable",
]) {

View File

@@ -31,6 +31,7 @@ def create_charts(
"tax_rate",
"account_currency",
]:
account_number = cstr(child.get("account_number")).strip()
account_name, account_name_in_db = add_suffix_if_duplicate(
account_name, account_number, accounts
@@ -38,9 +39,7 @@ def create_charts(
is_group = identify_is_group(child)
report_type = (
"Balance Sheet"
if root_type in ["Asset", "Liability", "Equity"]
else "Profit and Loss"
"Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
)
account = frappe.get_doc(
@@ -142,7 +141,7 @@ def get_chart(chart_template, existing_company=None):
for fname in os.listdir(path):
fname = frappe.as_unicode(fname)
if fname.endswith(".json"):
with open(os.path.join(path, fname)) as f:
with open(os.path.join(path, fname), "r") as f:
chart = f.read()
if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree")
@@ -174,7 +173,7 @@ def get_charts_for_country(country, with_standard=False):
for fname in os.listdir(path):
fname = frappe.as_unicode(fname)
if (fname.startswith(country_code) or fname.startswith(country)) and fname.endswith(".json"):
with open(os.path.join(path, fname)) as f:
with open(os.path.join(path, fname), "r") as f:
_get_chart_name(f.read())
# if more than one charts, returned then add the standard
@@ -248,13 +247,7 @@ def validate_bank_account(coa, bank_account):
def _get_account_names(account_master):
for account_name, child in account_master.items():
if account_name not in [
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
]:
if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
accounts.append(account_name)
_get_account_names(child)

View File

@@ -261,20 +261,28 @@ class TestAccount(unittest.TestCase):
acc.insert()
self.assertTrue(
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 4"})
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
)
)
self.assertTrue(
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 5"})
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
)
# Try renaming child company account
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
self.assertRaises(
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
)
# Rename child company account with allow_account_creation_against_child_company enabled
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
)
update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue(
@@ -283,7 +291,9 @@ class TestAccount(unittest.TestCase):
)
)
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
)
to_delete = [
"Test Group Account - _TC3",
@@ -308,7 +318,9 @@ class TestAccount(unittest.TestCase):
self.assertEqual(acc.account_currency, "INR")
# Make a JV against this account
make_journal_entry("Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True)
make_journal_entry(
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
)
acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save)

View File

@@ -40,12 +40,16 @@ class AccountClosingBalance(Document):
def make_closing_entries(closing_entries, voucher_name, company, closing_date):
accounting_dimensions = get_accounting_dimensions()
previous_closing_entries = get_previous_closing_entries(company, closing_date, accounting_dimensions)
previous_closing_entries = get_previous_closing_entries(
company, closing_date, accounting_dimensions
)
combined_entries = closing_entries + previous_closing_entries
merged_entries = aggregate_with_last_account_closing_balance(combined_entries, accounting_dimensions)
merged_entries = aggregate_with_last_account_closing_balance(
combined_entries, accounting_dimensions
)
for _key, value in merged_entries.items():
for key, value in merged_entries.items():
cle = frappe.new_doc("Account Closing Balance")
cle.update(value)
cle.update(value["dimensions"])

View File

@@ -57,12 +57,9 @@ frappe.ui.form.on("Accounting Dimension", {
}
},
label: function (frm) {
frm.set_value("fieldname", frappe.model.scrub(frm.doc.label));
},
document_type: function (frm) {
frm.set_value("label", frm.doc.document_type);
frm.set_value("fieldname", frappe.model.scrub(frm.doc.document_type));
frappe.db.get_value(
"Accounting Dimension",

View File

@@ -40,8 +40,7 @@ class AccountingDimension(Document):
self.set_fieldname_and_label()
def validate(self):
if self.document_type in (
*core_doctypes_list,
if self.document_type in core_doctypes_list + (
"Accounting Dimension",
"Project",
"Cost Center",
@@ -49,10 +48,13 @@ class AccountingDimension(Document):
"Company",
"Account",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
exists = frappe.db.get_value("Accounting Dimension", {"document_type": self.document_type}, ["name"])
exists = frappe.db.get_value(
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
)
if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension"))
@@ -111,6 +113,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
repostable_doctypes = get_allowed_types_from_settings()
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break"
else:
@@ -145,7 +148,7 @@ def add_dimension_to_budget_doctype(df, doc):
df.update(
{
"insert_after": "cost_center",
"depends_on": f"eval:doc.budget_against == '{doc.document_type}'",
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type),
}
)
@@ -179,17 +182,19 @@ def delete_accounting_dimension(doc):
frappe.db.sql(
"""
DELETE FROM `tabCustom Field`
WHERE fieldname = {}
AND dt IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname, *doclist]),
WHERE fieldname = %s
AND dt IN (%s)"""
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
)
frappe.db.sql(
"""
DELETE FROM `tabProperty Setter`
WHERE field_name = {}
AND doc_type IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname, *doclist]),
WHERE field_name = %s
AND doc_type IN (%s)"""
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
)
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
@@ -238,6 +243,7 @@ def get_doctypes_with_dimensions():
def get_accounting_dimensions(as_list=True, filters=None):
if not filters:
filters = {"disabled": 0}
@@ -266,6 +272,7 @@ def get_checks_for_pl_and_bs_accounts():
def get_dimension_with_children(doctype, dimensions):
if isinstance(dimensions, str):
dimensions = [dimensions]
@@ -273,7 +280,9 @@ def get_dimension_with_children(doctype, dimensions):
for dimension in dimensions:
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
children = frappe.get_all(
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
)
all_dimensions += [c.name for c in children]
return all_dimensions
@@ -281,10 +290,14 @@ def get_dimension_with_children(doctype, dimensions):
@frappe.whitelist()
def get_dimensions(with_cost_center_and_project=False):
c = frappe.qb.DocType("Accounting Dimension Detail")
p = frappe.qb.DocType("Accounting Dimension")
dimension_filters = (
frappe.qb.from_(p).select(p.label, p.fieldname, p.document_type).where(p.disabled == 0).run(as_dict=1)
frappe.qb.from_(p)
.select(p.label, p.fieldname, p.document_type)
.where(p.disabled == 0)
.run(as_dict=1)
)
default_dimensions = (
frappe.qb.from_(c)

View File

@@ -55,7 +55,9 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def create_accounting_dimension_filter():
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}):
if not frappe.db.get_value(
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",

View File

@@ -84,10 +84,7 @@ class AccountingPeriod(Document):
for doctype_for_closing in self.get_doctypes_for_closing():
self.append(
"closed_documents",
{
"document_type": doctype_for_closing.document_type,
"closed": doctype_for_closing.closed,
},
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
)

View File

@@ -34,7 +34,9 @@ class TestAccountingPeriod(unittest.TestCase):
ap1 = create_accounting_period(period_name="Test Accounting Period 2")
ap1.save()
doc = create_sales_invoice(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
doc = create_sales_invoice(
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
)
self.assertRaises(ClosedAccountingPeriod, doc.save)
def tearDown(self):

View File

@@ -3,23 +3,4 @@
frappe.ui.form.on("Accounts Settings", {
refresh: function (frm) {},
enable_immutable_ledger: function (frm) {
if (!frm.doc.enable_immutable_ledger) {
return;
}
let msg = __("Enabling this will change the way how cancelled transactions are handled.");
msg += " ";
msg += __("Please enable only if the understand the effects of enabling this.");
msg += "<br>";
msg += "Do you still want to enable immutable ledger?";
frappe.confirm(
msg,
() => {},
() => {
frm.set_value("enable_immutable_ledger", 0);
}
);
},
});

View File

@@ -12,7 +12,6 @@
"unlink_advance_payment_on_cancelation_of_order",
"column_break_13",
"delete_linked_ledger_entries",
"enable_immutable_ledger",
"invoicing_features_section",
"check_supplier_invoice_uniqueness",
"automatically_fetch_payment_terms",
@@ -106,7 +105,7 @@
},
{
"default": "0",
"description": "Enabling this ensures each Purchase Invoice has a unique value in Supplier Invoice No. field within a particular fiscal year",
"description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
"fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness"
@@ -455,13 +454,6 @@
"fieldname": "remarks_section",
"fieldtype": "Section Break",
"label": "Remarks Column Length"
},
{
"default": "0",
"description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well",
"fieldname": "enable_immutable_ledger",
"fieldtype": "Check",
"label": "Enable Immutable Ledger"
}
],
"icon": "icon-cog",
@@ -469,7 +461,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-05-11 23:19:44.673975",
"modified": "2024-01-30 14:04:26.553554",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -498,4 +490,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -39,7 +39,6 @@ class AccountsSettings(Document):
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
enable_common_party_accounting: DF.Check
enable_fuzzy_matching: DF.Check
enable_immutable_ledger: DF.Check
enable_party_matching: DF.Check
frozen_accounts_modifier: DF.Link | None
general_ledger_remarks_length: DF.Int

View File

@@ -54,7 +54,6 @@ class BankAccount(Document):
self.validate_company()
self.validate_iban()
self.validate_account()
self.update_default_bank_account()
def validate_account(self):
if self.account:
@@ -101,51 +100,19 @@ class BankAccount(Document):
if to_check % 97 != 1:
frappe.throw(_("IBAN is not valid"))
def update_default_bank_account(self):
if self.is_default and not self.disabled:
frappe.db.set_value(
"Bank Account",
{
"party_type": self.party_type,
"party": self.party,
"is_company_account": self.is_company_account,
"is_default": 1,
"disabled": 0,
},
"is_default",
0,
)
@frappe.whitelist()
def make_bank_account(doctype, docname):
doc = frappe.new_doc("Bank Account")
doc.party_type = doctype
doc.party = docname
doc.is_default = 1
return doc
def get_party_bank_account(party_type, party):
return frappe.db.get_value(
"Bank Account",
{"party_type": party_type, "party": party, "is_default": 1, "disabled": 0},
"name",
)
def get_default_company_bank_account(company, party_type, party):
default_company_bank_account = frappe.db.get_value(party_type, party, "default_bank_account")
if default_company_bank_account:
if company != frappe.get_cached_value("Bank Account", default_company_bank_account, "company"):
default_company_bank_account = None
if not default_company_bank_account:
default_company_bank_account = frappe.db.get_value(
"Bank Account", {"company": company, "is_company_account": 1, "is_default": 1}
)
return default_company_bank_account
return frappe.db.get_value(party_type, party, "default_bank_account")
@frappe.whitelist()

View File

@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
try:
bank_account.validate_iban()
except ValidationError:
msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(iban)
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban)
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()

View File

@@ -127,7 +127,7 @@ def get_payment_entries_for_bank_clearance(
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
journal_entries = frappe.db.sql(
f"""
"""
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
@@ -141,7 +141,9 @@ def get_payment_entries_for_bank_clearance(
and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""",
""".format(
condition=condition
),
{"account": account, "from": from_date, "to": to_date},
as_dict=1,
)
@@ -150,7 +152,7 @@ def get_payment_entries_for_bank_clearance(
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql(
f"""
"""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
@@ -165,7 +167,9 @@ def get_payment_entries_for_bank_clearance(
{condition}
order by
posting_date ASC, name DESC
""",
""".format(
condition=condition
),
{
"account": account,
"from": from_date,
@@ -235,7 +239,10 @@ def get_payment_entries_for_bank_clearance(
).run(as_dict=True)
entries = (
list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices)
list(payment_entries)
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(pos_purchase_invoices)
)
return entries

View File

@@ -68,7 +68,9 @@ class TestBankClearance(unittest.TestCase):
)
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 = create_repayment_entry(
loan.name, "_Test Customer", getdate(), loan.loan_amount
)
repayment_entry.save()
repayment_entry.submit()

View File

@@ -59,10 +59,6 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
);
frm.add_custom_button(__("Auto Reconcile"), function () {
if (!frm.doc.bank_account) {
frappe.msgprint(__("Please select Bank Account"));
return;
}
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
args: {

View File

@@ -26,7 +26,6 @@
{
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Company",
"options": "Company"
},
@@ -119,7 +118,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-04-28 14:40:50.910884",
"modified": "2023-03-07 11:02:24.535714",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",
@@ -140,4 +139,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -81,7 +81,9 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
def get_account_balance(bank_account, till_date):
# returns account balance till the specified date
account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1})
filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@@ -94,7 +96,10 @@ def get_account_balance(bank_account, till_date):
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = (
flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
flt(balance_as_per_system)
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
)
return bank_bal
@@ -495,12 +500,12 @@ def check_matching(
bank_account,
company,
transaction,
document_types=None,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
):
exact_match = True if "exact_match" in document_types else False
@@ -533,21 +538,23 @@ def check_matching(
for query in queries:
matching_vouchers.extend(query.run(as_dict=True))
return sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
return (
sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
)
def get_queries(
bank_account,
company,
transaction,
document_types=None,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
exact_match=None,
common_filters=None,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
exact_match,
common_filters,
):
# get queries to get matching vouchers
account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
@@ -580,15 +587,15 @@ def get_matching_queries(
bank_account,
company,
transaction,
document_types=None,
exact_match=None,
account_from_to=None,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
common_filters=None,
document_types,
exact_match,
account_from_to,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
common_filters,
):
queries = []
currency = get_account_currency(bank_account)
@@ -647,13 +654,17 @@ def get_bt_matching_query(exact_match, transaction):
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0
ref_rank = frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
ref_rank = (
frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
)
unallocated_rank = (
frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0)
)
party_condition = (
(bt.party_type == transaction.party_type) & (bt.party == transaction.party) & bt.party.isnotnull()
(bt.party_type == transaction.party_type)
& (bt.party == transaction.party)
& bt.party.isnotnull()
)
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -705,7 +716,9 @@ def get_pe_matching_query(
amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0
party_condition = (
(pe.party_type == transaction.party_type) & (pe.party == transaction.party) & pe.party.isnotnull()
(pe.party_type == transaction.party_type)
& (pe.party == transaction.party)
& pe.party.isnotnull()
)
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -719,7 +732,7 @@ def get_pe_matching_query(
(ref_rank + amount_rank + party_rank + 1).as_("rank"),
ConstantColumn("Payment Entry").as_("doctype"),
pe.name,
pe.paid_amount_after_tax.as_("paid_amount"),
pe.paid_amount,
pe.reference_no,
pe.reference_date,
pe.party,
@@ -736,7 +749,7 @@ def get_pe_matching_query(
.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
)
if frappe.flags.auto_reconcile_vouchers is True:
if frappe.flags.auto_reconcile_vouchers == True:
query = query.where(ref_condition)
return query
@@ -797,7 +810,7 @@ def get_je_matching_query(
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
)
if frappe.flags.auto_reconcile_vouchers is True:
if frappe.flags.auto_reconcile_vouchers == True:
query = query.where(ref_condition)
return query

View File

@@ -1,11 +1,12 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, today
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, today
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
auto_reconcile_vouchers,
@@ -21,7 +22,7 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
self.create_customer()
self.clear_old_entries()
bank_dt = qb.DocType("Bank")
qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
q = qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
self.create_bank_account()
def tearDown(self):

View File

@@ -45,7 +45,7 @@ class BankStatementImport(DataImport):
# end: auto-generated types
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super(BankStatementImport, self).__init__(*args, **kwargs)
def validate(self):
doc_before_save = self.get_doc_before_save()
@@ -54,6 +54,7 @@ class BankStatementImport(DataImport):
or (doc_before_save and doc_before_save.import_file != self.import_file)
or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url)
):
template_options_dict = {}
column_to_field_map = {}
bank = frappe.get_doc("Bank", self.bank)
@@ -68,6 +69,7 @@ class BankStatementImport(DataImport):
self.validate_google_sheets_url()
def start_import(self):
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
self.import_file, self.google_sheets_url
)
@@ -123,7 +125,7 @@ def download_errored_template(data_import_name):
def parse_data_from_template(raw_data):
data = []
for _i, row in enumerate(raw_data):
for i, row in enumerate(raw_data):
if all(v in INVALID_VALUES for v in row):
# empty row
continue
@@ -133,7 +135,9 @@ def parse_data_from_template(raw_data):
return data
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
def start_import(
data_import, bank_account, import_file_path, google_sheets_url, bank, template_options
):
"""This method runs in background job"""
update_mapping_db(bank, template_options)
@@ -144,9 +148,6 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
data = parse_data_from_template(import_file.raw_data)
# Importer expects 'Data Import' class, which has 'payload_count' attribute
if not data_import.get("payload_count"):
data_import.payload_count = len(data) - 1
if import_file_path:
add_bank_account(data, bank_account)

View File

@@ -1,3 +1,5 @@
from typing import Tuple, Union
import frappe
from frappe.utils import flt
from rapidfuzz import fuzz, process
@@ -17,7 +19,7 @@ class AutoMatchParty:
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> tuple | None:
def match(self) -> Union[Tuple, None]:
result = None
result = AutoMatchbyAccountIBAN(
bank_party_account_number=self.bank_party_account_number,
@@ -48,7 +50,7 @@ class AutoMatchbyAccountIBAN:
result = self.match_account_in_party()
return result
def match_account_in_party(self) -> tuple | None:
def match_account_in_party(self) -> Union[Tuple, None]:
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
result = None
parties = get_parties_in_order(self.deposit)
@@ -95,7 +97,7 @@ class AutoMatchbyPartyNameDescription:
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> tuple | None:
def match(self) -> Union[Tuple, None]:
# fuzzy search by customer/supplier & employee
if not (self.bank_party_name or self.description):
return None
@@ -103,7 +105,7 @@ class AutoMatchbyPartyNameDescription:
result = self.match_party_name_desc_in_party()
return result
def match_party_name_desc_in_party(self) -> tuple | None:
def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
"""Fuzzy search party name and/or description against parties in the system"""
result = None
parties = get_parties_in_order(self.deposit)
@@ -127,7 +129,7 @@ class AutoMatchbyPartyNameDescription:
return result
def fuzzy_search_and_return_result(self, party, names, field) -> tuple | None:
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
skip = False
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
party_name, skip = self.process_fuzzy_result(result)
@@ -140,7 +142,7 @@ class AutoMatchbyPartyNameDescription:
party_name,
), skip
def process_fuzzy_result(self, result: list | None):
def process_fuzzy_result(self, result: Union[list, None]):
"""
If there are multiple valid close matches return None as result may be faulty.
Return the result only if one accurate match stands out.

View File

@@ -56,19 +56,17 @@ class BankTransaction(Document):
Bank Transaction should be on the same currency as the Bank Account.
"""
if self.currency and self.bank_account:
if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"):
account_currency = frappe.get_cached_value("Account", account, "account_currency")
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
account_currency = frappe.get_cached_value("Account", account, "account_currency")
if self.currency != account_currency:
frappe.throw(
_(
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
).format(
frappe.bold(self.currency),
frappe.bold(self.bank_account),
frappe.bold(account_currency),
)
if self.currency != account_currency:
frappe.throw(
_(
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
).format(
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
)
)
def set_status(self):
if self.docstatus == 2:
@@ -182,7 +180,7 @@ class BankTransaction(Document):
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
for payment_entry in to_remove:
self.remove(payment_entry)
self.remove(to_remove)
@frappe.whitelist()
def remove_payment_entries(self):
@@ -237,7 +235,9 @@ def get_clearance_details(transaction, payment_entry):
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
bt_allocations = get_total_allocated_amount(payment_entry.payment_document, payment_entry.payment_entry)
bt_allocations = get_total_allocated_amount(
payment_entry.payment_document, payment_entry.payment_entry
)
unallocated_amount = min(
transaction.unallocated_amount,
@@ -332,6 +332,7 @@ def get_total_allocated_amount(doctype, docname):
def get_paid_amount(payment_entry, currency, gl_bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
if payment_entry.payment_document == "Payment Entry":
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
@@ -370,7 +371,9 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
)
elif payment_entry.payment_document == "Loan Repayment":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
)
elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value(
@@ -380,7 +383,9 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
else:
frappe.throw(
f"Please reconcile {payment_entry.payment_document}: {payment_entry.payment_entry} manually"
"Please reconcile {0}: {1} manually".format(
payment_entry.payment_document, payment_entry.payment_entry
)
)

View File

@@ -18,12 +18,12 @@ def upload_bank_statement():
fcontent = frappe.local.uploaded_file
fname = frappe.local.uploaded_filename
if frappe.safe_encode(fname).lower().endswith(b"csv"):
if frappe.safe_encode(fname).lower().endswith("csv".encode("utf-8")):
from frappe.utils.csvutils import read_csv_content
rows = read_csv_content(fcontent, False)
elif frappe.safe_encode(fname).lower().endswith(b"xlsx"):
elif frappe.safe_encode(fname).lower().endswith("xlsx".encode("utf-8")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(fcontent=fcontent)

View File

@@ -436,7 +436,9 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
if not frappe.db.get_value(
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
):
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
mode_of_payment.save()

View File

@@ -1,100 +0,0 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Bisect Accounting Statements", {
onload(frm) {
frm.trigger("render_heatmap");
},
refresh(frm) {
frm.add_custom_button(__("Bisect Left"), () => {
frm.trigger("bisect_left");
});
frm.add_custom_button(__("Bisect Right"), () => {
frm.trigger("bisect_right");
});
frm.add_custom_button(__("Up"), () => {
frm.trigger("move_up");
});
frm.add_custom_button(__("Build Tree"), () => {
frm.trigger("build_tree");
});
},
render_heatmap(frm) {
let bisect_heatmap = frm.get_field("bisect_heatmap").$wrapper;
bisect_heatmap.addClass("bisect_heatmap_location");
// milliseconds in a day
let msiad = 24 * 60 * 60 * 1000;
let datapoints = {};
let fr_dt = new Date(frm.doc.from_date).getTime();
let to_dt = new Date(frm.doc.to_date).getTime();
let bisect_start = new Date(frm.doc.current_from_date).getTime();
let bisect_end = new Date(frm.doc.current_to_date).getTime();
for (let x = fr_dt; x <= to_dt; x += msiad) {
let epoch_in_seconds = x / 1000;
if (bisect_start <= x && x <= bisect_end) {
datapoints[epoch_in_seconds] = 1.0;
} else {
datapoints[epoch_in_seconds] = 0.0;
}
}
new frappe.Chart(".bisect_heatmap_location", {
type: "heatmap",
data: {
dataPoints: datapoints,
start: new Date(frm.doc.from_date),
end: new Date(frm.doc.to_date),
},
countLabel: "Bisecting",
discreteDomains: 1,
});
},
bisect_left(frm) {
frm.call({
doc: frm.doc,
method: "bisect_left",
freeze: true,
freeze_message: __("Bisecting Left ..."),
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
bisect_right(frm) {
frm.call({
doc: frm.doc,
freeze: true,
freeze_message: __("Bisecting Right ..."),
method: "bisect_right",
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
move_up(frm) {
frm.call({
doc: frm.doc,
freeze: true,
freeze_message: __("Moving up in tree ..."),
method: "move_up",
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
build_tree(frm) {
frm.call({
doc: frm.doc,
freeze: true,
freeze_message: __("Rebuilding BTree for period ..."),
method: "build_tree",
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
});

View File

@@ -1,194 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-09-15 21:28:28.054773",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section_break_cvfg",
"company",
"column_break_hcam",
"from_date",
"column_break_qxbi",
"to_date",
"column_break_iwny",
"algorithm",
"section_break_8ph9",
"current_node",
"section_break_ngid",
"bisect_heatmap",
"section_break_hmsy",
"bisecting_from",
"current_from_date",
"column_break_uqyd",
"bisecting_to",
"current_to_date",
"section_break_hbyo",
"heading_cppb",
"p_l_summary",
"column_break_aivo",
"balance_sheet_summary",
"b_s_summary",
"column_break_gvwx",
"difference_heading",
"difference"
],
"fields": [
{
"fieldname": "column_break_qxbi",
"fieldtype": "Column Break"
},
{
"fieldname": "from_date",
"fieldtype": "Datetime",
"label": "From Date"
},
{
"fieldname": "to_date",
"fieldtype": "Datetime",
"label": "To Date"
},
{
"default": "BFS",
"fieldname": "algorithm",
"fieldtype": "Select",
"label": "Algorithm",
"options": "BFS\nDFS"
},
{
"fieldname": "column_break_iwny",
"fieldtype": "Column Break"
},
{
"fieldname": "current_node",
"fieldtype": "Link",
"label": "Current Node",
"options": "Bisect Nodes"
},
{
"fieldname": "section_break_hmsy",
"fieldtype": "Section Break"
},
{
"fieldname": "current_from_date",
"fieldtype": "Datetime",
"read_only": 1
},
{
"fieldname": "current_to_date",
"fieldtype": "Datetime",
"read_only": 1
},
{
"fieldname": "column_break_uqyd",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_hbyo",
"fieldtype": "Section Break"
},
{
"fieldname": "p_l_summary",
"fieldtype": "Float",
"read_only": 1
},
{
"fieldname": "b_s_summary",
"fieldtype": "Float",
"read_only": 1
},
{
"fieldname": "difference",
"fieldtype": "Float",
"read_only": 1
},
{
"fieldname": "column_break_aivo",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_gvwx",
"fieldtype": "Column Break"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "column_break_hcam",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_ngid",
"fieldtype": "Section Break"
},
{
"fieldname": "section_break_8ph9",
"fieldtype": "Section Break",
"hidden": 1
},
{
"fieldname": "bisect_heatmap",
"fieldtype": "HTML",
"label": "Heatmap"
},
{
"fieldname": "heading_cppb",
"fieldtype": "Heading",
"label": "Profit and Loss Summary"
},
{
"fieldname": "balance_sheet_summary",
"fieldtype": "Heading",
"label": "Balance Sheet Summary"
},
{
"fieldname": "difference_heading",
"fieldtype": "Heading",
"label": "Difference"
},
{
"fieldname": "bisecting_from",
"fieldtype": "Heading",
"label": "Bisecting From"
},
{
"fieldname": "bisecting_to",
"fieldtype": "Heading",
"label": "Bisecting To"
},
{
"fieldname": "section_break_cvfg",
"fieldtype": "Section Break"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-12-01 16:49:54.073890",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bisect Accounting Statements",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Administrator",
"share": 1,
"write": 1
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,226 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import datetime
from collections import deque
from math import floor
import frappe
from dateutil.relativedelta import relativedelta
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate
from frappe.utils.data import guess_date_format
class BisectAccountingStatements(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
algorithm: DF.Literal["BFS", "DFS"]
b_s_summary: DF.Float
company: DF.Link | None
current_from_date: DF.Datetime | None
current_node: DF.Link | None
current_to_date: DF.Datetime | None
difference: DF.Float
from_date: DF.Datetime | None
p_l_summary: DF.Float
to_date: DF.Datetime | None
# end: auto-generated types
def validate(self):
self.validate_dates()
def validate_dates(self):
if getdate(self.from_date) > getdate(self.to_date):
frappe.throw(
_("From Date: {0} cannot be greater than To date: {1}").format(
frappe.bold(self.from_date), frappe.bold(self.to_date)
)
)
def bfs(self, from_date: datetime, to_date: datetime):
# Make Root node
node = frappe.new_doc("Bisect Nodes")
node.root = None
node.period_from_date = from_date
node.period_to_date = to_date
node.insert()
period_queue = deque([node])
while period_queue:
cur_node = period_queue.popleft()
delta = cur_node.period_to_date - cur_node.period_from_date
if delta.days == 0:
continue
else:
cur_floor = floor(delta.days / 2)
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
left_node = frappe.new_doc("Bisect Nodes")
left_node.period_from_date = cur_node.period_from_date
left_node.period_to_date = next_to_date
left_node.root = cur_node.name
left_node.generated = False
left_node.insert()
cur_node.left_child = left_node.name
period_queue.append(left_node)
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
right_node = frappe.new_doc("Bisect Nodes")
right_node.period_from_date = next_from_date
right_node.period_to_date = cur_node.period_to_date
right_node.root = cur_node.name
right_node.generated = False
right_node.insert()
cur_node.right_child = right_node.name
period_queue.append(right_node)
cur_node.save()
def dfs(self, from_date: datetime, to_date: datetime):
# Make Root node
node = frappe.new_doc("Bisect Nodes")
node.root = None
node.period_from_date = from_date
node.period_to_date = to_date
node.insert()
period_stack = [node]
while period_stack:
cur_node = period_stack.pop()
delta = cur_node.period_to_date - cur_node.period_from_date
if delta.days == 0:
continue
else:
cur_floor = floor(delta.days / 2)
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
left_node = frappe.new_doc("Bisect Nodes")
left_node.period_from_date = cur_node.period_from_date
left_node.period_to_date = next_to_date
left_node.root = cur_node.name
left_node.generated = False
left_node.insert()
cur_node.left_child = left_node.name
period_stack.append(left_node)
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
right_node = frappe.new_doc("Bisect Nodes")
right_node.period_from_date = next_from_date
right_node.period_to_date = cur_node.period_to_date
right_node.root = cur_node.name
right_node.generated = False
right_node.insert()
cur_node.right_child = right_node.name
period_stack.append(right_node)
cur_node.save()
@frappe.whitelist()
def build_tree(self):
frappe.db.delete("Bisect Nodes")
# Convert str to datetime format
dt_format = guess_date_format(self.from_date)
from_date = datetime.datetime.strptime(self.from_date, dt_format)
to_date = datetime.datetime.strptime(self.to_date, dt_format)
if self.algorithm == "BFS":
self.bfs(from_date, to_date)
if self.algorithm == "DFS":
self.dfs(from_date, to_date)
# set root as current node
root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0]
self.get_report_summary()
self.current_node = root.name
self.current_from_date = self.from_date
self.current_to_date = self.to_date
self.save()
def get_report_summary(self):
filters = {
"company": self.company,
"filter_based_on": "Date Range",
"period_start_date": self.current_from_date,
"period_end_date": self.current_to_date,
"periodicity": "Yearly",
}
pl_summary = frappe.get_doc("Report", "Profit and Loss Statement")
self.p_l_summary = pl_summary.execute_script_report(filters=filters)[5]
bs_summary = frappe.get_doc("Report", "Balance Sheet")
self.b_s_summary = bs_summary.execute_script_report(filters=filters)[5]
self.difference = abs(self.p_l_summary - self.b_s_summary)
def update_node(self):
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
current_node.balance_sheet_summary = self.b_s_summary
current_node.profit_loss_summary = self.p_l_summary
current_node.difference = self.difference
current_node.generated = True
current_node.save()
def current_node_has_summary_info(self):
"Assertion method"
return frappe.db.get_value("Bisect Nodes", self.current_node, "generated")
def fetch_summary_info_from_current_node(self):
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
self.p_l_summary = current_node.balance_sheet_summary
self.b_s_summary = current_node.profit_loss_summary
self.difference = abs(self.p_l_summary - self.b_s_summary)
def fetch_or_calculate(self):
if self.current_node_has_summary_info():
self.fetch_summary_info_from_current_node()
else:
self.get_report_summary()
self.update_node()
@frappe.whitelist()
def bisect_left(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
if cur_node.left_child is not None:
lft_node = frappe.get_doc("Bisect Nodes", cur_node.left_child)
self.current_node = cur_node.left_child
self.current_from_date = lft_node.period_from_date
self.current_to_date = lft_node.period_to_date
self.fetch_or_calculate()
self.save()
else:
frappe.msgprint(_("No more children on Left"))
@frappe.whitelist()
def bisect_right(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
if cur_node.right_child is not None:
rgt_node = frappe.get_doc("Bisect Nodes", cur_node.right_child)
self.current_node = cur_node.right_child
self.current_from_date = rgt_node.period_from_date
self.current_to_date = rgt_node.period_to_date
self.fetch_or_calculate()
self.save()
else:
frappe.msgprint(_("No more children on Right"))
@frappe.whitelist()
def move_up(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
if cur_node.root is not None:
root = frappe.get_doc("Bisect Nodes", cur_node.root)
self.current_node = cur_node.root
self.current_from_date = root.period_from_date
self.current_to_date = root.period_to_date
self.fetch_or_calculate()
self.save()
else:
frappe.msgprint(_("Reached Root"))

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestBisectAccountingStatements(FrappeTestCase):
pass

View File

@@ -1,8 +0,0 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Bisect Nodes", {
// refresh(frm) {
// },
// });

View File

@@ -1,97 +0,0 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2023-09-27 14:56:38.112462",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"root",
"left_child",
"right_child",
"period_from_date",
"period_to_date",
"difference",
"balance_sheet_summary",
"profit_loss_summary",
"generated"
],
"fields": [
{
"fieldname": "root",
"fieldtype": "Link",
"label": "Root",
"options": "Bisect Nodes"
},
{
"fieldname": "left_child",
"fieldtype": "Link",
"label": "Left Child",
"options": "Bisect Nodes"
},
{
"fieldname": "right_child",
"fieldtype": "Link",
"label": "Right Child",
"options": "Bisect Nodes"
},
{
"fieldname": "period_from_date",
"fieldtype": "Datetime",
"label": "Period_from_date"
},
{
"fieldname": "period_to_date",
"fieldtype": "Datetime",
"label": "Period To Date"
},
{
"fieldname": "difference",
"fieldtype": "Float",
"label": "Difference"
},
{
"fieldname": "balance_sheet_summary",
"fieldtype": "Float",
"label": "Balance Sheet Summary"
},
{
"fieldname": "profit_loss_summary",
"fieldtype": "Float",
"label": "Profit and Loss Summary"
},
{
"default": "0",
"fieldname": "generated",
"fieldtype": "Check",
"label": "Generated"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-12-01 17:46:12.437996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bisect Nodes",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,29 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class BisectNodes(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
balance_sheet_summary: DF.Float
difference: DF.Float
generated: DF.Check
left_child: DF.Link | None
name: DF.Int | None
period_from_date: DF.Datetime | None
period_to_date: DF.Datetime | None
profit_loss_summary: DF.Float
right_child: DF.Link | None
root: DF.Link | None
# end: auto-generated types
pass

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestBisectNodes(FrappeTestCase):
pass

View File

@@ -70,11 +70,10 @@ class Budget(Document):
select
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
where
ba.parent = b.name and b.docstatus < 2 and b.company = {} and {}={} and
b.fiscal_year={} and b.name != {} and ba.account in ({}) """.format(
"%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))
),
(self.company, budget_against, self.fiscal_year, self.name, *tuple(accounts)),
ba.parent = b.name and b.docstatus < 2 and b.company = %s and %s=%s and
b.fiscal_year=%s and b.name != %s and ba.account in (%s) """
% ("%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))),
(self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts),
as_dict=1,
)
@@ -97,14 +96,12 @@ class Budget(Document):
if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company:
frappe.throw(
_("Account {0} does not belongs to company {1}").format(d.account, self.company)
)
frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
elif account_details.report_type != "Profit and Loss":
frappe.throw(
_(
"Budget cannot be assigned against {0}, as it's not an Income or Expense account"
).format(d.account)
_("Budget cannot be assigned against {0}, as it's not an Income or Expense account").format(
d.account
)
)
if d.account in account_list:
@@ -177,24 +174,30 @@ def validate_expense_against_budget(args, expense_amount=0):
and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
):
doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = f"""and exists(select name from `tab{doctype}`
where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec
condition = """and exists(select name from `tab%s`
where lft<=%s and rgt>=%s and name=b.%s)""" % (
doctype,
lft,
rgt,
budget_against,
) # nosec
args.is_tree = True
else:
condition = f"and b.{budget_against}={frappe.db.escape(args.get(budget_against))}"
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
args.is_tree = False
args.budget_against_field = budget_against
args.budget_against_doctype = doctype
budget_records = frappe.db.sql(
f"""
"""
select
b.{budget_against} as budget_against, ba.budget_amount, b.monthly_distribution,
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request,
ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
@@ -207,7 +210,9 @@ def validate_expense_against_budget(args, expense_amount=0):
b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1
{condition}
""",
""".format(
condition=condition, budget_against_field=budget_against
),
(args.fiscal_year, args.account),
as_dict=True,
) # nosec
@@ -219,18 +224,12 @@ def validate_expense_against_budget(args, expense_amount=0):
def validate_budget_records(args, budget_records, expense_amount):
for budget in budget_records:
if flt(budget.budget_amount):
amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget)
args["for_material_request"] = budget.for_material_request
args["for_purchase_order"] = budget.for_purchase_order
if yearly_action in ("Stop", "Warn"):
compare_expense_with_budget(
args,
flt(budget.budget_amount),
_("Annual"),
yearly_action,
budget.budget_against,
expense_amount,
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
)
if monthly_action in ["Stop", "Warn"]:
@@ -241,32 +240,18 @@ def validate_budget_records(args, budget_records, expense_amount):
args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget(
args,
budget_amount,
_("Accumulated Monthly"),
monthly_action,
budget.budget_against,
expense_amount,
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
)
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
args.actual_expense, args.requested_amount, args.ordered_amount = get_actual_expense(args), 0, 0
if not amount:
args.requested_amount, args.ordered_amount = get_requested_amount(args), get_ordered_amount(args)
if args.get("doctype") == "Material Request" and args.for_material_request:
amount = args.requested_amount + args.ordered_amount
elif args.get("doctype") == "Purchase Order" and args.for_purchase_order:
amount = args.ordered_amount
total_expense = args.actual_expense + amount
actual_expense = get_actual_expense(args)
total_expense = actual_expense + amount
if total_expense > budget_amount:
if args.actual_expense > budget_amount:
if actual_expense > budget_amount:
error_tense = _("is already")
diff = args.actual_expense - budget_amount
diff = actual_expense - budget_amount
else:
error_tense = _("will be")
diff = total_expense - budget_amount
@@ -283,10 +268,9 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
frappe.bold(fmt_money(diff, currency=currency)),
)
msg += get_expense_breakup(args, currency, budget_against)
if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles(
frappe.session.user
if (
frappe.flags.exception_approver_role
and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)
):
action = "Warn"
@@ -296,83 +280,6 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded"))
def get_expense_breakup(args, currency, budget_against):
msg = "<hr>Total Expenses booked through - <ul>"
common_filters = frappe._dict(
{
args.budget_against_field: budget_against,
"account": args.account,
"company": args.company,
}
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"General Ledger",
label="Actual Expenses",
filters=common_filters.copy().update(
{
"from_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_start_date"),
"to_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_end_date"),
"is_cancelled": 0,
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.actual_expense, currency=currency))
+ "</li>"
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"Material Request",
label="Material Requests",
report_type="Report Builder",
doctype="Material Request",
filters=common_filters.copy().update(
{
"status": [["!=", "Stopped"]],
"docstatus": 1,
"material_request_type": "Purchase",
"schedule_date": [["fiscal year", "2023-2024"]],
"item_code": args.item_code,
"per_ordered": [["<", 100]],
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.requested_amount, currency=currency))
+ "</li>"
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"Purchase Order",
label="Unbilled Orders",
report_type="Report Builder",
doctype="Purchase Order",
filters=common_filters.copy().update(
{
"status": [["!=", "Closed"]],
"docstatus": 1,
"transaction_date": [["fiscal year", "2023-2024"]],
"item_code": args.item_code,
"per_billed": [["<", 100]],
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.ordered_amount, currency=currency))
+ "</li></ul>"
)
return msg
def get_actions(args, budget):
yearly_action = budget.action_if_annual_budget_exceeded
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
@@ -388,15 +295,31 @@ def get_actions(args, budget):
return yearly_action, monthly_action
def get_requested_amount(args):
def get_amount(args, budget):
amount = 0
if args.get("doctype") == "Material Request" and budget.for_material_request:
amount = (
get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args)
)
elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
amount = get_ordered_amount(args, budget) + get_actual_expense(args)
return amount
def get_requested_amount(args, budget):
item_code = args.get("item_code")
condition = get_other_condition(args, "Material Request")
condition = get_other_condition(args, budget, "Material Request")
data = frappe.db.sql(
""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {} and
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition),
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {0} and
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(
condition
),
item_code,
as_list=1,
)
@@ -404,15 +327,17 @@ def get_requested_amount(args):
return data[0][0] if data else 0
def get_ordered_amount(args):
def get_ordered_amount(args, budget):
item_code = args.get("item_code")
condition = get_other_condition(args, "Purchase Order")
condition = get_other_condition(args, budget, "Purchase Order")
data = frappe.db.sql(
f""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
from `tabPurchase Order Item` child, `tabPurchase Order` parent where
parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
and parent.status != 'Closed' and {condition}""",
and parent.status != 'Closed' and {0}""".format(
condition
),
item_code,
as_list=1,
)
@@ -420,12 +345,12 @@ def get_ordered_amount(args):
return data[0][0] if data else 0
def get_other_condition(args, for_doc):
def get_other_condition(args, budget, for_doc):
condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = args.get("budget_against_field")
if budget_against_field and args.get(budget_against_field):
condition += f" and child.{budget_against_field} = '{args.get(budget_against_field)}'"
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
if args.get("fiscal_year"):
date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
@@ -433,8 +358,12 @@ def get_other_condition(args, for_doc):
"Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
)
condition += f""" and parent.{date_field}
between '{start_date}' and '{end_date}' """
condition += """ and parent.%s
between '%s' and '%s' """ % (
date_field,
start_date,
end_date,
)
return condition
@@ -453,17 +382,21 @@ def get_actual_expense(args):
args.update(lft_rgt)
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
condition2 = """and exists(select name from `tab{doctype}`
where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})"""
and name=gle.{budget_against_field})""".format(
doctype=args.budget_against_doctype, budget_against_field=budget_against_field # nosec
)
else:
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
where name=gle.{budget_against_field} and
gle.{budget_against_field} = %({budget_against_field})s)"""
condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against} and
gle.{budget_against} = %({budget_against})s)""".format(
doctype=args.budget_against_doctype, budget_against=budget_against_field
)
amount = flt(
frappe.db.sql(
f"""
"""
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
where
@@ -474,7 +407,9 @@ def get_actual_expense(args):
and gle.company=%(company)s
and gle.docstatus=1
{condition2}
""",
""".format(
condition1=condition1, condition2=condition2
),
(args),
)[0][0]
) # nosec

View File

@@ -41,7 +41,9 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -61,7 +63,9 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -93,7 +97,9 @@ class TestBudget(unittest.TestCase):
)
fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
mr = frappe.get_doc(
@@ -132,7 +138,9 @@ class TestBudget(unittest.TestCase):
)
fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
@@ -150,7 +158,9 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
project = frappe.get_value("Project", {"project_name": "_Test Project"})
@@ -213,7 +223,7 @@ class TestBudget(unittest.TestCase):
if month > 9:
month = 9
for _i in range(month + 1):
for i in range(month + 1):
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
@@ -227,7 +237,9 @@ class TestBudget(unittest.TestCase):
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
self.assertRaises(BudgetError, jv.cancel)
@@ -243,7 +255,7 @@ class TestBudget(unittest.TestCase):
month = 9
project = frappe.get_value("Project", {"project_name": "_Test Project"})
for _i in range(month + 1):
for i in range(month + 1):
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
@@ -258,7 +270,9 @@ class TestBudget(unittest.TestCase):
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
self.assertRaises(BudgetError, jv.cancel)
@@ -270,7 +284,9 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -300,7 +316,9 @@ class TestBudget(unittest.TestCase):
).insert(ignore_permissions=True)
budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -405,11 +423,13 @@ def make_budget(**args):
fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project":
project_name = "{}%".format("_Test Project/" + fiscal_year)
project_name = "{0}%".format("_Test Project/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", project_name)})
else:
cost_center_name = "{}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", cost_center_name)})
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all(
"Budget", fields=["name"], filters={"name": ("like", cost_center_name)}
)
for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
@@ -431,18 +451,24 @@ def make_budget(**args):
budget.action_if_annual_budget_exceeded = "Stop"
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
budget.budget_against = budget_against
budget.append("accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000})
budget.append(
"accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000}
)
if args.applicable_on_material_request:
budget.applicable_on_material_request = 1
budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or "Warn"
budget.action_if_annual_budget_exceeded_on_mr = (
args.action_if_annual_budget_exceeded_on_mr or "Warn"
)
budget.action_if_accumulated_monthly_budget_exceeded_on_mr = (
args.action_if_accumulated_monthly_budget_exceeded_on_mr or "Warn"
)
if args.applicable_on_purchase_order:
budget.applicable_on_purchase_order = 1
budget.action_if_annual_budget_exceeded_on_po = args.action_if_annual_budget_exceeded_on_po or "Warn"
budget.action_if_annual_budget_exceeded_on_po = (
args.action_if_annual_budget_exceeded_on_po or "Warn"
)
budget.action_if_accumulated_monthly_budget_exceeded_on_po = (
args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
)

View File

@@ -38,7 +38,9 @@ class ChartofAccountsImporter(Document):
def validate(self):
if self.import_file:
get_coa("Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1)
get_coa(
"Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1
)
def validate_columns(data):
@@ -114,7 +116,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
file_path = file_doc.get_full_path()
data = []
with open(file_path) as in_file:
with open(file_path, "r") as in_file:
csv_reader = list(csv.reader(in_file))
headers = csv_reader[0]
del csv_reader[0] # delete top row and headers row
@@ -213,10 +215,10 @@ def build_forest(data):
for row in data:
account_name, parent_account, account_number, parent_account_number = row[0:4]
if account_number:
account_name = f"{account_number} - {account_name}"
account_name = "{} - {}".format(account_number, account_name)
if parent_account_number:
parent_account_number = cstr(parent_account_number).strip()
parent_account = f"{parent_account_number} - {parent_account}"
parent_account = "{} - {}".format(parent_account_number, parent_account)
if parent_account == account_name == child:
return [parent_account]
@@ -228,7 +230,7 @@ def build_forest(data):
frappe.bold(parent_account)
)
)
return [child, *parent_account_list]
return [child] + parent_account_list
charts_map, paths = {}, []
@@ -248,12 +250,12 @@ def build_forest(data):
) = i
if not account_name:
error_messages.append(f"Row {line_no}: Please enter Account Name")
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
name = account_name
if account_number:
account_number = cstr(account_number).strip()
account_name = f"{account_number} - {account_name}"
account_name = "{} - {}".format(account_number, account_name)
charts_map[account_name] = {}
charts_map[account_name]["account_name"] = name
@@ -350,9 +352,9 @@ def get_template(template_type, company):
def get_sample_template(writer, company):
currency = frappe.db.get_value("Company", company, "default_currency")
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv")) as f:
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f:
for row in f:
row = [*row.strip().split(","), currency]
row = row.strip().split(",") + [currency]
writer.writerow(row)
return writer
@@ -461,7 +463,7 @@ def unset_existing_data(company):
"Purchase Taxes and Charges Template",
]:
frappe.db.sql(
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
'''delete from `tab{0}` where `company`="%s"'''.format(doctype) % (company) # nosec
)

View File

@@ -66,71 +66,71 @@ def create_or_update_cheque_print_format(template_name):
cheque_print.html = """
<style>
.print-format {{
.print-format {
padding: 0px;
}}
@media screen {{
.print-format {{
}
@media screen {
.print-format {
padding: 0in;
}}
}}
}
}
</style>
<div style="position: relative; top:{starting_position_from_top_edge}cm">
<div style="width:{cheque_width}cm;height:{cheque_height}cm;">
<span style="top:{acc_pay_dist_from_top_edge}cm; left:{acc_pay_dist_from_left_edge}cm;
<div style="position: relative; top:%(starting_position_from_top_edge)scm">
<div style="width:%(cheque_width)scm;height:%(cheque_height)scm;">
<span style="top:%(acc_pay_dist_from_top_edge)scm; left:%(acc_pay_dist_from_left_edge)scm;
border-bottom: solid 1px;border-top:solid 1px; width:2cm;text-align: center; position: absolute;">
{message_to_show}
%(message_to_show)s
</span>
<span style="top:{date_dist_from_top_edge}cm; left:{date_dist_from_left_edge}cm;
<span style="top:%(date_dist_from_top_edge)scm; left:%(date_dist_from_left_edge)scm;
position: absolute;">
{{{{ frappe.utils.formatdate(doc.reference_date) or '' }}}}
{{ frappe.utils.formatdate(doc.reference_date) or '' }}
</span>
<span style="top:{acc_no_dist_from_top_edge}cm;left:{acc_no_dist_from_left_edge}cm;
<span style="top:%(acc_no_dist_from_top_edge)scm;left:%(acc_no_dist_from_left_edge)scm;
position: absolute; min-width: 6cm;">
{{{{ doc.account_no or '' }}}}
{{ doc.account_no or '' }}
</span>
<span style="top:{payer_name_from_top_edge}cm;left: {payer_name_from_left_edge}cm;
<span style="top:%(payer_name_from_top_edge)scm;left: %(payer_name_from_left_edge)scm;
position: absolute; min-width: 6cm;">
{{{{doc.party_name}}}}
{{doc.party_name}}
</span>
<span style="top:{amt_in_words_from_top_edge}cm; left:{amt_in_words_from_left_edge}cm;
position: absolute; display: block; width: {amt_in_word_width}cm;
line-height:{amt_in_words_line_spacing}cm; word-wrap: break-word;">
{{{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}}}
<span style="top:%(amt_in_words_from_top_edge)scm; left:%(amt_in_words_from_left_edge)scm;
position: absolute; display: block; width: %(amt_in_word_width)scm;
line-height:%(amt_in_words_line_spacing)scm; word-wrap: break-word;">
{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}
</span>
<span style="top:{amt_in_figures_from_top_edge}cm;left: {amt_in_figures_from_left_edge}cm;
<span style="top:%(amt_in_figures_from_top_edge)scm;left: %(amt_in_figures_from_left_edge)scm;
position: absolute; min-width: 4cm;">
{{{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}}}
{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}
</span>
<span style="top:{signatory_from_top_edge}cm;left: {signatory_from_left_edge}cm;
<span style="top:%(signatory_from_top_edge)scm;left: %(signatory_from_left_edge)scm;
position: absolute; min-width: 6cm;">
{{{{doc.company}}}}
{{doc.company}}
</span>
</div>
</div>""".format(
starting_position_from_top_edge=doc.starting_position_from_top_edge
</div>""" % {
"starting_position_from_top_edge": doc.starting_position_from_top_edge
if doc.cheque_size == "A4"
else 0.0,
cheque_width=doc.cheque_width,
cheque_height=doc.cheque_height,
acc_pay_dist_from_top_edge=doc.acc_pay_dist_from_top_edge,
acc_pay_dist_from_left_edge=doc.acc_pay_dist_from_left_edge,
message_to_show=doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
date_dist_from_top_edge=doc.date_dist_from_top_edge,
date_dist_from_left_edge=doc.date_dist_from_left_edge,
acc_no_dist_from_top_edge=doc.acc_no_dist_from_top_edge,
acc_no_dist_from_left_edge=doc.acc_no_dist_from_left_edge,
payer_name_from_top_edge=doc.payer_name_from_top_edge,
payer_name_from_left_edge=doc.payer_name_from_left_edge,
amt_in_words_from_top_edge=doc.amt_in_words_from_top_edge,
amt_in_words_from_left_edge=doc.amt_in_words_from_left_edge,
amt_in_word_width=doc.amt_in_word_width,
amt_in_words_line_spacing=doc.amt_in_words_line_spacing,
amt_in_figures_from_top_edge=doc.amt_in_figures_from_top_edge,
amt_in_figures_from_left_edge=doc.amt_in_figures_from_left_edge,
signatory_from_top_edge=doc.signatory_from_top_edge,
signatory_from_left_edge=doc.signatory_from_left_edge,
)
"cheque_width": doc.cheque_width,
"cheque_height": doc.cheque_height,
"acc_pay_dist_from_top_edge": doc.acc_pay_dist_from_top_edge,
"acc_pay_dist_from_left_edge": doc.acc_pay_dist_from_left_edge,
"message_to_show": doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
"date_dist_from_top_edge": doc.date_dist_from_top_edge,
"date_dist_from_left_edge": doc.date_dist_from_left_edge,
"acc_no_dist_from_top_edge": doc.acc_no_dist_from_top_edge,
"acc_no_dist_from_left_edge": doc.acc_no_dist_from_left_edge,
"payer_name_from_top_edge": doc.payer_name_from_top_edge,
"payer_name_from_left_edge": doc.payer_name_from_left_edge,
"amt_in_words_from_top_edge": doc.amt_in_words_from_top_edge,
"amt_in_words_from_left_edge": doc.amt_in_words_from_left_edge,
"amt_in_word_width": doc.amt_in_word_width,
"amt_in_words_line_spacing": doc.amt_in_words_line_spacing,
"amt_in_figures_from_top_edge": doc.amt_in_figures_from_top_edge,
"amt_in_figures_from_left_edge": doc.amt_in_figures_from_left_edge,
"signatory_from_top_edge": doc.signatory_from_top_edge,
"signatory_from_left_edge": doc.signatory_from_left_edge,
}
cheque_print.save(ignore_permissions=True)

View File

@@ -125,7 +125,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2024-04-24 10:55:54.083042",
"modified": "2022-01-31 13:22:58.916273",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
@@ -163,15 +163,6 @@
{
"read": 1,
"role": "Purchase User"
},
{
"email": 1,
"export": 1,
"print": 1,
"report": 1,
"role": "Employee",
"select": 1,
"share": 1
}
],
"search_fields": "parent_cost_center, is_group",

View File

@@ -34,7 +34,9 @@ class CostCenter(NestedSet):
def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(self.cost_center_number, self.cost_center_name, self.company)
self.name = get_autoname_with_number(
self.cost_center_number, self.cost_center_name, self.company
)
def validate(self):
self.validate_mandatory()
@@ -107,14 +109,14 @@ class CostCenter(NestedSet):
new_cost_center = get_name_with_abbr(newdn, self.company)
# Validate properties before merging
super().before_rename(olddn, new_cost_center, merge, "is_group")
super(CostCenter, self).before_rename(olddn, new_cost_center, merge, "is_group")
if not merge:
new_cost_center = get_name_with_number(new_cost_center, self.cost_center_number)
return new_cost_center
def after_rename(self, olddn, newdn, merge=False):
super().after_rename(olddn, newdn, merge)
super(CostCenter, self).after_rename(olddn, newdn, merge)
if not merge:
new_cost_center = frappe.db.get_value(

View File

@@ -10,6 +10,7 @@ test_records = frappe.get_test_records("Cost Center")
class TestCostCenter(unittest.TestCase):
def test_cost_center_creation_against_child_node(self):
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
frappe.get_doc(test_records[1]).insert()

View File

@@ -48,7 +48,7 @@ class CostCenterAllocation(Document):
# end: auto-generated types
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super(CostCenterAllocation, self).__init__(*args, **kwargs)
self._skip_from_date_validation = False
def validate(self):
@@ -63,7 +63,9 @@ class CostCenterAllocation(Document):
total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
if total_percentage != 100:
frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
frappe.throw(
_("Total percentage against cost centers should be 100"), WrongPercentageAllocation
)
def validate_from_date_based_on_existing_gle(self):
# Check if GLE exists against the main cost center

View File

@@ -3,36 +3,22 @@
frappe.ui.form.on("Currency Exchange Settings", {
service_provider: function (frm) {
frm.call({
method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint",
args: {
service_provider: frm.doc.service_provider,
use_http: frm.doc.use_http,
},
callback: function (r) {
if (r && r.message) {
if (frm.doc.service_provider == "exchangerate.host") {
let result = ["result"];
let params = {
date: "{transaction_date}",
from: "{from_currency}",
to: "{to_currency}",
};
add_param(frm, r.message, params, result);
} else if (frm.doc.service_provider == "frankfurter.app") {
let result = ["rates", "{to_currency}"];
let params = {
base: "{from_currency}",
symbols: "{to_currency}",
};
add_param(frm, r.message, params, result);
}
}
},
});
},
use_http: function (frm) {
frm.trigger("service_provider");
if (frm.doc.service_provider == "exchangerate.host") {
let result = ["result"];
let params = {
date: "{transaction_date}",
from: "{from_currency}",
to: "{to_currency}",
};
add_param(frm, "https://api.exchangerate.host/convert", params, result);
} else if (frm.doc.service_provider == "frankfurter.app") {
let result = ["rates", "{to_currency}"];
let params = {
base: "{from_currency}",
symbols: "{to_currency}",
};
add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
}
},
});

View File

@@ -9,7 +9,6 @@
"disabled",
"service_provider",
"api_endpoint",
"use_http",
"access_key",
"url",
"column_break_3",
@@ -92,19 +91,12 @@
"fieldname": "access_key",
"fieldtype": "Data",
"label": "Access Key"
},
{
"default": "0",
"depends_on": "eval: doc.service_provider != \"Custom\"",
"fieldname": "use_http",
"fieldtype": "Check",
"label": "Use HTTP Protocol"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-18 08:32:26.895076",
"modified": "2023-10-04 15:30:25.333860",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",

View File

@@ -31,7 +31,6 @@ class CurrencyExchangeSettings(Document):
result_key: DF.Table[CurrencyExchangeSettingsResult]
service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"]
url: DF.Data | None
use_http: DF.Check
# end: auto-generated types
def validate(self):
@@ -43,6 +42,7 @@ class CurrencyExchangeSettings(Document):
def set_parameters_and_result(self):
if self.service_provider == "exchangerate.host":
if not self.access_key:
frappe.throw(
_("Access Key is required for Service Provider: {0}").format(
@@ -53,7 +53,7 @@ class CurrencyExchangeSettings(Document):
self.set("result_key", [])
self.set("req_params", [])
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
self.api_endpoint = "https://api.exchangerate.host/convert"
self.append("result_key", {"key": "result"})
self.append("req_params", {"key": "access_key", "value": self.access_key})
self.append("req_params", {"key": "amount", "value": "1"})
@@ -64,7 +64,7 @@ class CurrencyExchangeSettings(Document):
self.set("result_key", [])
self.set("req_params", [])
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
self.api_endpoint = "https://frankfurter.app/{transaction_date}"
self.append("result_key", {"key": "rates"})
self.append("result_key", {"key": "{to_currency}"})
self.append("req_params", {"key": "base", "value": "{from_currency}"})
@@ -77,7 +77,9 @@ class CurrencyExchangeSettings(Document):
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
)
api_url = self.api_endpoint.format(transaction_date=nowdate(), to_currency="INR", from_currency="USD")
api_url = self.api_endpoint.format(
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
)
try:
response = requests.get(api_url, params=params)
@@ -97,23 +99,7 @@ class CurrencyExchangeSettings(Document):
]
except Exception:
frappe.throw(_("Invalid result key. Response:") + " " + response.text)
if not isinstance(value, int | float):
if not isinstance(value, (int, float)):
frappe.throw(_("Returned exchange rate is neither integer not float."))
self.url = response.url
@frappe.whitelist()
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
if service_provider == "exchangerate.host":
api = "api.exchangerate.host/convert"
elif service_provider == "frankfurter.app":
api = "frankfurter.app/{transaction_date}"
protocol = "https://"
if use_http:
protocol = "http://"
return protocol + api
return None

View File

@@ -139,22 +139,6 @@ class Dunning(AccountsController):
)
row.dunning_level = len(past_dunnings) + 1
def on_cancel(self):
super().on_cancel()
self.ignore_linked_doctypes = [
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
"Unreconcile Payment",
"Unreconcile Payment Entries",
"Payment Ledger Entry",
"Serial and Batch Bundle",
]
def resolve_dunning(doc, state):
"""

View File

@@ -109,7 +109,9 @@ class TestDunning(FrappeTestCase):
def create_dunning(overdue_days, dunning_type_name=None):
posting_date = add_days(today(), -1 * overdue_days)
sales_invoice = create_sales_invoice_against_cost_center(posting_date=posting_date, qty=1, rate=100)
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=posting_date, qty=1, rate=100
)
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
if dunning_type_name:

View File

@@ -268,6 +268,7 @@ class ExchangeRateRevaluation(Document):
# Handle Accounts with '0' balance in Account/Base Currency
for d in [x for x in account_details if x.zero_balance]:
if d.balance != 0:
current_exchange_rate = new_exchange_rate = 0
@@ -280,8 +281,7 @@ class ExchangeRateRevaluation(Document):
new_balance_in_account_currency = 0
current_exchange_rate = (
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party)
or 0.0
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
)
gain_loss = new_balance_in_account_currency - (
@@ -335,7 +335,9 @@ class ExchangeRateRevaluation(Document):
revaluation_jv = self.make_jv_for_revaluation()
if revaluation_jv:
frappe.msgprint(f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}")
frappe.msgprint(
f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}"
)
return {
"revaluation_jv": revaluation_jv.name if revaluation_jv else None,
@@ -392,8 +394,7 @@ class ExchangeRateRevaluation(Document):
journal_account.update(
{
dr_or_cr: flt(
abs(d.get("balance_in_account_currency")),
d.precision("balance_in_account_currency"),
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
),
reverse_dr_or_cr: 0,
"debit": 0,
@@ -519,9 +520,7 @@ class ExchangeRateRevaluation(Document):
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
),
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": flt(
d.get("current_exchange_rate"), d.precision("current_exchange_rate")
),
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
@@ -599,7 +598,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
@frappe.whitelist()
def get_account_details(
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float | None = None
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float = None
):
if not (company and posting_date):
frappe.throw(_("Company and Posting Date is mandatory"))
@@ -612,7 +611,7 @@ def get_account_details(
frappe.throw(_("Party Type and Party is mandatory for {0} account").format(account_type))
account_details = {}
erpnext.get_company_currency(company)
company_currency = erpnext.get_company_currency(company)
account_details = {
"account_currency": account_currency,
@@ -626,22 +625,24 @@ def get_account_details(
rounding_loss_allowance=rounding_loss_allowance,
)
if account_balance and (account_balance[0].balance or account_balance[0].balance_in_account_currency):
if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance(
if account_balance and (
account_balance[0].balance or account_balance[0].balance_in_account_currency
):
account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance(
company, posting_date, account_balance
):
row = account_with_new_balance[0]
account_details.update(
{
"balance_in_base_currency": row["balance_in_base_currency"],
"balance_in_account_currency": row["balance_in_account_currency"],
"current_exchange_rate": row["current_exchange_rate"],
"new_exchange_rate": row["new_exchange_rate"],
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
"zero_balance": row["zero_balance"],
"gain_loss": row["gain_loss"],
}
)
)
row = account_with_new_balance[0]
account_details.update(
{
"balance_in_base_currency": row["balance_in_base_currency"],
"balance_in_account_currency": row["balance_in_account_currency"],
"current_exchange_rate": row["current_exchange_rate"],
"new_exchange_rate": row["new_exchange_rate"],
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
"zero_balance": row["zero_balance"],
"gain_loss": row["gain_loss"],
}
)
return account_details

View File

@@ -1,14 +1,21 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, today
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.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.stock.doctype.item.test_item import create_item
class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
@@ -66,7 +73,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
err.extend("accounts", accounts)
row = err.accounts[0]
row.new_exchange_rate = 85
row.new_balance_in_base_currency = flt(row.new_exchange_rate * flt(row.balance_in_account_currency))
row.new_balance_in_base_currency = flt(
row.new_exchange_rate * flt(row.balance_in_account_currency)
)
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
err.set_total_gain_loss()
err = err.save().submit()
@@ -118,9 +127,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
pe.save().submit()
# Cancel the auto created gain/loss JE to simulate balance only in base currency
je = frappe.db.get_all("Journal Entry Account", filters={"reference_name": si.name}, pluck="parent")[
0
]
je = frappe.db.get_all(
"Journal Entry Account", filters={"reference_name": si.name}, pluck="parent"
)[0]
frappe.get_doc("Journal Entry", je).cancel()
err = frappe.new_doc("Exchange Rate Revaluation")
@@ -226,9 +235,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
self.assertEqual(flt(acc.debit, precision), 0.0)
self.assertEqual(flt(acc.credit, precision), 0.0)
row = next(x for x in je.accounts if x.account == self.debtors_usd)
row = [x for x in je.accounts if x.account == self.debtors_usd][0]
self.assertEqual(flt(row.credit_in_account_currency, precision), 5.0) # in USD
row = next(x for x in je.accounts if x.account != self.debtors_usd)
row = [x for x in je.accounts if x.account != self.debtors_usd][0]
self.assertEqual(flt(row.debit_in_account_currency, precision), 421.06) # in INR
# total_debit and total_credit will be 0.0, as JV is posting only to account currency fields
@@ -285,5 +294,5 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
"new_balance_in_account_currency": 100.0,
}
for key, _val in expected_data.items():
for key, val in expected_data.items():
self.assertEqual(expected_data.get(key), account_details.get(key))

View File

@@ -87,7 +87,7 @@
"module": "Accounts",
"name": "Fiscal Year",
"naming_rule": "By fieldname",
"owner": "Administrator",
"owner": "Administrator",
"permissions": [
{
"create": 1,
@@ -119,14 +119,6 @@
{
"read": 1,
"role": "Employee"
},
{
"read": 1,
"role": "Accounts Manager"
},
{
"read": 1,
"role": "Stock Manager"
}
],
"show_name_in_global_search": 1,

View File

@@ -108,9 +108,9 @@ class FiscalYear(Document):
if overlap:
frappe.throw(
_(
"Year start date or end date is overlapping with {0}. To avoid please set company"
).format(existing.name),
_("Year start date or end date is overlapping with {0}. To avoid please set company").format(
existing.name
),
frappe.NameError,
)
@@ -126,9 +126,9 @@ def check_duplicate_fiscal_year(doc):
not frappe.flags.in_test
):
frappe.throw(
_(
"Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}"
).format(fiscal_year)
_("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(
fiscal_year
)
)

View File

@@ -107,18 +107,13 @@ class GLEntry(Document):
]:
# Update outstanding amt on against voucher
if (
self.against_voucher_type
in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.against_voucher
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_outstanding_amt(
self.account,
self.party_type,
self.party,
self.against_voucher_type,
self.against_voucher,
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
)
def check_mandatory(self):
@@ -184,13 +179,12 @@ class GLEntry(Document):
and self.company == dimension.company
and dimension.mandatory_for_pl
and not dimension.disabled
and not self.is_cancelled
):
if not self.get(dimension.fieldname):
frappe.throw(
_(
"Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}."
).format(dimension.label, self.account)
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
dimension.label, self.account
)
)
if (
@@ -198,13 +192,12 @@ class GLEntry(Document):
and self.company == dimension.company
and dimension.mandatory_for_bs
and not dimension.disabled
and not self.is_cancelled
):
if not self.get(dimension.fieldname):
frappe.throw(
_(
"Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}."
).format(dimension.label, self.account)
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
dimension.label, self.account
)
)
def check_pl_account(self):
@@ -252,7 +245,9 @@ class GLEntry(Document):
if not self.cost_center:
return
is_group, company = frappe.get_cached_value("Cost Center", self.cost_center, ["is_group", "company"])
is_group, company = frappe.get_cached_value(
"Cost Center", self.cost_center, ["is_group", "company"]
)
if company != self.company:
frappe.throw(
@@ -321,7 +316,7 @@ def update_outstanding_amt(
account, party_type, party, against_voucher_type, against_voucher, on_cancel=False
):
if party_type and party:
party_condition = " and party_type={} and party={}".format(
party_condition = " and party_type={0} and party={1}".format(
frappe.db.escape(party_type), frappe.db.escape(party)
)
else:
@@ -329,19 +324,23 @@ def update_outstanding_amt(
if against_voucher_type == "Sales Invoice":
party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to")
account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})"
account_condition = "and account in ({0}, {1})".format(
frappe.db.escape(account), frappe.db.escape(party_account)
)
else:
account_condition = f" and account = {frappe.db.escape(account)}"
account_condition = " and account = {0}".format(frappe.db.escape(account))
# get final outstanding amt
bal = flt(
frappe.db.sql(
f"""
"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry`
where against_voucher_type=%s and against_voucher=%s
and voucher_type != 'Invoice Discounting'
{party_condition} {account_condition}""",
{0} {1}""".format(
party_condition, account_condition
),
(against_voucher_type, against_voucher),
)[0][0]
or 0.0
@@ -352,10 +351,12 @@ def update_outstanding_amt(
elif against_voucher_type == "Journal Entry":
against_voucher_amount = flt(
frappe.db.sql(
f"""
"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
and account = %s and (against_voucher is null or against_voucher='') {party_condition}""",
and account = %s and (against_voucher is null or against_voucher='') {0}""".format(
party_condition
),
(against_voucher, account),
)[0][0]
)
@@ -374,9 +375,7 @@ def update_outstanding_amt(
# Validation : Outstanding can not be negative for JV
if bal < 0 and not on_cancel:
frappe.throw(
_("Outstanding for {0} cannot be less than zero ({1})").format(
against_voucher, fmt_money(bal)
)
_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))
)
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
@@ -392,7 +391,9 @@ def update_outstanding_amt(
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == "Yes" and not adv_adj:
frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", None, "frozen_accounts_modifier")
frozen_accounts_modifier = frappe.db.get_value(
"Accounts Settings", None, "frozen_accounts_modifier"
)
if not frozen_accounts_modifier:
frappe.throw(_("Account {0} is frozen").format(account))
@@ -447,7 +448,7 @@ def rename_temporarily_named_docs(doctype):
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
newname = doc.name
frappe.db.sql(
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0 where name = %s",
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
(newname, oldname),
auto_commit=True,
)

View File

@@ -14,7 +14,9 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestGLEntry(unittest.TestCase):
def test_round_off_entry(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC")
frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC")
frappe.db.set_value(
"Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC"
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -71,9 +73,7 @@ class TestGLEntry(unittest.TestCase):
)
self.assertTrue(all(entry.to_rename == 0 for entry in new_gl_entries))
self.assertTrue(
all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries, strict=False))
)
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
new_naming_series_current_value = frappe.db.sql(
"SELECT current from tabSeries where name = %s", naming_series

View File

@@ -83,7 +83,9 @@ class InvoiceDiscounting(AccountsController):
frappe.throw(
_(
"Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}"
).format(record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice))
).format(
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)
)
)
def calculate_total_amount(self):
@@ -103,9 +105,7 @@ class InvoiceDiscounting(AccountsController):
self.status = status
self.db_set("status", status)
for d in self.invoices:
frappe.get_doc("Sales Invoice", d.sales_invoice).set_status(
update=True, update_modified=False
)
frappe.get_doc("Sales Invoice", d.sales_invoice).set_status(update=True, update_modified=False)
else:
self.status = "Draft"
if self.docstatus == 1:

View File

@@ -75,8 +75,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
gle = get_gl_entries("Invoice Discounting", inv_disc.name)
expected_gle = {inv.debit_to: [0.0, 200], self.ar_credit: [200, 0.0]}
for _i, gle_value in enumerate(gle):
self.assertEqual([gle_value.debit, gle_value.credit], expected_gle.get(gle_value.account))
for i, gle in enumerate(gle):
self.assertEqual([gle.debit, gle.credit], expected_gle.get(gle.account))
def test_loan_on_submit(self):
inv = create_sales_invoice(rate=300)
@@ -92,7 +92,9 @@ class TestInvoiceDiscounting(unittest.TestCase):
period=60,
)
self.assertEqual(inv_disc.status, "Sanctioned")
self.assertEqual(inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period))
self.assertEqual(
inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period)
)
def test_on_disbursed(self):
inv = create_sales_invoice(rate=500)
@@ -260,9 +262,13 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_discounted)
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
)
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)
@@ -298,9 +304,13 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_unpaid)
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
)
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)

View File

@@ -32,7 +32,7 @@ class ItemTaxTemplate(Document):
def autoname(self):
if self.company and self.title:
abbr = frappe.get_cached_value("Company", self.company, "abbr")
self.name = f"{self.title} - {abbr}"
self.name = "{0} - {1}".format(self.title, abbr)
def validate_tax_accounts(self):
"""Check whether Tax Rate is not entered twice for same Tax Type"""

View File

@@ -196,7 +196,7 @@ frappe.ui.form.on("Journal Entry", {
!(frm.doc.accounts || []).length ||
((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)
) {
if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) {
if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
return frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
@@ -308,7 +308,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
filters: [[jvd.reference_type, "docstatus", "=", 1]],
};
if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) {
if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
// Filter by cost center
if (jvd.cost_center) {
@@ -320,7 +320,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
}
if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) {
if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
// party_type and party mandatory
frappe.model.validate_missing(jvd, "party_type");
frappe.model.validate_missing(jvd, "party");
@@ -453,10 +453,7 @@ frappe.ui.form.on("Journal Entry Account", {
}
},
cost_center: function (frm, dt, dn) {
// Don't reset for Gain/Loss type journals, as it will make Debit and Credit values '0'
if (frm.doc.voucher_type != "Exchange Gain Or Loss") {
erpnext.journal_entry.set_account_details(frm, dt, dn);
}
erpnext.journal_entry.set_account_details(frm, dt, dn);
},
account: function (frm, dt, dn) {

View File

@@ -6,7 +6,7 @@ import json
import frappe
from frappe import _, msgprint, scrub
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
from frappe.utils import cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
import erpnext
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
@@ -112,7 +112,7 @@ class JournalEntry(AccountsController):
# end: auto-generated types
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super(JournalEntry, self).__init__(*args, **kwargs)
def validate(self):
if self.voucher_type == "Opening Entry":
@@ -146,7 +146,6 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
self.validate_advance_accounts()
if self.docstatus == 0:
self.apply_tax_withholding()
@@ -154,20 +153,6 @@ class JournalEntry(AccountsController):
if not self.title:
self.title = self.get_title()
def validate_advance_accounts(self):
journal_accounts = set([x.account for x in self.accounts])
advance_accounts = set()
advance_accounts.add(
frappe.get_cached_value("Company", self.company, "default_advance_received_account")
)
advance_accounts.add(frappe.get_cached_value("Company", self.company, "default_advance_paid_account"))
if advance_accounts_used := journal_accounts & advance_accounts:
frappe.msgprint(
_(
"Making Journal Entries against advance accounts: {0} is not recommended. These Journals won't be available for Reconciliation."
).format(frappe.bold(comma_and(advance_accounts_used)))
)
def validate_for_repost(self):
validate_docs_for_voucher_types(["Journal Entry"])
validate_docs_for_deferred_accounting([self.name], [])
@@ -206,7 +191,7 @@ class JournalEntry(AccountsController):
def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller
super().on_cancel()
super(JournalEntry, self).on_cancel()
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
@@ -241,7 +226,10 @@ class JournalEntry(AccountsController):
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
def validate_inter_company_accounts(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
if (
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
@@ -387,7 +375,10 @@ class JournalEntry(AccountsController):
asset.set_status()
def update_inter_company_jv(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
if (
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
frappe.db.set_value(
"Journal Entry",
self.inter_company_journal_entry_reference,
@@ -415,25 +406,17 @@ class JournalEntry(AccountsController):
if d.account == inv_disc_doc.short_term_loan and d.reference_name == inv_disc:
if self.docstatus == 1:
if d.credit > 0:
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Sanctioned", d.idx
)
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Sanctioned", d.idx)
status = "Disbursed"
elif d.debit > 0:
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Disbursed", d.idx
)
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Disbursed", d.idx)
status = "Settled"
else:
if d.credit > 0:
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Disbursed", d.idx
)
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Disbursed", d.idx)
status = "Sanctioned"
elif d.debit > 0:
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Settled", d.idx
)
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Settled", d.idx)
status = "Disbursed"
break
if status:
@@ -454,7 +437,7 @@ class JournalEntry(AccountsController):
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
and d.account_type == "Depreciation"
and d.debit
):
asset = frappe.get_doc("Asset", d.reference_name)
@@ -502,7 +485,10 @@ class JournalEntry(AccountsController):
)
def unlink_inter_company_jv(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
if (
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
frappe.db.set_value(
"Journal Entry",
self.inter_company_journal_entry_reference,
@@ -524,9 +510,9 @@ class JournalEntry(AccountsController):
if account_type in ["Receivable", "Payable"]:
if not (d.party_type and d.party):
frappe.throw(
_(
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"
).format(d.idx, d.account)
_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(
d.idx, d.account
)
)
elif (
d.party_type
@@ -591,18 +577,16 @@ class JournalEntry(AccountsController):
def system_generated_gain_loss(self):
return (
self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency and self.is_system_generated
self.voucher_type == "Exchange Gain Or Loss"
and self.multi_currency
and self.is_system_generated
)
def validate_against_jv(self):
for d in self.get("accounts"):
if d.reference_type == "Journal Entry":
account_root_type = frappe.get_cached_value("Account", d.account, "root_type")
if (
account_root_type == "Asset"
and flt(d.debit) > 0
and not self.system_generated_gain_loss()
):
if account_root_type == "Asset" and flt(d.debit) > 0 and not self.system_generated_gain_loss():
frappe.throw(
_(
"Row #{0}: For {1}, you can select reference document only if account gets credited"
@@ -684,13 +668,11 @@ class JournalEntry(AccountsController):
if d.reference_type == "Purchase Order" and flt(d.credit) > 0:
frappe.throw(
_("Row {0}: Credit entry can not be linked with a {1}").format(
d.idx, d.reference_type
)
_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, d.reference_type)
)
# set totals
if d.reference_name not in self.reference_totals:
if not d.reference_name in self.reference_totals:
self.reference_totals[d.reference_name] = 0.0
if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
@@ -708,10 +690,7 @@ class JournalEntry(AccountsController):
# check if party and account match
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
if (
self.voucher_type in ("Deferred Revenue", "Deferred Expense")
and d.reference_detail_no
):
if self.voucher_type in ("Deferred Revenue", "Deferred Expense") and d.reference_detail_no:
debit_or_credit = "Debit" if d.debit else "Credit"
party_account = get_deferred_booking_accounts(
d.reference_type, d.reference_detail_no, debit_or_credit
@@ -720,8 +699,7 @@ class JournalEntry(AccountsController):
else:
if d.reference_type == "Sales Invoice":
party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name)
or against_voucher[1]
get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
)
else:
party_account = against_voucher[1]
@@ -845,9 +823,7 @@ class JournalEntry(AccountsController):
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
if self.difference:
frappe.throw(
_("Total Debit must be equal to Total Credit. The difference is {0}").format(
self.difference
)
_("Total Debit must be equal to Total Credit. The difference is {0}").format(self.difference)
)
def set_total_debit_credit(self):
@@ -911,6 +887,7 @@ class JournalEntry(AccountsController):
and self.posting_date
)
):
ignore_exchange_rate = False
if self.get("flags") and self.flags.get("ignore_exchange_rate"):
ignore_exchange_rate = True
@@ -1156,21 +1133,27 @@ class JournalEntry(AccountsController):
self.validate_total_debit_and_credit()
def get_values(self):
cond = f" and outstanding_amount <= {self.write_off_amount}" if flt(self.write_off_amount) > 0 else ""
cond = (
" and outstanding_amount <= {0}".format(self.write_off_amount)
if flt(self.write_off_amount) > 0
else ""
)
if self.write_off_based_on == "Accounts Receivable":
return frappe.db.sql(
"""select name, debit_to as account, customer as party, outstanding_amount
from `tabSales Invoice` where docstatus = 1 and company = {}
and outstanding_amount > 0 {}""".format("%s", cond),
from `tabSales Invoice` where docstatus = 1 and company = %s
and outstanding_amount > 0 %s"""
% ("%s", cond),
self.company,
as_dict=True,
)
elif self.write_off_based_on == "Accounts Payable":
return frappe.db.sql(
"""select name, credit_to as account, supplier as party, outstanding_amount
from `tabPurchase Invoice` where docstatus = 1 and company = {}
and outstanding_amount > 0 {}""".format("%s", cond),
from `tabPurchase Invoice` where docstatus = 1 and company = %s
and outstanding_amount > 0 %s"""
% ("%s", cond),
self.company,
as_dict=True,
)
@@ -1279,7 +1262,7 @@ def get_payment_entry_against_order(
"amount_field_bank": amount_field_bank,
"amount": amount,
"debit_in_account_currency": debit_in_account_currency,
"remarks": f"Advance Payment received against {dt} {dn}",
"remarks": "Advance Payment received against {0} {1}".format(dt, dn),
"is_advance": "Yes",
"bank_account": bank_account,
"journal_entry": journal_entry,
@@ -1318,7 +1301,7 @@ def get_payment_entry_against_invoice(
"amount_field_bank": amount_field_bank,
"amount": amount if amount else abs(ref_doc.outstanding_amount),
"debit_in_account_currency": debit_in_account_currency,
"remarks": f"Payment received against {dt} {dn}. {ref_doc.remarks}",
"remarks": "Payment received against {0} {1}. {2}".format(dt, dn, ref_doc.remarks),
"is_advance": "No",
"bank_account": bank_account,
"journal_entry": journal_entry,
@@ -1344,7 +1327,9 @@ def get_payment_entry(ref_doc, args):
)
je = frappe.new_doc("Journal Entry")
je.update({"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")})
je.update(
{"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")}
)
party_row = je.append(
"accounts",
@@ -1367,7 +1352,9 @@ def get_payment_entry(ref_doc, args):
bank_row = je.append("accounts")
# Make it bank_details
bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account"))
bank_account = get_default_bank_cash_account(
ref_doc.company, "Bank", account=args.get("bank_account")
)
if bank_account:
bank_row.update(bank_account)
# Modified to include the posting date for which the exchange rate is required.
@@ -1407,7 +1394,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return []
return frappe.db.sql(
f"""
"""
SELECT jv.name, jv.posting_date, jv.user_remark
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
WHERE jv_detail.parent = jv.name
@@ -1418,14 +1405,16 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
OR jv_detail.reference_type = ''
)
AND jv.docstatus = 1
AND jv.`{searchfield}` LIKE %(txt)s
AND jv.`{0}` LIKE %(txt)s
ORDER BY jv.name DESC
LIMIT %(limit)s offset %(offset)s
""",
""".format(
searchfield
),
dict(
account=filters.get("account"),
party=cstr(filters.get("party")),
txt=f"%{txt}%",
txt="%{0}%".format(txt),
offset=start,
limit=page_len,
),
@@ -1447,15 +1436,19 @@ def get_outstanding(args):
condition = " and party=%(party)s" if args.get("party") else ""
against_jv_amount = frappe.db.sql(
f"""
"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {condition}
and (reference_type is null or reference_type = '')""",
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0}
and (reference_type is null or reference_type = '')""".format(
condition
),
args,
)
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
amount_field = "credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
amount_field = (
"credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
)
return {amount_field: abs(against_jv_amount)}
elif args.get("doctype") in ("Sales Invoice", "Purchase Invoice"):
party_type = "Customer" if args.get("doctype") == "Sales Invoice" else "Supplier"
@@ -1468,7 +1461,9 @@ def get_outstanding(args):
due_date = invoice.get("due_date")
exchange_rate = invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
exchange_rate = (
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
)
if args["doctype"] == "Sales Invoice":
amount_field = (
@@ -1506,7 +1501,9 @@ def get_party_account_and_currency(company, party_type, party):
@frappe.whitelist()
def get_account_details_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
def get_account_details_and_party_type(
account, date, company, debit=None, credit=None, exchange_rate=None
):
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)

View File

@@ -69,8 +69,10 @@ class TestJournalEntry(unittest.TestCase):
self.assertTrue(
frappe.db.sql(
f"""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s and {dr_or_cr}=400""",
"""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s and {0}=400""".format(
dr_or_cr
),
(submitted_voucher.doctype, submitted_voucher.name),
)
)
@@ -82,8 +84,9 @@ class TestJournalEntry(unittest.TestCase):
def advance_paid_testcase(self, base_jv, test_voucher, dr_or_cr):
# Test advance paid field
advance_paid = frappe.db.sql(
"""select advance_paid from `tab{}`
where name={}""".format(test_voucher.doctype, "%s"),
"""select advance_paid from `tab%s`
where name=%s"""
% (test_voucher.doctype, "%s"),
(test_voucher.name),
)
payment_against_order = base_jv.get("accounts")[0].get(dr_or_cr)
@@ -156,7 +159,9 @@ class TestJournalEntry(unittest.TestCase):
jv.cancel()
def test_multi_currency(self):
jv = make_journal_entry("_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False)
jv = make_journal_entry(
"_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False
)
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
@@ -472,7 +477,9 @@ class TestJournalEntry(unittest.TestCase):
query = query.select(gl[field])
query = query.where(
(gl.voucher_type == "Journal Entry") & (gl.voucher_no == self.voucher_no) & (gl.is_cancelled == 0)
(gl.voucher_type == "Journal Entry")
& (gl.voucher_no == self.voucher_no)
& (gl.is_cancelled == 0)
).orderby(gl.account)
gl_entries = query.run(as_dict=True)

View File

@@ -1,8 +0,0 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Ledger Health", {
// refresh(frm) {
// },
// });

View File

@@ -1,70 +0,0 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2024-03-26 17:01:47.443986",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"voucher_type",
"voucher_no",
"checked_on",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch"
],
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Data",
"label": "Voucher Type"
},
{
"fieldname": "voucher_no",
"fieldtype": "Data",
"label": "Voucher No"
},
{
"default": "0",
"fieldname": "debit_credit_mismatch",
"fieldtype": "Check",
"label": "Debit-Credit mismatch"
},
{
"fieldname": "checked_on",
"fieldtype": "Datetime",
"label": "Checked On"
},
{
"default": "0",
"fieldname": "general_and_payment_ledger_mismatch",
"fieldtype": "Check",
"label": "General and Payment Ledger mismatch"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-04-09 11:16:07.044484",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Ledger Health",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,25 +0,0 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LedgerHealth(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
checked_on: DF.Datetime | None
debit_credit_mismatch: DF.Check
general_and_payment_ledger_mismatch: DF.Check
name: DF.Int | None
voucher_no: DF.Data | None
voucher_type: DF.Data | None
# end: auto-generated types
pass

View File

@@ -1,109 +0,0 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.accounts.utils import run_ledger_health_checks
class TestLedgerHealth(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.create_customer()
self.configure_monitoring_tool()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def configure_monitoring_tool(self):
monitor_settings = frappe.get_doc("Ledger Health Monitor")
monitor_settings.enable_health_monitor = True
monitor_settings.enable_for_last_x_days = 60
monitor_settings.debit_credit_mismatch = True
monitor_settings.general_and_payment_ledger_mismatch = True
exists = [x for x in monitor_settings.companies if x.company == self.company]
if not exists:
monitor_settings.append("companies", {"company": self.company})
monitor_settings.save()
def clear_old_entries(self):
super().clear_old_entries()
lh = qb.DocType("Ledger Health")
qb.from_(lh).delete().run()
def create_journal(self):
je = frappe.new_doc("Journal Entry")
je.company = self.company
je.voucher_type = "Journal Entry"
je.posting_date = nowdate()
je.append(
"accounts",
{
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 10000,
},
)
je.append("accounts", {"account": self.income_account, "credit_in_account_currency": 10000})
je.save().submit()
self.je = je
def test_debit_credit_mismatch(self):
self.create_journal()
# manually cause debit-credit mismatch
gle = frappe.db.get_all(
"GL Entry", filters={"voucher_no": self.je.name, "account": self.income_account}
)[0]
frappe.db.set_value("GL Entry", gle.name, "credit", 8000)
run_ledger_health_checks()
expected = {
"voucher_type": self.je.doctype,
"voucher_no": self.je.name,
"debit_credit_mismatch": True,
"general_and_payment_ledger_mismatch": False,
}
actual = frappe.db.get_all(
"Ledger Health",
fields=[
"voucher_type",
"voucher_no",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch",
],
)
self.assertEqual(len(actual), 1)
self.assertEqual(expected, actual[0])
def test_gl_and_pl_mismatch(self):
self.create_journal()
# manually cause GL and PL discrepancy
ple = frappe.db.get_all("Payment Ledger Entry", filters={"voucher_no": self.je.name})[0]
frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", 11000)
run_ledger_health_checks()
expected = {
"voucher_type": self.je.doctype,
"voucher_no": self.je.name,
"debit_credit_mismatch": False,
"general_and_payment_ledger_mismatch": True,
}
actual = frappe.db.get_all(
"Ledger Health",
fields=[
"voucher_type",
"voucher_no",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch",
],
)
self.assertEqual(len(actual), 1)
self.assertEqual(expected, actual[0])

View File

@@ -1,8 +0,0 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Ledger Health Monitor", {
// refresh(frm) {
// },
// });

View File

@@ -1,104 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-03-27 09:38:07.427997",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"enable_health_monitor",
"monitor_section",
"monitor_for_last_x_days",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch",
"section_break_xdsp",
"companies"
],
"fields": [
{
"default": "0",
"fieldname": "enable_health_monitor",
"fieldtype": "Check",
"label": "Enable Health Monitor"
},
{
"fieldname": "monitor_section",
"fieldtype": "Section Break",
"label": "Configuration"
},
{
"default": "0",
"fieldname": "debit_credit_mismatch",
"fieldtype": "Check",
"label": "Debit-Credit Mismatch"
},
{
"default": "0",
"fieldname": "general_and_payment_ledger_mismatch",
"fieldtype": "Check",
"label": "Discrepancy between General and Payment Ledger"
},
{
"default": "60",
"fieldname": "monitor_for_last_x_days",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Monitor for Last 'X' days",
"reqd": 1
},
{
"fieldname": "section_break_xdsp",
"fieldtype": "Section Break",
"label": "Companies"
},
{
"fieldname": "companies",
"fieldtype": "Table",
"options": "Ledger Health Monitor Company"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-27 10:14:16.511681",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Ledger Health Monitor",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts User",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -1,28 +0,0 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LedgerHealthMonitor(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.ledger_health_monitor_company.ledger_health_monitor_company import (
LedgerHealthMonitorCompany,
)
companies: DF.Table[LedgerHealthMonitorCompany]
debit_credit_mismatch: DF.Check
enable_health_monitor: DF.Check
general_and_payment_ledger_mismatch: DF.Check
monitor_for_last_x_days: DF.Int
# end: auto-generated types
pass

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestLedgerHealthMonitor(FrappeTestCase):
pass

View File

@@ -1,32 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-03-27 10:04:45.727054",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 10:06:22.806155",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Ledger Health Monitor Company",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,23 +0,0 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LedgerHealthMonitorCompany(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass

View File

@@ -83,10 +83,7 @@ class TestLedgerMerge(unittest.TestCase):
"account": "Indirect Income - _TC",
"merge_accounts": [
{"account": "Indirect Test Income - _TC", "account_name": "Indirect Test Income"},
{
"account": "Administrative Test Income - _TC",
"account_name": "Administrative Test Income",
},
{"account": "Administrative Test Income - _TC", "account_name": "Administrative Test Income"},
],
}
).insert(ignore_permissions=True)

View File

@@ -52,11 +52,13 @@ def get_loyalty_details(
condition += " and expiry_date>='%s' " % expiry_date
loyalty_point_details = frappe.db.sql(
f"""select sum(loyalty_points) as loyalty_points,
"""select sum(loyalty_points) as loyalty_points,
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and posting_date <= %s
{condition}
group by customer""",
group by customer""".format(
condition=condition
),
(customer, loyalty_program, expiry_date),
as_dict=1,
)
@@ -77,7 +79,9 @@ def get_loyalty_program_details_with_points(
include_expired_entry=False,
current_transaction_amount=0,
):
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
lp_details = get_loyalty_program_details(
customer, loyalty_program, company=company, silent=silent
)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
lp_details.update(
get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)

View File

@@ -19,7 +19,9 @@ class TestLoyaltyProgram(unittest.TestCase):
create_records()
def test_loyalty_points_earned_single_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
# create a new sales invoice
si_original = create_sales_invoice_record()
si_original.insert()
@@ -67,7 +69,9 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel()
def test_loyalty_points_earned_multiple_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty"
)
# assign multiple tier program to the customer
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
customer.loyalty_program = frappe.get_doc(
@@ -124,7 +128,9 @@ class TestLoyaltyProgram(unittest.TestCase):
def test_cancel_sales_invoice(self):
"""cancelling the sales invoice should cancel the earned points"""
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
# create a new sales invoice
si = create_sales_invoice_record()
si.insert()
@@ -134,7 +140,7 @@ class TestLoyaltyProgram(unittest.TestCase):
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
)
self.assertEqual(True, lpe is not None)
self.assertEqual(True, not (lpe is None))
# cancelling sales invoice
si.cancel()
@@ -142,7 +148,9 @@ class TestLoyaltyProgram(unittest.TestCase):
self.assertEqual(True, (lpe is None))
def test_sales_invoice_return(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
# create a new sales invoice
si_original = create_sales_invoice_record(2)
si_original.conversion_rate = flt(1)
@@ -338,7 +346,9 @@ def create_records():
).insert()
# create item price
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
if not frappe.db.exists(
"Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}
):
frappe.get_doc(
{
"doctype": "Item Price",

View File

@@ -54,7 +54,9 @@ class MonthlyDistribution(Document):
total = sum(flt(d.percentage_allocation) for d in self.get("percentages"))
if flt(total, 2) != 100.0:
frappe.throw(_("Percentage Allocation should be equal to 100%") + f" ({flt(total, 2)!s}%)")
frappe.throw(
_("Percentage Allocation should be equal to 100%") + " ({0}%)".format(str(flt(total, 2)))
)
def get_periodwise_distribution_data(distribution_id, period_list, periodicity):

View File

@@ -83,7 +83,9 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
old_default_receivable_account = frappe.db.get_value(
"Company", company, "default_receivable_account"
)
frappe.db.set_value("Company", company, "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
@@ -119,7 +121,9 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
self.assertTrue(error_log)
# teardown
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
frappe.db.set_value(
"Company", company, "default_receivable_account", old_default_receivable_account
)
def test_renaming_of_invoice_using_invoice_number_field(self):
company = "_Test Opening Invoice Company"
@@ -165,7 +169,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 1.0,
"outstanding_amount": 300,
"party": args.get("party_1") or f"_Test {party}",
"party": args.get("party_1") or "_Test {0}".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@@ -175,7 +179,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 2.0,
"outstanding_amount": 250,
"party": args.get("party_2") or f"_Test {party} 1",
"party": args.get("party_2") or "_Test {0} 1".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",

View File

@@ -38,10 +38,7 @@ class PartyLink(Document):
if existing_party_link:
frappe.throw(
_("{} {} is already linked with {} {}").format(
self.primary_role,
bold(self.primary_party),
self.secondary_role,
bold(self.secondary_party),
self.primary_role, bold(self.primary_party), self.secondary_role, bold(self.secondary_party)
)
)

View File

@@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("paid_from", function () {
frm.events.validate_company(frm);
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
return {
@@ -87,7 +87,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("paid_to", function () {
frm.events.validate_company(frm);
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
return {
@@ -134,7 +134,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("payment_term", "references", function (frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (
["Purchase Invoice", "Sales Invoice"].includes(child.reference_doctype) &&
in_list(["Purchase Invoice", "Sales Invoice"], child.reference_doctype) &&
child.reference_name
) {
return {
@@ -395,6 +395,10 @@ frappe.ui.form.on("Payment Entry", {
return {
query: "erpnext.controllers.queries.employee_query",
};
} else if (frm.doc.party_type == "Customer") {
return {
query: "erpnext.controllers.queries.customer_query",
};
}
});
@@ -470,9 +474,6 @@ frappe.ui.form.on("Payment Entry", {
() => frm.events.set_dynamic_labels(frm),
() => {
frm.set_party_account_based_on_party = false;
if (r.message.party_bank_account) {
frm.set_value("party_bank_account", r.message.party_bank_account);
}
if (r.message.bank_account) {
frm.set_value("bank_account", r.message.bank_account);
}
@@ -626,7 +627,7 @@ frappe.ui.form.on("Payment Entry", {
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from) {
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
@@ -1045,7 +1046,7 @@ frappe.ui.form.on("Payment Entry", {
}
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"));
if (paid_amount > total_negative_outstanding) {
if (total_negative_outstanding == 0) {
@@ -1216,7 +1217,7 @@ frappe.ui.form.on("Payment Entry", {
if (
frm.doc.party_type == "Customer" &&
!["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype)
!in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
frappe.msgprint(
@@ -1230,7 +1231,7 @@ frappe.ui.form.on("Payment Entry", {
if (
frm.doc.party_type == "Supplier" &&
!["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype)
!in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
frappe.msgprint(
@@ -1326,7 +1327,7 @@ frappe.ui.form.on("Payment Entry", {
bank_account: function (frm) {
const field = frm.doc.payment_type == "Pay" ? "paid_from" : "paid_to";
if (frm.doc.bank_account && ["Pay", "Receive"].includes(frm.doc.payment_type)) {
if (frm.doc.bank_account && in_list(["Pay", "Receive"], frm.doc.payment_type)) {
frappe.call({
method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details",
args: {
@@ -1334,9 +1335,7 @@ frappe.ui.form.on("Payment Entry", {
},
callback: function (r) {
if (r.message) {
if (!frm.doc.mode_of_payment) {
frm.set_value(field, r.message.account);
}
frm.set_value(field, r.message.account);
frm.set_value("bank", r.message.bank);
frm.set_value("bank_account_no", r.message.bank_account_no);
}
@@ -1667,8 +1666,6 @@ frappe.ui.form.on("Payment Entry Reference", {
frm.doc.payment_type == "Receive"
? frm.doc.paid_from_account_currency
: frm.doc.paid_to_account_currency,
party_type: frm.doc.party_type,
party: frm.doc.party,
},
callback: function (r, rt) {
if (r.message) {

View File

@@ -20,7 +20,6 @@
"party",
"party_name",
"book_advance_payments_in_separate_party_account",
"reconcile_on_advance_payment_date",
"column_break_11",
"bank_account",
"party_bank_account",
@@ -151,7 +150,6 @@
"reqd": 1
},
{
"allow_on_submit": 1,
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
@@ -478,7 +476,6 @@
"label": "More Information"
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
@@ -582,7 +579,6 @@
"fieldtype": "Select",
"hidden": 1,
"label": "Payment Order Status",
"no_copy": 1,
"options": "Initiated\nPayment Ordered",
"read_only": 1
},
@@ -751,7 +747,6 @@
"fieldtype": "Check",
"hidden": 1,
"label": "Book Advance Payments in Separate Party Account",
"no_copy": 1,
"read_only": 1
},
{
@@ -767,16 +762,6 @@
"label": "In Words",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"fetch_from": "company.reconcile_on_advance_payment_date",
"fieldname": "reconcile_on_advance_payment_date",
"fieldtype": "Check",
"hidden": 1,
"label": "Reconcile on Advance Payment Date",
"no_copy": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
@@ -790,7 +775,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2024-05-17 10:21:11.199445",
"modified": "2024-01-03 12:46:41.759121",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@@ -836,4 +821,4 @@
"states": [],
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -16,7 +16,6 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.bank_account.bank_account import (
get_bank_account_details,
get_default_company_bank_account,
get_party_bank_account,
)
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
@@ -53,7 +52,7 @@ class InvalidPaymentEntry(ValidationError):
class PaymentEntry(AccountsController):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super(PaymentEntry, self).__init__(*args, **kwargs)
if not self.is_new():
self.setup_party_account_field()
@@ -76,7 +75,6 @@ class PaymentEntry(AccountsController):
self.setup_party_account_field()
self.set_missing_values()
self.set_liability_account()
self.validate_advance_account_currency()
self.set_missing_ref_details(force=True)
self.validate_payment_type()
self.validate_party_details()
@@ -114,10 +112,6 @@ class PaymentEntry(AccountsController):
if self.docstatus > 0 or self.payment_type == "Internal Transfer":
return
self.book_advance_payments_in_separate_party_account = False
if self.party_type not in ("Customer", "Supplier"):
return
if not frappe.db.get_value(
"Company", self.company, "book_advance_payments_in_separate_party_account"
):
@@ -159,22 +153,6 @@ class PaymentEntry(AccountsController):
alert=True,
)
def validate_advance_account_currency(self):
if self.book_advance_payments_in_separate_party_account is True:
company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
if self.payment_type == "Receive" and self.paid_from_account_currency != company_currency:
frappe.throw(
_("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format(
frappe.bold(self.paid_from), frappe.bold(self.paid_from_account_currency)
)
)
if self.payment_type == "Pay" and self.paid_to_account_currency != company_currency:
frappe.throw(
_("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format(
frappe.bold(self.paid_to), frappe.bold(self.paid_to_account_currency)
)
)
def on_cancel(self):
self.ignore_linked_doctypes = (
"GL Entry",
@@ -187,7 +165,7 @@ class PaymentEntry(AccountsController):
"Unreconcile Payment",
"Unreconcile Payment Entries",
)
super().on_cancel()
super(PaymentEntry, self).on_cancel()
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.update_advance_paid()
@@ -297,7 +275,9 @@ class PaymentEntry(AccountsController):
# If term based allocation is enabled, throw
if (
d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name):
) and self.term_based_allocation_enabled_for_reference(
d.reference_doctype, d.reference_name
):
frappe.throw(
_(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
@@ -309,9 +289,7 @@ class PaymentEntry(AccountsController):
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(
_(d.reference_doctype), d.reference_name
)
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
elif (
@@ -335,14 +313,14 @@ class PaymentEntry(AccountsController):
and latest.payment_term_outstanding
and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
)
and self.term_based_allocation_enabled_for_reference(
d.reference_doctype, d.reference_name
)
and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name)
):
frappe.throw(
_(
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term)
).format(
d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
)
)
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
@@ -414,30 +392,20 @@ class PaymentEntry(AccountsController):
self,
force: bool = False,
update_ref_details_only_for: list | None = None,
reference_exchange_details: dict | None = None,
ref_exchange_rate: float | None = None,
) -> None:
for d in self.get("references"):
if d.allocated_amount:
if update_ref_details_only_for and (
(d.reference_doctype, d.reference_name) not in update_ref_details_only_for
not (d.reference_doctype, d.reference_name) in update_ref_details_only_for
):
continue
ref_details = get_reference_details(
d.reference_doctype,
d.reference_name,
self.party_account_currency,
self.party_type,
self.party,
d.reference_doctype, d.reference_name, self.party_account_currency
)
# Only update exchange rate when the reference is Journal Entry
if (
reference_exchange_details
and d.reference_doctype == reference_exchange_details.reference_doctype
and d.reference_name == reference_exchange_details.reference_name
):
ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
if ref_exchange_rate:
ref_details.update({"exchange_rate": ref_exchange_rate})
for field, value in ref_details.items():
if d.exchange_gain_loss:
@@ -470,9 +438,7 @@ class PaymentEntry(AccountsController):
else:
if ref_doc:
if self.paid_from_account_currency == ref_doc.currency:
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get(
"conversion_rate"
)
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
if not self.source_exchange_rate:
self.source_exchange_rate = get_exchange_rate(
@@ -509,7 +475,7 @@ class PaymentEntry(AccountsController):
if d.reference_doctype not in valid_reference_doctypes:
frappe.throw(
_("Reference Doctype must be one of {0}").format(
comma_or(_(d) for d in valid_reference_doctypes)
comma_or((_(d) for d in valid_reference_doctypes))
)
)
@@ -532,8 +498,7 @@ class PaymentEntry(AccountsController):
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name)
or ref_doc.debit_to
get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
)
elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to
@@ -546,10 +511,7 @@ class PaymentEntry(AccountsController):
):
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
_(d.reference_doctype),
d.reference_name,
ref_party_account,
self.party_account,
_(d.reference_doctype), d.reference_name, ref_party_account, self.party_account
)
)
@@ -560,15 +522,13 @@ class PaymentEntry(AccountsController):
)
if ref_doc.docstatus != 1:
frappe.throw(
_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name)
)
frappe.throw(_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name))
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry")
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry")
return ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Shareholder":
return ("Journal Entry",)
elif self.party_type == "Employee":
@@ -733,7 +693,9 @@ class PaymentEntry(AccountsController):
if not (is_single_currency and reference_is_multi_currency):
return allocated_amount
allocated_amount = flt(allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount"))
allocated_amount = flt(
allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
)
return allocated_amount
@@ -796,6 +758,7 @@ class PaymentEntry(AccountsController):
accounts = []
for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"):
# Preserve user updated included in paid amount
if d.included_in_paid_amount:
tax_withholding_details.update({"included_in_paid_amount": d.included_in_paid_amount})
@@ -915,6 +878,7 @@ class PaymentEntry(AccountsController):
flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount")
)
else:
# Use source/target exchange rate, so no difference amount is calculated.
# then update exchange gain/loss amount in reference table
# if there is an exchange gain/loss amount in reference table, submit a JE for that
@@ -1052,6 +1016,7 @@ class PaymentEntry(AccountsController):
)
]
else:
remarks = [
_("Amount {0} {1} {2} {3}").format(
_(self.party_account_currency),
@@ -1071,19 +1036,14 @@ class PaymentEntry(AccountsController):
if d.allocated_amount:
remarks.append(
_("Amount {0} {1} against {2} {3}").format(
_(self.party_account_currency),
d.allocated_amount,
d.reference_doctype,
d.reference_name,
_(self.party_account_currency), d.allocated_amount, d.reference_doctype, d.reference_name
)
)
for d in self.get("deductions"):
if d.amount:
remarks.append(
_("Amount {0} {1} deducted against {2}").format(
_(self.company_currency), d.amount, d.account
)
_("Amount {0} {1} deducted against {2}").format(_(self.company_currency), d.amount, d.account)
)
self.set("remarks", "\n".join(remarks))
@@ -1131,71 +1091,78 @@ class PaymentEntry(AccountsController):
)
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
if self.book_advance_payments_in_separate_party_account:
gle = party_gl_dict.copy()
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1
elif (
is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
if self.payment_type == "Receive":
amount = self.base_paid_amount
else:
amount = self.base_received_amount
exchange_rate = self.get_exchange_rate()
amount_in_account_currency = amount * exchange_rate
gle.update(
{
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
dr_or_cr: amount,
dr_or_cr + "_in_account_currency": amount_in_account_currency,
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
"cost_center": self.cost_center,
}
)
gl_entries.append(gle)
else:
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"):
reverse_dr_or_cr = 1
elif (
is_return and self.party_type in payable_party_types and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
if self.book_advance_payments_in_separate_party_account:
gle.update(
{
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)
gl_entries.append(gle)
gl_entries.append(gle)
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
gl_entries.append(gle)
def make_advance_gl_entries(
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
@@ -1210,7 +1177,7 @@ class PaymentEntry(AccountsController):
def add_advance_gl_entries(self, gl_entries: list, entry: object | dict | None):
"""
If 'entry' is passed, GL entries only for that reference is added.
If 'entry' is passed, GL enties only for that reference is added.
"""
if self.book_advance_payments_in_separate_party_account:
references = [x for x in self.get("references")]
@@ -1222,22 +1189,11 @@ class PaymentEntry(AccountsController):
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Payment Entry",
"Sales Order",
"Purchase Order",
):
self.add_advance_gl_for_reference(gl_entries, ref)
def get_dr_and_account_for_advances(self, reference):
if reference.reference_doctype == "Sales Invoice":
return "credit", reference.account
if reference.reference_doctype == "Payment Entry":
if reference.account_type == "Receivable" and reference.payment_type == "Pay":
return "credit", self.party_account
else:
return "debit", self.party_account
return "debit", reference.account
def add_advance_gl_for_reference(self, gl_entries, invoice):
args_dict = {
"party_type": self.party_type,
@@ -1249,19 +1205,16 @@ class PaymentEntry(AccountsController):
"voucher_detail_no": invoice.name,
}
if self.reconcile_on_advance_payment_date:
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field)
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
else:
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field)
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
args_dict["account"] = account
dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit"
args_dict["account"] = invoice.account
args_dict[dr_or_cr] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict.update(
@@ -1643,8 +1596,7 @@ def get_outstanding_reference_documents(args, validate=False):
return []
elif supplier_status["hold_type"] == "Payments":
if (
not supplier_status["release_date"]
or getdate(nowdate()) <= supplier_status["release_date"]
not supplier_status["release_date"] or getdate(nowdate()) <= supplier_status["release_date"]
):
return []
@@ -1654,7 +1606,7 @@ def get_outstanding_reference_documents(args, validate=False):
# Get positive outstanding sales /purchase invoices
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={} and voucher_no={}".format(
condition = " and voucher_type={0} and voucher_no={1}".format(
frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])
)
common_filter.append(ple.voucher_type == args["voucher_type"])
@@ -1669,7 +1621,7 @@ def get_outstanding_reference_documents(args, validate=False):
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
if args.get(dim.fieldname):
condition += f" and {dim.fieldname}='{args.get(dim.fieldname)}'"
condition += " and {0}='{1}'".format(dim.fieldname, args.get(dim.fieldname))
accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname))
date_fields_dict = {
@@ -1679,21 +1631,21 @@ def get_outstanding_reference_documents(args, validate=False):
for fieldname, date_fields in date_fields_dict.items():
if args.get(date_fields[0]) and args.get(date_fields[1]):
condition += " and {} between '{}' and '{}'".format(
condition += " and {0} between '{1}' and '{2}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
)
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
elif args.get(date_fields[0]):
# if only from date is supplied
condition += f" and {fieldname} >= '{args.get(date_fields[0])}'"
condition += " and {0} >= '{1}'".format(fieldname, args.get(date_fields[0]))
posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0])))
elif args.get(date_fields[1]):
# if only to date is supplied
condition += f" and {fieldname} <= '{args.get(date_fields[1])}'"
condition += " and {0} <= '{1}'".format(fieldname, args.get(date_fields[1]))
posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
if args.get("company"):
condition += " and company = {}".format(frappe.db.escape(args.get("company")))
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
common_filter.append(ple.company == args.get("company"))
outstanding_invoices = []
@@ -1708,7 +1660,7 @@ def get_outstanding_reference_documents(args, validate=False):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
[party_account],
party_account,
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
@@ -1809,10 +1761,12 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list
return outstanding_invoices_after_split
def get_currency_data(outstanding_invoices: list, company: str | None = None) -> dict:
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict:
"""Get currency and conversion data for a list of invoices."""
exc_rates = frappe._dict()
company_currency = frappe.db.get_value("Company", company, "default_currency") if company else None
company_currency = (
frappe.db.get_value("Company", company, "default_currency") if company else None
)
for doctype in ["Sales Invoice", "Purchase Invoice"]:
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
@@ -1907,7 +1861,7 @@ def get_orders_to_be_billed(
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
if filters.get(dim.fieldname):
condition += f" and {dim.fieldname}='{filters.get(dim.fieldname)}'"
condition += " and {0}='{1}'".format(dim.fieldname, filters.get(dim.fieldname))
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
@@ -2030,8 +1984,6 @@ def get_negative_outstanding_invoices(
@frappe.whitelist()
def get_party_details(company, party_type, party, date, cost_center=None):
bank_account = ""
party_bank_account = ""
if not frappe.db.exists(party_type, party):
frappe.throw(_("{0} {1} does not exist").format(_(party_type), party))
@@ -2042,8 +1994,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
party_name = frappe.db.get_value(party_type, party, _party_name)
party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center)
if party_type in ["Customer", "Supplier"]:
party_bank_account = get_party_bank_account(party_type, party)
bank_account = get_default_company_bank_account(company, party_type, party)
bank_account = get_party_bank_account(party_type, party)
return {
"party_account": party_account,
@@ -2051,7 +2002,6 @@ def get_party_details(company, party_type, party, date, cost_center=None):
"party_account_currency": account_currency,
"party_balance": party_balance,
"account_balance": account_balance,
"party_bank_account": party_bank_account,
"bank_account": bank_account,
}
@@ -2061,14 +2011,18 @@ def get_account_details(account, date, cost_center=None):
frappe.has_permission("Payment Entry", throw=True)
# to check if the passed account is accessible under reference doctype Payment Entry
account_list = frappe.get_list("Account", {"name": account}, reference_doctype="Payment Entry", limit=1)
account_list = frappe.get_list(
"Account", {"name": account}, reference_doctype="Payment Entry", limit=1
)
# There might be some user permissions which will allow account under certain doctypes
# except for Payment Entry, only in such case we should throw permission error
if not account_list:
frappe.throw(_("Account: {0} is not permitted under Payment Entry").format(account))
account_balance = get_balance_on(account, date, cost_center=cost_center, ignore_account_permission=True)
account_balance = get_balance_on(
account, date, cost_center=cost_center, ignore_account_permission=True
)
return frappe._dict(
{
@@ -2085,75 +2039,53 @@ def get_company_defaults(company):
return frappe.get_cached_value("Company", company, fields, as_dict=1)
def get_outstanding_on_journal_entry(voucher_no, party_type, party):
ple = frappe.qb.DocType("Payment Ledger Entry")
outstanding = (
frappe.qb.from_(ple)
.select(Sum(ple.amount_in_account_currency))
.where(
(ple.against_voucher_no == voucher_no)
& (ple.party_type == party_type)
& (ple.party == party)
& (ple.delinked == 0)
def get_outstanding_on_journal_entry(name):
gl = frappe.qb.DocType("GL Entry")
res = (
frappe.qb.from_(gl)
.select(
Case()
.when(
gl.party_type == "Customer",
Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
)
.else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
.as_("outstanding_amount")
)
).run()
outstanding_amount = outstanding[0][0] if outstanding else 0
total = (
frappe.qb.from_(ple)
.select(Sum(ple.amount_in_account_currency))
.where(
(ple.voucher_no == voucher_no)
& (ple.party_type == party_type)
& (ple.party == party)
& (ple.delinked == 0)
(Coalesce(gl.party_type, "") != "")
& (gl.is_cancelled == 0)
& ((gl.voucher_no == name) | (gl.against_voucher == name))
)
).run()
).run(as_dict=True)
total_amount = total[0][0] if total else 0
outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
return outstanding_amount, total_amount
return outstanding_amount
@frappe.whitelist()
def get_reference_details(
reference_doctype, reference_name, party_account_currency, party_type=None, party=None
):
def get_reference_details(reference_doctype, reference_name, party_account_currency):
total_amount = outstanding_amount = exchange_rate = account = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
# Only applies for Reverse Payment Entries
account_type = None
payment_type = None
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
ref_doc.company
)
if reference_doctype == "Dunning":
total_amount = outstanding_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
exchange_rate = get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
else:
exchange_rate = 1
outstanding_amount, total_amount = get_outstanding_on_journal_entry(
reference_name, party_type, party
)
elif reference_doctype == "Payment Entry":
if reverse_payment_details := frappe.db.get_all(
"Payment Entry",
filters={"name": reference_name},
fields=["payment_type", "party_type"],
)[0]:
payment_type = reverse_payment_details.payment_type
account_type = frappe.db.get_value(
"Party Type", reverse_payment_details.party_type, "account_type"
)
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
elif reference_doctype != "Journal Entry":
if not total_amount:
@@ -2199,8 +2131,6 @@ def get_reference_details(
"outstanding_amount": flt(outstanding_amount),
"exchange_rate": flt(exchange_rate),
"bill_no": ref_doc.get("bill_no"),
"account_type": account_type,
"payment_type": payment_type,
}
)
if account:
@@ -2221,7 +2151,9 @@ def get_payment_entry(
):
doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (100.0 + over_billing_allowance):
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
100.0 + over_billing_allowance
):
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
if not party_type:
@@ -2274,7 +2206,9 @@ def get_payment_entry(
pe.paid_from_account_currency = (
party_account_currency if payment_type == "Receive" else bank.account_currency
)
pe.paid_to_account_currency = party_account_currency if payment_type == "Pay" else bank.account_currency
pe.paid_to_account_currency = (
party_account_currency if payment_type == "Pay" else bank.account_currency
)
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
@@ -2303,6 +2237,7 @@ def get_payment_entry(
doc.payment_terms_template,
"allocate_payment_based_on_payment_terms",
):
for reference in get_reference_as_per_payment_terms(
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
@@ -2486,7 +2421,9 @@ def set_paid_amount_and_received_amount(
return paid_amount, received_amount
def apply_early_payment_discount(paid_amount, received_amount, doc, party_account_currency, reference_date):
def apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
):
total_discount = 0
valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
@@ -2496,6 +2433,7 @@ def apply_early_payment_discount(paid_amount, received_amount, doc, party_accoun
if doc.doctype in eligible_for_payments and has_payment_schedule:
for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
if term.discount_type == "Percentage":
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
discount_amount = flt(grand_total) * (term.discount / 100)
@@ -2524,7 +2462,9 @@ def apply_early_payment_discount(paid_amount, received_amount, doc, party_accoun
return paid_amount, received_amount, total_discount, valid_discounts
def set_pending_discount_loss(pe, doc, discount_amount, base_total_discount_loss, party_account_currency):
def set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
):
# If multi-currency, get base discount amount to adjust with base currency deductions/losses
if party_account_currency != doc.company_currency:
discount_amount = discount_amount * doc.get("conversion_rate", 1)
@@ -2544,8 +2484,7 @@ def set_pending_discount_loss(pe, doc, discount_amount, base_total_discount_loss
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, account_type),
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * positive_negative,
}
)
@@ -2568,7 +2507,9 @@ def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
def get_total_discount_percent(doc, valid_discounts) -> float:
"""Get total percentage and amount discount applied as a percentage."""
total_discount_percent = (
sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage")
sum(
discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
)
or 0.0
)
@@ -2611,7 +2552,9 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
# The same account head could be used more than once
for tax in doc.get("taxes", []):
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (total_discount_percentage / 100)
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (
total_discount_percentage / 100
)
account = tax.get("account_head")
if not tax_discount_loss.get(account):
@@ -2628,8 +2571,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"deductions",
{
"account": account,
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(loss, precision),
},
)
@@ -2652,8 +2594,7 @@ def get_reference_as_per_payment_terms(
if not is_multi_currency_acc:
# If accounting is done in company currency for multi-currency transaction
payment_term_outstanding = flt(
payment_term_outstanding * doc.get("conversion_rate"),
payment_term.precision("payment_amount"),
payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
)
if payment_term_outstanding:
@@ -2681,7 +2622,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
paid_amount = frappe.db.sql(
f"""
"""
select ifnull(sum({dr_or_cr}), 0) as paid_amount
from `tabGL Entry`
where against_voucher_type = %s
@@ -2691,7 +2632,9 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
and account = %s
and due_date = %s
and {dr_or_cr} > 0
""",
""".format(
dr_or_cr=dr_or_cr
),
(dt, dn, party_type, party, account, due_date),
)

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe import qb
@@ -9,8 +10,8 @@ from frappe.utils import add_days, flt, nowdate
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.accounts.doctype.payment_entry.payment_entry import (
InvalidPaymentEntry,
get_outstanding_reference_documents,
get_party_details,
get_payment_entry,
get_reference_details,
)
@@ -161,7 +162,7 @@ class TestPaymentEntry(FrappeTestCase):
supplier.on_hold = 0
supplier.save()
except Exception:
except:
pass
else:
raise Exception
@@ -468,7 +469,9 @@ class TestPaymentEntry(FrappeTestCase):
si.save()
si.submit()
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700)
pe = get_payment_entry(
"Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700
)
pe.reference_no = si.name
pe.reference_date = nowdate()
@@ -635,7 +638,9 @@ class TestPaymentEntry(FrappeTestCase):
pe.set_exchange_rate()
pe.set_amounts()
self.assertEqual(pe.source_exchange_rate, 65.1, f"{pe.source_exchange_rate} is not equal to {65.1}")
self.assertEqual(
pe.source_exchange_rate, 65.1, "{0} is not equal to {1}".format(pe.source_exchange_rate, 65.1)
)
def test_internal_transfer_usd_to_inr(self):
pe = frappe.new_doc("Payment Entry")
@@ -891,7 +896,9 @@ class TestPaymentEntry(FrappeTestCase):
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
pi = make_purchase_invoice_against_cost_center(
cost_center=cost_center, credit_to="Creditors - _TC"
)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
self.assertEqual(pe.cost_center, pi.cost_center)
@@ -932,7 +939,9 @@ class TestPaymentEntry(FrappeTestCase):
si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=si.cost_center)
party_balance = get_balance_on(party_type="Customer", party=si.customer, cost_center=si.cost_center)
party_balance = get_balance_on(
party_type="Customer", party=si.customer, cost_center=si.cost_center
)
party_account_balance = get_balance_on(si.debit_to, cost_center=si.cost_center)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
@@ -1075,13 +1084,9 @@ class TestPaymentEntry(FrappeTestCase):
pe.source_exchange_rate = 50
pe.save()
ref_details = get_reference_details(
so.doctype, so.name, pe.paid_from_account_currency, "Customer", so.customer
)
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
expected_response = {
"account": get_party_account("Customer", so.customer, so.company),
"account_type": None, # only applies for Reverse Payment Entry
"payment_type": None, # only applies for Reverse Payment Entry
"total_amount": 5000.0,
"outstanding_amount": 5000.0,
"exchange_rate": 1.0,
@@ -1198,7 +1203,7 @@ class TestPaymentEntry(FrappeTestCase):
Overallocation validation shouldn't fire for Template without "Allocate Payment based on Payment Terms" enabled
"""
create_customer()
customer = create_customer()
create_payment_terms_template()
template = frappe.get_doc("Payment Terms Template", "Test Receivable Template")
@@ -1319,6 +1324,8 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries()
def test_ledger_entries_for_advance_as_liability(self):
from erpnext.accounts.doctype.account.test_account import create_account
company = "_Test Company"
advance_account = create_account(
@@ -1420,7 +1427,7 @@ class TestPaymentEntry(FrappeTestCase):
self.check_pl_entries()
# Unreconcile
(
unrecon = (
frappe.get_doc(
{
"doctype": "Unreconcile Payment",
@@ -1440,68 +1447,6 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries()
self.check_pl_entries()
def test_advance_as_liability_against_order(self):
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_invoice as _make_purchase_invoice,
)
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
company = "_Test Company"
advance_account = create_account(
parent_account="Current Liabilities - _TC",
account_name="Advances Paid",
company=company,
account_type="Liability",
)
frappe.db.set_value(
"Company",
company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": advance_account,
},
)
po = create_purchase_order(supplier="_Test Supplier")
pe = get_payment_entry("Purchase Order", po.name, bank_account="Cash - _TC")
pe.save().submit()
pre_reconciliation_gle = [
{"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0},
{"account": advance_account, "debit": 5000.0, "credit": 0.0},
]
self.voucher_no = pe.name
self.expected_gle = pre_reconciliation_gle
self.check_gl_entries()
# Make Purchase Invoice against the order
pi = _make_purchase_invoice(po.name)
pi.append(
"advances",
{
"reference_type": pe.doctype,
"reference_name": pe.name,
"reference_row": pe.references[0].name,
"advance_amount": 5000,
"allocated_amount": 5000,
},
)
pi.save().submit()
# # assert General and Payment Ledger entries post partial reconciliation
self.expected_gle = [
{"account": pi.credit_to, "debit": 5000.0, "credit": 0.0},
{"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0},
{"account": advance_account, "debit": 5000.0, "credit": 0.0},
{"account": advance_account, "debit": 0.0, "credit": 5000.0},
]
self.voucher_no = pe.name
self.check_gl_entries()
def check_pl_entries(self):
ple = frappe.qb.DocType("Payment Ledger Entry")
pl_entries = (
@@ -1538,7 +1483,9 @@ class TestPaymentEntry(FrappeTestCase):
create_payment_terms_template()
# SI has an earlier due date and SI2 has a later due date
si = create_sales_invoice(qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4))
si = create_sales_invoice(
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
)
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
si2.payment_terms_template = "Test Receivable Template"
si2.submit()
@@ -1567,168 +1514,6 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
self.assertEqual(references[2].payment_term, "Tax Receivable")
def test_reverse_payment_reconciliation(self):
customer = create_customer(frappe.generate_hash(length=10), "INR")
pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Receive",
paid_from="Debtors - _TC",
paid_to="_Test Cash - _TC",
)
pe.submit()
reverse_pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Pay",
paid_from="_Test Cash - _TC",
paid_to="Debtors - _TC",
)
reverse_pe.submit()
pr = frappe.get_doc("Payment Reconciliation")
pr.company = "_Test Company"
pr.party_type = "Customer"
pr.party = customer
pr.receivable_payable_account = "Debtors - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(reverse_pe.name, pr.invoices[0].invoice_number)
self.assertEqual(pe.name, pr.payments[0].reference_name)
invoices = [x.as_dict() for x in pr.invoices]
payments = [pr.payments[0].as_dict()]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
def test_advance_reverse_payment_reconciliation(self):
company = "_Test Company"
customer = create_customer(frappe.generate_hash(length=10), "INR")
advance_account = create_account(
parent_account="Current Liabilities - _TC",
account_name="Advances Received",
company=company,
account_type="Receivable",
)
frappe.db.set_value(
"Company",
company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_received_account": advance_account,
},
)
# Reverse Payment(essentially an Invoice)
reverse_pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Pay",
paid_from="_Test Cash - _TC",
paid_to=advance_account,
)
reverse_pe.save() # use save() to trigger set_liability_account()
reverse_pe.submit()
# Advance Payment
pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Receive",
paid_from=advance_account,
paid_to="_Test Cash - _TC",
)
pe.save() # use save() to trigger set_liability_account()
pe.submit()
# Partially reconcile advance against invoice
pr = frappe.get_doc("Payment Reconciliation")
pr.company = company
pr.party_type = "Customer"
pr.party = customer
pr.receivable_payable_account = "Debtors - _TC"
pr.default_advance_account = advance_account
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].allocated_amount = 400
pr.reconcile()
# assert General and Payment Ledger entries post partial reconciliation
self.expected_gle = [
{"account": advance_account, "debit": 400.0, "credit": 0.0},
{"account": advance_account, "debit": 0.0, "credit": 1000.0},
{"account": advance_account, "debit": 0.0, "credit": 400.0},
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
]
self.expected_ple = [
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": -1000.0,
},
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": reverse_pe.name,
"amount": -400.0,
},
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": 400.0,
},
]
self.voucher_no = pe.name
self.check_gl_entries()
self.check_pl_entries()
# Unreconcile
(
frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
"allocations": [
{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}
],
}
)
.save()
.submit()
)
# assert General and Payment Ledger entries post unreconciliation
self.expected_gle = [
{"account": advance_account, "debit": 0.0, "credit": 1000.0},
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
]
self.expected_ple = [
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": -1000.0,
},
]
self.voucher_no = pe.name
self.check_gl_entries()
self.check_pl_entries()
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
@@ -1747,10 +1532,6 @@ def create_payment_entry(**args):
payment_entry.reference_no = "Test001"
payment_entry.reference_date = nowdate()
get_party_details(
payment_entry.company, payment_entry.party_type, payment_entry.party, payment_entry.posting_date
)
if args.get("save"):
payment_entry.save()
if args.get("submit"):
@@ -1760,11 +1541,12 @@ def create_payment_entry(**args):
def create_payment_terms_template():
create_payment_term("Basic Amount Receivable")
create_payment_term("Tax Receivable")
if not frappe.db.exists("Payment Terms Template", "Test Receivable Template"):
frappe.get_doc(
payment_term_template = frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "Test Receivable Template",

View File

@@ -10,8 +10,6 @@
"due_date",
"bill_no",
"payment_term",
"account_type",
"payment_type",
"column_break_4",
"total_amount",
"outstanding_amount",
@@ -110,22 +108,12 @@
"fieldtype": "Link",
"label": "Account",
"options": "Account"
},
{
"fieldname": "account_type",
"fieldtype": "Data",
"label": "Account Type"
},
{
"fieldname": "payment_type",
"fieldtype": "Data",
"label": "Payment Type"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-04-05 09:44:08.310593",
"modified": "2023-06-08 07:40:38.487874",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",

View File

@@ -15,7 +15,6 @@ class PaymentEntryReference(Document):
from frappe.types import DF
account: DF.Link | None
account_type: DF.Data | None
allocated_amount: DF.Float
bill_no: DF.Data | None
due_date: DF.Date | None
@@ -26,7 +25,6 @@ class PaymentEntryReference(Document):
parentfield: DF.Data
parenttype: DF.Data
payment_term: DF.Link | None
payment_type: DF.Data | None
reference_doctype: DF.Link
reference_name: DF.DynamicLink
total_amount: DF.Float

View File

@@ -137,9 +137,9 @@ class PaymentLedgerEntry(Document):
):
if not self.get(dimension.fieldname):
frappe.throw(
_(
"Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}."
).format(dimension.label, self.account)
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
dimension.label, self.account
)
)
if (
@@ -150,9 +150,9 @@ class PaymentLedgerEntry(Document):
):
if not self.get(dimension.fieldname):
frappe.throw(
_(
"Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}."
).format(dimension.label, self.account)
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
dimension.label, self.account
)
)
def validate(self):

View File

@@ -84,14 +84,11 @@ class TestPaymentLedgerEntry(FrappeTestCase):
self.customer = customer.name
def create_sales_invoice(
self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
if posting_date is None:
posting_date = nowdate()
sinv = create_sales_invoice(
qty=qty,
rate=rate,
@@ -115,12 +112,10 @@ class TestPaymentLedgerEntry(FrappeTestCase):
)
return sinv
def create_payment_entry(self, amount=100, posting_date=None):
def create_payment_entry(self, amount=100, posting_date=nowdate()):
"""
Helper function to populate default values in payment entry
"""
if posting_date is None:
posting_date = nowdate()
payment = create_payment_entry(
company=self.company,
payment_type="Receive",
@@ -133,10 +128,9 @@ class TestPaymentLedgerEntry(FrappeTestCase):
payment.posting_date = posting_date
return payment
def create_sales_order(self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False):
if posting_date is None:
posting_date = nowdate()
def create_sales_order(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
so = make_sales_order(
company=self.company,
transaction_date=posting_date,
@@ -165,7 +159,9 @@ class TestPaymentLedgerEntry(FrappeTestCase):
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_journal_entry(self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None):
def create_journal_entry(
self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
):
je = frappe.new_doc("Journal Entry")
je.posting_date = posting_date or nowdate()
je.company = self.company
@@ -323,7 +319,9 @@ class TestPaymentLedgerEntry(FrappeTestCase):
ple.amount,
ple.delinked,
)
.where((ple.against_voucher_type == cr_note1.doctype) & (ple.against_voucher_no == cr_note1.name))
.where(
(ple.against_voucher_type == cr_note1.doctype) & (ple.against_voucher_no == cr_note1.name)
)
.orderby(ple.creation)
.run(as_dict=True)
)
@@ -364,7 +362,9 @@ class TestPaymentLedgerEntry(FrappeTestCase):
)
cr_note2.is_return = 1
cr_note2 = cr_note2.save().submit()
je1 = self.create_journal_entry(self.debit_to, self.debit_to, amount, posting_date=transaction_date)
je1 = self.create_journal_entry(
self.debit_to, self.debit_to, amount, posting_date=transaction_date
)
je1.get("accounts")[0].party_type = je1.get("accounts")[1].party_type = "Customer"
je1.get("accounts")[0].party = je1.get("accounts")[1].party = self.customer
je1.get("accounts")[0].reference_type = cr_note2.doctype
@@ -419,7 +419,9 @@ class TestPaymentLedgerEntry(FrappeTestCase):
ple.amount,
ple.delinked,
)
.where((ple.against_voucher_type == cr_note2.doctype) & (ple.against_voucher_no == cr_note2.name))
.where(
(ple.against_voucher_type == cr_note2.doctype) & (ple.against_voucher_no == cr_note2.name)
)
.orderby(ple.creation)
.run(as_dict=True)
)
@@ -516,7 +518,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
amount = 100
so = self.create_sales_order(qty=1, rate=amount, posting_date=transaction_date).save().submit()
get_payment_entry(so.doctype, so.name).save().submit()
pe = get_payment_entry(so.doctype, so.name).save().submit()
so.reload()
so.cancel()

View File

@@ -71,7 +71,6 @@ frappe.ui.form.on("Payment Order", {
target: frm,
date_field: "posting_date",
setters: {
party_type: "Supplier",
party: frm.doc.supplier || "",
},
get_query_filters: {
@@ -92,7 +91,6 @@ frappe.ui.form.on("Payment Order", {
source_doctype: "Payment Request",
target: frm,
setters: {
party_type: "Supplier",
party: frm.doc.supplier || "",
},
get_query_filters: {

View File

@@ -90,7 +90,9 @@ def make_journal_entry(doc, supplier, mode_of_payment=None):
je = frappe.new_doc("Journal Entry")
je.payment_order = doc.name
je.posting_date = nowdate()
mode_of_payment_type = frappe._dict(frappe.get_all("Mode of Payment", fields=["name", "type"], as_list=1))
mode_of_payment_type = frappe._dict(
frappe.get_all("Mode of Payment", fields=["name", "type"], as_list=1)
)
je.voucher_type = "Bank Entry"
if mode_of_payment and mode_of_payment_type.get(mode_of_payment) == "Cash":

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -40,7 +41,9 @@ class TestPaymentOrder(FrappeTestCase):
payment_entry.insert()
payment_entry.submit()
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry", self.bank_account)
doc = create_payment_order_against_payment_entry(
payment_entry, "Payment Entry", self.bank_account
)
reference_doc = doc.get("references")[0]
self.assertEqual(reference_doc.reference_name, payment_entry.name)
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")

View File

@@ -176,12 +176,8 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
},
callback: (r) => {
if (!r.exc && r.message) {
if (typeof r.message === "string") {
this.frm.set_value("receivable_payable_account", r.message);
} else if (Array.isArray(r.message)) {
this.frm.set_value("receivable_payable_account", r.message[0]);
this.frm.set_value("default_advance_account", r.message[1]);
}
this.frm.set_value("receivable_payable_account", r.message[0]);
this.frm.set_value("default_advance_account", r.message[1]);
}
this.frm.refresh();
},
@@ -258,7 +254,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.data = [];
const dialog = new frappe.ui.Dialog({
title: __("Select Difference Account"),
size: "extra-large",
fields: [
{
fieldname: "allocation",
@@ -284,13 +279,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
in_list_view: 1,
read_only: 1,
},
{
fieldtype: "Date",
fieldname: "gain_loss_posting_date",
label: __("Posting Date"),
in_list_view: 1,
reqd: 1,
},
{
fieldtype: "Link",
options: "Account",
@@ -331,12 +319,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
"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();
@@ -352,7 +334,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
reference_name: d.reference_name,
difference_amount: d.difference_amount,
difference_account: d.difference_account,
gain_loss_posting_date: d.gain_loss_posting_date,
});
}
});

View File

@@ -195,8 +195,6 @@
},
{
"depends_on": "eval:doc.party",
"description": "Only 'Payment Entries' made against this advance account are supported.",
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/advance-in-separate-party-account",
"fieldname": "default_advance_account",
"fieldtype": "Link",
"label": "Default Advance Account",
@@ -231,7 +229,7 @@
"is_virtual": 1,
"issingle": 1,
"links": [],
"modified": "2024-04-23 12:38:29.557315",
"modified": "2023-12-14 13:38:16.264013",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",

View File

@@ -67,7 +67,7 @@ class PaymentReconciliation(Document):
# end: auto-generated types
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super(PaymentReconciliation, self).__init__(*args, **kwargs)
self.common_filter_conditions = []
self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = []
@@ -286,6 +286,7 @@ class PaymentReconciliation(Document):
self.return_invoices = self.return_invoices_query.run(as_dict=True)
def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry")
@@ -339,15 +340,10 @@ class PaymentReconciliation(Document):
self.build_qb_filter_conditions(get_invoices=True)
accounts = [self.receivable_payable_account]
if self.default_advance_account:
accounts.append(self.default_advance_account)
non_reconciled_invoices = get_outstanding_invoices(
self.party_type,
self.party,
accounts,
self.receivable_payable_account,
common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
@@ -411,7 +407,9 @@ class PaymentReconciliation(Document):
payment_entry[0].get("reference_name")
)
new_difference_amount = self.get_difference_amount(payment_entry[0], invoice[0], allocated_amount)
new_difference_amount = self.get_difference_amount(
payment_entry[0], invoice[0], allocated_amount
)
return new_difference_amount
@frappe.whitelist()
@@ -443,7 +441,6 @@ class PaymentReconciliation(Document):
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
res.update({"gain_loss_posting_date": pay.get("posting_date")})
if pay.get("amount") == 0:
entries.append(res)
@@ -529,9 +526,9 @@ class PaymentReconciliation(Document):
if running_doc:
frappe.throw(
_(
"A Reconciliation Job {0} is running for the same filters. Cannot reconcile now"
).format(get_link_to_form("Auto Reconcile", running_doc))
_("A Reconciliation Job {0} is running for the same filters. Cannot reconcile now").format(
get_link_to_form("Auto Reconcile", running_doc)
)
)
return
@@ -560,7 +557,6 @@ class PaymentReconciliation(Document):
"allocated_amount": flt(row.get("allocated_amount")),
"difference_amount": flt(row.get("difference_amount")),
"difference_account": row.get("difference_account"),
"difference_posting_date": row.get("gain_loss_posting_date"),
"cost_center": row.get("cost_center"),
}
)
@@ -624,7 +620,9 @@ class PaymentReconciliation(Document):
invoice_exchange_map.update(purchase_invoice_map)
journals = [d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"]
journals = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
]
journals.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
)
@@ -716,7 +714,7 @@ class PaymentReconciliation(Document):
def get_journal_filter_conditions(self):
conditions = []
je = qb.DocType("Journal Entry")
qb.DocType("Journal Entry Account")
jea = qb.DocType("Journal Entry Account")
conditions.append(je.company == self.company)
if self.from_payment_date:
@@ -836,7 +834,7 @@ def adjust_allocations_for_taxes(doc):
@frappe.whitelist()
def get_queries_for_dimension_filters(company: str | None = None):
def get_queries_for_dimension_filters(company: str = None):
dimensions_with_filters = []
for d in get_dimensions()[0]:
filters = {}

Some files were not shown because too many files have changed in this diff Show More