Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-40584

This commit is contained in:
mergify[bot]
2024-04-10 15:57:49 +00:00
committed by GitHub
605 changed files with 5625 additions and 6617 deletions

View File

@@ -29,3 +29,6 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
# bulk format python code with black # bulk format python code with black
baec607ff5905b1c67531096a9cf50ec7ff00a5d baec607ff5905b1c67531096a9cf50ec7ff00a5d
# ruff
960ef14b7a68cfec9e309ec12845f521cb6a721c

View File

@@ -55,28 +55,15 @@ repos:
erpnext/templates/includes/.* erpnext/templates/includes/.*
)$ )$
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/astral-sh/ruff-pre-commit
rev: 6.0.0 rev: v0.2.0
hooks: hooks:
- id: flake8 - id: ruff
additional_dependencies: [ name: "Run ruff linter and apply fixes"
'flake8-bugbear', args: ["--fix"]
'flake8-tuple',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- repo: https://github.com/adityahase/black - id: ruff-format
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119 name: "Format Python code"
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$"
ci: ci:

View File

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

View File

@@ -11,14 +11,14 @@ class ERPNextAddress(Address):
def validate(self): def validate(self):
self.validate_reference() self.validate_reference()
self.update_compnay_address() self.update_compnay_address()
super(ERPNextAddress, self).validate() super().validate()
def link_address(self): def link_address(self):
"""Link address based on owner""" """Link address based on owner"""
if self.is_your_company_address: if self.is_your_company_address:
return return
return super(ERPNextAddress, self).link_address() return super().link_address()
def update_compnay_address(self): def update_compnay_address(self):
for link in self.get("links"): for link in self.get("links"):
@@ -26,11 +26,11 @@ class ERPNextAddress(Address):
self.is_your_company_address = 1 self.is_your_company_address = 1
def validate_reference(self): def validate_reference(self):
if self.is_your_company_address and not [ if self.is_your_company_address and not [row for row in self.links if row.link_doctype == "Company"]:
row for row in self.links if row.link_doctype == "Company"
]:
frappe.throw( 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"), title=_("Company Not Linked"),
) )

View File

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

View File

@@ -24,14 +24,10 @@ from erpnext.accounts.utils import get_account_currency
def validate_service_stop_date(doc): def validate_service_stop_date(doc):
"""Validates service_stop_date for Purchase Invoice and Sales Invoice""" """Validates service_stop_date for Purchase Invoice and Sales Invoice"""
enable_check = ( enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
old_stop_dates = {} old_stop_dates = {}
old_doc = frappe.db.get_all( old_doc = frappe.db.get_all(f"{doc.doctype} Item", {"parent": doc.name}, ["name", "service_stop_date"])
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
)
for d in old_doc: for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or "" old_stop_dates[d.name] = d.service_stop_date or ""
@@ -62,16 +58,14 @@ def build_conditions(process_type, account, company):
) )
if account: if account:
conditions += "AND %s='%s'" % (deferred_account, account) conditions += f"AND {deferred_account}='{account}'"
elif company: elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}" conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions return conditions
def convert_deferred_expense_to_expense( def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=""):
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 # 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: if not start_date:
@@ -81,16 +75,14 @@ def convert_deferred_expense_to_expense(
# check for the purchase invoice for which GL entries has to be done # check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list(
""" f"""
select distinct item.parent select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s 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.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0} {conditions}
""".format( """,
conditions
),
(end_date, start_date), (end_date, start_date),
) # nosec ) # nosec
@@ -103,9 +95,7 @@ def convert_deferred_expense_to_expense(
send_mail(deferred_process) send_mail(deferred_process)
def convert_deferred_revenue_to_income( def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""):
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 # 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: if not start_date:
@@ -115,16 +105,14 @@ def convert_deferred_revenue_to_income(
# check for the sales invoice for which GL entries has to be done # check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list(
""" f"""
select distinct item.parent select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s 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.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0} {conditions}
""".format( """,
conditions
),
(end_date, start_date), (end_date, start_date),
) # nosec ) # nosec
@@ -243,9 +231,7 @@ def calculate_monthly_amount(
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount( already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item doc, item
) )
base_amount = flt( base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
@@ -265,17 +251,13 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
if account_currency == doc.company_currency: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
amount = flt( amount = flt(item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount"))
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
else: else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount( already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item doc, item
) )
base_amount = flt( base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
@@ -296,26 +278,22 @@ def get_already_booked_amount(doc, item):
gl_entries_details = frappe.db.sql( gl_entries_details = frappe.db.sql(
""" """
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no select sum({}) as total_credit, sum({}) 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 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 and is_cancelled = 0
group by voucher_detail_no group by voucher_detail_no
""".format( """.format(total_credit_debit, total_credit_debit_currency),
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True, as_dict=True,
) )
journal_entry_details = frappe.db.sql( journal_entry_details = frappe.db.sql(
""" """
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no SELECT sum(c.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and 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 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 and p.docstatus < 2 group by reference_detail_no
""".format( """.format(total_credit_debit, total_credit_debit_currency),
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True, as_dict=True,
) )
@@ -337,9 +315,7 @@ def get_already_booked_amount(doc, item):
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = ( enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
"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") accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
@@ -440,9 +416,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
via_journal_entry = cint( via_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry") frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
) )
submit_journal_entry = cint( submit_journal_entry = cint(frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries"))
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
)
book_deferred_entries_based_on = frappe.db.get_singles_value( book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on" "Accounts Settings", "book_deferred_entries_based_on"
) )
@@ -462,9 +436,7 @@ def process_deferred_accounting(posting_date=None):
posting_date = today() posting_date = today()
if not cint( if not cint(
frappe.db.get_singles_value( frappe.db.get_singles_value("Accounts Settings", "automatically_process_deferred_accounting_entry")
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
): ):
return return
@@ -587,16 +559,13 @@ def book_revenue_via_journal_entry(
deferred_process=None, deferred_process=None,
submit="No", submit="No",
): ):
if amount == 0: if amount == 0:
return return
journal_entry = frappe.new_doc("Journal Entry") journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date journal_entry.posting_date = posting_date
journal_entry.company = doc.company journal_entry.company = doc.company
journal_entry.voucher_type = ( journal_entry.voucher_type = "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process journal_entry.process_deferred_accounting = deferred_process
debit_entry = { debit_entry = {
@@ -645,7 +614,6 @@ def book_revenue_via_journal_entry(
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr): def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == "Sales Invoice": if doctype == "Sales Invoice":
credit_account, debit_account = frappe.db.get_value( credit_account, debit_account = frappe.db.get_value(
"Sales Invoice Item", "Sales Invoice Item",

View File

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

View File

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

View File

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

View File

@@ -40,16 +40,12 @@ class AccountClosingBalance(Document):
def make_closing_entries(closing_entries, voucher_name, company, closing_date): def make_closing_entries(closing_entries, voucher_name, company, closing_date):
accounting_dimensions = get_accounting_dimensions() accounting_dimensions = get_accounting_dimensions()
previous_closing_entries = get_previous_closing_entries( previous_closing_entries = get_previous_closing_entries(company, closing_date, accounting_dimensions)
company, closing_date, accounting_dimensions
)
combined_entries = closing_entries + previous_closing_entries combined_entries = closing_entries + previous_closing_entries
merged_entries = aggregate_with_last_account_closing_balance( merged_entries = aggregate_with_last_account_closing_balance(combined_entries, accounting_dimensions)
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 = frappe.new_doc("Account Closing Balance")
cle.update(value) cle.update(value)
cle.update(value["dimensions"]) cle.update(value["dimensions"])

View File

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

View File

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

View File

@@ -84,7 +84,10 @@ class AccountingPeriod(Document):
for doctype_for_closing in self.get_doctypes_for_closing(): for doctype_for_closing in self.get_doctypes_for_closing():
self.append( self.append(
"closed_documents", "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,9 +34,7 @@ class TestAccountingPeriod(unittest.TestCase):
ap1 = create_accounting_period(period_name="Test Accounting Period 2") ap1 = create_accounting_period(period_name="Test Accounting Period 2")
ap1.save() ap1.save()
doc = create_sales_invoice( doc = create_sales_invoice(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
)
self.assertRaises(ClosedAccountingPeriod, doc.save) self.assertRaises(ClosedAccountingPeriod, doc.save)
def tearDown(self): def tearDown(self):

View File

@@ -115,6 +115,10 @@ def get_party_bank_account(party_type, party):
return frappe.db.get_value(party_type, party, "default_bank_account") return frappe.db.get_value(party_type, party, "default_bank_account")
def get_default_company_bank_account(company):
return frappe.db.get_value("Bank Account", {"company": company, "is_company_account": 1, "is_default": 1})
@frappe.whitelist() @frappe.whitelist()
def get_bank_account_details(bank_account): def get_bank_account_details(bank_account):
return frappe.get_cached_value( return frappe.get_cached_value(

View File

@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
try: try:
bank_account.validate_iban() bank_account.validate_iban()
except ValidationError: except ValidationError:
msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(iban) msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
self.fail(msg=msg) self.fail(msg=msg)
for not_iban in invalid_ibans: for not_iban in invalid_ibans:
bank_account.iban = not_iban bank_account.iban = not_iban
msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban) msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
with self.assertRaises(ValidationError, msg=msg): with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban() 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')" condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
journal_entries = frappe.db.sql( journal_entries = frappe.db.sql(
""" f"""
select select
"Journal Entry" as payment_document, t1.name as payment_entry, "Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date, t1.cheque_no as cheque_number, t1.cheque_date,
@@ -141,9 +141,7 @@ def get_payment_entries_for_bank_clearance(
and ifnull(t1.is_opening, 'No') = 'No' {condition} and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC order by t1.posting_date ASC, t1.name DESC
""".format( """,
condition=condition
),
{"account": account, "from": from_date, "to": to_date}, {"account": account, "from": from_date, "to": to_date},
as_dict=1, as_dict=1,
) )
@@ -152,7 +150,7 @@ def get_payment_entries_for_bank_clearance(
condition += "and bank_account = %(bank_account)s" condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql( payment_entries = frappe.db.sql(
""" f"""
select select
"Payment Entry" as payment_document, name as payment_entry, "Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date, reference_no as cheque_number, reference_date as cheque_date,
@@ -167,9 +165,7 @@ def get_payment_entries_for_bank_clearance(
{condition} {condition}
order by order by
posting_date ASC, name DESC posting_date ASC, name DESC
""".format( """,
condition=condition
),
{ {
"account": account, "account": account,
"from": from_date, "from": from_date,
@@ -239,10 +235,7 @@ def get_payment_entries_for_bank_clearance(
).run(as_dict=True) ).run(as_dict=True)
entries = ( entries = (
list(payment_entries) list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices)
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(pos_purchase_invoices)
) )
return entries return entries

View File

@@ -68,9 +68,7 @@ class TestBankClearance(unittest.TestCase):
) )
loan.submit() loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate()) make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
repayment_entry = create_repayment_entry( repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
loan.name, "_Test Customer", getdate(), loan.loan_amount
)
repayment_entry.save() repayment_entry.save()
repayment_entry.submit() repayment_entry.submit()

View File

@@ -81,9 +81,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
def get_account_balance(bank_account, till_date): def get_account_balance(bank_account, till_date):
# returns account balance till the specified date # returns account balance till the specified date
account = frappe.db.get_value("Bank Account", bank_account, "account") account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict( filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1})
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
data = get_entries(filters) data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"]) balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@@ -96,10 +94,7 @@ def get_account_balance(bank_account, till_date):
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters) amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = ( bank_bal = (
flt(balance_as_per_system) flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
) )
return bank_bal return bank_bal
@@ -538,9 +533,7 @@ def check_matching(
for query in queries: for query in queries:
matching_vouchers.extend(query.run(as_dict=True)) matching_vouchers.extend(query.run(as_dict=True))
return ( return sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
)
def get_queries( def get_queries(
@@ -654,17 +647,13 @@ def get_bt_matching_query(exact_match, transaction):
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0) 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 amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0
ref_rank = ( ref_rank = frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
)
unallocated_rank = ( unallocated_rank = (
frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0) frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0)
) )
party_condition = ( party_condition = (
(bt.party_type == transaction.party_type) (bt.party_type == transaction.party_type) & (bt.party == transaction.party) & bt.party.isnotnull()
& (bt.party == transaction.party)
& bt.party.isnotnull()
) )
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -716,9 +705,7 @@ def get_pe_matching_query(
amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0 amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0
party_condition = ( party_condition = (
(pe.party_type == transaction.party_type) (pe.party_type == transaction.party_type) & (pe.party == transaction.party) & pe.party.isnotnull()
& (pe.party == transaction.party)
& pe.party.isnotnull()
) )
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -749,7 +736,7 @@ def get_pe_matching_query(
.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date) .orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
) )
if frappe.flags.auto_reconcile_vouchers == True: if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition) query = query.where(ref_condition)
return query return query
@@ -810,7 +797,7 @@ def get_je_matching_query(
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date) .orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
) )
if frappe.flags.auto_reconcile_vouchers == True: if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition) query = query.where(ref_condition)
return query return query

View File

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

View File

@@ -45,7 +45,7 @@ class BankStatementImport(DataImport):
# end: auto-generated types # end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BankStatementImport, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def validate(self): def validate(self):
doc_before_save = self.get_doc_before_save() doc_before_save = self.get_doc_before_save()
@@ -54,7 +54,6 @@ class BankStatementImport(DataImport):
or (doc_before_save and doc_before_save.import_file != self.import_file) 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) or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url)
): ):
template_options_dict = {} template_options_dict = {}
column_to_field_map = {} column_to_field_map = {}
bank = frappe.get_doc("Bank", self.bank) bank = frappe.get_doc("Bank", self.bank)
@@ -69,7 +68,6 @@ class BankStatementImport(DataImport):
self.validate_google_sheets_url() self.validate_google_sheets_url()
def start_import(self): def start_import(self):
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template( preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
self.import_file, self.google_sheets_url self.import_file, self.google_sheets_url
) )
@@ -125,7 +123,7 @@ def download_errored_template(data_import_name):
def parse_data_from_template(raw_data): def parse_data_from_template(raw_data):
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): if all(v in INVALID_VALUES for v in row):
# empty row # empty row
continue continue
@@ -135,9 +133,7 @@ def parse_data_from_template(raw_data):
return data return data
def start_import( def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
data_import, bank_account, import_file_path, google_sheets_url, bank, template_options
):
"""This method runs in background job""" """This method runs in background job"""
update_mapping_db(bank, template_options) update_mapping_db(bank, template_options)

View File

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

View File

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

View File

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

View File

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

View File

@@ -70,10 +70,11 @@ class Budget(Document):
select select
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
where where
ba.parent = b.name and b.docstatus < 2 and b.company = %s and %s=%s and ba.parent = b.name and b.docstatus < 2 and b.company = {} and {}={} and
b.fiscal_year=%s and b.name != %s and ba.account in (%s) """ b.fiscal_year={} and b.name != {} and ba.account in ({}) """.format(
% ("%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))), "%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))
(self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts), ),
(self.company, budget_against, self.fiscal_year, self.name, *tuple(accounts)),
as_dict=1, as_dict=1,
) )
@@ -96,12 +97,14 @@ class Budget(Document):
if account_details.is_group: if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account)) frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company: 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": elif account_details.report_type != "Profit and Loss":
frappe.throw( 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: if d.account in account_list:
@@ -174,30 +177,24 @@ def validate_expense_against_budget(args, expense_amount=0):
and args.account and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}) and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
): ):
doctype = dimension.get("document_type") doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"): if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"]) lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = """and exists(select name from `tab%s` condition = f"""and exists(select name from `tab{doctype}`
where lft<=%s and rgt>=%s and name=b.%s)""" % ( where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec
doctype,
lft,
rgt,
budget_against,
) # nosec
args.is_tree = True args.is_tree = True
else: else:
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against))) condition = f"and b.{budget_against}={frappe.db.escape(args.get(budget_against))}"
args.is_tree = False args.is_tree = False
args.budget_against_field = budget_against args.budget_against_field = budget_against
args.budget_against_doctype = doctype args.budget_against_doctype = doctype
budget_records = frappe.db.sql( budget_records = frappe.db.sql(
""" f"""
select select
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution, b.{budget_against} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request, ifnull(b.applicable_on_material_request, 0) as for_material_request,
ifnull(applicable_on_purchase_order, 0) as for_purchase_order, ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses, ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
@@ -210,9 +207,7 @@ def validate_expense_against_budget(args, expense_amount=0):
b.name=ba.parent and b.fiscal_year=%s b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1 and ba.account=%s and b.docstatus=1
{condition} {condition}
""".format( """,
condition=condition, budget_against_field=budget_against
),
(args.fiscal_year, args.account), (args.fiscal_year, args.account),
as_dict=True, as_dict=True,
) # nosec ) # nosec
@@ -240,7 +235,12 @@ def validate_budget_records(args, budget_records, expense_amount):
args["month_end_date"] = get_last_day(args.posting_date) args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget( compare_expense_with_budget(
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount args,
budget_amount,
_("Accumulated Monthly"),
monthly_action,
budget.budget_against,
amount,
) )
@@ -268,9 +268,8 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
frappe.bold(fmt_money(diff, currency=currency)), frappe.bold(fmt_money(diff, currency=currency)),
) )
if ( if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles(
frappe.flags.exception_approver_role frappe.session.user
and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)
): ):
action = "Warn" action = "Warn"
@@ -316,10 +315,8 @@ def get_requested_amount(args, budget):
data = frappe.db.sql( data = frappe.db.sql(
""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount """ 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 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 {0} 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( parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition),
condition
),
item_code, item_code,
as_list=1, as_list=1,
) )
@@ -332,12 +329,10 @@ def get_ordered_amount(args, budget):
condition = get_other_condition(args, budget, "Purchase Order") condition = get_other_condition(args, budget, "Purchase Order")
data = frappe.db.sql( data = frappe.db.sql(
""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount f""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
from `tabPurchase Order Item` child, `tabPurchase Order` parent where 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 parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
and parent.status != 'Closed' and {0}""".format( and parent.status != 'Closed' and {condition}""",
condition
),
item_code, item_code,
as_list=1, as_list=1,
) )
@@ -350,7 +345,7 @@ def get_other_condition(args, budget, for_doc):
budget_against_field = args.get("budget_against_field") budget_against_field = args.get("budget_against_field")
if budget_against_field and args.get(budget_against_field): if budget_against_field and args.get(budget_against_field):
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field)) condition += f" and child.{budget_against_field} = '{args.get(budget_against_field)}'"
if args.get("fiscal_year"): if args.get("fiscal_year"):
date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date" date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
@@ -358,12 +353,8 @@ def get_other_condition(args, budget, for_doc):
"Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"] "Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
) )
condition += """ and parent.%s condition += f""" and parent.{date_field}
between '%s' and '%s' """ % ( between '{start_date}' and '{end_date}' """
date_field,
start_date,
end_date,
)
return condition return condition
@@ -382,21 +373,17 @@ def get_actual_expense(args):
args.update(lft_rgt) args.update(lft_rgt)
condition2 = """and exists(select name from `tab{doctype}` condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
where lft>=%(lft)s and rgt<=%(rgt)s where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})""".format( and name=gle.{budget_against_field})"""
doctype=args.budget_against_doctype, budget_against_field=budget_against_field # nosec
)
else: else:
condition2 = """and exists(select name from `tab{doctype}` condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
where name=gle.{budget_against} and where name=gle.{budget_against_field} and
gle.{budget_against} = %({budget_against})s)""".format( gle.{budget_against_field} = %({budget_against_field})s)"""
doctype=args.budget_against_doctype, budget_against=budget_against_field
)
amount = flt( amount = flt(
frappe.db.sql( frappe.db.sql(
""" f"""
select sum(gle.debit) - sum(gle.credit) select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle from `tabGL Entry` gle
where where
@@ -407,9 +394,7 @@ def get_actual_expense(args):
and gle.company=%(company)s and gle.company=%(company)s
and gle.docstatus=1 and gle.docstatus=1
{condition2} {condition2}
""".format( """,
condition1=condition1, condition2=condition2
),
(args), (args),
)[0][0] )[0][0]
) # nosec ) # nosec

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,6 @@ class CurrencyExchangeSettings(Document):
def set_parameters_and_result(self): def set_parameters_and_result(self):
if self.service_provider == "exchangerate.host": if self.service_provider == "exchangerate.host":
if not self.access_key: if not self.access_key:
frappe.throw( frappe.throw(
_("Access Key is required for Service Provider: {0}").format( _("Access Key is required for Service Provider: {0}").format(
@@ -78,9 +77,7 @@ class CurrencyExchangeSettings(Document):
transaction_date=nowdate(), to_currency="INR", from_currency="USD" transaction_date=nowdate(), to_currency="INR", from_currency="USD"
) )
api_url = self.api_endpoint.format( api_url = self.api_endpoint.format(transaction_date=nowdate(), to_currency="INR", from_currency="USD")
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
)
try: try:
response = requests.get(api_url, params=params) response = requests.get(api_url, params=params)
@@ -100,14 +97,14 @@ class CurrencyExchangeSettings(Document):
] ]
except Exception: except Exception:
frappe.throw(_("Invalid result key. Response:") + " " + response.text) 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.")) frappe.throw(_("Returned exchange rate is neither integer not float."))
self.url = response.url self.url = response.url
@frappe.whitelist() @frappe.whitelist()
def get_api_endpoint(service_provider: str = None, use_http: bool = False): 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 and service_provider in ["exchangerate.host", "frankfurter.app"]:
if service_provider == "exchangerate.host": if service_provider == "exchangerate.host":
api = "api.exchangerate.host/convert" api = "api.exchangerate.host/convert"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -83,9 +83,7 @@ class InvoiceDiscounting(AccountsController):
frappe.throw( frappe.throw(
_( _(
"Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}" "Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}"
).format( ).format(record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice))
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)
)
) )
def calculate_total_amount(self): def calculate_total_amount(self):
@@ -105,7 +103,9 @@ class InvoiceDiscounting(AccountsController):
self.status = status self.status = status
self.db_set("status", status) self.db_set("status", status)
for d in self.invoices: 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: else:
self.status = "Draft" self.status = "Draft"
if self.docstatus == 1: if self.docstatus == 1:

View File

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

View File

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

View File

@@ -112,7 +112,7 @@ class JournalEntry(AccountsController):
# end: auto-generated types # end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def validate(self): def validate(self):
if self.voucher_type == "Opening Entry": if self.voucher_type == "Opening Entry":
@@ -191,7 +191,7 @@ class JournalEntry(AccountsController):
def on_cancel(self): def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller # References for this Journal are removed on the `on_cancel` event in accounts_controller
super(JournalEntry, self).on_cancel() super().on_cancel()
self.ignore_linked_doctypes = ( self.ignore_linked_doctypes = (
"GL Entry", "GL Entry",
"Stock Ledger Entry", "Stock Ledger Entry",
@@ -226,10 +226,7 @@ class JournalEntry(AccountsController):
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid() frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
def validate_inter_company_accounts(self): def validate_inter_company_accounts(self):
if ( if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
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) doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
account_currency = frappe.get_cached_value("Company", self.company, "default_currency") account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency") previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
@@ -375,10 +372,7 @@ class JournalEntry(AccountsController):
asset.set_status() asset.set_status()
def update_inter_company_jv(self): def update_inter_company_jv(self):
if ( if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
frappe.db.set_value( frappe.db.set_value(
"Journal Entry", "Journal Entry",
self.inter_company_journal_entry_reference, self.inter_company_journal_entry_reference,
@@ -406,17 +400,25 @@ class JournalEntry(AccountsController):
if d.account == inv_disc_doc.short_term_loan and d.reference_name == inv_disc: if d.account == inv_disc_doc.short_term_loan and d.reference_name == inv_disc:
if self.docstatus == 1: if self.docstatus == 1:
if d.credit > 0: 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" status = "Disbursed"
elif d.debit > 0: 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" status = "Settled"
else: else:
if d.credit > 0: 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" status = "Sanctioned"
elif d.debit > 0: 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" status = "Disbursed"
break break
if status: if status:
@@ -485,10 +487,7 @@ class JournalEntry(AccountsController):
) )
def unlink_inter_company_jv(self): def unlink_inter_company_jv(self):
if ( if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
frappe.db.set_value( frappe.db.set_value(
"Journal Entry", "Journal Entry",
self.inter_company_journal_entry_reference, self.inter_company_journal_entry_reference,
@@ -510,9 +509,9 @@ class JournalEntry(AccountsController):
if account_type in ["Receivable", "Payable"]: if account_type in ["Receivable", "Payable"]:
if not (d.party_type and d.party): if not (d.party_type and d.party):
frappe.throw( 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 ( elif (
d.party_type d.party_type
@@ -577,16 +576,18 @@ class JournalEntry(AccountsController):
def system_generated_gain_loss(self): def system_generated_gain_loss(self):
return ( return (
self.voucher_type == "Exchange Gain Or Loss" self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency and self.is_system_generated
and self.multi_currency
and self.is_system_generated
) )
def validate_against_jv(self): def validate_against_jv(self):
for d in self.get("accounts"): for d in self.get("accounts"):
if d.reference_type == "Journal Entry": if d.reference_type == "Journal Entry":
account_root_type = frappe.get_cached_value("Account", d.account, "root_type") 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( frappe.throw(
_( _(
"Row #{0}: For {1}, you can select reference document only if account gets credited" "Row #{0}: For {1}, you can select reference document only if account gets credited"
@@ -668,11 +669,13 @@ class JournalEntry(AccountsController):
if d.reference_type == "Purchase Order" and flt(d.credit) > 0: if d.reference_type == "Purchase Order" and flt(d.credit) > 0:
frappe.throw( 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 # set totals
if not d.reference_name in self.reference_totals: if d.reference_name not in self.reference_totals:
self.reference_totals[d.reference_name] = 0.0 self.reference_totals[d.reference_name] = 0.0
if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"): if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
@@ -690,7 +693,10 @@ class JournalEntry(AccountsController):
# check if party and account match # check if party and account match
if d.reference_type in ("Sales Invoice", "Purchase Invoice"): 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" debit_or_credit = "Debit" if d.debit else "Credit"
party_account = get_deferred_booking_accounts( party_account = get_deferred_booking_accounts(
d.reference_type, d.reference_detail_no, debit_or_credit d.reference_type, d.reference_detail_no, debit_or_credit
@@ -699,7 +705,8 @@ class JournalEntry(AccountsController):
else: else:
if d.reference_type == "Sales Invoice": if d.reference_type == "Sales Invoice":
party_account = ( 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: else:
party_account = against_voucher[1] party_account = against_voucher[1]
@@ -823,7 +830,9 @@ class JournalEntry(AccountsController):
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
if self.difference: if self.difference:
frappe.throw( 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): def set_total_debit_credit(self):
@@ -887,7 +896,6 @@ class JournalEntry(AccountsController):
and self.posting_date and self.posting_date
) )
): ):
ignore_exchange_rate = False ignore_exchange_rate = False
if self.get("flags") and self.flags.get("ignore_exchange_rate"): if self.get("flags") and self.flags.get("ignore_exchange_rate"):
ignore_exchange_rate = True ignore_exchange_rate = True
@@ -1133,27 +1141,21 @@ class JournalEntry(AccountsController):
self.validate_total_debit_and_credit() self.validate_total_debit_and_credit()
def get_values(self): def get_values(self):
cond = ( cond = f" and outstanding_amount <= {self.write_off_amount}" if flt(self.write_off_amount) > 0 else ""
" 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": if self.write_off_based_on == "Accounts Receivable":
return frappe.db.sql( return frappe.db.sql(
"""select name, debit_to as account, customer as party, outstanding_amount """select name, debit_to as account, customer as party, outstanding_amount
from `tabSales Invoice` where docstatus = 1 and company = %s from `tabSales Invoice` where docstatus = 1 and company = {}
and outstanding_amount > 0 %s""" and outstanding_amount > 0 {}""".format("%s", cond),
% ("%s", cond),
self.company, self.company,
as_dict=True, as_dict=True,
) )
elif self.write_off_based_on == "Accounts Payable": elif self.write_off_based_on == "Accounts Payable":
return frappe.db.sql( return frappe.db.sql(
"""select name, credit_to as account, supplier as party, outstanding_amount """select name, credit_to as account, supplier as party, outstanding_amount
from `tabPurchase Invoice` where docstatus = 1 and company = %s from `tabPurchase Invoice` where docstatus = 1 and company = {}
and outstanding_amount > 0 %s""" and outstanding_amount > 0 {}""".format("%s", cond),
% ("%s", cond),
self.company, self.company,
as_dict=True, as_dict=True,
) )
@@ -1262,7 +1264,7 @@ def get_payment_entry_against_order(
"amount_field_bank": amount_field_bank, "amount_field_bank": amount_field_bank,
"amount": amount, "amount": amount,
"debit_in_account_currency": debit_in_account_currency, "debit_in_account_currency": debit_in_account_currency,
"remarks": "Advance Payment received against {0} {1}".format(dt, dn), "remarks": f"Advance Payment received against {dt} {dn}",
"is_advance": "Yes", "is_advance": "Yes",
"bank_account": bank_account, "bank_account": bank_account,
"journal_entry": journal_entry, "journal_entry": journal_entry,
@@ -1301,7 +1303,7 @@ def get_payment_entry_against_invoice(
"amount_field_bank": amount_field_bank, "amount_field_bank": amount_field_bank,
"amount": amount if amount else abs(ref_doc.outstanding_amount), "amount": amount if amount else abs(ref_doc.outstanding_amount),
"debit_in_account_currency": debit_in_account_currency, "debit_in_account_currency": debit_in_account_currency,
"remarks": "Payment received against {0} {1}. {2}".format(dt, dn, ref_doc.remarks), "remarks": f"Payment received against {dt} {dn}. {ref_doc.remarks}",
"is_advance": "No", "is_advance": "No",
"bank_account": bank_account, "bank_account": bank_account,
"journal_entry": journal_entry, "journal_entry": journal_entry,
@@ -1327,9 +1329,7 @@ def get_payment_entry(ref_doc, args):
) )
je = frappe.new_doc("Journal Entry") je = frappe.new_doc("Journal Entry")
je.update( je.update({"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")})
{"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")}
)
party_row = je.append( party_row = je.append(
"accounts", "accounts",
@@ -1352,9 +1352,7 @@ def get_payment_entry(ref_doc, args):
bank_row = je.append("accounts") bank_row = je.append("accounts")
# Make it bank_details # Make it bank_details
bank_account = get_default_bank_cash_account( bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account"))
ref_doc.company, "Bank", account=args.get("bank_account")
)
if bank_account: if bank_account:
bank_row.update(bank_account) bank_row.update(bank_account)
# Modified to include the posting date for which the exchange rate is required. # Modified to include the posting date for which the exchange rate is required.
@@ -1394,7 +1392,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return [] return []
return frappe.db.sql( return frappe.db.sql(
""" f"""
SELECT jv.name, jv.posting_date, jv.user_remark SELECT jv.name, jv.posting_date, jv.user_remark
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
WHERE jv_detail.parent = jv.name WHERE jv_detail.parent = jv.name
@@ -1405,16 +1403,14 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
OR jv_detail.reference_type = '' OR jv_detail.reference_type = ''
) )
AND jv.docstatus = 1 AND jv.docstatus = 1
AND jv.`{0}` LIKE %(txt)s AND jv.`{searchfield}` LIKE %(txt)s
ORDER BY jv.name DESC ORDER BY jv.name DESC
LIMIT %(limit)s offset %(offset)s LIMIT %(limit)s offset %(offset)s
""".format( """,
searchfield
),
dict( dict(
account=filters.get("account"), account=filters.get("account"),
party=cstr(filters.get("party")), party=cstr(filters.get("party")),
txt="%{0}%".format(txt), txt=f"%{txt}%",
offset=start, offset=start,
limit=page_len, limit=page_len,
), ),
@@ -1436,19 +1432,15 @@ def get_outstanding(args):
condition = " and party=%(party)s" if args.get("party") else "" condition = " and party=%(party)s" if args.get("party") else ""
against_jv_amount = frappe.db.sql( against_jv_amount = frappe.db.sql(
""" f"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency) select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0} from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {condition}
and (reference_type is null or reference_type = '')""".format( and (reference_type is null or reference_type = '')""",
condition
),
args, args,
) )
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0 against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
amount_field = ( amount_field = "credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
"credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
)
return {amount_field: abs(against_jv_amount)} return {amount_field: abs(against_jv_amount)}
elif args.get("doctype") in ("Sales Invoice", "Purchase Invoice"): elif args.get("doctype") in ("Sales Invoice", "Purchase Invoice"):
party_type = "Customer" if args.get("doctype") == "Sales Invoice" else "Supplier" party_type = "Customer" if args.get("doctype") == "Sales Invoice" else "Supplier"
@@ -1461,9 +1453,7 @@ def get_outstanding(args):
due_date = invoice.get("due_date") due_date = invoice.get("due_date")
exchange_rate = ( exchange_rate = invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
)
if args["doctype"] == "Sales Invoice": if args["doctype"] == "Sales Invoice":
amount_field = ( amount_field = (
@@ -1501,9 +1491,7 @@ def get_party_account_and_currency(company, party_type, party):
@frappe.whitelist() @frappe.whitelist()
def get_account_details_and_party_type( def get_account_details_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
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.""" """Returns dict of account details and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"): if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1) frappe.msgprint(_("No Permission"), raise_exception=1)

View File

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

View File

@@ -0,0 +1,8 @@
// 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

@@ -0,0 +1,70 @@
{
"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

@@ -0,0 +1,25 @@
# 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

@@ -0,0 +1,109 @@
# 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

@@ -0,0 +1,8 @@
// 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

@@ -0,0 +1,104 @@
{
"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

@@ -0,0 +1,28 @@
# 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

@@ -0,0 +1,9 @@
# 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

@@ -0,0 +1,32 @@
{
"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

@@ -0,0 +1,23 @@
# 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,7 +83,10 @@ class TestLedgerMerge(unittest.TestCase):
"account": "Indirect Income - _TC", "account": "Indirect Income - _TC",
"merge_accounts": [ "merge_accounts": [
{"account": "Indirect Test Income - _TC", "account_name": "Indirect Test Income"}, {"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) ).insert(ignore_permissions=True)

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,7 +38,10 @@ class PartyLink(Document):
if existing_party_link: if existing_party_link:
frappe.throw( frappe.throw(
_("{} {} is already linked with {} {}").format( _("{} {} 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

@@ -470,6 +470,9 @@ frappe.ui.form.on("Payment Entry", {
() => frm.events.set_dynamic_labels(frm), () => frm.events.set_dynamic_labels(frm),
() => { () => {
frm.set_party_account_based_on_party = false; 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) { if (r.message.bank_account) {
frm.set_value("bank_account", r.message.bank_account); frm.set_value("bank_account", r.message.bank_account);
} }

View File

@@ -16,6 +16,7 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.bank_account.bank_account import ( from erpnext.accounts.doctype.bank_account.bank_account import (
get_bank_account_details, get_bank_account_details,
get_default_company_bank_account,
get_party_bank_account, get_party_bank_account,
) )
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import ( from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
@@ -52,7 +53,7 @@ class InvalidPaymentEntry(ValidationError):
class PaymentEntry(AccountsController): class PaymentEntry(AccountsController):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PaymentEntry, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if not self.is_new(): if not self.is_new():
self.setup_party_account_field() self.setup_party_account_field()
@@ -112,6 +113,9 @@ class PaymentEntry(AccountsController):
if self.docstatus > 0 or self.payment_type == "Internal Transfer": if self.docstatus > 0 or self.payment_type == "Internal Transfer":
return return
if self.party_type not in ("Customer", "Supplier"):
return
if not frappe.db.get_value( if not frappe.db.get_value(
"Company", self.company, "book_advance_payments_in_separate_party_account" "Company", self.company, "book_advance_payments_in_separate_party_account"
): ):
@@ -165,7 +169,7 @@ class PaymentEntry(AccountsController):
"Unreconcile Payment", "Unreconcile Payment",
"Unreconcile Payment Entries", "Unreconcile Payment Entries",
) )
super(PaymentEntry, self).on_cancel() super().on_cancel()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.update_outstanding_amounts() self.update_outstanding_amounts()
self.update_advance_paid() self.update_advance_paid()
@@ -275,9 +279,7 @@ class PaymentEntry(AccountsController):
# If term based allocation is enabled, throw # If term based allocation is enabled, throw
if ( if (
d.payment_term is None or d.payment_term == "" d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference( ) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name):
d.reference_doctype, d.reference_name
):
frappe.throw( frappe.throw(
_( _(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
@@ -289,7 +291,9 @@ class PaymentEntry(AccountsController):
# The reference has already been fully paid # The reference has already been fully paid
if not latest: if not latest:
frappe.throw( 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 # The reference has already been partly paid
elif ( elif (
@@ -313,14 +317,14 @@ class PaymentEntry(AccountsController):
and latest.payment_term_outstanding and latest.payment_term_outstanding
and (flt(d.allocated_amount) > flt(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( frappe.throw(
_( _(
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
).format( ).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term)
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): if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
@@ -392,12 +396,12 @@ class PaymentEntry(AccountsController):
self, self,
force: bool = False, force: bool = False,
update_ref_details_only_for: list | None = None, update_ref_details_only_for: list | None = None,
ref_exchange_rate: float | None = None, reference_exchange_details: dict | None = None,
) -> None: ) -> None:
for d in self.get("references"): for d in self.get("references"):
if d.allocated_amount: if d.allocated_amount:
if update_ref_details_only_for and ( if update_ref_details_only_for and (
not (d.reference_doctype, d.reference_name) in update_ref_details_only_for (d.reference_doctype, d.reference_name) not in update_ref_details_only_for
): ):
continue continue
@@ -406,8 +410,12 @@ class PaymentEntry(AccountsController):
) )
# Only update exchange rate when the reference is Journal Entry # Only update exchange rate when the reference is Journal Entry
if ref_exchange_rate and d.reference_doctype == "Journal Entry": if (
ref_details.update({"exchange_rate": ref_exchange_rate}) 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})
for field, value in ref_details.items(): for field, value in ref_details.items():
if d.exchange_gain_loss: if d.exchange_gain_loss:
@@ -440,7 +448,9 @@ class PaymentEntry(AccountsController):
else: else:
if ref_doc: if ref_doc:
if self.paid_from_account_currency == ref_doc.currency: 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: if not self.source_exchange_rate:
self.source_exchange_rate = get_exchange_rate( self.source_exchange_rate = get_exchange_rate(
@@ -477,7 +487,7 @@ class PaymentEntry(AccountsController):
if d.reference_doctype not in valid_reference_doctypes: if d.reference_doctype not in valid_reference_doctypes:
frappe.throw( frappe.throw(
_("Reference Doctype must be one of {0}").format( _("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)
) )
) )
@@ -500,7 +510,8 @@ class PaymentEntry(AccountsController):
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"): if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer": if self.party_type == "Customer":
ref_party_account = ( 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": elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to ref_party_account = ref_doc.credit_to
@@ -513,7 +524,10 @@ class PaymentEntry(AccountsController):
): ):
frappe.throw( frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format( _("{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,
) )
) )
@@ -524,7 +538,9 @@ class PaymentEntry(AccountsController):
) )
if ref_doc.docstatus != 1: 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): def get_valid_reference_doctypes(self):
if self.party_type == "Customer": if self.party_type == "Customer":
@@ -695,9 +711,7 @@ class PaymentEntry(AccountsController):
if not (is_single_currency and reference_is_multi_currency): if not (is_single_currency and reference_is_multi_currency):
return allocated_amount return allocated_amount
allocated_amount = flt( allocated_amount = flt(allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount"))
allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
)
return allocated_amount return allocated_amount
@@ -760,7 +774,6 @@ class PaymentEntry(AccountsController):
accounts = [] accounts = []
for d in self.taxes: for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"): if d.account_head == tax_withholding_details.get("account_head"):
# Preserve user updated included in paid amount # Preserve user updated included in paid amount
if d.included_in_paid_amount: if d.included_in_paid_amount:
tax_withholding_details.update({"included_in_paid_amount": d.included_in_paid_amount}) tax_withholding_details.update({"included_in_paid_amount": d.included_in_paid_amount})
@@ -880,7 +893,6 @@ class PaymentEntry(AccountsController):
flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount")
) )
else: else:
# Use source/target exchange rate, so no difference amount is calculated. # Use source/target exchange rate, so no difference amount is calculated.
# then update exchange gain/loss amount in reference table # 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 # if there is an exchange gain/loss amount in reference table, submit a JE for that
@@ -1018,7 +1030,6 @@ class PaymentEntry(AccountsController):
) )
] ]
else: else:
remarks = [ remarks = [
_("Amount {0} {1} {2} {3}").format( _("Amount {0} {1} {2} {3}").format(
_(self.party_account_currency), _(self.party_account_currency),
@@ -1038,14 +1049,19 @@ class PaymentEntry(AccountsController):
if d.allocated_amount: if d.allocated_amount:
remarks.append( remarks.append(
_("Amount {0} {1} against {2} {3}").format( _("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"): for d in self.get("deductions"):
if d.amount: if d.amount:
remarks.append( 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)) self.set("remarks", "\n".join(remarks))
@@ -1119,21 +1135,31 @@ class PaymentEntry(AccountsController):
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not 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") cost_center = frappe.db.get_value(
d.reference_doctype, d.reference_name, "cost_center"
)
gle = party_gl_dict.copy() gle = party_gl_dict.copy()
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(
d
)
reverse_dr_or_cr = 0 reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable") payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable") 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"): if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1 reverse_dr_or_cr = 1
elif ( elif (
is_return and self.party_type in payable_party_types and (self.payment_type == "Receive") is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
): ):
reverse_dr_or_cr = 1 reverse_dr_or_cr = 1
@@ -1197,6 +1223,18 @@ class PaymentEntry(AccountsController):
): ):
self.add_advance_gl_for_reference(gl_entries, ref) 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): def add_advance_gl_for_reference(self, gl_entries, invoice):
args_dict = { args_dict = {
"party_type": self.party_type, "party_type": self.party_type,
@@ -1216,10 +1254,8 @@ class PaymentEntry(AccountsController):
if getdate(posting_date) < getdate(self.posting_date): if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date posting_date = self.posting_date
dr_or_cr = ( dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
"credit" if invoice.reference_doctype in ["Sales Invoice", "Payment Entry"] else "debit" args_dict["account"] = account
)
args_dict["account"] = invoice.account
args_dict[dr_or_cr] = invoice.allocated_amount args_dict[dr_or_cr] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict.update( args_dict.update(
@@ -1601,7 +1637,8 @@ def get_outstanding_reference_documents(args, validate=False):
return [] return []
elif supplier_status["hold_type"] == "Payments": elif supplier_status["hold_type"] == "Payments":
if ( 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 [] return []
@@ -1611,7 +1648,7 @@ def get_outstanding_reference_documents(args, validate=False):
# Get positive outstanding sales /purchase invoices # Get positive outstanding sales /purchase invoices
condition = "" condition = ""
if args.get("voucher_type") and args.get("voucher_no"): if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={0} and voucher_no={1}".format( condition = " and voucher_type={} and voucher_no={}".format(
frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"]) frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])
) )
common_filter.append(ple.voucher_type == args["voucher_type"]) common_filter.append(ple.voucher_type == args["voucher_type"])
@@ -1626,7 +1663,7 @@ def get_outstanding_reference_documents(args, validate=False):
active_dimensions = get_dimensions()[0] active_dimensions = get_dimensions()[0]
for dim in active_dimensions: for dim in active_dimensions:
if args.get(dim.fieldname): if args.get(dim.fieldname):
condition += " and {0}='{1}'".format(dim.fieldname, args.get(dim.fieldname)) condition += f" and {dim.fieldname}='{args.get(dim.fieldname)}'"
accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname)) accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname))
date_fields_dict = { date_fields_dict = {
@@ -1636,21 +1673,21 @@ def get_outstanding_reference_documents(args, validate=False):
for fieldname, date_fields in date_fields_dict.items(): for fieldname, date_fields in date_fields_dict.items():
if args.get(date_fields[0]) and args.get(date_fields[1]): if args.get(date_fields[0]) and args.get(date_fields[1]):
condition += " and {0} between '{1}' and '{2}'".format( condition += " and {} between '{}' and '{}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1]) 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])]) posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
elif args.get(date_fields[0]): elif args.get(date_fields[0]):
# if only from date is supplied # if only from date is supplied
condition += " and {0} >= '{1}'".format(fieldname, args.get(date_fields[0])) condition += f" and {fieldname} >= '{args.get(date_fields[0])}'"
posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0]))) posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0])))
elif args.get(date_fields[1]): elif args.get(date_fields[1]):
# if only to date is supplied # if only to date is supplied
condition += " and {0} <= '{1}'".format(fieldname, args.get(date_fields[1])) condition += f" and {fieldname} <= '{args.get(date_fields[1])}'"
posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1]))) posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
if args.get("company"): if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) condition += " and company = {}".format(frappe.db.escape(args.get("company")))
common_filter.append(ple.company == args.get("company")) common_filter.append(ple.company == args.get("company"))
outstanding_invoices = [] outstanding_invoices = []
@@ -1766,12 +1803,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list
return outstanding_invoices_after_split return outstanding_invoices_after_split
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict: def get_currency_data(outstanding_invoices: list, company: str | None = None) -> dict:
"""Get currency and conversion data for a list of invoices.""" """Get currency and conversion data for a list of invoices."""
exc_rates = frappe._dict() exc_rates = frappe._dict()
company_currency = ( company_currency = frappe.db.get_value("Company", company, "default_currency") if company else None
frappe.db.get_value("Company", company, "default_currency") if company else None
)
for doctype in ["Sales Invoice", "Purchase Invoice"]: for doctype in ["Sales Invoice", "Purchase Invoice"]:
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
@@ -1866,7 +1901,7 @@ def get_orders_to_be_billed(
active_dimensions = get_dimensions()[0] active_dimensions = get_dimensions()[0]
for dim in active_dimensions: for dim in active_dimensions:
if filters.get(dim.fieldname): if filters.get(dim.fieldname):
condition += " and {0}='{1}'".format(dim.fieldname, filters.get(dim.fieldname)) condition += f" and {dim.fieldname}='{filters.get(dim.fieldname)}'"
if party_account_currency == company_currency: if party_account_currency == company_currency:
grand_total_field = "base_grand_total" grand_total_field = "base_grand_total"
@@ -1999,7 +2034,9 @@ def get_party_details(company, party_type, party, date, cost_center=None):
party_name = frappe.db.get_value(party_type, party, _party_name) 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) party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center)
if party_type in ["Customer", "Supplier"]: if party_type in ["Customer", "Supplier"]:
bank_account = get_party_bank_account(party_type, party) party_bank_account = get_party_bank_account(party_type, party)
bank_account = get_default_company_bank_account(company)
return { return {
"party_account": party_account, "party_account": party_account,
@@ -2007,6 +2044,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
"party_account_currency": account_currency, "party_account_currency": account_currency,
"party_balance": party_balance, "party_balance": party_balance,
"account_balance": account_balance, "account_balance": account_balance,
"party_bank_account": party_bank_account,
"bank_account": bank_account, "bank_account": bank_account,
} }
@@ -2016,18 +2054,14 @@ def get_account_details(account, date, cost_center=None):
frappe.has_permission("Payment Entry", throw=True) frappe.has_permission("Payment Entry", throw=True)
# to check if the passed account is accessible under reference doctype Payment Entry # to check if the passed account is accessible under reference doctype Payment Entry
account_list = frappe.get_list( account_list = frappe.get_list("Account", {"name": account}, reference_doctype="Payment Entry", limit=1)
"Account", {"name": account}, reference_doctype="Payment Entry", limit=1
)
# There might be some user permissions which will allow account under certain doctypes # 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 # except for Payment Entry, only in such case we should throw permission error
if not account_list: if not account_list:
frappe.throw(_("Account: {0} is not permitted under Payment Entry").format(account)) frappe.throw(_("Account: {0} is not permitted under Payment Entry").format(account))
account_balance = get_balance_on( account_balance = get_balance_on(account, date, cost_center=cost_center, ignore_account_permission=True)
account, date, cost_center=cost_center, ignore_account_permission=True
)
return frappe._dict( return frappe._dict(
{ {
@@ -2074,9 +2108,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
total_amount = outstanding_amount = exchange_rate = account = None total_amount = outstanding_amount = exchange_rate = account = None
ref_doc = frappe.get_doc(reference_doctype, reference_name) ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency( company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
ref_doc.company
) # Only applies for Reverse Payment Entries
account_type = None
payment_type = None
if reference_doctype == "Dunning": if reference_doctype == "Dunning":
total_amount = outstanding_amount = ref_doc.get("dunning_amount") total_amount = outstanding_amount = ref_doc.get("dunning_amount")
@@ -2085,13 +2121,23 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1: elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount") total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency: if ref_doc.multi_currency:
exchange_rate = get_exchange_rate( exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
party_account_currency, company_currency, ref_doc.posting_date
)
else: else:
exchange_rate = 1 exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name) outstanding_amount = get_outstanding_on_journal_entry(reference_name)
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
elif reference_doctype != "Journal Entry": elif reference_doctype != "Journal Entry":
if not total_amount: if not total_amount:
if party_account_currency == company_currency: if party_account_currency == company_currency:
@@ -2136,6 +2182,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"outstanding_amount": flt(outstanding_amount), "outstanding_amount": flt(outstanding_amount),
"exchange_rate": flt(exchange_rate), "exchange_rate": flt(exchange_rate),
"bill_no": ref_doc.get("bill_no"), "bill_no": ref_doc.get("bill_no"),
"account_type": account_type,
"payment_type": payment_type,
} }
) )
if account: if account:
@@ -2156,9 +2204,7 @@ def get_payment_entry(
): ):
doc = frappe.get_doc(dt, dn) doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance") 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) >= ( if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (100.0 + over_billing_allowance):
100.0 + over_billing_allowance
):
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt))) frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
if not party_type: if not party_type:
@@ -2211,9 +2257,7 @@ def get_payment_entry(
pe.paid_from_account_currency = ( pe.paid_from_account_currency = (
party_account_currency if payment_type == "Receive" else bank.account_currency party_account_currency if payment_type == "Receive" else bank.account_currency
) )
pe.paid_to_account_currency = ( pe.paid_to_account_currency = party_account_currency if payment_type == "Pay" else bank.account_currency
party_account_currency if payment_type == "Pay" else bank.account_currency
)
pe.paid_amount = paid_amount pe.paid_amount = paid_amount
pe.received_amount = received_amount pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head") pe.letter_head = doc.get("letter_head")
@@ -2242,7 +2286,6 @@ def get_payment_entry(
doc.payment_terms_template, doc.payment_terms_template,
"allocate_payment_based_on_payment_terms", "allocate_payment_based_on_payment_terms",
): ):
for reference in get_reference_as_per_payment_terms( for reference in get_reference_as_per_payment_terms(
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
): ):
@@ -2426,9 +2469,7 @@ def set_paid_amount_and_received_amount(
return paid_amount, received_amount return paid_amount, received_amount
def apply_early_payment_discount( def apply_early_payment_discount(paid_amount, received_amount, doc, party_account_currency, reference_date):
paid_amount, received_amount, doc, party_account_currency, reference_date
):
total_discount = 0 total_discount = 0
valid_discounts = [] valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"] eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
@@ -2438,7 +2479,6 @@ def apply_early_payment_discount(
if doc.doctype in eligible_for_payments and has_payment_schedule: if doc.doctype in eligible_for_payments and has_payment_schedule:
for term in doc.payment_schedule: for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and reference_date <= term.discount_date: if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
if term.discount_type == "Percentage": if term.discount_type == "Percentage":
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total") grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
discount_amount = flt(grand_total) * (term.discount / 100) discount_amount = flt(grand_total) * (term.discount / 100)
@@ -2467,9 +2507,7 @@ def apply_early_payment_discount(
return paid_amount, received_amount, total_discount, valid_discounts return paid_amount, received_amount, total_discount, valid_discounts
def set_pending_discount_loss( def set_pending_discount_loss(pe, doc, discount_amount, base_total_discount_loss, party_account_currency):
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 multi-currency, get base discount amount to adjust with base currency deductions/losses
if party_account_currency != doc.company_currency: if party_account_currency != doc.company_currency:
discount_amount = discount_amount * doc.get("conversion_rate", 1) discount_amount = discount_amount * doc.get("conversion_rate", 1)
@@ -2489,7 +2527,8 @@ def set_pending_discount_loss(
pe.set_gain_or_loss( pe.set_gain_or_loss(
account_details={ account_details={
"account": frappe.get_cached_value("Company", pe.company, account_type), "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, "amount": discount_amount * positive_negative,
} }
) )
@@ -2512,9 +2551,7 @@ def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
def get_total_discount_percent(doc, valid_discounts) -> float: def get_total_discount_percent(doc, valid_discounts) -> float:
"""Get total percentage and amount discount applied as a percentage.""" """Get total percentage and amount discount applied as a percentage."""
total_discount_percent = ( total_discount_percent = (
sum( sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage")
discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
)
or 0.0 or 0.0
) )
@@ -2557,9 +2594,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
# The same account head could be used more than once # The same account head could be used more than once
for tax in doc.get("taxes", []): for tax in doc.get("taxes", []):
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * ( base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (total_discount_percentage / 100)
total_discount_percentage / 100
)
account = tax.get("account_head") account = tax.get("account_head")
if not tax_discount_loss.get(account): if not tax_discount_loss.get(account):
@@ -2576,7 +2611,8 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"deductions", "deductions",
{ {
"account": account, "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), "amount": flt(loss, precision),
}, },
) )
@@ -2599,7 +2635,8 @@ def get_reference_as_per_payment_terms(
if not is_multi_currency_acc: if not is_multi_currency_acc:
# If accounting is done in company currency for multi-currency transaction # If accounting is done in company currency for multi-currency transaction
payment_term_outstanding = flt( 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: if payment_term_outstanding:
@@ -2627,7 +2664,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
dr_or_cr = "debit_in_account_currency - credit_in_account_currency" dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
paid_amount = frappe.db.sql( paid_amount = frappe.db.sql(
""" f"""
select ifnull(sum({dr_or_cr}), 0) as paid_amount select ifnull(sum({dr_or_cr}), 0) as paid_amount
from `tabGL Entry` from `tabGL Entry`
where against_voucher_type = %s where against_voucher_type = %s
@@ -2637,9 +2674,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
and account = %s and account = %s
and due_date = %s and due_date = %s
and {dr_or_cr} > 0 and {dr_or_cr} > 0
""".format( """,
dr_or_cr=dr_or_cr
),
(dt, dn, party_type, party, account, due_date), (dt, dn, party_type, party, account, due_date),
) )

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe import qb from frappe import qb
@@ -10,7 +9,6 @@ from frappe.utils import add_days, flt, nowdate
from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.accounts.doctype.payment_entry.payment_entry import ( from erpnext.accounts.doctype.payment_entry.payment_entry import (
InvalidPaymentEntry,
get_outstanding_reference_documents, get_outstanding_reference_documents,
get_payment_entry, get_payment_entry,
get_reference_details, get_reference_details,
@@ -162,7 +160,7 @@ class TestPaymentEntry(FrappeTestCase):
supplier.on_hold = 0 supplier.on_hold = 0
supplier.save() supplier.save()
except: except Exception:
pass pass
else: else:
raise Exception raise Exception
@@ -469,9 +467,7 @@ class TestPaymentEntry(FrappeTestCase):
si.save() si.save()
si.submit() si.submit()
pe = get_payment_entry( pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700)
"Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700
)
pe.reference_no = si.name pe.reference_no = si.name
pe.reference_date = nowdate() pe.reference_date = nowdate()
@@ -638,9 +634,7 @@ class TestPaymentEntry(FrappeTestCase):
pe.set_exchange_rate() pe.set_exchange_rate()
pe.set_amounts() pe.set_amounts()
self.assertEqual( self.assertEqual(pe.source_exchange_rate, 65.1, f"{pe.source_exchange_rate} is not equal to {65.1}")
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): def test_internal_transfer_usd_to_inr(self):
pe = frappe.new_doc("Payment Entry") pe = frappe.new_doc("Payment Entry")
@@ -896,9 +890,7 @@ class TestPaymentEntry(FrappeTestCase):
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
pi = make_purchase_invoice_against_cost_center( pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
cost_center=cost_center, credit_to="Creditors - _TC"
)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
self.assertEqual(pe.cost_center, pi.cost_center) self.assertEqual(pe.cost_center, pi.cost_center)
@@ -939,9 +931,7 @@ class TestPaymentEntry(FrappeTestCase):
si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC") 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) account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=si.cost_center)
party_balance = get_balance_on( party_balance = get_balance_on(party_type="Customer", party=si.customer, cost_center=si.cost_center)
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) 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") pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
@@ -1087,6 +1077,8 @@ class TestPaymentEntry(FrappeTestCase):
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency) ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
expected_response = { expected_response = {
"account": get_party_account("Customer", so.customer, so.company), "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, "total_amount": 5000.0,
"outstanding_amount": 5000.0, "outstanding_amount": 5000.0,
"exchange_rate": 1.0, "exchange_rate": 1.0,
@@ -1203,7 +1195,7 @@ class TestPaymentEntry(FrappeTestCase):
Overallocation validation shouldn't fire for Template without "Allocate Payment based on Payment Terms" enabled Overallocation validation shouldn't fire for Template without "Allocate Payment based on Payment Terms" enabled
""" """
customer = create_customer() create_customer()
create_payment_terms_template() create_payment_terms_template()
template = frappe.get_doc("Payment Terms Template", "Test Receivable Template") template = frappe.get_doc("Payment Terms Template", "Test Receivable Template")
@@ -1324,8 +1316,6 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries() self.check_gl_entries()
def test_ledger_entries_for_advance_as_liability(self): def test_ledger_entries_for_advance_as_liability(self):
from erpnext.accounts.doctype.account.test_account import create_account
company = "_Test Company" company = "_Test Company"
advance_account = create_account( advance_account = create_account(
@@ -1427,7 +1417,7 @@ class TestPaymentEntry(FrappeTestCase):
self.check_pl_entries() self.check_pl_entries()
# Unreconcile # Unreconcile
unrecon = ( (
frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Unreconcile Payment", "doctype": "Unreconcile Payment",
@@ -1483,9 +1473,7 @@ class TestPaymentEntry(FrappeTestCase):
create_payment_terms_template() create_payment_terms_template()
# SI has an earlier due date and SI2 has a later due date # SI has an earlier due date and SI2 has a later due date
si = create_sales_invoice( si = create_sales_invoice(qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4))
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 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
si2.payment_terms_template = "Test Receivable Template" si2.payment_terms_template = "Test Receivable Template"
si2.submit() si2.submit()
@@ -1554,12 +1542,10 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(len(pr.payments), 0) self.assertEqual(len(pr.payments), 0)
def test_advance_reverse_payment_reconciliation(self): def test_advance_reverse_payment_reconciliation(self):
from erpnext.accounts.doctype.account.test_account import create_account
company = "_Test Company" company = "_Test Company"
customer = create_customer(frappe.generate_hash(length=10), "INR") customer = create_customer(frappe.generate_hash(length=10), "INR")
advance_account = create_account( advance_account = create_account(
parent_account="Current Assets - _TC", parent_account="Current Liabilities - _TC",
account_name="Advances Received", account_name="Advances Received",
company=company, company=company,
account_type="Receivable", account_type="Receivable",
@@ -1615,9 +1601,9 @@ class TestPaymentEntry(FrappeTestCase):
# assert General and Payment Ledger entries post partial reconciliation # assert General and Payment Ledger entries post partial reconciliation
self.expected_gle = [ self.expected_gle = [
{"account": "Debtors - _TC", "debit": 0.0, "credit": 400.0},
{"account": advance_account, "debit": 400.0, "credit": 0.0}, {"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": 1000.0},
{"account": advance_account, "debit": 0.0, "credit": 400.0},
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
] ]
self.expected_ple = [ self.expected_ple = [
@@ -1628,7 +1614,7 @@ class TestPaymentEntry(FrappeTestCase):
"amount": -1000.0, "amount": -1000.0,
}, },
{ {
"account": "Debtors - _TC", "account": advance_account,
"voucher_no": pe.name, "voucher_no": pe.name,
"against_voucher_no": reverse_pe.name, "against_voucher_no": reverse_pe.name,
"amount": -400.0, "amount": -400.0,
@@ -1645,14 +1631,16 @@ class TestPaymentEntry(FrappeTestCase):
self.check_pl_entries() self.check_pl_entries()
# Unreconcile # Unreconcile
unrecon = ( (
frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Unreconcile Payment", "doctype": "Unreconcile Payment",
"company": company, "company": company,
"voucher_type": pe.doctype, "voucher_type": pe.doctype,
"voucher_no": pe.name, "voucher_no": pe.name,
"allocations": [{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}], "allocations": [
{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}
],
} }
) )
.save() .save()
@@ -1703,12 +1691,11 @@ def create_payment_entry(**args):
def create_payment_terms_template(): def create_payment_terms_template():
create_payment_term("Basic Amount Receivable") create_payment_term("Basic Amount Receivable")
create_payment_term("Tax Receivable") create_payment_term("Tax Receivable")
if not frappe.db.exists("Payment Terms Template", "Test Receivable Template"): if not frappe.db.exists("Payment Terms Template", "Test Receivable Template"):
payment_term_template = frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Payment Terms Template", "doctype": "Payment Terms Template",
"template_name": "Test Receivable Template", "template_name": "Test Receivable Template",

View File

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

View File

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

View File

@@ -137,9 +137,9 @@ class PaymentLedgerEntry(Document):
): ):
if not self.get(dimension.fieldname): if not self.get(dimension.fieldname):
frappe.throw( 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 ( if (
@@ -150,9 +150,9 @@ class PaymentLedgerEntry(Document):
): ):
if not self.get(dimension.fieldname): if not self.get(dimension.fieldname):
frappe.throw( 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): def validate(self):

View File

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

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ class PaymentReconciliation(Document):
# end: auto-generated types # end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PaymentReconciliation, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.common_filter_conditions = [] self.common_filter_conditions = []
self.accounting_dimension_filter_conditions = [] self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = [] self.ple_posting_date_filter = []
@@ -286,7 +286,6 @@ class PaymentReconciliation(Document):
self.return_invoices = self.return_invoices_query.run(as_dict=True) self.return_invoices = self.return_invoices_query.run(as_dict=True)
def get_dr_or_cr_notes(self): def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True) self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry") ple = qb.DocType("Payment Ledger Entry")
@@ -412,9 +411,7 @@ class PaymentReconciliation(Document):
payment_entry[0].get("reference_name") payment_entry[0].get("reference_name")
) )
new_difference_amount = self.get_difference_amount( new_difference_amount = self.get_difference_amount(payment_entry[0], invoice[0], allocated_amount)
payment_entry[0], invoice[0], allocated_amount
)
return new_difference_amount return new_difference_amount
@frappe.whitelist() @frappe.whitelist()
@@ -532,9 +529,9 @@ class PaymentReconciliation(Document):
if running_doc: if running_doc:
frappe.throw( 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 return
@@ -627,9 +624,7 @@ class PaymentReconciliation(Document):
invoice_exchange_map.update(purchase_invoice_map) invoice_exchange_map.update(purchase_invoice_map)
journals = [ journals = [d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"]
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
]
journals.extend( journals.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"] [d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
) )
@@ -721,7 +716,7 @@ class PaymentReconciliation(Document):
def get_journal_filter_conditions(self): def get_journal_filter_conditions(self):
conditions = [] conditions = []
je = qb.DocType("Journal Entry") je = qb.DocType("Journal Entry")
jea = qb.DocType("Journal Entry Account") qb.DocType("Journal Entry Account")
conditions.append(je.company == self.company) conditions.append(je.company == self.company)
if self.from_payment_date: if self.from_payment_date:
@@ -841,7 +836,7 @@ def adjust_allocations_for_taxes(doc):
@frappe.whitelist() @frappe.whitelist()
def get_queries_for_dimension_filters(company: str = None): def get_queries_for_dimension_filters(company: str | None = None):
dimensions_with_filters = [] dimensions_with_filters = []
for d in get_dimensions()[0]: for d in get_dimensions()[0]:
filters = {} filters = {}

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe import qb from frappe import qb
@@ -102,6 +101,14 @@ class TestPaymentReconciliation(FrappeTestCase):
"account_currency": "USD", "account_currency": "USD",
"account_type": "Payable", "account_type": "Payable",
}, },
# 'Payable' account for capturing advance paid, under 'Assets' group
{
"attribute": "advance_payable_account",
"account_name": "Advance Paid",
"parent_account": "Current Assets - _PR",
"account_currency": "INR",
"account_type": "Payable",
},
] ]
for x in accounts: for x in accounts:
@@ -127,11 +134,14 @@ class TestPaymentReconciliation(FrappeTestCase):
setattr(self, x.attribute, acc.name) setattr(self, x.attribute, acc.name)
def create_sales_invoice( def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
): ):
""" """
Helper function to populate default values in sales invoice Helper function to populate default values in sales invoice
""" """
if posting_date is None:
posting_date = nowdate()
sinv = create_sales_invoice( sinv = create_sales_invoice(
qty=qty, qty=qty,
rate=rate, rate=rate,
@@ -155,10 +165,13 @@ class TestPaymentReconciliation(FrappeTestCase):
) )
return sinv return sinv
def create_payment_entry(self, amount=100, posting_date=nowdate(), customer=None): def create_payment_entry(self, amount=100, posting_date=None, customer=None):
""" """
Helper function to populate default values in payment entry Helper function to populate default values in payment entry
""" """
if posting_date is None:
posting_date = nowdate()
payment = create_payment_entry( payment = create_payment_entry(
company=self.company, company=self.company,
payment_type="Receive", payment_type="Receive",
@@ -172,11 +185,14 @@ class TestPaymentReconciliation(FrappeTestCase):
return payment return payment
def create_purchase_invoice( def create_purchase_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
): ):
""" """
Helper function to populate default values in sales invoice Helper function to populate default values in sales invoice
""" """
if posting_date is None:
posting_date = nowdate()
pinv = make_purchase_invoice( pinv = make_purchase_invoice(
qty=qty, qty=qty,
rate=rate, rate=rate,
@@ -201,11 +217,14 @@ class TestPaymentReconciliation(FrappeTestCase):
return pinv return pinv
def create_purchase_order( def create_purchase_order(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
): ):
""" """
Helper function to populate default values in sales invoice Helper function to populate default values in sales invoice
""" """
if posting_date is None:
posting_date = nowdate()
pord = create_purchase_order( pord = create_purchase_order(
qty=qty, qty=qty,
rate=rate, rate=rate,
@@ -250,9 +269,7 @@ class TestPaymentReconciliation(FrappeTestCase):
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate() pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
return pr return pr
def create_journal_entry( def create_journal_entry(self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None):
self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
):
je = frappe.new_doc("Journal Entry") je = frappe.new_doc("Journal Entry")
je.posting_date = posting_date or nowdate() je.posting_date = posting_date or nowdate()
je.company = self.company je.company = self.company
@@ -402,7 +419,7 @@ class TestPaymentReconciliation(FrappeTestCase):
rate = 100 rate = 100
invoices = [] invoices = []
payments = [] payments = []
for i in range(5): for _i in range(5):
invoices.append(self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date)) invoices.append(self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date))
pe = self.create_payment_entry(amount=rate, posting_date=transaction_date).save().submit() pe = self.create_payment_entry(amount=rate, posting_date=transaction_date).save().submit()
payments.append(pe) payments.append(pe)
@@ -821,9 +838,7 @@ class TestPaymentReconciliation(FrappeTestCase):
cr_note.cancel() cr_note.cancel()
pay = self.create_payment_entry( pay = self.create_payment_entry(amount=amount, posting_date=transaction_date, customer=self.customer3)
amount=amount, posting_date=transaction_date, customer=self.customer3
)
pay.paid_from = self.debtors_eur pay.paid_from = self.debtors_eur
pay.paid_from_account_currency = "EUR" pay.paid_from_account_currency = "EUR"
pay.source_exchange_rate = exchange_rate pay.source_exchange_rate = exchange_rate
@@ -1025,9 +1040,7 @@ class TestPaymentReconciliation(FrappeTestCase):
rate = 100 rate = 100
# 'Main - PR' Cost Center # 'Main - PR' Cost Center
si1 = self.create_sales_invoice( si1 = self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True)
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
)
si1.cost_center = self.main_cc.name si1.cost_center = self.main_cc.name
si1.submit() si1.submit()
@@ -1043,9 +1056,7 @@ class TestPaymentReconciliation(FrappeTestCase):
je1 = je1.save().submit() je1 = je1.save().submit()
# 'Sub - PR' Cost Center # 'Sub - PR' Cost Center
si2 = self.create_sales_invoice( si2 = self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True)
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
)
si2.cost_center = self.sub_cc.name si2.cost_center = self.sub_cc.name
si2.submit() si2.submit()
@@ -1332,6 +1343,188 @@ class TestPaymentReconciliation(FrappeTestCase):
# Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again. # Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
pr.reconcile() pr.reconcile()
def test_reverse_payment_against_payment_for_supplier(self):
"""
Reconcile a payment against a reverse payment, for a supplier.
"""
self.supplier = "_Test Supplier"
amount = 4000
pe = self.create_payment_entry(amount=amount)
pe.party_type = "Supplier"
pe.party = self.supplier
pe.payment_type = "Pay"
pe.paid_from = self.cash
pe.paid_to = self.creditors
pe.save().submit()
reverse_pe = self.create_payment_entry(amount=amount)
reverse_pe.party_type = "Supplier"
reverse_pe.party = self.supplier
reverse_pe.payment_type = "Receive"
reverse_pe.paid_from = self.creditors
reverse_pe.paid_to = self.cash
reverse_pe.save().submit()
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name)
self.assertEqual(pr.payments[0].reference_name, pe.name)
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pe.reload()
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.references[0].exchange_rate, 1)
# There should not be any Exc Gain/Loss
self.assertEqual(pe.references[0].exchange_gain_loss, 0)
self.assertEqual(pe.references[0].reference_name, reverse_pe.name)
journals = frappe.db.get_all(
"Journal Entry",
filters={
"voucher_type": "Exchange Gain Or Loss",
"reference_type": "Payment Entry",
"reference_name": ("in", [pe.name, reverse_pe.name]),
},
)
# There should be no Exchange Gain/Loss created
self.assertEqual(journals, [])
def test_advance_reverse_payment_against_payment_for_supplier(self):
"""
Reconcile an Advance payment against reverse payment, for a supplier.
"""
frappe.db.set_value(
"Company",
self.company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
},
)
self.supplier = "_Test Supplier"
amount = 4000
pe = self.create_payment_entry(amount=amount)
pe.party_type = "Supplier"
pe.party = self.supplier
pe.payment_type = "Pay"
pe.paid_from = self.cash
pe.paid_to = self.advance_payable_account
pe.save().submit()
reverse_pe = self.create_payment_entry(amount=amount)
reverse_pe.party_type = "Supplier"
reverse_pe.party = self.supplier
reverse_pe.payment_type = "Receive"
reverse_pe.paid_from = self.advance_payable_account
reverse_pe.paid_to = self.cash
reverse_pe.save().submit()
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.default_advance_account = self.advance_payable_account
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name)
self.assertEqual(pr.payments[0].reference_name, pe.name)
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pe.reload()
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.references[0].exchange_rate, 1)
# There should not be any Exc Gain/Loss
self.assertEqual(pe.references[0].exchange_gain_loss, 0)
self.assertEqual(pe.references[0].reference_name, reverse_pe.name)
journals = frappe.db.get_all(
"Journal Entry",
filters={
"voucher_type": "Exchange Gain Or Loss",
"reference_type": "Payment Entry",
"reference_name": ("in", [pe.name, reverse_pe.name]),
},
)
# There should be no Exchange Gain/Loss created
self.assertEqual(journals, [])
# Assert Ledger Entries
gl_entries = frappe.db.get_all(
"GL Entry",
filters={"voucher_no": pe.name},
fields=["account", "voucher_no", "against_voucher", "debit", "credit"],
order_by="account, against_voucher, debit",
)
expected_gle = [
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher": pe.name,
"debit": 0.0,
"credit": amount,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher": pe.name,
"debit": amount,
"credit": 0.0,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher": reverse_pe.name,
"debit": amount,
"credit": 0.0,
},
{
"account": "Cash - _PR",
"voucher_no": pe.name,
"against_voucher": None,
"debit": 0.0,
"credit": amount,
},
]
self.assertEqual(gl_entries, expected_gle)
pl_entries = frappe.db.get_all(
"Payment Ledger Entry",
filters={"voucher_no": pe.name},
fields=["account", "voucher_no", "against_voucher_no", "amount"],
order_by="account, against_voucher_no, amount",
)
expected_ple = [
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": -amount,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": amount,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher_no": reverse_pe.name,
"amount": -amount,
},
]
self.assertEqual(pl_entries, expected_ple)
def make_customer(customer_name, currency=None): def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name): if not frappe.db.exists("Customer", customer_name):

View File

@@ -104,7 +104,7 @@ class PaymentRequest(Document):
) )
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart": if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account) ref_amount = get_amount(ref_doc, self.payment_account)
if existing_payment_request_amount + flt(self.grand_total) > ref_amount: if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
@@ -157,7 +157,7 @@ class PaymentRequest(Document):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if ( if (
hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart" hasattr(ref_doc, "order_type") and ref_doc.order_type == "Shopping Cart"
) or self.flags.mute_email: ) or self.flags.mute_email:
send_mail = False send_mail = False
@@ -209,7 +209,7 @@ class PaymentRequest(Document):
def make_invoice(self): def make_invoice(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart": if hasattr(ref_doc, "order_type") and ref_doc.order_type == "Shopping Cart":
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
si = make_sales_invoice(self.reference_name, ignore_permissions=True) si = make_sales_invoice(self.reference_name, ignore_permissions=True)
@@ -295,14 +295,10 @@ class PaymentRequest(Document):
else: else:
party_account = get_party_account("Customer", ref_doc.get("customer"), ref_doc.company) party_account = get_party_account("Customer", ref_doc.get("customer"), ref_doc.company)
party_account_currency = ref_doc.get("party_account_currency") or get_account_currency( party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(party_account)
party_account
)
bank_amount = self.grand_total bank_amount = self.grand_total
if ( if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
):
party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total") party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
else: else:
party_amount = self.grand_total party_amount = self.grand_total
@@ -320,7 +316,7 @@ class PaymentRequest(Document):
"mode_of_payment": self.mode_of_payment, "mode_of_payment": self.mode_of_payment,
"reference_no": self.name, "reference_no": self.name,
"reference_date": nowdate(), "reference_date": nowdate(),
"remarks": "Payment Entry against {0} {1} via Payment Request {2}".format( "remarks": "Payment Entry against {} {} via Payment Request {}".format(
self.reference_doctype, self.reference_name, self.name self.reference_doctype, self.reference_name, self.name
), ),
} }
@@ -443,15 +439,11 @@ def make_payment_request(**args):
frappe.db.set_value( frappe.db.set_value(
"Sales Order", args.dn, "loyalty_points", int(args.loyalty_points), update_modified=False "Sales Order", args.dn, "loyalty_points", int(args.loyalty_points), update_modified=False
) )
frappe.db.set_value( frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
"Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False
)
grand_total = grand_total - loyalty_amount grand_total = grand_total - loyalty_amount
bank_account = ( bank_account = (
get_party_bank_account(args.get("party_type"), args.get("party")) get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else ""
if args.get("party_type")
else ""
) )
draft_payment_request = frappe.db.get_value( draft_payment_request = frappe.db.get_value(

View File

@@ -93,7 +93,7 @@ class TestPaymentRequest(unittest.TestCase):
return_doc=1, return_doc=1,
) )
pe = pr.create_payment_entry() pr.create_payment_entry()
pr.load_from_db() pr.load_from_db()
self.assertEqual(pr.status, "Paid") self.assertEqual(pr.status, "Paid")
@@ -158,7 +158,7 @@ class TestPaymentRequest(unittest.TestCase):
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
for i, gle in enumerate(gl_entries): for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[gle.account][0], gle.account) self.assertEqual(expected_gle[gle.account][0], gle.account)
self.assertEqual(expected_gle[gle.account][1], gle.debit) self.assertEqual(expected_gle[gle.account][1], gle.debit)
self.assertEqual(expected_gle[gle.account][2], gle.credit) self.assertEqual(expected_gle[gle.account][2], gle.credit)

View File

@@ -36,9 +36,7 @@ class PaymentTermsTemplate(Document):
total_portion += flt(term.get("invoice_portion", 0)) total_portion += flt(term.get("invoice_portion", 0))
if flt(total_portion, 2) != 100.00: if flt(total_portion, 2) != 100.00:
frappe.msgprint( frappe.msgprint(_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red")
_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red"
)
def validate_terms(self): def validate_terms(self):
terms = [] terms = []

View File

@@ -67,7 +67,8 @@ class PeriodClosingVoucher(AccountsController):
enqueue_after_commit=True, enqueue_after_commit=True,
) )
frappe.msgprint( frappe.msgprint(
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True _("The GL Entries will be cancelled in the background, it can take a few minutes."),
alert=True,
) )
else: else:
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
@@ -109,9 +110,7 @@ class PeriodClosingVoucher(AccountsController):
self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self
) )
self.year_start_date = get_fiscal_year( self.year_start_date = get_fiscal_year(self.posting_date, self.fiscal_year, company=self.company)[1]
self.posting_date, self.fiscal_year, company=self.company
)[1]
self.check_if_previous_year_closed() self.check_if_previous_year_closed()
@@ -225,7 +224,9 @@ class PeriodClosingVoucher(AccountsController):
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) "credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0 if flt(acc.bal_in_account_currency) > 0
else 0, else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0, "credit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) > 0
else 0,
"is_period_closing_voucher_entry": 1, "is_period_closing_voucher_entry": 1,
}, },
item=acc, item=acc,
@@ -249,7 +250,9 @@ class PeriodClosingVoucher(AccountsController):
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) "credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0 if flt(acc.bal_in_account_currency) < 0
else 0, else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0, "credit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) < 0
else 0,
"is_period_closing_voucher_entry": 1, "is_period_closing_voucher_entry": 1,
}, },
item=acc, item=acc,

View File

@@ -70,7 +70,7 @@ class POSClosingEntry(StatusUpdater):
for key, value in pos_occurences.items(): for key, value in pos_occurences.items():
if len(value) > 1: if len(value) > 1:
error_list.append( error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value))) _(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
) )
if error_list: if error_list:
@@ -165,9 +165,7 @@ def get_pos_invoices(start, end, pos_profile, user):
as_dict=1, as_dict=1,
) )
data = list( data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data)
)
# need to get taxes and payments so can't avoid get_doc # need to get taxes and payments so can't avoid get_doc
data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data] data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data]
@@ -238,7 +236,11 @@ def make_closing_entry_from_opening(opening_entry):
else: else:
payments.append( payments.append(
frappe._dict( frappe._dict(
{"mode_of_payment": p.mode_of_payment, "opening_amount": 0, "expected_amount": p.amount} {
"mode_of_payment": p.mode_of_payment,
"opening_amount": 0,
"expected_amount": p.amount,
}
) )
) )

View File

@@ -62,9 +62,7 @@ class TestPOSClosingEntry(unittest.TestCase):
test_user, pos_profile = init_user_and_profile() test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name) opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv = create_pos_invoice( pos_inv = create_pos_invoice(rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1)
rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1
)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv.submit() pos_inv.submit()
@@ -211,7 +209,7 @@ def get_test_item_qty(pos_profile):
item_group="All Item Groups", item_group="All Item Groups",
) )
test_item_qty = [item for item in test_item_pos["items"] if item["item_code"] == "_Test Item"][ test_item_qty = next(item for item in test_item_pos["items"] if item["item_code"] == "_Test Item").get(
0 "actual_qty"
].get("actual_qty") )
return test_item_qty return test_item_qty

View File

@@ -183,7 +183,7 @@ class POSInvoice(SalesInvoice):
# end: auto-generated types # end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(POSInvoice, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def validate(self): def validate(self):
if not cint(self.is_pos): if not cint(self.is_pos):
@@ -308,7 +308,9 @@ class POSInvoice(SalesInvoice):
) )
if paid_amt and pay.amount != paid_amt: if paid_amt and pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment)) return frappe.throw(
_("Payment related to {0} is not completed").format(pay.mode_of_payment)
)
def validate_stock_availablility(self): def validate_stock_availablility(self):
if self.is_return: if self.is_return:
@@ -328,7 +330,7 @@ class POSInvoice(SalesInvoice):
available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse) available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse)
item_code, warehouse, qty = ( item_code, warehouse, _qty = (
frappe.bold(d.item_code), frappe.bold(d.item_code),
frappe.bold(d.warehouse), frappe.bold(d.warehouse),
frappe.bold(d.qty), frappe.bold(d.qty),
@@ -408,8 +410,7 @@ class POSInvoice(SalesInvoice):
if ( if (
self.change_amount self.change_amount
and self.account_for_change_amount and self.account_for_change_amount
and frappe.get_cached_value("Account", self.account_for_change_amount, "company") and frappe.get_cached_value("Account", self.account_for_change_amount, "company") != self.company
!= self.company
): ):
frappe.throw( frappe.throw(
_("The selected change account {} doesn't belongs to Company {}.").format( _("The selected change account {} doesn't belongs to Company {}.").format(

View File

@@ -245,7 +245,6 @@ class TestPOSInvoice(unittest.TestCase):
self.assertEqual(pos_return.get("payments")[1].amount, -500) self.assertEqual(pos_return.get("payments")[1].amount, -500)
def test_pos_return_for_serialized_item(self): def test_pos_return_for_serialized_item(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item( se = make_serialized_item(
@@ -287,7 +286,6 @@ class TestPOSInvoice(unittest.TestCase):
) )
def test_partial_pos_returns(self): def test_partial_pos_returns(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item( se = make_serialized_item(
@@ -359,9 +357,7 @@ class TestPOSInvoice(unittest.TestCase):
) )
pos.set("payments", []) pos.set("payments", [])
pos.append( pos.append("payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 50})
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 50}
)
pos.append( pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60, "default": 1} "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60, "default": 1}
) )
@@ -379,7 +375,6 @@ class TestPOSInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, inv.insert) self.assertRaises(frappe.ValidationError, inv.insert)
def test_serialized_item_transaction(self): def test_serialized_item_transaction(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item( se = make_serialized_item(
@@ -434,7 +429,6 @@ class TestPOSInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, pos2.submit) self.assertRaises(frappe.ValidationError, pos2.submit)
def test_delivered_serialized_item_transaction(self): def test_delivered_serialized_item_transaction(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item( se = make_serialized_item(
@@ -583,9 +577,7 @@ class TestPOSInvoice(unittest.TestCase):
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
create_records() create_records()
frappe.db.set_value( frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
before_lp_details = get_loyalty_program_details_with_points( before_lp_details = get_loyalty_program_details_with_points(
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty" "Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
) )
@@ -659,9 +651,7 @@ class TestPOSInvoice(unittest.TestCase):
consolidate_pos_invoices() consolidate_pos_invoices()
pos_inv.load_from_db() pos_inv.load_from_db()
rounded_total = frappe.db.get_value( rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
"Sales Invoice", pos_inv.consolidated_invoice, "rounded_total"
)
self.assertEqual(rounded_total, 3470) self.assertEqual(rounded_total, 3470)
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
@@ -708,9 +698,7 @@ class TestPOSInvoice(unittest.TestCase):
consolidate_pos_invoices() consolidate_pos_invoices()
pos_inv.load_from_db() pos_inv.load_from_db()
rounded_total = frappe.db.get_value( rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
"Sales Invoice", pos_inv.consolidated_invoice, "rounded_total"
)
self.assertEqual(rounded_total, 840) self.assertEqual(rounded_total, 840)
def test_merging_with_validate_selling_price(self): def test_merging_with_validate_selling_price(self):
@@ -762,9 +750,7 @@ class TestPOSInvoice(unittest.TestCase):
consolidate_pos_invoices() consolidate_pos_invoices()
pos_inv2.load_from_db() pos_inv2.load_from_db()
rounded_total = frappe.db.get_value( rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
"Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total"
)
self.assertEqual(rounded_total, 400) self.assertEqual(rounded_total, 400)
def test_pos_batch_reservation(self): def test_pos_batch_reservation(self):
@@ -788,9 +774,7 @@ class TestPOSInvoice(unittest.TestCase):
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
# POS Invoice 1, for the batch without bundle # POS Invoice 1, for the batch without bundle
pos_inv1 = create_pos_invoice( pos_inv1 = create_pos_invoice(item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1)
item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1
)
pos_inv1.items[0].batch_no = batch_no pos_inv1.items[0].batch_no = batch_no
pos_inv1.save() pos_inv1.save()
@@ -800,9 +784,7 @@ class TestPOSInvoice(unittest.TestCase):
self.assertFalse(pos_inv1.items[0].serial_and_batch_bundle) self.assertFalse(pos_inv1.items[0].serial_and_batch_bundle)
batches = get_auto_batch_nos( batches = get_auto_batch_nos(
frappe._dict( frappe._dict({"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"})
{"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"}
)
) )
for batch in batches: for batch in batches:
@@ -817,9 +799,7 @@ class TestPOSInvoice(unittest.TestCase):
self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle) self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle)
batches = get_auto_batch_nos( batches = get_auto_batch_nos(
frappe._dict( frappe._dict({"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"})
{"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"}
)
) )
for batch in batches: for batch in batches:
@@ -896,19 +876,19 @@ class TestPOSInvoice(unittest.TestCase):
pos_inv = create_pos_invoice(qty=1, do_not_submit=1) pos_inv = create_pos_invoice(qty=1, do_not_submit=1)
pos_inv.items[0].rate = 300 pos_inv.items[0].rate = 300
pos_inv.save() pos_inv.save()
self.assertEquals(pos_inv.items[0].discount_percentage, 10) self.assertEqual(pos_inv.items[0].discount_percentage, 10)
# rate shouldn't change # rate shouldn't change
self.assertEquals(pos_inv.items[0].rate, 405) self.assertEqual(pos_inv.items[0].rate, 405)
pos_inv.ignore_pricing_rule = 1 pos_inv.ignore_pricing_rule = 1
pos_inv.save() pos_inv.save()
self.assertEquals(pos_inv.ignore_pricing_rule, 1) self.assertEqual(pos_inv.ignore_pricing_rule, 1)
# rate should reset since pricing rules are ignored # rate should reset since pricing rules are ignored
self.assertEquals(pos_inv.items[0].rate, 450) self.assertEqual(pos_inv.items[0].rate, 450)
pos_inv.items[0].rate = 300 pos_inv.items[0].rate = 300
pos_inv.save() pos_inv.save()
self.assertEquals(pos_inv.items[0].rate, 300) self.assertEqual(pos_inv.items[0].rate, 300)
finally: finally:
item_price.delete() item_price.delete()
@@ -920,7 +900,6 @@ class TestPOSInvoice(unittest.TestCase):
init_user_and_profile, init_user_and_profile,
) )
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
frappe.db.savepoint("before_test_delivered_serial_no_case") frappe.db.savepoint("before_test_delivered_serial_no_case")

View File

@@ -54,7 +54,7 @@ class POSInvoiceMergeLog(Document):
for key, value in pos_occurences.items(): for key, value in pos_occurences.items():
if len(value) > 1: if len(value) > 1:
error_list.append( error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value))) _(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
) )
if error_list: if error_list:
@@ -81,7 +81,9 @@ class POSInvoiceMergeLog(Document):
bold_pos_invoice = frappe.bold(d.pos_invoice) bold_pos_invoice = frappe.bold(d.pos_invoice)
bold_status = frappe.bold(status) bold_status = frappe.bold(status)
if docstatus != 1: if docstatus != 1:
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice)) frappe.throw(
_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice)
)
if status == "Consolidated": if status == "Consolidated":
frappe.throw( frappe.throw(
_("Row #{}: POS Invoice {} has been {}").format(d.idx, bold_pos_invoice, bold_status) _("Row #{}: POS Invoice {} has been {}").format(d.idx, bold_pos_invoice, bold_status)
@@ -100,15 +102,17 @@ class POSInvoiceMergeLog(Document):
d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated
) )
msg += " " msg += " "
msg += _("Original invoice should be consolidated before or along with the return invoice.") msg += _(
"Original invoice should be consolidated before or along with the return invoice."
)
msg += "<br><br>" msg += "<br><br>"
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against) msg += _("You can add original invoice {} manually to proceed.").format(
bold_return_against
)
frappe.throw(msg) frappe.throw(msg)
def on_submit(self): def on_submit(self):
pos_invoice_docs = [ pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices
]
returns = [d for d in pos_invoice_docs if d.get("is_return") == 1] returns = [d for d in pos_invoice_docs if d.get("is_return") == 1]
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0] sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
@@ -124,9 +128,7 @@ class POSInvoiceMergeLog(Document):
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
def on_cancel(self): def on_cancel(self):
pos_invoice_docs = [ pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices
]
self.update_pos_invoices(pos_invoice_docs) self.update_pos_invoices(pos_invoice_docs)
self.cancel_linked_invoices() self.cancel_linked_invoices()
@@ -217,7 +219,9 @@ class POSInvoiceMergeLog(Document):
for t in taxes: for t in taxes:
if t.account_head == tax.account_head and t.cost_center == tax.cost_center: if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount) t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount) t.base_tax_amount = flt(t.base_tax_amount) + flt(
tax.base_tax_amount_after_discount_amount
)
update_item_wise_tax_detail(t, tax) update_item_wise_tax_detail(t, tax)
found = True found = True
if not found: if not found:
@@ -333,9 +337,7 @@ def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
else: else:
consolidated_tax_detail.update({item_code: [tax_data[0], tax_data[1]]}) consolidated_tax_detail.update({item_code: [tax_data[0], tax_data[1]]})
consolidate_tax_row.item_wise_tax_detail = json.dumps( consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(",", ":"))
consolidated_tax_detail, separators=(",", ":")
)
def get_all_unconsolidated_invoices(): def get_all_unconsolidated_invoices():
@@ -380,9 +382,7 @@ def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
if len(invoices) >= 10 and closing_entry: if len(invoices) >= 10 and closing_entry:
closing_entry.set_status(update=True, status="Queued") closing_entry.set_status(update=True, status="Queued")
enqueue_job( enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry
)
else: else:
create_merge_logs(invoice_by_customer, closing_entry) create_merge_logs(invoice_by_customer, closing_entry)
@@ -431,9 +431,7 @@ def split_invoices(invoices):
if not item.serial_no and not item.serial_and_batch_bundle: if not item.serial_no and not item.serial_and_batch_bundle:
continue continue
return_against_is_added = any( return_against_is_added = any(d for d in _invoices if d.pos_invoice == pos_invoice.return_against)
d for d in _invoices if d.pos_invoice == pos_invoice.return_against
)
if return_against_is_added: if return_against_is_added:
break break
@@ -482,7 +480,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
if closing_entry: if closing_entry:
closing_entry.set_status(update=True, status="Failed") closing_entry.set_status(update=True, status="Failed")
if type(error_message) == list: if isinstance(error_message, list):
error_message = frappe.json.dumps(error_message) error_message = frappe.json.dumps(error_message)
closing_entry.db_set("error_message", error_message) closing_entry.db_set("error_message", error_message)
raise raise
@@ -533,7 +531,7 @@ def enqueue_job(job, **kwargs):
timeout=10000, timeout=10000,
event="processing_merge_logs", event="processing_merge_logs",
job_id=job_id, job_id=job_id,
now=frappe.conf.developer_mode or frappe.flags.in_test now=frappe.conf.developer_mode or frappe.flags.in_test,
) )
if job == create_merge_logs: if job == create_merge_logs:

View File

@@ -31,15 +31,11 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
pos_inv.submit() pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append( pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}
)
pos_inv2.submit() pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append( pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}
)
pos_inv3.submit() pos_inv3.submit()
consolidate_pos_invoices() consolidate_pos_invoices()
@@ -68,15 +64,11 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
pos_inv.submit() pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append( pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}
)
pos_inv2.submit() pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append( pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}
)
pos_inv3.submit() pos_inv3.submit()
pos_inv_cn = make_sales_return(pos_inv.name) pos_inv_cn = make_sales_return(pos_inv.name)
@@ -312,7 +304,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
init_user_and_profile() init_user_and_profile()
item_rates = [69, 59, 29] item_rates = [69, 59, 29]
for i in [1, 2]: for _i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1) inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = [] inv.items = []
for rate in item_rates: for rate in item_rates:
@@ -406,7 +398,6 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
The second and third POS Invoice should be consolidated with a single Merge Log The second and third POS Invoice should be consolidated with a single Merge Log
""" """
from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
frappe.db.sql("delete from `tabPOS Invoice`") frappe.db.sql("delete from `tabPOS Invoice`")

View File

@@ -180,10 +180,8 @@ class POSProfile(Document):
condition = " where pfu.default = 1 " condition = " where pfu.default = 1 "
pos_view_users = frappe.db.sql_list( pos_view_users = frappe.db.sql_list(
"""select pfu.user f"""select pfu.user
from `tabPOS Profile User` as pfu {0}""".format( from `tabPOS Profile User` as pfu {condition}"""
condition
)
) )
for user in pos_view_users: for user in pos_view_users:
@@ -210,16 +208,13 @@ def get_item_groups(pos_profile):
def get_child_nodes(group_type, root): def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
return frappe.db.sql( return frappe.db.sql(
""" Select name, lft, rgt from `tab{tab}` where f""" Select name, lft, rgt from `tab{group_type}` where
lft >= {lft} and rgt <= {rgt} order by lft""".format( lft >= {lft} and rgt <= {rgt} order by lft""",
tab=group_type, lft=lft, rgt=rgt
),
as_dict=1, as_dict=1,
) )
def required_accounting_dimensions(): def required_accounting_dimensions():
p = frappe.qb.DocType("Accounting Dimension") p = frappe.qb.DocType("Accounting Dimension")
c = frappe.qb.DocType("Accounting Dimension Detail") c = frappe.qb.DocType("Accounting Dimension Detail")

View File

@@ -7,7 +7,6 @@ import frappe
from erpnext.accounts.doctype.pos_profile.pos_profile import ( from erpnext.accounts.doctype.pos_profile.pos_profile import (
get_child_nodes, get_child_nodes,
required_accounting_dimensions,
) )
from erpnext.stock.get_item_details import get_pos_profile from erpnext.stock.get_item_details import get_pos_profile
@@ -55,11 +54,9 @@ def get_customers_list(pos_profile=None):
return ( return (
frappe.db.sql( frappe.db.sql(
""" select name, customer_name, customer_group, f""" select name, customer_name, customer_group,
territory, customer_pos_id from tabCustomer where disabled = 0 territory, customer_pos_id from tabCustomer where disabled = 0
and {cond}""".format( and {cond}""",
cond=cond
),
tuple(customer_groups), tuple(customer_groups),
as_dict=1, as_dict=1,
) )
@@ -78,7 +75,7 @@ def get_items_list(pos_profile, company):
cond = "and i.item_group in (%s)" % (", ".join(["%s"] * len(args_list))) cond = "and i.item_group in (%s)" % (", ".join(["%s"] * len(args_list)))
return frappe.db.sql( return frappe.db.sql(
""" f"""
select select
i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no, i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no,
i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image, i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image,
@@ -91,10 +88,8 @@ def get_items_list(pos_profile, company):
where where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.is_fixed_asset = 0 i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.is_fixed_asset = 0
{cond} {cond}
""".format( """,
cond=cond tuple([company, *args_list]),
),
tuple([company] + args_list),
as_dict=1, as_dict=1,
) )

View File

@@ -186,9 +186,9 @@ class PricingRule(Document):
if self.priority and cint(self.priority) == 1: if self.priority and cint(self.priority) == 1:
throw( throw(
_("As the field {0} is enabled, the value of the field {1} should be more than 1.").format( _(
frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority") "As the field {0} is enabled, the value of the field {1} should be more than 1."
) ).format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority"))
) )
def validate_applicable_for_selling_or_buying(self): def validate_applicable_for_selling_or_buying(self):
@@ -458,9 +458,11 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
) )
if pricing_rule.apply_rule_on_other_items: if pricing_rule.apply_rule_on_other_items:
item_details["apply_rule_on_other_items"] = json.dumps(pricing_rule.apply_rule_on_other_items) item_details["apply_rule_on_other_items"] = json.dumps(
pricing_rule.apply_rule_on_other_items
)
if pricing_rule.coupon_code_based == 1 and args.coupon_code == None: if pricing_rule.coupon_code_based == 1 and args.coupon_code is None:
return item_details return item_details
if not pricing_rule.validate_applied_rule: if not pricing_rule.validate_applied_rule:
@@ -504,7 +506,6 @@ def update_args_for_pricing_rule(args):
if args.transaction_type == "selling": if args.transaction_type == "selling":
if args.customer and not (args.customer_group and args.territory): if args.customer and not (args.customer_group and args.territory):
if args.quotation_to and args.quotation_to != "Customer": if args.quotation_to and args.quotation_to != "Customer":
customer = frappe._dict() customer = frappe._dict()
else: else:
@@ -535,9 +536,9 @@ def get_pricing_rule_details(args, pricing_rule):
def apply_price_discount_rule(pricing_rule, item_details, args): def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount item_details.pricing_rule_for = pricing_rule.rate_or_discount
if ( if (pricing_rule.margin_type in ["Amount", "Percentage"] and pricing_rule.currency == args.currency) or (
pricing_rule.margin_type in ["Amount", "Percentage"] and pricing_rule.currency == args.currency pricing_rule.margin_type == "Percentage"
) or (pricing_rule.margin_type == "Percentage"): ):
item_details.margin_type = pricing_rule.margin_type item_details.margin_type = pricing_rule.margin_type
item_details.has_margin = True item_details.has_margin = True
@@ -685,7 +686,7 @@ def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
return frappe.get_all( return frappe.get_all(
"UOM Conversion Detail", "UOM Conversion Detail",
filters={"parent": ("in", items), "uom": ("like", "{0}%".format(txt))}, filters={"parent": ("in", items), "uom": ("like", f"{txt}%")},
fields=["distinct uom"], fields=["distinct uom"],
as_list=1, as_list=1,
) )

View File

@@ -103,8 +103,6 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(details.get("discount_percentage"), 15) self.assertEqual(details.get("discount_percentage"), 15)
def test_pricing_rule_for_margin(self): def test_pricing_rule_for_margin(self):
from frappe import MandatoryError
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
test_record = { test_record = {
@@ -205,8 +203,6 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(details.get("discount_percentage"), 10) self.assertEqual(details.get("discount_percentage"), 10)
def test_pricing_rule_for_variants(self): def test_pricing_rule_for_variants(self):
from frappe import MandatoryError
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
if not frappe.db.exists("Item", "Test Variant PRT"): if not frappe.db.exists("Item", "Test Variant PRT"):
@@ -1181,8 +1177,7 @@ def delete_existing_pricing_rules():
"Pricing Rule Item Group", "Pricing Rule Item Group",
"Pricing Rule Brand", "Pricing Rule Brand",
]: ]:
frappe.db.sql(f"delete from `tab{doctype}`")
frappe.db.sql("delete from `tab{0}`".format(doctype))
def make_item_price(item, price_list_name, item_price): def make_item_price(item, price_list_name, item_price):

View File

@@ -101,14 +101,12 @@ def _get_pricing_rules(apply_on, args, values):
if not args.get(apply_on_field): if not args.get(apply_on_field):
return [] return []
child_doc = "`tabPricing Rule {0}`".format(apply_on) child_doc = f"`tabPricing Rule {apply_on}`"
conditions = item_variant_condition = item_conditions = "" conditions = item_variant_condition = item_conditions = ""
values[apply_on_field] = args.get(apply_on_field) values[apply_on_field] = args.get(apply_on_field)
if apply_on_field in ["item_code", "brand"]: if apply_on_field in ["item_code", "brand"]:
item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format( item_conditions = f"{child_doc}.{apply_on_field}= %({apply_on_field})s"
child_doc=child_doc, apply_on_field=apply_on_field
)
if apply_on_field == "item_code": if apply_on_field == "item_code":
if args.get("uom", None): if args.get("uom", None):
@@ -121,23 +119,19 @@ def _get_pricing_rules(apply_on, args, values):
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of") args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
if args.variant_of: if args.variant_of:
item_variant_condition = " or {child_doc}.item_code=%(variant_of)s ".format( item_variant_condition = f" or {child_doc}.item_code=%(variant_of)s "
child_doc=child_doc
)
values["variant_of"] = args.variant_of values["variant_of"] = args.variant_of
elif apply_on_field == "item_group": elif apply_on_field == "item_group":
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False) item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
if args.get("uom", None): if args.get("uom", None):
item_conditions += ( item_conditions += " and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format( child_doc=child_doc, item_uom=args.get("uom")
child_doc=child_doc, item_uom=args.get("uom")
)
) )
conditions += get_other_conditions(conditions, values, args) conditions += get_other_conditions(conditions, values, args)
warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`") warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`")
if warehouse_conditions: if warehouse_conditions:
warehouse_conditions = " and {0}".format(warehouse_conditions) warehouse_conditions = f" and {warehouse_conditions}"
if not args.price_list: if not args.price_list:
args.price_list = None args.price_list = None
@@ -163,7 +157,7 @@ def _get_pricing_rules(apply_on, args, values):
item_variant_condition=item_variant_condition, item_variant_condition=item_variant_condition,
transaction_type=args.transaction_type, transaction_type=args.transaction_type,
warehouse_cond=warehouse_conditions, warehouse_cond=warehouse_conditions,
apply_on_other_field="other_{0}".format(apply_on_field), apply_on_other_field=f"other_{apply_on_field}",
conditions=conditions, conditions=conditions,
), ),
values, values,
@@ -202,14 +196,13 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
frappe.throw(_("Invalid {0}").format(args.get(field))) frappe.throw(_("Invalid {0}").format(args.get(field)))
parent_groups = frappe.db.sql_list( parent_groups = frappe.db.sql_list(
"""select name from `tab%s` """select name from `tab{}`
where lft<=%s and rgt>=%s""" where lft<={} and rgt>={}""".format(parenttype, "%s", "%s"),
% (parenttype, "%s", "%s"),
(lft, rgt), (lft, rgt),
) )
if parenttype in ["Customer Group", "Item Group", "Territory"]: if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype)) parent_field = f"parent_{frappe.scrub(parenttype)}"
root_name = frappe.db.get_list( root_name = frappe.db.get_list(
parenttype, parenttype,
{"is_group": 1, parent_field: ("is", "not set")}, {"is_group": 1, parent_field: ("is", "not set")},
@@ -235,10 +228,10 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
def get_other_conditions(conditions, values, args): def get_other_conditions(conditions, values, args):
for field in ["company", "customer", "supplier", "campaign", "sales_partner"]: for field in ["company", "customer", "supplier", "campaign", "sales_partner"]:
if args.get(field): if args.get(field):
conditions += " and ifnull(`tabPricing Rule`.{0}, '') in (%({1})s, '')".format(field, field) conditions += f" and ifnull(`tabPricing Rule`.{field}, '') in (%({field})s, '')"
values[field] = args.get(field) values[field] = args.get(field)
else: else:
conditions += " and ifnull(`tabPricing Rule`.{0}, '') = ''".format(field) conditions += f" and ifnull(`tabPricing Rule`.{field}, '') = ''"
for parenttype in ["Customer Group", "Territory", "Supplier Group"]: for parenttype in ["Customer Group", "Territory", "Supplier Group"]:
group_condition = _get_tree_conditions(args, parenttype, "`tabPricing Rule`") group_condition = _get_tree_conditions(args, parenttype, "`tabPricing Rule`")
@@ -510,7 +503,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
"transaction_date" if frappe.get_meta(doctype).has_field("transaction_date") else "posting_date" "transaction_date" if frappe.get_meta(doctype).has_field("transaction_date") else "posting_date"
) )
child_doctype = "{0} Item".format(doctype) child_doctype = f"{doctype} Item"
apply_on = frappe.scrub(pr_doc.get("apply_on")) apply_on = frappe.scrub(pr_doc.get("apply_on"))
values = [pr_doc.valid_from, pr_doc.valid_upto] values = [pr_doc.valid_from, pr_doc.valid_upto]
@@ -520,9 +513,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
warehouses = get_child_warehouses(pr_doc.warehouse) warehouses = get_child_warehouses(pr_doc.warehouse)
condition += """ and `tab{child_doc}`.warehouse in ({warehouses}) condition += """ and `tab{child_doc}`.warehouse in ({warehouses})
""".format( """.format(child_doc=child_doctype, warehouses=",".join(["%s"] * len(warehouses)))
child_doc=child_doctype, warehouses=",".join(["%s"] * len(warehouses))
)
values.extend(warehouses) values.extend(warehouses)
@@ -534,16 +525,14 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
values.extend(items) values.extend(items)
data_set = frappe.db.sql( data_set = frappe.db.sql(
""" SELECT `tab{child_doc}`.stock_qty, f""" SELECT `tab{child_doctype}`.stock_qty,
`tab{child_doc}`.amount `tab{child_doctype}`.amount
FROM `tab{child_doc}`, `tab{parent_doc}` FROM `tab{child_doctype}`, `tab{doctype}`
WHERE WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name and `tab{parent_doc}`.{date_field} `tab{child_doctype}`.parent = `tab{doctype}`.name and `tab{doctype}`.{date_field}
between %s and %s and `tab{parent_doc}`.docstatus = 1 between %s and %s and `tab{doctype}`.docstatus = 1
{condition} group by `tab{child_doc}`.name {condition} group by `tab{child_doctype}`.name
""".format( """,
parent_doc=doctype, child_doc=child_doctype, condition=condition, date_field=date_field
),
tuple(values), tuple(values),
as_dict=1, as_dict=1,
) )
@@ -562,11 +551,9 @@ def apply_pricing_rule_on_transaction(doc):
conditions = get_other_conditions(conditions, values, doc) conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql( pricing_rules = frappe.db.sql(
""" Select `tabPricing Rule`.* from `tabPricing Rule` f""" Select `tabPricing Rule`.* from `tabPricing Rule`
where {conditions} and `tabPricing Rule`.disable = 0 where {conditions} and `tabPricing Rule`.disable = 0
""".format( """,
conditions=conditions
),
values, values,
as_dict=1, as_dict=1,
) )
@@ -591,7 +578,9 @@ def apply_pricing_rule_on_transaction(doc):
continue continue
if ( if (
d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field) d.validate_applied_rule
and doc.get(field) is not None
and doc.get(field) < d.get(pr_field)
): ):
frappe.msgprint(_("User has not applied rule on the invoice {0}").format(doc.name)) frappe.msgprint(_("User has not applied rule on the invoice {0}").format(doc.name))
else: else:
@@ -660,9 +649,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
qty = pricing_rule.free_qty or 1 qty = pricing_rule.free_qty or 1
if pricing_rule.is_recursive: if pricing_rule.is_recursive:
transaction_qty = ( transaction_qty = (args.get("qty") if args else doc.total_qty) - pricing_rule.apply_recursion_over
args.get("qty") if args else doc.total_qty
) - pricing_rule.apply_recursion_over
if transaction_qty: if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty: if pricing_rule.round_free_qty:

View File

@@ -40,7 +40,7 @@ class TestProcessDeferredAccounting(unittest.TestCase):
si.save() si.save()
si.submit() si.submit()
process_deferred_accounting = doc = frappe.get_doc( process_deferred_accounting = frappe.get_doc(
dict( dict(
doctype="Process Deferred Accounting", doctype="Process Deferred Accounting",
posting_date="2023-07-01", posting_date="2023-07-01",

View File

@@ -66,9 +66,7 @@ class ProcessPaymentReconciliation(Document):
def on_cancel(self): def on_cancel(self):
self.db_set("status", "Cancelled") self.db_set("status", "Cancelled")
log = frappe.db.get_value( log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": self.name})
"Process Payment Reconciliation Log", filters={"process_pr": self.name}
)
if log: if log:
frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Cancelled") frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Cancelled")
@@ -416,7 +414,6 @@ def reconcile(doc: None | str = None) -> None:
# If Payment Entry, update details only for newly linked references # If Payment Entry, update details only for newly linked references
# This is for performance # This is for performance
if allocations[0].reference_type == "Payment Entry": if allocations[0].reference_type == "Payment Entry":
references = [(x.invoice_type, x.invoice_number) for x in allocations] references = [(x.invoice_type, x.invoice_number) for x in allocations]
pe = frappe.get_doc(allocations[0].reference_type, allocations[0].reference_name) pe = frappe.get_doc(allocations[0].reference_type, allocations[0].reference_name)
pe.flags.ignore_validate_update_after_submit = True pe.flags.ignore_validate_update_after_submit = True
@@ -430,13 +427,14 @@ def reconcile(doc: None | str = None) -> None:
# Update reconciled count # Update reconciled count
reconciled_count = frappe.db.count( reconciled_count = frappe.db.count(
"Process Payment Reconciliation Log Allocations", filters={"parent": log, "reconciled": True} "Process Payment Reconciliation Log Allocations",
filters={"parent": log, "reconciled": True},
) )
frappe.db.set_value( frappe.db.set_value(
"Process Payment Reconciliation Log", log, "reconciled_entries", reconciled_count "Process Payment Reconciliation Log", log, "reconciled_entries", reconciled_count
) )
except Exception as err: except Exception:
# Update the parent doc about the exception # Update the parent doc about the exception
frappe.db.rollback() frappe.db.rollback()
@@ -474,15 +472,14 @@ def reconcile(doc: None | str = None) -> None:
frappe.db.set_value("Process Payment Reconciliation Log", log, "reconciled", True) frappe.db.set_value("Process Payment Reconciliation Log", log, "reconciled", True)
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
else: else:
if not (
if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"): frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"
):
# trigger next batch in job # trigger next batch in job
# generate reconcile job name # generate reconcile job name
allocation = get_next_allocation(log) allocation = get_next_allocation(log)
if allocation: if allocation:
reconcile_job_name = ( reconcile_job_name = f"process_{doc}_reconcile_allocation_{allocation[0].idx}_{allocation[-1].idx}"
f"process_{doc}_reconcile_allocation_{allocation[0].idx}_{allocation[-1].idx}"
)
else: else:
reconcile_job_name = f"process_{doc}_reconcile" reconcile_job_name = f"process_{doc}_reconcile"
@@ -506,7 +503,7 @@ def reconcile(doc: None | str = None) -> None:
def is_any_doc_running(for_filter: str | dict | None = None) -> str | None: def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None running_doc = None
if for_filter: if for_filter:
if type(for_filter) == str: if isinstance(for_filter, str):
for_filter = frappe.json.loads(for_filter) for_filter = frappe.json.loads(for_filter)
running_doc = frappe.db.get_value( running_doc = frappe.db.get_value(

View File

@@ -24,9 +24,7 @@ class ProcessPaymentReconciliationLog(Document):
process_pr: DF.Link process_pr: DF.Link
reconciled: DF.Check reconciled: DF.Check
reconciled_entries: DF.Int reconciled_entries: DF.Int
status: DF.Literal[ status: DF.Literal["Running", "Paused", "Reconciled", "Partially Reconciled", "Failed", "Cancelled"]
"Running", "Paused", "Reconciled", "Partially Reconciled", "Failed", "Cancelled"
]
total_allocations: DF.Int total_allocations: DF.Int
# end: auto-generated types # end: auto-generated types

View File

@@ -46,9 +46,7 @@ class ProcessStatementOfAccounts(Document):
company: DF.Link company: DF.Link
cost_center: DF.TableMultiSelect[PSOACostCenter] cost_center: DF.TableMultiSelect[PSOACostCenter]
currency: DF.Link | None currency: DF.Link | None
customer_collection: DF.Literal[ customer_collection: DF.Literal["", "Customer Group", "Territory", "Sales Partner", "Sales Person"]
"", "Customer Group", "Territory", "Sales Partner", "Sales Person"
]
customers: DF.Table[ProcessStatementOfAccountsCustomer] customers: DF.Table[ProcessStatementOfAccountsCustomer]
enable_auto_email: DF.Check enable_auto_email: DF.Check
filter_duration: DF.Int filter_duration: DF.Int
@@ -406,9 +404,7 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
{mcond} {mcond}
ORDER BY ORDER BY
contact.creation desc contact.creation desc
""".format( """.format(mcond=get_match_cond("Contact")),
mcond=get_match_cond("Contact")
),
customer_name, customer_name,
) )
@@ -481,9 +477,7 @@ def send_emails(document_name, from_scheduler=False, posting_date=None):
else: else:
new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3) new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3)
new_from_date = add_months(new_to_date, -1 * doc.filter_duration) new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
doc.add_comment( doc.add_comment("Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now()))
"Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())
)
if doc.report == "General Ledger": if doc.report == "General Ledger":
doc.db_set("to_date", new_to_date, commit=True) doc.db_set("to_date", new_to_date, commit=True)
doc.db_set("from_date", new_from_date, commit=True) doc.db_set("from_date", new_from_date, commit=True)

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase

View File

@@ -1,14 +1,11 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from datetime import datetime
from typing import Union
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import getdate from frappe.utils import getdate
from erpnext.accounts.doctype.subscription.subscription import process_all from erpnext.accounts.doctype.subscription.subscription import DateTimeLikeObject, process_all
class ProcessSubscription(Document): class ProcessSubscription(Document):
@@ -30,7 +27,7 @@ class ProcessSubscription(Document):
def create_subscription_process( def create_subscription_process(
subscription: str | None = None, posting_date: Union[str, datetime.date] | None = None subscription: str | None = None, posting_date: DateTimeLikeObject | None = None
): ):
"""Create a new Process Subscription document""" """Create a new Process Subscription document"""
doc = frappe.new_doc("Process Subscription") doc = frappe.new_doc("Process Subscription")

View File

@@ -169,9 +169,7 @@ class PromotionalScheme(Document):
docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name}) docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name})
for docname in docnames: for docname in docnames:
if frappe.db.exists( if frappe.db.exists("Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}):
"Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}
):
raise_for_transaction_exists(self.name) raise_for_transaction_exists(self.name)
if docnames and not transaction_exists: if docnames and not transaction_exists:
@@ -246,7 +244,7 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
args = get_args_for_pricing_rule(doc) args = get_args_for_pricing_rule(doc)
applicable_for = frappe.scrub(doc.get("applicable_for")) applicable_for = frappe.scrub(doc.get("applicable_for"))
for idx, d in enumerate(doc.get(child_doc)): for _idx, d in enumerate(doc.get(child_doc)):
if d.name in rules: if d.name in rules:
if not args.get(applicable_for): if not args.get(applicable_for):
docname = get_pricing_rule_docname(d) docname = get_pricing_rule_docname(d)
@@ -256,7 +254,14 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
for applicable_for_value in args.get(applicable_for): for applicable_for_value in args.get(applicable_for):
docname = get_pricing_rule_docname(d, applicable_for, applicable_for_value) docname = get_pricing_rule_docname(d, applicable_for, applicable_for_value)
pr = prepare_pricing_rule( pr = prepare_pricing_rule(
args, doc, child_doc, discount_fields, d, docname, applicable_for, applicable_for_value args,
doc,
child_doc,
discount_fields,
d,
docname,
applicable_for,
applicable_for_value,
) )
new_doc.append(pr) new_doc.append(pr)
@@ -282,7 +287,7 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
def get_pricing_rule_docname( def get_pricing_rule_docname(
row: dict, applicable_for: str = None, applicable_for_value: str = None row: dict, applicable_for: str | None = None, applicable_for_value: str | None = None
) -> str: ) -> str:
fields = ["promotional_scheme_id", "name"] fields = ["promotional_scheme_id", "name"]
filters = {"promotional_scheme_id": row.name} filters = {"promotional_scheme_id": row.name}

View File

@@ -226,7 +226,7 @@ class PurchaseInvoice(BuyingController):
# end: auto-generated types # end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PurchaseInvoice, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.status_updater = [ self.status_updater = [
{ {
"source_dt": "Purchase Invoice Item", "source_dt": "Purchase Invoice Item",
@@ -243,7 +243,7 @@ class PurchaseInvoice(BuyingController):
] ]
def onload(self): def onload(self):
super(PurchaseInvoice, self).onload() super().onload()
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category") supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
self.set_onload("supplier_tds", supplier_tds) self.set_onload("supplier_tds", supplier_tds)
@@ -263,7 +263,7 @@ class PurchaseInvoice(BuyingController):
self.validate_posting_time() self.validate_posting_time()
super(PurchaseInvoice, self).validate() super().validate()
if not self.is_return: if not self.is_return:
self.po_required() self.po_required()
@@ -323,7 +323,6 @@ class PurchaseInvoice(BuyingController):
if flt(self.paid_amount) + flt(self.write_off_amount) - flt( if flt(self.paid_amount) + flt(self.write_off_amount) - flt(
self.get("rounded_total") or self.grand_total self.get("rounded_total") or self.grand_total
) > 1 / (10 ** (self.precision("base_grand_total") + 1)): ) > 1 / (10 ** (self.precision("base_grand_total") + 1)):
frappe.throw(_("""Paid amount + Write Off Amount can not be greater than Grand Total""")) frappe.throw(_("""Paid amount + Write Off Amount can not be greater than Grand Total"""))
def create_remarks(self): def create_remarks(self):
@@ -352,7 +351,7 @@ class PurchaseInvoice(BuyingController):
self.tax_withholding_category = tds_category self.tax_withholding_category = tds_category
self.set_onload("supplier_tds", tds_category) self.set_onload("supplier_tds", tds_category)
super(PurchaseInvoice, self).set_missing_values(for_validate) super().set_missing_values(for_validate)
def validate_credit_to_acc(self): def validate_credit_to_acc(self):
if not self.credit_to: if not self.credit_to:
@@ -386,12 +385,12 @@ class PurchaseInvoice(BuyingController):
check_list = [] check_list = []
for d in self.get("items"): for d in self.get("items"):
if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt:
check_list.append(d.purchase_order) check_list.append(d.purchase_order)
check_on_hold_or_closed_status("Purchase Order", d.purchase_order) check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
def validate_with_previous_doc(self): def validate_with_previous_doc(self):
super(PurchaseInvoice, self).validate_with_previous_doc( super().validate_with_previous_doc(
{ {
"Purchase Order": { "Purchase Order": {
"ref_dn_field": "purchase_order", "ref_dn_field": "purchase_order",
@@ -439,7 +438,7 @@ class PurchaseInvoice(BuyingController):
exc=WarehouseMissingError, exc=WarehouseMissingError,
) )
super(PurchaseInvoice, self).validate_warehouse() super().validate_warehouse()
def validate_item_code(self): def validate_item_code(self):
for d in self.get("items"): for d in self.get("items"):
@@ -475,7 +474,6 @@ class PurchaseInvoice(BuyingController):
or not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier") or not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")
) )
): ):
if self.update_stock and item.warehouse and (not item.from_warehouse): if self.update_stock and item.warehouse and (not item.from_warehouse):
if ( if (
for_validate for_validate
@@ -503,12 +501,16 @@ class PurchaseInvoice(BuyingController):
if negative_expense_booked_in_pr: if negative_expense_booked_in_pr:
if ( if (
for_validate and item.expense_account and item.expense_account != stock_not_billed_account for_validate
and item.expense_account
and item.expense_account != stock_not_billed_account
): ):
msg = _( msg = _(
"Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}" "Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}"
).format( ).format(
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt) item.idx,
frappe.bold(stock_not_billed_account),
frappe.bold(item.purchase_receipt),
) )
frappe.msgprint(msg, title=_("Expense Head Changed")) frappe.msgprint(msg, title=_("Expense Head Changed"))
@@ -517,7 +519,9 @@ class PurchaseInvoice(BuyingController):
# If no purchase receipt present then book expense in 'Stock Received But Not Billed' # If no purchase receipt present then book expense in 'Stock Received But Not Billed'
# This is done in cases when Purchase Invoice is created before Purchase Receipt # This is done in cases when Purchase Invoice is created before Purchase Receipt
if ( if (
for_validate and item.expense_account and item.expense_account != stock_not_billed_account for_validate
and item.expense_account
and item.expense_account != stock_not_billed_account
): ):
msg = _( msg = _(
"Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}." "Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}."
@@ -568,7 +572,6 @@ class PurchaseInvoice(BuyingController):
def po_required(self): def po_required(self):
if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes": if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes":
if frappe.get_value( if frappe.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order" "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order"
): ):
@@ -578,7 +581,9 @@ class PurchaseInvoice(BuyingController):
if not d.purchase_order: if not d.purchase_order:
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code)) msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
msg += "<br><br>" msg += "<br><br>"
msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format( msg += _(
"To submit the invoice without purchase order please set {0} as {1} in {2}"
).format(
frappe.bold(_("Purchase Order Required")), frappe.bold(_("Purchase Order Required")),
frappe.bold("No"), frappe.bold("No"),
get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"), get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
@@ -588,7 +593,6 @@ class PurchaseInvoice(BuyingController):
def pr_required(self): def pr_required(self):
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
if frappe.db.get_value("Buying Settings", None, "pr_required") == "Yes": if frappe.db.get_value("Buying Settings", None, "pr_required") == "Yes":
if frappe.get_value( if frappe.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_receipt" "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_receipt"
): ):
@@ -621,7 +625,8 @@ class PurchaseInvoice(BuyingController):
frappe.throw(_("Purchase Order {0} is not submitted").format(d.purchase_order)) frappe.throw(_("Purchase Order {0} is not submitted").format(d.purchase_order))
if d.purchase_receipt: if d.purchase_receipt:
submitted = frappe.db.sql( submitted = frappe.db.sql(
"select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt "select name from `tabPurchase Receipt` where docstatus = 1 and name = %s",
d.purchase_receipt,
) )
if not submitted: if not submitted:
frappe.throw(_("Purchase Receipt {0} is not submitted").format(d.purchase_receipt)) frappe.throw(_("Purchase Receipt {0} is not submitted").format(d.purchase_receipt))
@@ -669,7 +674,9 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"): for item in self.get("items"):
if item.purchase_receipt: if item.purchase_receipt:
frappe.throw( frappe.throw(
_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt) _("Stock cannot be updated against Purchase Receipt {0}").format(
item.purchase_receipt
)
) )
def validate_for_repost(self): def validate_for_repost(self):
@@ -679,7 +686,7 @@ class PurchaseInvoice(BuyingController):
validate_docs_for_deferred_accounting([], [self.name]) validate_docs_for_deferred_accounting([], [self.name])
def on_submit(self): def on_submit(self):
super(PurchaseInvoice, self).on_submit() super().on_submit()
self.check_prev_docstatus() self.check_prev_docstatus()
@@ -716,9 +723,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock == 1: if self.update_stock == 1:
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
if ( if frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction":
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
self.update_project() self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
@@ -762,7 +767,10 @@ class PurchaseInvoice(BuyingController):
for entry in provisional_entries: for entry in provisional_entries:
frappe.db.set_value( frappe.db.set_value(
"GL Entry", "GL Entry",
{"voucher_type": "Purchase Receipt", "voucher_detail_no": entry.voucher_detail_no}, {
"voucher_type": "Purchase Receipt",
"voucher_detail_no": entry.voucher_detail_no,
},
"is_cancelled", "is_cancelled",
1, 1,
) )
@@ -897,7 +905,7 @@ class PurchaseInvoice(BuyingController):
if flt(item.base_net_amount): if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account) account_currency = get_account_currency(item.expense_account)
if item.item_code: if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") frappe.get_cached_value("Item", item.item_code, "asset_category")
if ( if (
self.update_stock self.update_stock
@@ -1002,7 +1010,9 @@ class PurchaseInvoice(BuyingController):
if flt(item.rm_supp_cost): if flt(item.rm_supp_cost):
supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"] supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"]
if not supplier_warehouse_account: if not supplier_warehouse_account:
frappe.throw(_("Please set account in Warehouse {0}").format(self.supplier_warehouse)) frappe.throw(
_("Please set account in Warehouse {0}").format(self.supplier_warehouse)
)
gl_entries.append( gl_entries.append(
self.get_gl_dict( self.get_gl_dict(
{ {
@@ -1040,7 +1050,9 @@ class PurchaseInvoice(BuyingController):
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt) purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
if not purchase_receipt_doc: if not purchase_receipt_doc:
purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt) purchase_receipt_doc = frappe.get_doc(
"Purchase Receipt", item.purchase_receipt
)
purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
@@ -1089,10 +1101,9 @@ class PurchaseInvoice(BuyingController):
and self.conversion_rate != exchange_rate_map[item.purchase_receipt] and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
and item.net_rate == net_rate_map[item.pr_detail] and item.net_rate == net_rate_map[item.pr_detail]
): ):
discrepancy_caused_by_exchange_rate_difference = (
discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * ( item.qty * item.net_rate
exchange_rate_map[item.purchase_receipt] - self.conversion_rate ) * (exchange_rate_map[item.purchase_receipt] - self.conversion_rate)
)
gl_entries.append( gl_entries.append(
self.get_gl_dict( self.get_gl_dict(
@@ -1179,9 +1190,7 @@ class PurchaseInvoice(BuyingController):
}, },
) )
def make_stock_adjustment_entry( def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
self, gl_entries, item, voucher_wise_stock_value, account_currency
):
net_amt_precision = item.precision("base_net_amount") net_amt_precision = item.precision("base_net_amount")
val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9 val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
@@ -1197,7 +1206,6 @@ class PurchaseInvoice(BuyingController):
and warehouse_debit_amount and warehouse_debit_amount
!= flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision) != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
): ):
cost_of_goods_sold_account = self.get_company_default("default_expense_account") cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision) stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount stock_adjustment_amt = warehouse_debit_amount - stock_amount
@@ -1420,9 +1428,7 @@ class PurchaseInvoice(BuyingController):
# base_rounding_adjustment may become zero due to small precision # base_rounding_adjustment may become zero due to small precision
# eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2 # eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
# then base_rounding_adjustment becomes zero and error is thrown in GL Entry # then base_rounding_adjustment becomes zero and error is thrown in GL Entry
if ( if not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment:
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center( round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
) )
@@ -1445,7 +1451,7 @@ class PurchaseInvoice(BuyingController):
def on_cancel(self): def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self) check_if_return_invoice_linked_with_payment_entry(self)
super(PurchaseInvoice, self).on_cancel() super().on_cancel()
self.check_on_hold_or_closed_status() self.check_on_hold_or_closed_status()
@@ -1476,9 +1482,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock == 1: if self.update_stock == 1:
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
if ( if frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction":
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
self.update_project() self.update_project()
self.db_set("status", "Cancelled") self.db_set("status", "Cancelled")
@@ -1510,9 +1514,7 @@ class PurchaseInvoice(BuyingController):
pj = frappe.qb.DocType("Project") pj = frappe.qb.DocType("Project")
for proj, value in projects.items(): for proj, value in projects.items():
res = ( res = frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
)
current_purchase_cost = res and res[0][0] or 0 current_purchase_cost = res and res[0][0] or 0
frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value) frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
@@ -1780,9 +1782,7 @@ def get_purchase_document_details(doc):
) )
net_rate_map = frappe._dict( net_rate_map = frappe._dict(
frappe.get_all( frappe.get_all(child_doctype, filters={"name": ("in", items)}, fields=["name", "net_rate"], as_list=1)
child_doctype, filters={"name": ("in", items)}, fields=["name", "net_rate"], as_list=1
)
) )
return exchange_rate_map, net_rate_map return exchange_rate_map, net_rate_map

View File

@@ -2,8 +2,6 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase, change_settings from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, cint, flt, getdate, nowdate, today from frappe.utils import add_days, cint, flt, getdate, nowdate, today
@@ -223,7 +221,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
supplier.on_hold = 0 supplier.on_hold = 0
supplier.save() supplier.save()
except: except Exception:
pass pass
else: else:
raise Exception raise Exception
@@ -257,7 +255,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertEqual(pi.on_hold, 0) self.assertEqual(pi.on_hold, 0)
def test_gl_entries_with_perpetual_inventory_against_pr(self): def test_gl_entries_with_perpetual_inventory_against_pr(self):
pr = make_purchase_receipt( pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", company="_Test Company with perpetual inventory",
supplier_warehouse="Work In Progress - TCP1", supplier_warehouse="Work In Progress - TCP1",
@@ -308,7 +305,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
] ]
) )
for i, gle in enumerate(gl_entries): for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account) self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit) self.assertEqual(expected_values[gle.account][2], gle.credit)
@@ -332,9 +329,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi.submit() pi.submit()
# Get exchnage gain and loss account # Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value( exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account")
"Company", pi.company, "exchange_gain_loss_account"
)
# fetching the latest GL Entry with exchange gain and loss account account # fetching the latest GL Entry with exchange gain and loss account account
amount = frappe.db.get_value( amount = frappe.db.get_value(
@@ -550,12 +545,10 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"}) project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
existing_purchase_cost = frappe.db.sql( existing_purchase_cost = frappe.db.sql(
"""select sum(base_net_amount) f"""select sum(base_net_amount)
from `tabPurchase Invoice Item` from `tabPurchase Invoice Item`
where project = '{0}' where project = '{project.name}'
and docstatus=1""".format( and docstatus=1"""
project.name
)
) )
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0 existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
@@ -730,7 +723,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
"credit", "credit",
"credit_in_account_currency", "credit_in_account_currency",
): ):
for i, gle in enumerate(gl_entries): for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field]) self.assertEqual(expected_values[gle.account][field], gle[field])
# Check for valid currency # Check for valid currency
@@ -752,7 +745,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertFalse(gle) self.assertFalse(gle)
def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self): def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self):
pi = make_purchase_invoice( pi = make_purchase_invoice(
update_stock=1, update_stock=1,
posting_date=frappe.utils.nowdate(), posting_date=frappe.utils.nowdate(),
@@ -781,13 +773,12 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
(d[0], d) for d in [[pi.credit_to, 0.0, 250.0], [stock_in_hand_account, 250.0, 0.0]] (d[0], d) for d in [[pi.credit_to, 0.0, 250.0], [stock_in_hand_account, 250.0, 0.0]]
) )
for i, gle in enumerate(gl_entries): for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_gl_entries[gle.account][0], gle.account) self.assertEqual(expected_gl_entries[gle.account][0], gle.account)
self.assertEqual(expected_gl_entries[gle.account][1], gle.debit) self.assertEqual(expected_gl_entries[gle.account][1], gle.debit)
self.assertEqual(expected_gl_entries[gle.account][2], gle.credit) self.assertEqual(expected_gl_entries[gle.account][2], gle.credit)
def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self): def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self):
pi = make_purchase_invoice( pi = make_purchase_invoice(
update_stock=1, update_stock=1,
posting_date=frappe.utils.nowdate(), posting_date=frappe.utils.nowdate(),
@@ -822,7 +813,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
] ]
) )
for i, gle in enumerate(gl_entries): for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_gl_entries[gle.account][0], gle.account) self.assertEqual(expected_gl_entries[gle.account][0], gle.account)
self.assertEqual(expected_gl_entries[gle.account][1], gle.debit) self.assertEqual(expected_gl_entries[gle.account][1], gle.debit)
self.assertEqual(expected_gl_entries[gle.account][2], gle.credit) self.assertEqual(expected_gl_entries[gle.account][2], gle.credit)
@@ -894,9 +885,9 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi.load_from_db() pi.load_from_db()
serial_no = get_serial_nos_from_bundle(pi.get("items")[0].serial_and_batch_bundle)[0] serial_no = get_serial_nos_from_bundle(pi.get("items")[0].serial_and_batch_bundle)[0]
rejected_serial_no = get_serial_nos_from_bundle( rejected_serial_no = get_serial_nos_from_bundle(pi.get("items")[0].rejected_serial_and_batch_bundle)[
pi.get("items")[0].rejected_serial_and_batch_bundle 0
)[0] ]
self.assertEqual( self.assertEqual(
frappe.db.get_value("Serial No", serial_no, "warehouse"), frappe.db.get_value("Serial No", serial_no, "warehouse"),
@@ -1026,12 +1017,8 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
def test_duplicate_due_date_in_terms(self): def test_duplicate_due_date_in_terms(self):
pi = make_purchase_invoice(do_not_save=1) pi = make_purchase_invoice(do_not_save=1)
pi.append( pi.append("payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50))
"payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50) pi.append("payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50))
)
pi.append(
"payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50)
)
self.assertRaises(frappe.ValidationError, pi.insert) self.assertRaises(frappe.ValidationError, pi.insert)
@@ -1069,9 +1056,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
pi = make_purchase_invoice_against_cost_center( pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
cost_center=cost_center, credit_to="Creditors - _TC"
)
self.assertEqual(pi.cost_center, cost_center) self.assertEqual(pi.cost_center, cost_center)
expected_values = { expected_values = {
@@ -1531,9 +1516,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
def test_provisional_accounting_entry(self): def test_provisional_accounting_entry(self):
setup_provisional_accounting() setup_provisional_accounting()
pr = make_purchase_receipt( pr = make_purchase_receipt(item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2))
item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2)
)
pi = create_purchase_invoice_from_receipt(pr.name) pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1 pi.set_posting_time = 1
@@ -1542,7 +1525,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi.save() pi.save()
pi.submit() pi.submit()
self.assertEquals(pr.items[0].provisional_expense_account, "Provision Account - _TC") self.assertEqual(pr.items[0].provisional_expense_account, "Provision Account - _TC")
# Check GLE for Purchase Invoice # Check GLE for Purchase Invoice
expected_gle = [ expected_gle = [
@@ -1569,9 +1552,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date], ["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date],
] ]
check_gl_entries( check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date)
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
toggle_provisional_accounting_setting() toggle_provisional_accounting_setting()
@@ -1620,9 +1601,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date], ["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
] ]
check_gl_entries( check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date)
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
toggle_provisional_accounting_setting() toggle_provisional_accounting_setting()
@@ -1668,9 +1647,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
def test_adjust_incoming_rate(self): def test_adjust_incoming_rate(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
frappe.db.set_single_value( frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1
)
# Increase the cost of the item # Increase the cost of the item
@@ -1722,9 +1699,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
) )
self.assertEqual(stock_value_difference, 50) self.assertEqual(stock_value_difference, 50)
frappe.db.set_single_value( frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0
)
# Don't adjust incoming rate # Don't adjust incoming rate
@@ -1754,7 +1729,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_item_less_defaults(self): def test_item_less_defaults(self):
pi = frappe.new_doc("Purchase Invoice") pi = frappe.new_doc("Purchase Invoice")
pi.supplier = "_Test Supplier" pi.supplier = "_Test Supplier"
pi.company = "_Test Company" pi.company = "_Test Company"
@@ -2291,7 +2265,7 @@ def make_purchase_invoice(**args):
pi.cost_center = args.parent_cost_center pi.cost_center = args.parent_cost_center
bundle_id = None bundle_id = None
if not args.use_serial_batch_fields and ((args.get("batch_no") or args.get("serial_no"))): if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
batches = {} batches = {}
qty = args.qty or 5 qty = args.qty or 5
item_code = args.item or args.item_code or "_Test Item" item_code = args.item or args.item_code or "_Test Item"
@@ -2440,9 +2414,7 @@ def setup_provisional_accounting(**args):
parent_account=args.parent_account or "Current Liabilities - _TC", parent_account=args.parent_account or "Current Liabilities - _TC",
company=company, company=company,
) )
toggle_provisional_accounting_setting( toggle_provisional_accounting_setting(enable=1, company=company, provisional_account=provisional_account)
enable=1, company=company, provisional_account=provisional_account
)
def toggle_provisional_accounting_setting(**args): def toggle_provisional_accounting_setting(**args):

View File

@@ -37,4 +37,4 @@ class PurchaseTaxesandChargesTemplate(Document):
def autoname(self): def autoname(self):
if self.company and self.title: if self.company and self.title:
abbr = frappe.get_cached_value("Company", self.company, "abbr") abbr = frappe.get_cached_value("Company", self.company, "abbr")
self.name = "{0} - {1}".format(self.title, abbr) self.name = f"{self.title} - {abbr}"

View File

@@ -27,7 +27,7 @@ class RepostAccountingLedger(Document):
# end: auto-generated types # end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RepostAccountingLedger, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._allowed_types = get_allowed_types_from_settings() self._allowed_types = get_allowed_types_from_settings()
def validate(self): def validate(self):
@@ -154,7 +154,9 @@ def start_repost(account_repost_doc=str) -> None:
doc = frappe.get_doc(x.voucher_type, x.voucher_no) doc = frappe.get_doc(x.voucher_type, x.voucher_no)
if repost_doc.delete_cancelled_entries: if repost_doc.delete_cancelled_entries:
frappe.db.delete("GL Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}) frappe.db.delete(
"GL Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
)
frappe.db.delete( frappe.db.delete(
"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name} "Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
) )
@@ -200,7 +202,9 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
if docs_with_deferred_revenue or docs_with_deferred_expense: if docs_with_deferred_revenue or docs_with_deferred_expense:
frappe.throw( frappe.throw(
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format( _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])) frappe.bold(
comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])
)
) )
) )

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