From 494bd9ef78313436f0424b918f200dab8fc7c20b Mon Sep 17 00:00:00 2001
From: Ankush Menat Dear {{ doc.contact_person }}, Hello, {{ _("If you have any questions, please get back to us.") }} {{ _("Thank you for your business!") }} {message}
"
- msg += _("Please convert the parent account in corresponding child company to a group account.")
+ msg += _(
+ "Please convert the parent account in corresponding child company to a group account."
+ )
frappe.throw(msg, title=_("Invalid Parent Account"))
- filters = {
- "account_name": self.account_name,
- "company": company
- }
+ filters = {"account_name": self.account_name, "company": company}
if self.account_number:
filters["account_number"] = self.account_number
- child_account = frappe.db.get_value("Account", filters, 'name')
+ child_account = frappe.db.get_value("Account", filters, "name")
if not child_account:
doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True
- doc.update({
- "company": company,
- # parent account's currency should be passed down to child account's curreny
- # if it is None, it picks it up from default company currency, which might be unintended
- "account_currency": erpnext.get_company_currency(company),
- "parent_account": parent_acc_name_map[company]
- })
+ doc.update(
+ {
+ "company": company,
+ # parent account's currency should be passed down to child account's curreny
+ # if it is None, it picks it up from default company currency, which might be unintended
+ "account_currency": erpnext.get_company_currency(company),
+ "parent_account": parent_acc_name_map[company],
+ }
+ )
doc.save()
- frappe.msgprint(_("Account {0} is added in the child company {1}")
- .format(doc.name, company))
+ frappe.msgprint(_("Account {0} is added in the child company {1}").format(doc.name, company))
elif child_account:
# update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account)
parent_value_changed = False
- for field in ['account_type', 'freeze_account', 'balance_must_be']:
+ for field in ["account_type", "freeze_account", "balance_must_be"]:
if doc.get(field) != self.get(field):
parent_value_changed = True
doc.set(field, self.get(field))
@@ -243,8 +293,11 @@ class Account(NestedSet):
return frappe.db.get_value("GL Entry", {"account": self.name})
def check_if_child_exists(self):
- return frappe.db.sql("""select name from `tabAccount` where parent_account = %s
- and docstatus != 2""", self.name)
+ return frappe.db.sql(
+ """select name from `tabAccount` where parent_account = %s
+ and docstatus != 2""",
+ self.name,
+ )
def validate_mandatory(self):
if not self.root_type:
@@ -260,73 +313,99 @@ class Account(NestedSet):
super(Account, self).on_trash(True)
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql("""select name from tabAccount
+ return frappe.db.sql(
+ """select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s
- and %s like %s order by name limit %s, %s""" %
- ("%s", searchfield, "%s", "%s", "%s"),
- (filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
+ and %s like %s order by name limit %s, %s"""
+ % ("%s", searchfield, "%s", "%s", "%s"),
+ (filters["company"], "%%%s%%" % txt, start, page_len),
+ as_list=1,
+ )
+
def get_account_currency(account):
"""Helper function to get account currency"""
if not account:
return
+
def generator():
- account_currency, company = frappe.get_cached_value("Account", account, ["account_currency", "company"])
+ account_currency, company = frappe.get_cached_value(
+ "Account", account, ["account_currency", "company"]
+ )
if not account_currency:
- account_currency = frappe.get_cached_value('Company', company, "default_currency")
+ account_currency = frappe.get_cached_value("Company", company, "default_currency")
return account_currency
return frappe.local_cache("account_currency", account, generator)
+
def on_doctype_update():
frappe.db.add_index("Account", ["lft", "rgt"])
+
def get_account_autoname(account_number, account_name, company):
# first validate if company exists
- company = frappe.get_cached_value('Company', company, ["abbr", "name"], as_dict=True)
+ company = frappe.get_cached_value("Company", company, ["abbr", "name"], as_dict=True)
if not company:
- frappe.throw(_('Company {0} does not exist').format(company))
+ frappe.throw(_("Company {0} does not exist").format(company))
parts = [account_name.strip(), company.abbr]
if cstr(account_number).strip():
parts.insert(0, cstr(account_number).strip())
- return ' - '.join(parts)
+ return " - ".join(parts)
+
def validate_account_number(name, account_number, company):
if account_number:
- account_with_same_number = frappe.db.get_value("Account",
- {"account_number": account_number, "company": company, "name": ["!=", name]})
+ account_with_same_number = frappe.db.get_value(
+ "Account", {"account_number": account_number, "company": company, "name": ["!=", name]}
+ )
if account_with_same_number:
- frappe.throw(_("Account Number {0} already used in account {1}")
- .format(account_number, account_with_same_number))
+ frappe.throw(
+ _("Account Number {0} already used in account {1}").format(
+ account_number, account_with_same_number
+ )
+ )
+
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False):
account = frappe.db.get_value("Account", name, "company", as_dict=True)
- if not account: return
+ if not account:
+ return
- old_acc_name, old_acc_number = frappe.db.get_value('Account', name, \
- ["account_name", "account_number"])
+ old_acc_name, old_acc_number = frappe.db.get_value(
+ "Account", name, ["account_name", "account_number"]
+ )
# check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company)
- allow_independent_account_creation = frappe.get_value("Company", account.company, "allow_account_creation_against_child_company")
+ allow_independent_account_creation = frappe.get_value(
+ "Company", account.company, "allow_account_creation_against_child_company"
+ )
if ancestors and not allow_independent_account_creation:
for ancestor in ancestors:
- if frappe.db.get_value("Account", {'account_name': old_acc_name, 'company': ancestor}, 'name'):
+ if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")
- message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
+ message = _("Account {0} exists in parent company {1}.").format(
+ frappe.bold(old_acc_name), frappe.bold(ancestor)
+ )
message += "
"
- message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
+ message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(
+ frappe.bold(ancestor)
+ )
message += "
"
- message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
+ message += _("To overrule this, enable '{0}' in company {1}").format(
+ allow_child_account_creation, frappe.bold(account.company)
+ )
frappe.throw(message, title=_("Rename Not Allowed"))
@@ -339,42 +418,53 @@ def update_account_number(name, account_name, account_number=None, from_descenda
if not from_descendant:
# Update and rename in child company accounts as well
- descendants = get_descendants_of('Company', account.company)
+ descendants = get_descendants_of("Company", account.company)
if descendants:
- sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number, old_acc_number)
+ sync_update_account_number_in_child(
+ descendants, old_acc_name, account_name, account_number, old_acc_number
+ )
new_name = get_account_autoname(account_number, account_name, account.company)
if name != new_name:
frappe.rename_doc("Account", name, new_name, force=1)
return new_name
+
@frappe.whitelist()
def merge_account(old, new, is_group, root_type, company):
# Validate properties before merging
if not frappe.db.exists("Account", new):
throw(_("Account {0} does not exist").format(new))
- val = list(frappe.db.get_value("Account", new,
- ["is_group", "root_type", "company"]))
+ val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"]))
if val != [cint(is_group), root_type, company]:
- throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""))
+ throw(
+ _(
+ """Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
+ )
+ )
if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
- frappe.db.set_value("Account", new, "parent_account",
- frappe.db.get_value("Account", old, "parent_account"))
+ frappe.db.set_value(
+ "Account", new, "parent_account", frappe.db.get_value("Account", old, "parent_account")
+ )
frappe.rename_doc("Account", old, new, merge=1, force=1)
return new
+
@frappe.whitelist()
def get_root_company(company):
# return the topmost company in the hierarchy
- ancestors = get_ancestors_of('Company', company, "lft asc")
+ ancestors = get_ancestors_of("Company", company, "lft asc")
return [ancestors[0]] if ancestors else []
-def sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number=None, old_acc_number=None):
+
+def sync_update_account_number_in_child(
+ descendants, old_acc_name, account_name, account_number=None, old_acc_number=None
+):
filters = {
"company": ["in", descendants],
"account_name": old_acc_name,
@@ -382,5 +472,7 @@ def sync_update_account_number_in_child(descendants, old_acc_name, account_name,
if old_acc_number:
filters["account_number"] = old_acc_number
- for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
- update_account_number(d["name"], account_name, account_number, from_descendant=True)
+ for d in frappe.db.get_values(
+ "Account", filters=filters, fieldname=["company", "name"], as_dict=True
+ ):
+ update_account_number(d["name"], account_name, account_number, from_descendant=True)
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index a8de06cc6c..947b4853e8 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -10,7 +10,9 @@ from frappe.utils.nestedset import rebuild_tree
from unidecode import unidecode
-def create_charts(company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None):
+def create_charts(
+ company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None
+):
chart = custom_chart or get_chart(chart_template, existing_company)
if chart:
accounts = []
@@ -20,30 +22,41 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
if root_account:
root_type = child.get("root_type")
- if account_name not in ["account_name", "account_number", "account_type",
- "root_type", "is_group", "tax_rate"]:
+ if account_name not in [
+ "account_name",
+ "account_number",
+ "account_type",
+ "root_type",
+ "is_group",
+ "tax_rate",
+ ]:
account_number = cstr(child.get("account_number")).strip()
- account_name, account_name_in_db = add_suffix_if_duplicate(account_name,
- account_number, accounts)
+ account_name, account_name_in_db = add_suffix_if_duplicate(
+ account_name, account_number, accounts
+ )
is_group = identify_is_group(child)
- report_type = "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] \
- else "Profit and Loss"
+ report_type = (
+ "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
+ )
- account = frappe.get_doc({
- "doctype": "Account",
- "account_name": child.get('account_name') if from_coa_importer else account_name,
- "company": company,
- "parent_account": parent,
- "is_group": is_group,
- "root_type": root_type,
- "report_type": report_type,
- "account_number": account_number,
- "account_type": child.get("account_type"),
- "account_currency": child.get('account_currency') or frappe.db.get_value('Company', company, "default_currency"),
- "tax_rate": child.get("tax_rate")
- })
+ account = frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": child.get("account_name") if from_coa_importer else account_name,
+ "company": company,
+ "parent_account": parent,
+ "is_group": is_group,
+ "root_type": root_type,
+ "report_type": report_type,
+ "account_number": account_number,
+ "account_type": child.get("account_type"),
+ "account_currency": child.get("account_currency")
+ or frappe.db.get_value("Company", company, "default_currency"),
+ "tax_rate": child.get("tax_rate"),
+ }
+ )
if root_account or frappe.local.flags.allow_unverified_charts:
account.flags.ignore_mandatory = True
@@ -63,10 +76,10 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
rebuild_tree("Account", "parent_account")
frappe.local.flags.ignore_update_nsm = False
+
def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number:
- account_name_in_db = unidecode(" - ".join([account_number,
- account_name.strip().lower()]))
+ account_name_in_db = unidecode(" - ".join([account_number, account_name.strip().lower()]))
else:
account_name_in_db = unidecode(account_name.strip().lower())
@@ -76,16 +89,21 @@ def add_suffix_if_duplicate(account_name, account_number, accounts):
return account_name, account_name_in_db
+
def identify_is_group(child):
if child.get("is_group"):
is_group = child.get("is_group")
- elif len(set(child.keys()) - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])):
+ elif len(
+ set(child.keys())
+ - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
+ ):
is_group = 1
else:
is_group = 0
return is_group
+
def get_chart(chart_template, existing_company=None):
chart = {}
if existing_company:
@@ -95,11 +113,13 @@ def get_chart(chart_template, existing_company=None):
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
standard_chart_of_accounts,
)
+
return standard_chart_of_accounts.get()
elif chart_template == "Standard with Numbers":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
standard_chart_of_accounts_with_account_number,
)
+
return standard_chart_of_accounts_with_account_number.get()
else:
folders = ("verified",)
@@ -115,6 +135,7 @@ def get_chart(chart_template, existing_company=None):
if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree")
+
@frappe.whitelist()
def get_charts_for_country(country, with_standard=False):
charts = []
@@ -122,9 +143,10 @@ def get_charts_for_country(country, with_standard=False):
def _get_chart_name(content):
if content:
content = json.loads(content)
- if (content and content.get("disabled", "No") == "No") \
- or frappe.local.flags.allow_unverified_charts:
- charts.append(content["name"])
+ if (
+ content and content.get("disabled", "No") == "No"
+ ) or frappe.local.flags.allow_unverified_charts:
+ charts.append(content["name"])
country_code = frappe.db.get_value("Country", country, "code")
if country_code:
@@ -151,11 +173,21 @@ def get_charts_for_country(country, with_standard=False):
def get_account_tree_from_existing_company(existing_company):
- all_accounts = frappe.get_all('Account',
- filters={'company': existing_company},
- fields = ["name", "account_name", "parent_account", "account_type",
- "is_group", "root_type", "tax_rate", "account_number"],
- order_by="lft, rgt")
+ all_accounts = frappe.get_all(
+ "Account",
+ filters={"company": existing_company},
+ fields=[
+ "name",
+ "account_name",
+ "parent_account",
+ "account_type",
+ "is_group",
+ "root_type",
+ "tax_rate",
+ "account_number",
+ ],
+ order_by="lft, rgt",
+ )
account_tree = {}
@@ -164,6 +196,7 @@ def get_account_tree_from_existing_company(existing_company):
build_account_tree(account_tree, None, all_accounts)
return account_tree
+
def build_account_tree(tree, parent, all_accounts):
# find children
parent_account = parent.name if parent else ""
@@ -192,27 +225,29 @@ def build_account_tree(tree, parent, all_accounts):
# call recursively to build a subtree for current account
build_account_tree(tree[child.account_name], child, all_accounts)
+
@frappe.whitelist()
def validate_bank_account(coa, bank_account):
accounts = []
chart = get_chart(coa)
if chart:
+
def _get_account_names(account_master):
for account_name, child in account_master.items():
- if account_name not in ["account_number", "account_type",
- "root_type", "is_group", "tax_rate"]:
+ if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
accounts.append(account_name)
_get_account_names(child)
_get_account_names(chart)
- return (bank_account in accounts)
+ return bank_account in accounts
+
@frappe.whitelist()
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
- ''' get chart template from its folder and parse the json to be rendered as tree '''
+ """get chart template from its folder and parse the json to be rendered as tree"""
chart = chart_data or get_chart(chart_template)
# if no template selected, return as it is
@@ -220,22 +255,33 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
return
accounts = []
+
def _import_accounts(children, parent):
- ''' recursively called to form a parent-child based list of dict from chart template '''
+ """recursively called to form a parent-child based list of dict from chart template"""
for account_name, child in children.items():
account = {}
- if account_name in ["account_name", "account_number", "account_type",\
- "root_type", "is_group", "tax_rate"]: continue
+ if account_name in [
+ "account_name",
+ "account_number",
+ "account_type",
+ "root_type",
+ "is_group",
+ "tax_rate",
+ ]:
+ continue
if from_coa_importer:
- account_name = child['account_name']
+ account_name = child["account_name"]
- account['parent_account'] = parent
- account['expandable'] = True if identify_is_group(child) else False
- account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
- if child.get('account_number') else account_name
+ account["parent_account"] = parent
+ account["expandable"] = True if identify_is_group(child) else False
+ account["value"] = (
+ (cstr(child.get("account_number")).strip() + " - " + account_name)
+ if child.get("account_number")
+ else account_name
+ )
accounts.append(account)
- _import_accounts(child, account['value'])
+ _import_accounts(child, account["value"])
_import_accounts(chart, None)
return accounts
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py b/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py
index 79001d7705..3f25ada8b3 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py
@@ -20,6 +20,7 @@ charts = {}
all_account_types = []
all_roots = {}
+
def go():
global accounts, charts
default_account_types = get_default_account_types()
@@ -34,14 +35,16 @@ def go():
accounts, charts = {}, {}
country_path = os.path.join(path, country_dir)
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
- data_files = manifest.get("data", []) + manifest.get("init_xml", []) + \
- manifest.get("update_xml", [])
+ data_files = (
+ manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", [])
+ )
files_path = [os.path.join(country_path, d) for d in data_files]
xml_roots = get_xml_roots(files_path)
csv_content = get_csv_contents(files_path)
prefix = country_dir if csv_content else None
- account_types = get_account_types(xml_roots.get("account.account.type", []),
- csv_content.get("account.account.type", []), prefix)
+ account_types = get_account_types(
+ xml_roots.get("account.account.type", []), csv_content.get("account.account.type", []), prefix
+ )
account_types.update(default_account_types)
if xml_roots:
@@ -54,12 +57,15 @@ def go():
create_all_roots_file()
+
def get_default_account_types():
default_types_root = []
- default_types_root.append(ET.parse(os.path.join(path, "account", "data",
- "data_account_type.xml")).getroot())
+ default_types_root.append(
+ ET.parse(os.path.join(path, "account", "data", "data_account_type.xml")).getroot()
+ )
return get_account_types(default_types_root, None, prefix="account")
+
def get_xml_roots(files_path):
xml_roots = frappe._dict()
for filepath in files_path:
@@ -68,64 +74,69 @@ def get_xml_roots(files_path):
tree = ET.parse(filepath)
root = tree.getroot()
for node in root[0].findall("record"):
- if node.get("model") in ["account.account.template",
- "account.chart.template", "account.account.type"]:
+ if node.get("model") in [
+ "account.account.template",
+ "account.chart.template",
+ "account.account.type",
+ ]:
xml_roots.setdefault(node.get("model"), []).append(root)
break
return xml_roots
+
def get_csv_contents(files_path):
csv_content = {}
for filepath in files_path:
fname = os.path.basename(filepath)
- for file_type in ["account.account.template", "account.account.type",
- "account.chart.template"]:
+ for file_type in ["account.account.template", "account.account.type", "account.chart.template"]:
if fname.startswith(file_type) and fname.endswith(".csv"):
with open(filepath, "r") as csvfile:
try:
- csv_content.setdefault(file_type, [])\
- .append(read_csv_content(csvfile.read()))
+ csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read()))
except Exception as e:
continue
return csv_content
+
def get_account_types(root_list, csv_content, prefix=None):
types = {}
account_type_map = {
- 'cash': 'Cash',
- 'bank': 'Bank',
- 'tr_cash': 'Cash',
- 'tr_bank': 'Bank',
- 'receivable': 'Receivable',
- 'tr_receivable': 'Receivable',
- 'account rec': 'Receivable',
- 'payable': 'Payable',
- 'tr_payable': 'Payable',
- 'equity': 'Equity',
- 'stocks': 'Stock',
- 'stock': 'Stock',
- 'tax': 'Tax',
- 'tr_tax': 'Tax',
- 'tax-out': 'Tax',
- 'tax-in': 'Tax',
- 'charges_personnel': 'Chargeable',
- 'fixed asset': 'Fixed Asset',
- 'cogs': 'Cost of Goods Sold',
-
+ "cash": "Cash",
+ "bank": "Bank",
+ "tr_cash": "Cash",
+ "tr_bank": "Bank",
+ "receivable": "Receivable",
+ "tr_receivable": "Receivable",
+ "account rec": "Receivable",
+ "payable": "Payable",
+ "tr_payable": "Payable",
+ "equity": "Equity",
+ "stocks": "Stock",
+ "stock": "Stock",
+ "tax": "Tax",
+ "tr_tax": "Tax",
+ "tax-out": "Tax",
+ "tax-in": "Tax",
+ "charges_personnel": "Chargeable",
+ "fixed asset": "Fixed Asset",
+ "cogs": "Cost of Goods Sold",
}
for root in root_list:
for node in root[0].findall("record"):
- if node.get("model")=="account.account.type":
+ if node.get("model") == "account.account.type":
data = {}
for field in node.findall("field"):
- if field.get("name")=="code" and field.text.lower() != "none" \
- and account_type_map.get(field.text):
- data["account_type"] = account_type_map[field.text]
+ if (
+ field.get("name") == "code"
+ and field.text.lower() != "none"
+ and account_type_map.get(field.text)
+ ):
+ data["account_type"] = account_type_map[field.text]
node_id = prefix + "." + node.get("id") if prefix else node.get("id")
types[node_id] = data
- if csv_content and csv_content[0][0]=="id":
+ if csv_content and csv_content[0][0] == "id":
for row in csv_content[1:]:
row_dict = dict(zip(csv_content[0], row))
data = {}
@@ -136,21 +147,22 @@ def get_account_types(root_list, csv_content, prefix=None):
types[node_id] = data
return types
+
def make_maps_for_xml(xml_roots, account_types, country_dir):
"""make maps for `charts` and `accounts`"""
for model, root_list in xml_roots.items():
for root in root_list:
for node in root[0].findall("record"):
- if node.get("model")=="account.account.template":
+ if node.get("model") == "account.account.template":
data = {}
for field in node.findall("field"):
- if field.get("name")=="name":
+ if field.get("name") == "name":
data["name"] = field.text
- if field.get("name")=="parent_id":
+ if field.get("name") == "parent_id":
parent_id = field.get("ref") or field.get("eval")
data["parent_id"] = parent_id
- if field.get("name")=="user_type":
+ if field.get("name") == "user_type":
value = field.get("ref")
if account_types.get(value, {}).get("account_type"):
data["account_type"] = account_types[value]["account_type"]
@@ -160,16 +172,17 @@ def make_maps_for_xml(xml_roots, account_types, country_dir):
data["children"] = []
accounts[node.get("id")] = data
- if node.get("model")=="account.chart.template":
+ if node.get("model") == "account.chart.template":
data = {}
for field in node.findall("field"):
- if field.get("name")=="name":
+ if field.get("name") == "name":
data["name"] = field.text
- if field.get("name")=="account_root_id":
+ if field.get("name") == "account_root_id":
data["account_root_id"] = field.get("ref")
data["id"] = country_dir
charts.setdefault(node.get("id"), {}).update(data)
+
def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.account.template", []):
for row in content[1:]:
@@ -177,7 +190,7 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
account = {
"name": data.get("name"),
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
- "children": []
+ "children": [],
}
user_type = data.get("user_type/id") or data.get("user_type:id")
if account_types.get(user_type, {}).get("account_type"):
@@ -194,12 +207,14 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
for row in content[1:]:
if row:
data = dict(zip(content[0], row))
- charts.setdefault(data.get("id"), {}).update({
- "account_root_id": data.get("account_root_id:id") or \
- data.get("account_root_id/id"),
- "name": data.get("name"),
- "id": country_dir
- })
+ charts.setdefault(data.get("id"), {}).update(
+ {
+ "account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"),
+ "name": data.get("name"),
+ "id": country_dir,
+ }
+ )
+
def make_account_trees():
"""build tree hierarchy"""
@@ -218,6 +233,7 @@ def make_account_trees():
if "children" in accounts[id] and not accounts[id].get("children"):
del accounts[id]["children"]
+
def make_charts():
"""write chart files in app/setup/doctype/company/charts"""
for chart_id in charts:
@@ -236,34 +252,38 @@ def make_charts():
chart["country_code"] = src["id"][5:]
chart["tree"] = accounts[src["account_root_id"]]
-
for key, val in chart["tree"].items():
if key in ["name", "parent_id"]:
chart["tree"].pop(key)
if type(val) == dict:
val["root_type"] = ""
if chart:
- fpath = os.path.join("erpnext", "erpnext", "accounts", "doctype", "account",
- "chart_of_accounts", filename + ".json")
+ fpath = os.path.join(
+ "erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts", filename + ".json"
+ )
with open(fpath, "r") as chartfile:
old_content = chartfile.read()
- if not old_content or (json.loads(old_content).get("is_active", "No") == "No" \
- and json.loads(old_content).get("disabled", "No") == "No"):
+ if not old_content or (
+ json.loads(old_content).get("is_active", "No") == "No"
+ and json.loads(old_content).get("disabled", "No") == "No"
+ ):
with open(fpath, "w") as chartfile:
chartfile.write(json.dumps(chart, indent=4, sort_keys=True))
all_roots.setdefault(filename, chart["tree"].keys())
+
def create_all_roots_file():
- with open('all_roots.txt', 'w') as f:
+ with open("all_roots.txt", "w") as f:
for filename, roots in sorted(all_roots.items()):
f.write(filename)
- f.write('\n----------------------\n')
+ f.write("\n----------------------\n")
for r in sorted(roots):
- f.write(r.encode('utf-8'))
- f.write('\n')
- f.write('\n\n\n')
+ f.write(r.encode("utf-8"))
+ f.write("\n")
+ f.write("\n\n\n")
-if __name__=="__main__":
+
+if __name__ == "__main__":
go()
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py
index 9248ffa6e5..e30ad24a37 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py
@@ -7,182 +7,103 @@ from frappe import _
def get():
return {
- _("Application of Funds (Assets)"): {
- _("Current Assets"): {
- _("Accounts Receivable"): {
- _("Debtors"): {
- "account_type": "Receivable"
- }
- },
- _("Bank Accounts"): {
- "account_type": "Bank",
- "is_group": 1
- },
- _("Cash In Hand"): {
- _("Cash"): {
- "account_type": "Cash"
- },
- "account_type": "Cash"
- },
- _("Loans and Advances (Assets)"): {
- _("Employee Advances"): {
- },
- },
- _("Securities and Deposits"): {
- _("Earnest Money"): {}
- },
- _("Stock Assets"): {
- _("Stock In Hand"): {
- "account_type": "Stock"
- },
- "account_type": "Stock",
- },
- _("Tax Assets"): {
- "is_group": 1
- }
- },
- _("Fixed Assets"): {
- _("Capital Equipments"): {
- "account_type": "Fixed Asset"
- },
- _("Electronic Equipments"): {
- "account_type": "Fixed Asset"
- },
- _("Furnitures and Fixtures"): {
- "account_type": "Fixed Asset"
- },
- _("Office Equipments"): {
- "account_type": "Fixed Asset"
- },
- _("Plants and Machineries"): {
- "account_type": "Fixed Asset"
- },
- _("Buildings"): {
- "account_type": "Fixed Asset"
+ _("Application of Funds (Assets)"): {
+ _("Current Assets"): {
+ _("Accounts Receivable"): {_("Debtors"): {"account_type": "Receivable"}},
+ _("Bank Accounts"): {"account_type": "Bank", "is_group": 1},
+ _("Cash In Hand"): {_("Cash"): {"account_type": "Cash"}, "account_type": "Cash"},
+ _("Loans and Advances (Assets)"): {
+ _("Employee Advances"): {},
},
- _("Softwares"): {
- "account_type": "Fixed Asset"
+ _("Securities and Deposits"): {_("Earnest Money"): {}},
+ _("Stock Assets"): {
+ _("Stock In Hand"): {"account_type": "Stock"},
+ "account_type": "Stock",
},
- _("Accumulated Depreciation"): {
- "account_type": "Accumulated Depreciation"
- },
- _("CWIP Account"): {
- "account_type": "Capital Work in Progress",
- }
- },
- _("Investments"): {
- "is_group": 1
- },
- _("Temporary Accounts"): {
- _("Temporary Opening"): {
- "account_type": "Temporary"
- }
- },
- "root_type": "Asset"
- },
- _("Expenses"): {
- _("Direct Expenses"): {
- _("Stock Expenses"): {
- _("Cost of Goods Sold"): {
- "account_type": "Cost of Goods Sold"
- },
- _("Expenses Included In Asset Valuation"): {
- "account_type": "Expenses Included In Asset Valuation"
- },
- _("Expenses Included In Valuation"): {
- "account_type": "Expenses Included In Valuation"
- },
- _("Stock Adjustment"): {
- "account_type": "Stock Adjustment"
- }
- },
- },
- _("Indirect Expenses"): {
- _("Administrative Expenses"): {},
- _("Commission on Sales"): {},
- _("Depreciation"): {
- "account_type": "Depreciation"
- },
- _("Entertainment Expenses"): {},
- _("Freight and Forwarding Charges"): {
- "account_type": "Chargeable"
- },
- _("Legal Expenses"): {},
- _("Marketing Expenses"): {
- "account_type": "Chargeable"
- },
- _("Miscellaneous Expenses"): {
- "account_type": "Chargeable"
- },
- _("Office Maintenance Expenses"): {},
- _("Office Rent"): {},
- _("Postal Expenses"): {},
- _("Print and Stationery"): {},
- _("Round Off"): {
- "account_type": "Round Off"
- },
- _("Salary"): {},
- _("Sales Expenses"): {},
- _("Telephone Expenses"): {},
- _("Travel Expenses"): {},
- _("Utility Expenses"): {},
+ _("Tax Assets"): {"is_group": 1},
+ },
+ _("Fixed Assets"): {
+ _("Capital Equipments"): {"account_type": "Fixed Asset"},
+ _("Electronic Equipments"): {"account_type": "Fixed Asset"},
+ _("Furnitures and Fixtures"): {"account_type": "Fixed Asset"},
+ _("Office Equipments"): {"account_type": "Fixed Asset"},
+ _("Plants and Machineries"): {"account_type": "Fixed Asset"},
+ _("Buildings"): {"account_type": "Fixed Asset"},
+ _("Softwares"): {"account_type": "Fixed Asset"},
+ _("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"},
+ _("CWIP Account"): {
+ "account_type": "Capital Work in Progress",
+ },
+ },
+ _("Investments"): {"is_group": 1},
+ _("Temporary Accounts"): {_("Temporary Opening"): {"account_type": "Temporary"}},
+ "root_type": "Asset",
+ },
+ _("Expenses"): {
+ _("Direct Expenses"): {
+ _("Stock Expenses"): {
+ _("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold"},
+ _("Expenses Included In Asset Valuation"): {
+ "account_type": "Expenses Included In Asset Valuation"
+ },
+ _("Expenses Included In Valuation"): {"account_type": "Expenses Included In Valuation"},
+ _("Stock Adjustment"): {"account_type": "Stock Adjustment"},
+ },
+ },
+ _("Indirect Expenses"): {
+ _("Administrative Expenses"): {},
+ _("Commission on Sales"): {},
+ _("Depreciation"): {"account_type": "Depreciation"},
+ _("Entertainment Expenses"): {},
+ _("Freight and Forwarding Charges"): {"account_type": "Chargeable"},
+ _("Legal Expenses"): {},
+ _("Marketing Expenses"): {"account_type": "Chargeable"},
+ _("Miscellaneous Expenses"): {"account_type": "Chargeable"},
+ _("Office Maintenance Expenses"): {},
+ _("Office Rent"): {},
+ _("Postal Expenses"): {},
+ _("Print and Stationery"): {},
+ _("Round Off"): {"account_type": "Round Off"},
+ _("Salary"): {},
+ _("Sales Expenses"): {},
+ _("Telephone Expenses"): {},
+ _("Travel Expenses"): {},
+ _("Utility Expenses"): {},
_("Write Off"): {},
_("Exchange Gain/Loss"): {},
- _("Gain/Loss on Asset Disposal"): {}
- },
- "root_type": "Expense"
- },
- _("Income"): {
- _("Direct Income"): {
- _("Sales"): {},
- _("Service"): {}
- },
- _("Indirect Income"): {
- "is_group": 1
- },
- "root_type": "Income"
- },
- _("Source of Funds (Liabilities)"): {
- _("Current Liabilities"): {
- _("Accounts Payable"): {
- _("Creditors"): {
- "account_type": "Payable"
- },
- _("Payroll Payable"): {},
- },
- _("Stock Liabilities"): {
- _("Stock Received But Not Billed"): {
- "account_type": "Stock Received But Not Billed"
- },
- _("Asset Received But Not Billed"): {
- "account_type": "Asset Received But Not Billed"
- }
- },
- _("Duties and Taxes"): {
- "account_type": "Tax",
- "is_group": 1
+ _("Gain/Loss on Asset Disposal"): {},
+ },
+ "root_type": "Expense",
+ },
+ _("Income"): {
+ _("Direct Income"): {_("Sales"): {}, _("Service"): {}},
+ _("Indirect Income"): {"is_group": 1},
+ "root_type": "Income",
+ },
+ _("Source of Funds (Liabilities)"): {
+ _("Current Liabilities"): {
+ _("Accounts Payable"): {
+ _("Creditors"): {"account_type": "Payable"},
+ _("Payroll Payable"): {},
},
+ _("Stock Liabilities"): {
+ _("Stock Received But Not Billed"): {"account_type": "Stock Received But Not Billed"},
+ _("Asset Received But Not Billed"): {"account_type": "Asset Received But Not Billed"},
+ },
+ _("Duties and Taxes"): {"account_type": "Tax", "is_group": 1},
_("Loans (Liabilities)"): {
_("Secured Loans"): {},
_("Unsecured Loans"): {},
_("Bank Overdraft Account"): {},
},
- },
- "root_type": "Liability"
- },
+ },
+ "root_type": "Liability",
+ },
_("Equity"): {
- _("Capital Stock"): {
- "account_type": "Equity"
- },
- _("Dividends Paid"): {
- "account_type": "Equity"
- },
- _("Opening Balance Equity"): {
- "account_type": "Equity"
- },
- _("Retained Earnings"): {
- "account_type": "Equity"
- },
- "root_type": "Equity"
- }
+ _("Capital Stock"): {"account_type": "Equity"},
+ _("Dividends Paid"): {"account_type": "Equity"},
+ _("Opening Balance Equity"): {"account_type": "Equity"},
+ _("Retained Earnings"): {"account_type": "Equity"},
+ "root_type": "Equity",
+ },
}
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
index 31ae17189a..0e46f1e08a 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
@@ -6,288 +6,153 @@ from frappe import _
def get():
- return {
- _("Application of Funds (Assets)"): {
- _("Current Assets"): {
- _("Accounts Receivable"): {
- _("Debtors"): {
- "account_type": "Receivable",
- "account_number": "1310"
- },
- "account_number": "1300"
- },
- _("Bank Accounts"): {
- "account_type": "Bank",
- "is_group": 1,
- "account_number": "1200"
- },
- _("Cash In Hand"): {
- _("Cash"): {
- "account_type": "Cash",
- "account_number": "1110"
- },
- "account_type": "Cash",
- "account_number": "1100"
- },
- _("Loans and Advances (Assets)"): {
- _("Employee Advances"): {
- "account_number": "1610"
- },
- "account_number": "1600"
- },
- _("Securities and Deposits"): {
- _("Earnest Money"): {
- "account_number": "1651"
- },
- "account_number": "1650"
- },
- _("Stock Assets"): {
- _("Stock In Hand"): {
- "account_type": "Stock",
- "account_number": "1410"
- },
- "account_type": "Stock",
- "account_number": "1400"
- },
- _("Tax Assets"): {
- "is_group": 1,
- "account_number": "1500"
- },
- "account_number": "1100-1600"
- },
- _("Fixed Assets"): {
- _("Capital Equipments"): {
- "account_type": "Fixed Asset",
- "account_number": "1710"
- },
- _("Electronic Equipments"): {
- "account_type": "Fixed Asset",
- "account_number": "1720"
- },
- _("Furnitures and Fixtures"): {
- "account_type": "Fixed Asset",
- "account_number": "1730"
- },
- _("Office Equipments"): {
- "account_type": "Fixed Asset",
- "account_number": "1740"
- },
- _("Plants and Machineries"): {
- "account_type": "Fixed Asset",
- "account_number": "1750"
- },
- _("Buildings"): {
- "account_type": "Fixed Asset",
- "account_number": "1760"
- },
- _("Softwares"): {
- "account_type": "Fixed Asset",
- "account_number": "1770"
- },
- _("Accumulated Depreciation"): {
- "account_type": "Accumulated Depreciation",
- "account_number": "1780"
- },
- _("CWIP Account"): {
- "account_type": "Capital Work in Progress",
- "account_number": "1790"
- },
- "account_number": "1700"
- },
- _("Investments"): {
- "is_group": 1,
- "account_number": "1800"
- },
- _("Temporary Accounts"): {
- _("Temporary Opening"): {
- "account_type": "Temporary",
- "account_number": "1910"
- },
- "account_number": "1900"
- },
- "root_type": "Asset",
- "account_number": "1000"
- },
- _("Expenses"): {
- _("Direct Expenses"): {
- _("Stock Expenses"): {
- _("Cost of Goods Sold"): {
- "account_type": "Cost of Goods Sold",
- "account_number": "5111"
- },
- _("Expenses Included In Asset Valuation"): {
- "account_type": "Expenses Included In Asset Valuation",
- "account_number": "5112"
- },
- _("Expenses Included In Valuation"): {
- "account_type": "Expenses Included In Valuation",
- "account_number": "5118"
- },
- _("Stock Adjustment"): {
- "account_type": "Stock Adjustment",
- "account_number": "5119"
- },
- "account_number": "5110"
- },
- "account_number": "5100"
- },
- _("Indirect Expenses"): {
- _("Administrative Expenses"): {
- "account_number": "5201"
- },
- _("Commission on Sales"): {
- "account_number": "5202"
- },
- _("Depreciation"): {
- "account_type": "Depreciation",
- "account_number": "5203"
- },
- _("Entertainment Expenses"): {
- "account_number": "5204"
- },
- _("Freight and Forwarding Charges"): {
- "account_type": "Chargeable",
- "account_number": "5205"
- },
- _("Legal Expenses"): {
- "account_number": "5206"
- },
- _("Marketing Expenses"): {
- "account_type": "Chargeable",
- "account_number": "5207"
- },
- _("Office Maintenance Expenses"): {
- "account_number": "5208"
- },
- _("Office Rent"): {
- "account_number": "5209"
- },
- _("Postal Expenses"): {
- "account_number": "5210"
- },
- _("Print and Stationery"): {
- "account_number": "5211"
- },
- _("Round Off"): {
- "account_type": "Round Off",
- "account_number": "5212"
- },
- _("Salary"): {
- "account_number": "5213"
- },
- _("Sales Expenses"): {
- "account_number": "5214"
- },
- _("Telephone Expenses"): {
- "account_number": "5215"
- },
- _("Travel Expenses"): {
- "account_number": "5216"
- },
- _("Utility Expenses"): {
- "account_number": "5217"
- },
- _("Write Off"): {
- "account_number": "5218"
- },
- _("Exchange Gain/Loss"): {
- "account_number": "5219"
- },
- _("Gain/Loss on Asset Disposal"): {
- "account_number": "5220"
- },
- _("Miscellaneous Expenses"): {
- "account_type": "Chargeable",
- "account_number": "5221"
- },
- "account_number": "5200"
- },
- "root_type": "Expense",
- "account_number": "5000"
- },
- _("Income"): {
- _("Direct Income"): {
- _("Sales"): {
- "account_number": "4110"
- },
- _("Service"): {
- "account_number": "4120"
- },
- "account_number": "4100"
- },
- _("Indirect Income"): {
- "is_group": 1,
- "account_number": "4200"
- },
- "root_type": "Income",
- "account_number": "4000"
- },
- _("Source of Funds (Liabilities)"): {
- _("Current Liabilities"): {
- _("Accounts Payable"): {
- _("Creditors"): {
- "account_type": "Payable",
- "account_number": "2110"
- },
- _("Payroll Payable"): {
- "account_number": "2120"
- },
- "account_number": "2100"
- },
- _("Stock Liabilities"): {
- _("Stock Received But Not Billed"): {
- "account_type": "Stock Received But Not Billed",
- "account_number": "2210"
- },
- _("Asset Received But Not Billed"): {
- "account_type": "Asset Received But Not Billed",
- "account_number": "2211"
- },
- "account_number": "2200"
- },
- _("Duties and Taxes"): {
- _("TDS Payable"): {
- "account_number": "2310"
- },
- "account_type": "Tax",
- "is_group": 1,
- "account_number": "2300"
- },
- _("Loans (Liabilities)"): {
- _("Secured Loans"): {
- "account_number": "2410"
- },
- _("Unsecured Loans"): {
- "account_number": "2420"
- },
- _("Bank Overdraft Account"): {
- "account_number": "2430"
- },
- "account_number": "2400"
- },
- "account_number": "2100-2400"
- },
- "root_type": "Liability",
- "account_number": "2000"
- },
- _("Equity"): {
- _("Capital Stock"): {
- "account_type": "Equity",
- "account_number": "3100"
- },
- _("Dividends Paid"): {
- "account_type": "Equity",
- "account_number": "3200"
- },
- _("Opening Balance Equity"): {
- "account_type": "Equity",
- "account_number": "3300"
- },
- _("Retained Earnings"): {
- "account_type": "Equity",
- "account_number": "3400"
- },
- "root_type": "Equity",
- "account_number": "3000"
- }
- }
+ return {
+ _("Application of Funds (Assets)"): {
+ _("Current Assets"): {
+ _("Accounts Receivable"): {
+ _("Debtors"): {"account_type": "Receivable", "account_number": "1310"},
+ "account_number": "1300",
+ },
+ _("Bank Accounts"): {"account_type": "Bank", "is_group": 1, "account_number": "1200"},
+ _("Cash In Hand"): {
+ _("Cash"): {"account_type": "Cash", "account_number": "1110"},
+ "account_type": "Cash",
+ "account_number": "1100",
+ },
+ _("Loans and Advances (Assets)"): {
+ _("Employee Advances"): {"account_number": "1610"},
+ "account_number": "1600",
+ },
+ _("Securities and Deposits"): {
+ _("Earnest Money"): {"account_number": "1651"},
+ "account_number": "1650",
+ },
+ _("Stock Assets"): {
+ _("Stock In Hand"): {"account_type": "Stock", "account_number": "1410"},
+ "account_type": "Stock",
+ "account_number": "1400",
+ },
+ _("Tax Assets"): {"is_group": 1, "account_number": "1500"},
+ "account_number": "1100-1600",
+ },
+ _("Fixed Assets"): {
+ _("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"},
+ _("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"},
+ _("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"},
+ _("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"},
+ _("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"},
+ _("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"},
+ _("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"},
+ _("Accumulated Depreciation"): {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "1780",
+ },
+ _("CWIP Account"): {"account_type": "Capital Work in Progress", "account_number": "1790"},
+ "account_number": "1700",
+ },
+ _("Investments"): {"is_group": 1, "account_number": "1800"},
+ _("Temporary Accounts"): {
+ _("Temporary Opening"): {"account_type": "Temporary", "account_number": "1910"},
+ "account_number": "1900",
+ },
+ "root_type": "Asset",
+ "account_number": "1000",
+ },
+ _("Expenses"): {
+ _("Direct Expenses"): {
+ _("Stock Expenses"): {
+ _("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold", "account_number": "5111"},
+ _("Expenses Included In Asset Valuation"): {
+ "account_type": "Expenses Included In Asset Valuation",
+ "account_number": "5112",
+ },
+ _("Expenses Included In Valuation"): {
+ "account_type": "Expenses Included In Valuation",
+ "account_number": "5118",
+ },
+ _("Stock Adjustment"): {"account_type": "Stock Adjustment", "account_number": "5119"},
+ "account_number": "5110",
+ },
+ "account_number": "5100",
+ },
+ _("Indirect Expenses"): {
+ _("Administrative Expenses"): {"account_number": "5201"},
+ _("Commission on Sales"): {"account_number": "5202"},
+ _("Depreciation"): {"account_type": "Depreciation", "account_number": "5203"},
+ _("Entertainment Expenses"): {"account_number": "5204"},
+ _("Freight and Forwarding Charges"): {"account_type": "Chargeable", "account_number": "5205"},
+ _("Legal Expenses"): {"account_number": "5206"},
+ _("Marketing Expenses"): {"account_type": "Chargeable", "account_number": "5207"},
+ _("Office Maintenance Expenses"): {"account_number": "5208"},
+ _("Office Rent"): {"account_number": "5209"},
+ _("Postal Expenses"): {"account_number": "5210"},
+ _("Print and Stationery"): {"account_number": "5211"},
+ _("Round Off"): {"account_type": "Round Off", "account_number": "5212"},
+ _("Salary"): {"account_number": "5213"},
+ _("Sales Expenses"): {"account_number": "5214"},
+ _("Telephone Expenses"): {"account_number": "5215"},
+ _("Travel Expenses"): {"account_number": "5216"},
+ _("Utility Expenses"): {"account_number": "5217"},
+ _("Write Off"): {"account_number": "5218"},
+ _("Exchange Gain/Loss"): {"account_number": "5219"},
+ _("Gain/Loss on Asset Disposal"): {"account_number": "5220"},
+ _("Miscellaneous Expenses"): {"account_type": "Chargeable", "account_number": "5221"},
+ "account_number": "5200",
+ },
+ "root_type": "Expense",
+ "account_number": "5000",
+ },
+ _("Income"): {
+ _("Direct Income"): {
+ _("Sales"): {"account_number": "4110"},
+ _("Service"): {"account_number": "4120"},
+ "account_number": "4100",
+ },
+ _("Indirect Income"): {"is_group": 1, "account_number": "4200"},
+ "root_type": "Income",
+ "account_number": "4000",
+ },
+ _("Source of Funds (Liabilities)"): {
+ _("Current Liabilities"): {
+ _("Accounts Payable"): {
+ _("Creditors"): {"account_type": "Payable", "account_number": "2110"},
+ _("Payroll Payable"): {"account_number": "2120"},
+ "account_number": "2100",
+ },
+ _("Stock Liabilities"): {
+ _("Stock Received But Not Billed"): {
+ "account_type": "Stock Received But Not Billed",
+ "account_number": "2210",
+ },
+ _("Asset Received But Not Billed"): {
+ "account_type": "Asset Received But Not Billed",
+ "account_number": "2211",
+ },
+ "account_number": "2200",
+ },
+ _("Duties and Taxes"): {
+ _("TDS Payable"): {"account_number": "2310"},
+ "account_type": "Tax",
+ "is_group": 1,
+ "account_number": "2300",
+ },
+ _("Loans (Liabilities)"): {
+ _("Secured Loans"): {"account_number": "2410"},
+ _("Unsecured Loans"): {"account_number": "2420"},
+ _("Bank Overdraft Account"): {"account_number": "2430"},
+ "account_number": "2400",
+ },
+ "account_number": "2100-2400",
+ },
+ "root_type": "Liability",
+ "account_number": "2000",
+ },
+ _("Equity"): {
+ _("Capital Stock"): {"account_type": "Equity", "account_number": "3100"},
+ _("Dividends Paid"): {"account_type": "Equity", "account_number": "3200"},
+ _("Opening Balance Equity"): {"account_type": "Equity", "account_number": "3300"},
+ _("Retained Earnings"): {"account_type": "Equity", "account_number": "3400"},
+ "root_type": "Equity",
+ "account_number": "3000",
+ },
+ }
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 0715823b30..efc063de56 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -20,8 +20,9 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company"
acc.insert()
- account_number, account_name = frappe.db.get_value("Account", "1210 - Debtors - _TC",
- ["account_number", "account_name"])
+ account_number, account_name = frappe.db.get_value(
+ "Account", "1210 - Debtors - _TC", ["account_number", "account_name"]
+ )
self.assertEqual(account_number, "1210")
self.assertEqual(account_name, "Debtors")
@@ -30,8 +31,12 @@ class TestAccount(unittest.TestCase):
update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number)
- new_acc = frappe.db.get_value("Account", "1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
- ["account_name", "account_number"], as_dict=1)
+ new_acc = frappe.db.get_value(
+ "Account",
+ "1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
+ ["account_name", "account_number"],
+ as_dict=1,
+ )
self.assertEqual(new_acc.account_name, "Debtors 1 - Test -")
self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -")
@@ -79,7 +84,9 @@ class TestAccount(unittest.TestCase):
self.assertEqual(parent, "Securities and Deposits - _TC")
- merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company)
+ merge_account(
+ "Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company
+ )
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
# Parent account of the child account changes after merging
@@ -91,14 +98,28 @@ class TestAccount(unittest.TestCase):
doc = frappe.get_doc("Account", "Current Assets - _TC")
# Raise error as is_group property doesn't match
- self.assertRaises(frappe.ValidationError, merge_account, "Current Assets - _TC",\
- "Accumulated Depreciation - _TC", doc.is_group, doc.root_type, doc.company)
+ self.assertRaises(
+ frappe.ValidationError,
+ merge_account,
+ "Current Assets - _TC",
+ "Accumulated Depreciation - _TC",
+ doc.is_group,
+ doc.root_type,
+ doc.company,
+ )
doc = frappe.get_doc("Account", "Capital Stock - _TC")
# Raise error as root_type property doesn't match
- self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\
- "Softwares - _TC", doc.is_group, doc.root_type, doc.company)
+ self.assertRaises(
+ frappe.ValidationError,
+ merge_account,
+ "Capital Stock - _TC",
+ "Softwares - _TC",
+ doc.is_group,
+ doc.root_type,
+ doc.company,
+ )
def test_account_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
@@ -109,8 +130,12 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company 3"
acc.insert()
- acc_tc_4 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 4"})
- acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 5"})
+ acc_tc_4 = frappe.db.get_value(
+ "Account", {"account_name": "Test Sync Account", "company": "_Test Company 4"}
+ )
+ acc_tc_5 = frappe.db.get_value(
+ "Account", {"account_name": "Test Sync Account", "company": "_Test Company 5"}
+ )
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
@@ -138,8 +163,26 @@ class TestAccount(unittest.TestCase):
update_account_number(acc.name, "Test Rename Sync Account", "1234")
# Check if renamed in children
- self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 4", "account_number": "1234"}))
- self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 5", "account_number": "1234"}))
+ self.assertTrue(
+ frappe.db.exists(
+ "Account",
+ {
+ "account_name": "Test Rename Sync Account",
+ "company": "_Test Company 4",
+ "account_number": "1234",
+ },
+ )
+ )
+ self.assertTrue(
+ frappe.db.exists(
+ "Account",
+ {
+ "account_name": "Test Rename Sync Account",
+ "company": "_Test Company 5",
+ "account_number": "1234",
+ },
+ )
+ )
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
@@ -155,22 +198,46 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company 3"
acc.insert()
- self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 4"}))
- self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 5"}))
+ self.assertTrue(
+ frappe.db.exists(
+ "Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
+ )
+ )
+ self.assertTrue(
+ frappe.db.exists(
+ "Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
+ )
+ )
# Try renaming child company account
- acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Group Account", "company": "_Test Company 5"})
- self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
+ acc_tc_5 = frappe.db.get_value(
+ "Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
+ )
+ self.assertRaises(
+ frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
+ )
# Rename child company account with allow_account_creation_against_child_company enabled
- frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
+ frappe.db.set_value(
+ "Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
+ )
update_account_number(acc_tc_5, "Test Modified Account")
- self.assertTrue(frappe.db.exists("Account", {'name': "Test Modified Account - _TC5", "company": "_Test Company 5"}))
+ self.assertTrue(
+ frappe.db.exists(
+ "Account", {"name": "Test Modified Account - _TC5", "company": "_Test Company 5"}
+ )
+ )
- frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
+ frappe.db.set_value(
+ "Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
+ )
- to_delete = ["Test Group Account - _TC3", "Test Group Account - _TC4", "Test Modified Account - _TC5"]
+ to_delete = [
+ "Test Group Account - _TC3",
+ "Test Group Account - _TC4",
+ "Test Modified Account - _TC5",
+ ]
for doc in to_delete:
frappe.delete_doc("Account", doc)
@@ -184,20 +251,16 @@ def _make_test_records(verbose=None):
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
["_Test Cash", "Cash In Hand", 0, "Cash", None],
-
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
["_Test Employee Advance", "Current Liabilities", 0, None, None],
-
["_Test Account Tax Assets", "Current Assets", 1, None, None],
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None],
-
["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None],
-
["_Test Account Cost for Goods Sold", "Expenses", 0, None, None],
["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
@@ -206,38 +269,45 @@ def _make_test_records(verbose=None):
["_Test Account Discount", "Direct Expenses", 0, None, None],
["_Test Write Off", "Indirect Expenses", 0, None, None],
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
-
["_Test Account Sales", "Direct Income", 0, None, None],
-
# related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
-
# fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
-
# Receivable / Payable Account
["_Test Receivable", "Current Assets", 0, "Receivable", None],
["_Test Payable", "Current Liabilities", 0, "Payable", None],
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
- ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"]
+ ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"],
]
- for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]:
- test_objects = make_test_objects("Account", [{
- "doctype": "Account",
- "account_name": account_name,
- "parent_account": parent_account + " - " + abbr,
- "company": company,
- "is_group": is_group,
- "account_type": account_type,
- "account_currency": currency
- } for account_name, parent_account, is_group, account_type, currency in accounts])
+ for company, abbr in [
+ ["_Test Company", "_TC"],
+ ["_Test Company 1", "_TC1"],
+ ["_Test Company with perpetual inventory", "TCP1"],
+ ]:
+ test_objects = make_test_objects(
+ "Account",
+ [
+ {
+ "doctype": "Account",
+ "account_name": account_name,
+ "parent_account": parent_account + " - " + abbr,
+ "company": company,
+ "is_group": is_group,
+ "account_type": account_type,
+ "account_currency": currency,
+ }
+ for account_name, parent_account, is_group, account_type, currency in accounts
+ ],
+ )
return test_objects
+
def get_inventory_account(company, warehouse=None):
account = None
if warehouse:
@@ -247,19 +317,24 @@ def get_inventory_account(company, warehouse=None):
return account
+
def create_account(**kwargs):
- account = frappe.db.get_value("Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")})
+ account = frappe.db.get_value(
+ "Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
+ )
if account:
return account
else:
- account = frappe.get_doc(dict(
- doctype = "Account",
- account_name = kwargs.get('account_name'),
- account_type = kwargs.get('account_type'),
- parent_account = kwargs.get('parent_account'),
- company = kwargs.get('company'),
- account_currency = kwargs.get('account_currency')
- ))
+ account = frappe.get_doc(
+ dict(
+ doctype="Account",
+ account_name=kwargs.get("account_name"),
+ account_type=kwargs.get("account_type"),
+ parent_account=kwargs.get("parent_account"),
+ company=kwargs.get("company"),
+ account_currency=kwargs.get("account_currency"),
+ )
+ )
account.save()
return account.name
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index b6112e0cc5..897151a97b 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -17,13 +17,21 @@ class AccountingDimension(Document):
self.set_fieldname_and_label()
def validate(self):
- if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
- 'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') :
+ if self.document_type in core_doctypes_list + (
+ "Accounting Dimension",
+ "Project",
+ "Cost Center",
+ "Accounting Dimension Detail",
+ "Company",
+ "Account",
+ ):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
- exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
+ exists = frappe.db.get_value(
+ "Accounting Dimension", {"document_type": self.document_type}, ["name"]
+ )
if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension"))
@@ -42,13 +50,13 @@ class AccountingDimension(Document):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
- frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long')
+ frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long")
def on_trash(self):
if frappe.flags.in_test:
delete_accounting_dimension(doc=self)
else:
- frappe.enqueue(delete_accounting_dimension, doc=self, queue='long')
+ frappe.enqueue(delete_accounting_dimension, doc=self, queue="long")
def set_fieldname_and_label(self):
if not self.label:
@@ -60,6 +68,7 @@ class AccountingDimension(Document):
def on_update(self):
frappe.flags.accounting_dimensions = None
+
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
@@ -70,9 +79,9 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
- insert_after_field = 'dimension_col_break'
+ insert_after_field = "dimension_col_break"
else:
- insert_after_field = 'accounting_dimensions_section'
+ insert_after_field = "accounting_dimensions_section"
df = {
"fieldname": doc.fieldname,
@@ -80,13 +89,13 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"fieldtype": "Link",
"options": doc.document_type,
"insert_after": insert_after_field,
- "owner": "Administrator"
+ "owner": "Administrator",
}
meta = frappe.get_meta(doctype, cached=False)
fieldnames = [d.fieldname for d in meta.get("fields")]
- if df['fieldname'] not in fieldnames:
+ if df["fieldname"] not in fieldnames:
if doctype == "Budget":
add_dimension_to_budget_doctype(df.copy(), doc)
else:
@@ -94,14 +103,17 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
count += 1
- frappe.publish_progress(count*100/len(doclist), title = _("Creating Dimensions..."))
+ frappe.publish_progress(count * 100 / len(doclist), title=_("Creating Dimensions..."))
frappe.clear_cache(doctype=doctype)
+
def add_dimension_to_budget_doctype(df, doc):
- df.update({
- "insert_after": "cost_center",
- "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type)
- })
+ df.update(
+ {
+ "insert_after": "cost_center",
+ "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type),
+ }
+ )
create_custom_field("Budget", df)
@@ -112,36 +124,44 @@ def add_dimension_to_budget_doctype(df, doc):
property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type
property_setter_doc.save()
- frappe.clear_cache(doctype='Budget')
+ frappe.clear_cache(doctype="Budget")
else:
- frappe.get_doc({
- "doctype": "Property Setter",
- "doctype_or_field": "DocField",
- "doc_type": "Budget",
- "field_name": "budget_against",
- "property": "options",
- "property_type": "Text",
- "value": "\nCost Center\nProject\n" + doc.document_type
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Property Setter",
+ "doctype_or_field": "DocField",
+ "doc_type": "Budget",
+ "field_name": "budget_against",
+ "property": "options",
+ "property_type": "Text",
+ "value": "\nCost Center\nProject\n" + doc.document_type,
+ }
+ ).insert(ignore_permissions=True)
def delete_accounting_dimension(doc):
doclist = get_doctypes_with_dimensions()
- frappe.db.sql("""
+ frappe.db.sql(
+ """
DELETE FROM `tabCustom Field`
WHERE fieldname = %s
- AND dt IN (%s)""" % #nosec
- ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
+ AND dt IN (%s)"""
+ % ("%s", ", ".join(["%s"] * len(doclist))), # nosec
+ tuple([doc.fieldname] + doclist),
+ )
- frappe.db.sql("""
+ frappe.db.sql(
+ """
DELETE FROM `tabProperty Setter`
WHERE field_name = %s
- AND doc_type IN (%s)""" % #nosec
- ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
+ AND doc_type IN (%s)"""
+ % ("%s", ", ".join(["%s"] * len(doclist))), # nosec
+ tuple([doc.fieldname] + doclist),
+ )
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
- value_list = budget_against_property.value.split('\n')[3:]
+ value_list = budget_against_property.value.split("\n")[3:]
if doc.document_type in value_list:
value_list.remove(doc.document_type)
@@ -152,6 +172,7 @@ def delete_accounting_dimension(doc):
for doctype in doclist:
frappe.clear_cache(doctype=doctype)
+
@frappe.whitelist()
def disable_dimension(doc):
if frappe.flags.in_test:
@@ -159,10 +180,11 @@ def disable_dimension(doc):
else:
frappe.enqueue(toggle_disabling, doc=doc)
+
def toggle_disabling(doc):
doc = json.loads(doc)
- if doc.get('disabled'):
+ if doc.get("disabled"):
df = {"read_only": 1}
else:
df = {"read_only": 0}
@@ -170,7 +192,7 @@ def toggle_disabling(doc):
doclist = get_doctypes_with_dimensions()
for doctype in doclist:
- field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get('fieldname')})
+ field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get("fieldname")})
if field:
custom_field = frappe.get_doc("Custom Field", field)
custom_field.update(df)
@@ -178,26 +200,34 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype)
+
def get_doctypes_with_dimensions():
return frappe.get_hooks("accounting_dimension_doctypes")
+
def get_accounting_dimensions(as_list=True):
if frappe.flags.accounting_dimensions is None:
- frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension",
- fields=["label", "fieldname", "disabled", "document_type"])
+ frappe.flags.accounting_dimensions = frappe.get_all(
+ "Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"]
+ )
if as_list:
return [d.fieldname for d in frappe.flags.accounting_dimensions]
else:
return frappe.flags.accounting_dimensions
+
def get_checks_for_pl_and_bs_accounts():
- dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
+ dimensions = frappe.db.sql(
+ """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
- WHERE p.name = c.parent""", as_dict=1)
+ WHERE p.name = c.parent""",
+ as_dict=1,
+ )
return dimensions
+
def get_dimension_with_children(doctype, dimension):
if isinstance(dimension, list):
@@ -205,34 +235,39 @@ def get_dimension_with_children(doctype, dimension):
all_dimensions = []
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
- children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
+ children = frappe.get_all(
+ doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
+ )
all_dimensions += [c.name for c in children]
return all_dimensions
+
@frappe.whitelist()
def get_dimensions(with_cost_center_and_project=False):
- dimension_filters = frappe.db.sql("""
+ dimension_filters = frappe.db.sql(
+ """
SELECT label, fieldname, document_type
FROM `tabAccounting Dimension`
WHERE disabled = 0
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
- default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension
+ default_dimensions = frappe.db.sql(
+ """SELECT p.fieldname, c.company, c.default_dimension
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
- WHERE c.parent = p.name""", as_dict=1)
+ WHERE c.parent = p.name""",
+ as_dict=1,
+ )
if with_cost_center_and_project:
- dimension_filters.extend([
- {
- 'fieldname': 'cost_center',
- 'document_type': 'Cost Center'
- },
- {
- 'fieldname': 'project',
- 'document_type': 'Project'
- }
- ])
+ dimension_filters.extend(
+ [
+ {"fieldname": "cost_center", "document_type": "Cost Center"},
+ {"fieldname": "project", "document_type": "Project"},
+ ]
+ )
default_dimensions_map = {}
for dimension in default_dimensions:
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index f781a221dd..25ef2ea5c2 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -8,7 +8,8 @@ import frappe
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department']
+test_dependencies = ["Cost Center", "Location", "Warehouse", "Department"]
+
class TestAccountingDimension(unittest.TestCase):
def setUp(self):
@@ -18,24 +19,27 @@ class TestAccountingDimension(unittest.TestCase):
si = create_sales_invoice(do_not_save=1)
si.location = "Block 1"
- si.append("items", {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 1,
- "rate": 100,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "department": "_Test Department - _TC",
- "location": "Block 1"
- })
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "rate": 100,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "department": "_Test Department - _TC",
+ "location": "Block 1",
+ },
+ )
si.save()
si.submit()
gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"})
- self.assertEqual(gle.get('department'), "_Test Department - _TC")
+ self.assertEqual(gle.get("department"), "_Test Department - _TC")
def test_dimension_against_journal_entry(self):
je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False)
@@ -50,21 +54,24 @@ class TestAccountingDimension(unittest.TestCase):
gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"})
gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"})
- self.assertEqual(gle.get('department'), "_Test Department - _TC")
- self.assertEqual(gle1.get('department'), "_Test Department - _TC")
+ self.assertEqual(gle.get("department"), "_Test Department - _TC")
+ self.assertEqual(gle1.get("department"), "_Test Department - _TC")
def test_mandatory(self):
si = create_sales_invoice(do_not_save=1)
- si.append("items", {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 1,
- "rate": 100,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "location": ""
- })
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "rate": 100,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "location": "",
+ },
+ )
si.save()
self.assertRaises(frappe.ValidationError, si.submit)
@@ -72,31 +79,39 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self):
disable_dimension()
+
def create_dimension():
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
- frappe.get_doc({
- "doctype": "Accounting Dimension",
- "document_type": "Department",
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Accounting Dimension",
+ "document_type": "Department",
+ }
+ ).insert()
else:
dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0
dimension.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
- dimension1 = frappe.get_doc({
- "doctype": "Accounting Dimension",
- "document_type": "Location",
- })
+ dimension1 = frappe.get_doc(
+ {
+ "doctype": "Accounting Dimension",
+ "document_type": "Location",
+ }
+ )
- dimension1.append("dimension_defaults", {
- "company": "_Test Company",
- "reference_document": "Location",
- "default_dimension": "Block 1",
- "mandatory_for_bs": 1
- })
+ dimension1.append(
+ "dimension_defaults",
+ {
+ "company": "_Test Company",
+ "reference_document": "Location",
+ "default_dimension": "Block 1",
+ "mandatory_for_bs": 1,
+ },
+ )
dimension1.insert()
dimension1.save()
@@ -105,6 +120,7 @@ def create_dimension():
dimension1.disabled = 0
dimension1.save()
+
def disable_dimension():
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 1
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
index 7d32bad0e7..80f736fa5b 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
@@ -19,17 +19,27 @@ class AccountingDimensionFilter(Document):
WHERE d.name = a.parent
and d.name != %s
and d.accounting_dimension = %s
- """, (self.name, self.accounting_dimension), as_dict=1)
+ """,
+ (self.name, self.accounting_dimension),
+ as_dict=1,
+ )
account_list = [d.account for d in accounts]
- for account in self.get('accounts'):
+ for account in self.get("accounts"):
if account.applicable_on_account in account_list:
- frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
- account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
+ frappe.throw(
+ _("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
+ account.idx,
+ frappe.bold(account.applicable_on_account),
+ frappe.bold(self.accounting_dimension),
+ )
+ )
+
def get_dimension_filter_map():
- filters = frappe.db.sql("""
+ filters = frappe.db.sql(
+ """
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, a.is_mandatory
@@ -40,22 +50,30 @@ def get_dimension_filter_map():
p.name = a.parent
AND p.disabled = 0
AND p.name = d.parent
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
- build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
- f.allow_or_restrict, f.is_mandatory)
+ build_map(
+ dimension_filter_map,
+ f.fieldname,
+ f.applicable_on_account,
+ f.dimension_value,
+ f.allow_or_restrict,
+ f.is_mandatory,
+ )
return dimension_filter_map
+
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
- map_object.setdefault((dimension, account), {
- 'allowed_dimensions': [],
- 'is_mandatory': is_mandatory,
- 'allow_or_restrict': allow_or_restrict
- })
- map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)
+ map_object.setdefault(
+ (dimension, account),
+ {"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict},
+ )
+ map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
index e2f85ba21a..f13f2f9f27 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
@@ -12,7 +12,8 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
-test_dependencies = ['Location', 'Cost Center', 'Department']
+test_dependencies = ["Location", "Cost Center", "Department"]
+
class TestAccountingDimensionFilter(unittest.TestCase):
def setUp(self):
@@ -22,9 +23,9 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def test_allowed_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
- si.items[0].cost_center = 'Main - _TC'
- si.department = 'Accounts - _TC'
- si.location = 'Block 1'
+ si.items[0].cost_center = "Main - _TC"
+ si.department = "Accounts - _TC"
+ si.location = "Block 1"
si.save()
self.assertRaises(InvalidAccountDimensionError, si.submit)
@@ -32,12 +33,12 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def test_mandatory_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
- si.department = ''
- si.location = 'Block 1'
+ si.department = ""
+ si.location = "Block 1"
# Test with no department for Sales Account
- si.items[0].department = ''
- si.items[0].cost_center = '_Test Cost Center 2 - _TC'
+ si.items[0].department = ""
+ si.items[0].cost_center = "_Test Cost Center 2 - _TC"
si.save()
self.assertRaises(MandatoryAccountDimensionError, si.submit)
@@ -52,53 +53,54 @@ class TestAccountingDimensionFilter(unittest.TestCase):
if si.docstatus == 1:
si.cancel()
+
def create_accounting_dimension_filter():
- if not frappe.db.get_value('Accounting Dimension Filter',
- {'accounting_dimension': 'Cost Center'}):
- frappe.get_doc({
- 'doctype': 'Accounting Dimension Filter',
- 'accounting_dimension': 'Cost Center',
- 'allow_or_restrict': 'Allow',
- 'company': '_Test Company',
- 'accounts': [{
- 'applicable_on_account': 'Sales - _TC',
- }],
- 'dimensions': [{
- 'accounting_dimension': 'Cost Center',
- 'dimension_value': '_Test Cost Center 2 - _TC'
- }]
- }).insert()
+ if not frappe.db.get_value(
+ "Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
+ ):
+ frappe.get_doc(
+ {
+ "doctype": "Accounting Dimension Filter",
+ "accounting_dimension": "Cost Center",
+ "allow_or_restrict": "Allow",
+ "company": "_Test Company",
+ "accounts": [
+ {
+ "applicable_on_account": "Sales - _TC",
+ }
+ ],
+ "dimensions": [
+ {"accounting_dimension": "Cost Center", "dimension_value": "_Test Cost Center 2 - _TC"}
+ ],
+ }
+ ).insert()
else:
- doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
+ doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 0
doc.save()
- if not frappe.db.get_value('Accounting Dimension Filter',
- {'accounting_dimension': 'Department'}):
- frappe.get_doc({
- 'doctype': 'Accounting Dimension Filter',
- 'accounting_dimension': 'Department',
- 'allow_or_restrict': 'Allow',
- 'company': '_Test Company',
- 'accounts': [{
- 'applicable_on_account': 'Sales - _TC',
- 'is_mandatory': 1
- }],
- 'dimensions': [{
- 'accounting_dimension': 'Department',
- 'dimension_value': 'Accounts - _TC'
- }]
- }).insert()
+ if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Department"}):
+ frappe.get_doc(
+ {
+ "doctype": "Accounting Dimension Filter",
+ "accounting_dimension": "Department",
+ "allow_or_restrict": "Allow",
+ "company": "_Test Company",
+ "accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}],
+ "dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}],
+ }
+ ).insert()
else:
- doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
+ doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 0
doc.save()
+
def disable_dimension_filter():
- doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
+ doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 1
doc.save()
- doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
+ doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 1
doc.save()
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py
index e2949378e5..0c15d6a207 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -7,7 +7,9 @@ from frappe import _
from frappe.model.document import Document
-class OverlapError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
class AccountingPeriod(Document):
def validate(self):
@@ -17,11 +19,12 @@ class AccountingPeriod(Document):
self.bootstrap_doctypes_for_closing()
def autoname(self):
- company_abbr = frappe.get_cached_value('Company', self.company, "abbr")
+ company_abbr = frappe.get_cached_value("Company", self.company, "abbr")
self.name = " - ".join([self.period_name, company_abbr])
def validate_overlap(self):
- existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period`
+ existing_accounting_period = frappe.db.sql(
+ """select name from `tabAccounting Period`
where (
(%(start_date)s between start_date and end_date)
or (%(end_date)s between start_date and end_date)
@@ -32,18 +35,29 @@ class AccountingPeriod(Document):
"start_date": self.start_date,
"end_date": self.end_date,
"name": self.name,
- "company": self.company
- }, as_dict=True)
+ "company": self.company,
+ },
+ as_dict=True,
+ )
if len(existing_accounting_period) > 0:
- frappe.throw(_("Accounting Period overlaps with {0}")
- .format(existing_accounting_period[0].get("name")), OverlapError)
+ frappe.throw(
+ _("Accounting Period overlaps with {0}").format(existing_accounting_period[0].get("name")),
+ OverlapError,
+ )
@frappe.whitelist()
def get_doctypes_for_closing(self):
docs_for_closing = []
- doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
- "Bank Clearance", "Asset", "Stock Entry"]
+ doctypes = [
+ "Sales Invoice",
+ "Purchase Invoice",
+ "Journal Entry",
+ "Payroll Entry",
+ "Bank Clearance",
+ "Asset",
+ "Stock Entry",
+ ]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype)
@@ -53,7 +67,7 @@ class AccountingPeriod(Document):
def bootstrap_doctypes_for_closing(self):
if len(self.closed_documents) == 0:
for doctype_for_closing in self.get_doctypes_for_closing():
- self.append('closed_documents', {
- "document_type": doctype_for_closing.document_type,
- "closed": doctype_for_closing.closed
- })
+ self.append(
+ "closed_documents",
+ {"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
+ )
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
index c06c2e0338..85025d190f 100644
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
@@ -10,29 +10,38 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import Overlap
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
-test_dependencies = ['Item']
+test_dependencies = ["Item"]
+
class TestAccountingPeriod(unittest.TestCase):
def test_overlap(self):
- ap1 = create_accounting_period(start_date = "2018-04-01",
- end_date = "2018-06-30", company = "Wind Power LLC")
+ ap1 = create_accounting_period(
+ start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC"
+ )
ap1.save()
- ap2 = create_accounting_period(start_date = "2018-06-30",
- end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
+ ap2 = create_accounting_period(
+ start_date="2018-06-30",
+ end_date="2018-07-10",
+ company="Wind Power LLC",
+ period_name="Test Accounting Period 1",
+ )
self.assertRaises(OverlapError, ap2.save)
def test_accounting_period(self):
- ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
+ ap1 = create_accounting_period(period_name="Test Accounting Period 2")
ap1.save()
- doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
+ doc = create_sales_invoice(
+ do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
+ )
self.assertRaises(ClosedAccountingPeriod, doc.submit)
def tearDown(self):
for d in frappe.get_all("Accounting Period"):
frappe.delete_doc("Accounting Period", d.name)
+
def create_accounting_period(**args):
args = frappe._dict(args)
@@ -41,8 +50,6 @@ def create_accounting_period(**args):
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
accounting_period.company = args.company or "_Test Company"
accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
- accounting_period.append("closed_documents", {
- "document_type": 'Sales Invoice', "closed": 1
- })
+ accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1})
return accounting_period
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 4839207410..835498176c 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -18,11 +18,13 @@ class AccountsSettings(Document):
frappe.clear_cache()
def validate(self):
- frappe.db.set_default("add_taxes_from_item_tax_template",
- self.get("add_taxes_from_item_tax_template", 0))
+ frappe.db.set_default(
+ "add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
+ )
- frappe.db.set_default("enable_common_party_accounting",
- self.get("enable_common_party_accounting", 0))
+ frappe.db.set_default(
+ "enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
+ )
self.validate_stale_days()
self.enable_payment_schedule_in_print()
@@ -32,34 +34,91 @@ class AccountsSettings(Document):
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint(
- _("Stale Days should start from 1."), title='Error', indicator='red',
- raise_exception=1)
+ _("Stale Days should start from 1."), title="Error", indicator="red", raise_exception=1
+ )
def enable_payment_schedule_in_print(self):
show_in_print = cint(self.show_payment_schedule_in_print)
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
- make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
- make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
+ make_property_setter(
+ doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False
+ )
+ make_property_setter(
+ doctype,
+ "payment_schedule",
+ "print_hide",
+ 0 if show_in_print else 1,
+ "Check",
+ validate_fields_for_doctype=False,
+ )
def toggle_discount_accounting_fields(self):
enable_discount_accounting = cint(self.enable_discount_accounting)
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
- make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
+ make_property_setter(
+ doctype,
+ "discount_account",
+ "hidden",
+ not (enable_discount_accounting),
+ "Check",
+ validate_fields_for_doctype=False,
+ )
if enable_discount_accounting:
- make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
+ make_property_setter(
+ doctype,
+ "discount_account",
+ "mandatory_depends_on",
+ "eval: doc.discount_amount",
+ "Code",
+ validate_fields_for_doctype=False,
+ )
else:
- make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
+ make_property_setter(
+ doctype,
+ "discount_account",
+ "mandatory_depends_on",
+ "",
+ "Code",
+ validate_fields_for_doctype=False,
+ )
for doctype in ["Sales Invoice", "Purchase Invoice"]:
- make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
+ make_property_setter(
+ doctype,
+ "additional_discount_account",
+ "hidden",
+ not (enable_discount_accounting),
+ "Check",
+ validate_fields_for_doctype=False,
+ )
if enable_discount_accounting:
- make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
+ make_property_setter(
+ doctype,
+ "additional_discount_account",
+ "mandatory_depends_on",
+ "eval: doc.discount_amount",
+ "Code",
+ validate_fields_for_doctype=False,
+ )
else:
- make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
-
- make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
+ make_property_setter(
+ doctype,
+ "additional_discount_account",
+ "mandatory_depends_on",
+ "",
+ "Code",
+ validate_fields_for_doctype=False,
+ )
+ make_property_setter(
+ "Item",
+ "default_discount_account",
+ "hidden",
+ not (enable_discount_accounting),
+ "Check",
+ validate_fields_for_doctype=False,
+ )
def validate_pending_reposts(self):
if self.acc_frozen_upto:
diff --git a/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py
index bf1e967bdb..a350cc385d 100644
--- a/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py
@@ -7,12 +7,12 @@ class TestAccountsSettings(unittest.TestCase):
def tearDown(self):
# Just in case `save` method succeeds, we need to take things back to default so that other tests
# don't break
- cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
cur_settings.allow_stale = 1
cur_settings.save()
def test_stale_days(self):
- cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
cur_settings.allow_stale = 0
cur_settings.stale_days = 0
diff --git a/erpnext/accounts/doctype/bank/bank.py b/erpnext/accounts/doctype/bank/bank.py
index f111433c32..d44be9af23 100644
--- a/erpnext/accounts/doctype/bank/bank.py
+++ b/erpnext/accounts/doctype/bank/bank.py
@@ -15,4 +15,4 @@ class Bank(Document):
load_address_and_contact(self)
def on_trash(self):
- delete_contact_and_address('Bank', self.name)
+ delete_contact_and_address("Bank", self.name)
diff --git a/erpnext/accounts/doctype/bank/bank_dashboard.py b/erpnext/accounts/doctype/bank/bank_dashboard.py
index 36482aac96..7e40a1a6b8 100644
--- a/erpnext/accounts/doctype/bank/bank_dashboard.py
+++ b/erpnext/accounts/doctype/bank/bank_dashboard.py
@@ -3,11 +3,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'bank',
- 'transactions': [
- {
- 'label': _('Bank Details'),
- 'items': ['Bank Account', 'Bank Guarantee']
- }
- ]
+ "fieldname": "bank",
+ "transactions": [{"label": _("Bank Details"), "items": ["Bank Account", "Bank Guarantee"]}],
}
diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py
index f9140c31d6..addcf62e5b 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account.py
+++ b/erpnext/accounts/doctype/bank_account/bank_account.py
@@ -20,7 +20,7 @@ class BankAccount(Document):
self.name = self.account_name + " - " + self.bank
def on_trash(self):
- delete_contact_and_address('BankAccount', self.name)
+ delete_contact_and_address("BankAccount", self.name)
def validate(self):
self.validate_company()
@@ -31,9 +31,9 @@ class BankAccount(Document):
frappe.throw(_("Company is manadatory for company account"))
def validate_iban(self):
- '''
+ """
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
- '''
+ """
# IBAN field is optional
if not self.iban:
return
@@ -43,7 +43,7 @@ class BankAccount(Document):
return str(9 + ord(c) - 64)
# remove whitespaces, upper case to get the right number from ord()
- iban = ''.join(self.iban.split(' ')).upper()
+ iban = "".join(self.iban.split(" ")).upper()
# Move country code and checksum from the start to the end
flipped = iban[4:] + iban[:4]
@@ -52,12 +52,12 @@ class BankAccount(Document):
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
try:
- to_check = int(''.join(encoded))
+ to_check = int("".join(encoded))
except ValueError:
- frappe.throw(_('IBAN is not valid'))
+ frappe.throw(_("IBAN is not valid"))
if to_check % 97 != 1:
- frappe.throw(_('IBAN is not valid'))
+ frappe.throw(_("IBAN is not valid"))
@frappe.whitelist()
@@ -69,12 +69,14 @@ def make_bank_account(doctype, docname):
return doc
+
@frappe.whitelist()
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")
+
@frappe.whitelist()
def get_bank_account_details(bank_account):
- return frappe.db.get_value("Bank Account",
- bank_account, ['account', 'bank', 'bank_account_no'], as_dict=1)
+ return frappe.db.get_value(
+ "Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
+ )
diff --git a/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py b/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py
index db4d7e51d5..8bf8d8a5cd 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py
+++ b/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py
@@ -3,25 +3,18 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'bank_account',
- 'non_standard_fieldnames': {
- 'Customer': 'default_bank_account',
- 'Supplier': 'default_bank_account',
+ "fieldname": "bank_account",
+ "non_standard_fieldnames": {
+ "Customer": "default_bank_account",
+ "Supplier": "default_bank_account",
},
- 'transactions': [
+ "transactions": [
{
- 'label': _('Payments'),
- 'items': ['Payment Entry', 'Payment Request', 'Payment Order', 'Payroll Entry']
+ "label": _("Payments"),
+ "items": ["Payment Entry", "Payment Request", "Payment Order", "Payroll Entry"],
},
- {
- 'label': _('Party'),
- 'items': ['Customer', 'Supplier']
- },
- {
- 'items': ['Bank Guarantee']
- },
- {
- 'items': ['Journal Entry']
- }
- ]
+ {"label": _("Party"), "items": ["Customer", "Supplier"]},
+ {"items": ["Bank Guarantee"]},
+ {"items": ["Journal Entry"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/bank_account/test_bank_account.py b/erpnext/accounts/doctype/bank_account/test_bank_account.py
index 5f23f88af6..8949524a56 100644
--- a/erpnext/accounts/doctype/bank_account/test_bank_account.py
+++ b/erpnext/accounts/doctype/bank_account/test_bank_account.py
@@ -8,28 +8,28 @@ from frappe import ValidationError
# test_records = frappe.get_test_records('Bank Account')
-class TestBankAccount(unittest.TestCase):
+class TestBankAccount(unittest.TestCase):
def test_validate_iban(self):
valid_ibans = [
- 'GB82 WEST 1234 5698 7654 32',
- 'DE91 1000 0000 0123 4567 89',
- 'FR76 3000 6000 0112 3456 7890 189'
+ "GB82 WEST 1234 5698 7654 32",
+ "DE91 1000 0000 0123 4567 89",
+ "FR76 3000 6000 0112 3456 7890 189",
]
invalid_ibans = [
# wrong checksum (3rd place)
- 'GB72 WEST 1234 5698 7654 32',
- 'DE81 1000 0000 0123 4567 89',
- 'FR66 3000 6000 0112 3456 7890 189'
+ "GB72 WEST 1234 5698 7654 32",
+ "DE81 1000 0000 0123 4567 89",
+ "FR66 3000 6000 0112 3456 7890 189",
]
- bank_account = frappe.get_doc({'doctype':'Bank Account'})
+ bank_account = frappe.get_doc({"doctype": "Bank Account"})
try:
bank_account.validate_iban()
except AttributeError:
- msg = 'BankAccount.validate_iban() failed for empty IBAN'
+ msg = "BankAccount.validate_iban() failed for empty IBAN"
self.fail(msg=msg)
for iban in valid_ibans:
@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
try:
bank_account.validate_iban()
except ValidationError:
- msg = 'BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)
+ msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(iban)
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
- msg = 'BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)
+ msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban)
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index a3bbb2288d..96779d75be 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -7,9 +7,8 @@ from frappe import _, msgprint
from frappe.model.document import Document
from frappe.utils import flt, fmt_money, getdate, nowdate
-form_grid_templates = {
- "journal_entries": "templates/form_grid/bank_reconciliation_grid.html"
-}
+form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
+
class BankClearance(Document):
@frappe.whitelist()
@@ -24,7 +23,8 @@ class BankClearance(Document):
if not self.include_reconciled_entries:
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
- journal_entries = frappe.db.sql("""
+ journal_entries = frappe.db.sql(
+ """
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
@@ -38,12 +38,18 @@ class BankClearance(Document):
and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
- """.format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
+ """.format(
+ condition=condition
+ ),
+ {"account": self.account, "from": self.from_date, "to": self.to_date},
+ as_dict=1,
+ )
if self.bank_account:
- condition += 'and bank_account = %(bank_account)s'
+ condition += "and bank_account = %(bank_account)s"
- payment_entries = frappe.db.sql("""
+ payment_entries = frappe.db.sql(
+ """
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
@@ -58,12 +64,22 @@ class BankClearance(Document):
{condition}
order by
posting_date ASC, name DESC
- """.format(condition=condition), {"account": self.account, "from":self.from_date,
- "to": self.to_date, "bank_account": self.bank_account}, as_dict=1)
+ """.format(
+ condition=condition
+ ),
+ {
+ "account": self.account,
+ "from": self.from_date,
+ "to": self.to_date,
+ "bank_account": self.bank_account,
+ },
+ as_dict=1,
+ )
pos_sales_invoices, pos_purchase_invoices = [], []
if self.include_pos_transactions:
- pos_sales_invoices = frappe.db.sql("""
+ pos_sales_invoices = frappe.db.sql(
+ """
select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.customer as against_account, sip.clearance_date,
@@ -74,9 +90,13 @@ class BankClearance(Document):
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
order by
si.posting_date ASC, si.name DESC
- """, {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1)
+ """,
+ {"account": self.account, "from": self.from_date, "to": self.to_date},
+ as_dict=1,
+ )
- pos_purchase_invoices = frappe.db.sql("""
+ pos_purchase_invoices = frappe.db.sql(
+ """
select
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
@@ -87,18 +107,24 @@ class BankClearance(Document):
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
order by
pi.posting_date ASC, pi.name DESC
- """, {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
+ """,
+ {"account": self.account, "from": self.from_date, "to": self.to_date},
+ as_dict=1,
+ )
- entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
- key=lambda k: k['posting_date'] or getdate(nowdate()))
+ entries = sorted(
+ list(payment_entries)
+ + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
+ key=lambda k: k["posting_date"] or getdate(nowdate()),
+ )
- self.set('payment_entries', [])
+ self.set("payment_entries", [])
self.total_amount = 0.0
for d in entries:
- row = self.append('payment_entries', {})
+ row = self.append("payment_entries", {})
- amount = flt(d.get('debit', 0)) - flt(d.get('credit', 0))
+ amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
@@ -112,21 +138,24 @@ class BankClearance(Document):
@frappe.whitelist()
def update_clearance_date(self):
clearance_date_updated = False
- for d in self.get('payment_entries'):
+ for d in self.get("payment_entries"):
if d.clearance_date:
if not d.payment_document:
frappe.throw(_("Row #{0}: Payment document is required to complete the transaction"))
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
- frappe.throw(_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}")
- .format(d.idx, d.clearance_date, d.cheque_date))
+ frappe.throw(
+ _("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
+ d.idx, d.clearance_date, d.cheque_date
+ )
+ )
if d.clearance_date or self.include_reconciled_entries:
if not d.clearance_date:
d.clearance_date = None
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
- payment_entry.db_set('clearance_date', d.clearance_date)
+ payment_entry.db_set("clearance_date", d.clearance_date)
clearance_date_updated = True
diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
index cfbcf16b91..9144a29c6e 100644
--- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
+++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
@@ -23,10 +23,16 @@ class BankGuarantee(Document):
if not self.bank:
frappe.throw(_("Enter the name of the bank or lending institution before submittting."))
+
@frappe.whitelist()
def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list)
for col in column_list:
sanitize_searchfield(col)
- return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
- .format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]
+ return frappe.db.sql(
+ """ select {columns} from `tab{doctype}` where name=%s""".format(
+ columns=", ".join(column_list), doctype=doctype
+ ),
+ docname,
+ as_dict=1,
+ )[0]
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 317fcc02b5..4c25d7ccbe 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -22,48 +22,63 @@ from erpnext.accounts.utils import get_balance_on
class BankReconciliationTool(Document):
pass
+
@frappe.whitelist()
-def get_bank_transactions(bank_account, from_date = None, to_date = None):
+def get_bank_transactions(bank_account, from_date=None, to_date=None):
# returns bank transactions for a bank account
filters = []
- filters.append(['bank_account', '=', bank_account])
- filters.append(['docstatus', '=', 1])
- filters.append(['unallocated_amount', '>', 0])
+ filters.append(["bank_account", "=", bank_account])
+ filters.append(["docstatus", "=", 1])
+ filters.append(["unallocated_amount", ">", 0])
if to_date:
- filters.append(['date', '<=', to_date])
+ filters.append(["date", "<=", to_date])
if from_date:
- filters.append(['date', '>=', from_date])
+ filters.append(["date", ">=", from_date])
transactions = frappe.get_all(
- 'Bank Transaction',
- fields = ['date', 'deposit', 'withdrawal', 'currency',
- 'description', 'name', 'bank_account', 'company',
- 'unallocated_amount', 'reference_number', 'party_type', 'party'],
- filters = filters
+ "Bank Transaction",
+ fields=[
+ "date",
+ "deposit",
+ "withdrawal",
+ "currency",
+ "description",
+ "name",
+ "bank_account",
+ "company",
+ "unallocated_amount",
+ "reference_number",
+ "party_type",
+ "party",
+ ],
+ filters=filters,
)
return transactions
+
@frappe.whitelist()
def get_account_balance(bank_account, till_date):
# returns account balance till the specified date
- account = frappe.db.get_value('Bank Account', bank_account, 'account')
- filters = frappe._dict({
- "account": account,
- "report_date": till_date,
- "include_pos_transactions": 1
- })
+ account = frappe.db.get_value("Bank Account", bank_account, "account")
+ filters = frappe._dict(
+ {"account": account, "report_date": till_date, "include_pos_transactions": 1}
+ )
data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
- total_debit, total_credit = 0,0
+ total_debit, total_credit = 0, 0
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
- bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \
+ bank_bal = (
+ flt(balance_as_per_system)
+ - flt(total_debit)
+ + flt(total_credit)
+ amounts_not_reflected_in_system
+ )
return bank_bal
@@ -76,71 +91,94 @@ def update_bank_transaction(bank_transaction_name, reference_number, party_type=
bank_transaction.party_type = party_type
bank_transaction.party = party
bank_transaction.save()
- return frappe.db.get_all('Bank Transaction',
- filters={
- 'name': bank_transaction_name
- },
- fields=['date', 'deposit', 'withdrawal', 'currency',
- 'description', 'name', 'bank_account', 'company',
- 'unallocated_amount', 'reference_number',
- 'party_type', 'party'],
+ return frappe.db.get_all(
+ "Bank Transaction",
+ filters={"name": bank_transaction_name},
+ fields=[
+ "date",
+ "deposit",
+ "withdrawal",
+ "currency",
+ "description",
+ "name",
+ "bank_account",
+ "company",
+ "unallocated_amount",
+ "reference_number",
+ "party_type",
+ "party",
+ ],
)[0]
@frappe.whitelist()
-def create_journal_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, posting_date=None, entry_type=None,
- second_account=None, mode_of_payment=None, party_type=None, party=None, allow_edit=None):
+def create_journal_entry_bts(
+ bank_transaction_name,
+ reference_number=None,
+ reference_date=None,
+ posting_date=None,
+ entry_type=None,
+ second_account=None,
+ mode_of_payment=None,
+ party_type=None,
+ party=None,
+ allow_edit=None,
+):
# Create a new journal entry based on the bank transaction
bank_transaction = frappe.db.get_values(
- "Bank Transaction", bank_transaction_name,
- fieldname=["name", "deposit", "withdrawal", "bank_account"] ,
- as_dict=True
+ "Bank Transaction",
+ bank_transaction_name,
+ fieldname=["name", "deposit", "withdrawal", "bank_account"],
+ as_dict=True,
)[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
account_type = frappe.db.get_value("Account", second_account, "account_type")
if account_type in ["Receivable", "Payable"]:
if not (party_type and party):
- frappe.throw(_("Party Type and Party is required for Receivable / Payable account {0}").format( second_account))
+ frappe.throw(
+ _("Party Type and Party is required for Receivable / Payable account {0}").format(
+ second_account
+ )
+ )
accounts = []
# Multi Currency?
- accounts.append({
+ accounts.append(
+ {
"account": second_account,
- "credit_in_account_currency": bank_transaction.deposit
- if bank_transaction.deposit > 0
- else 0,
- "debit_in_account_currency":bank_transaction.withdrawal
- if bank_transaction.withdrawal > 0
- else 0,
- "party_type":party_type,
- "party":party,
- })
+ "credit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
+ "debit_in_account_currency": bank_transaction.withdrawal
+ if bank_transaction.withdrawal > 0
+ else 0,
+ "party_type": party_type,
+ "party": party,
+ }
+ )
- accounts.append({
+ accounts.append(
+ {
"account": company_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal
- if bank_transaction.withdrawal > 0
- else 0,
- "debit_in_account_currency":bank_transaction.deposit
- if bank_transaction.deposit > 0
- else 0,
- })
+ if bank_transaction.withdrawal > 0
+ else 0,
+ "debit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
+ }
+ )
company = frappe.get_value("Account", company_account, "company")
journal_entry_dict = {
- "voucher_type" : entry_type,
- "company" : company,
- "posting_date" : posting_date,
- "cheque_date" : reference_date,
- "cheque_no" : reference_number,
- "mode_of_payment" : mode_of_payment
+ "voucher_type": entry_type,
+ "company": company,
+ "posting_date": posting_date,
+ "cheque_date": reference_date,
+ "cheque_no": reference_number,
+ "mode_of_payment": mode_of_payment,
}
- journal_entry = frappe.new_doc('Journal Entry')
+ journal_entry = frappe.new_doc("Journal Entry")
journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts)
-
if allow_edit:
return journal_entry
@@ -152,21 +190,32 @@ def create_journal_entry_bts( bank_transaction_name, reference_number=None, refe
else:
paid_amount = bank_transaction.withdrawal
- vouchers = json.dumps([{
- "payment_doctype":"Journal Entry",
- "payment_name":journal_entry.name,
- "amount":paid_amount}])
+ vouchers = json.dumps(
+ [{"payment_doctype": "Journal Entry", "payment_name": journal_entry.name, "amount": paid_amount}]
+ )
return reconcile_vouchers(bank_transaction.name, vouchers)
+
@frappe.whitelist()
-def create_payment_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, party_type=None, party=None, posting_date=None,
- mode_of_payment=None, project=None, cost_center=None, allow_edit=None):
+def create_payment_entry_bts(
+ bank_transaction_name,
+ reference_number=None,
+ reference_date=None,
+ party_type=None,
+ party=None,
+ posting_date=None,
+ mode_of_payment=None,
+ project=None,
+ cost_center=None,
+ allow_edit=None,
+):
# Create a new payment entry based on the bank transaction
bank_transaction = frappe.db.get_values(
- "Bank Transaction", bank_transaction_name,
- fieldname=["name", "unallocated_amount", "deposit", "bank_account"] ,
- as_dict=True
+ "Bank Transaction",
+ bank_transaction_name,
+ fieldname=["name", "unallocated_amount", "deposit", "bank_account"],
+ as_dict=True,
)[0]
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0 else "Pay"
@@ -174,27 +223,26 @@ def create_payment_entry_bts( bank_transaction_name, reference_number=None, refe
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_value("Account", company_account, "company")
payment_entry_dict = {
- "company" : company,
- "payment_type" : payment_type,
- "reference_no" : reference_number,
- "reference_date" : reference_date,
- "party_type" : party_type,
- "party" : party,
- "posting_date" : posting_date,
+ "company": company,
+ "payment_type": payment_type,
+ "reference_no": reference_number,
+ "reference_date": reference_date,
+ "party_type": party_type,
+ "party": party,
+ "posting_date": posting_date,
"paid_amount": paid_amount,
- "received_amount": paid_amount
+ "received_amount": paid_amount,
}
payment_entry = frappe.new_doc("Payment Entry")
-
payment_entry.update(payment_entry_dict)
if mode_of_payment:
- payment_entry.mode_of_payment = mode_of_payment
+ payment_entry.mode_of_payment = mode_of_payment
if project:
- payment_entry.project = project
+ payment_entry.project = project
if cost_center:
- payment_entry.cost_center = cost_center
+ payment_entry.cost_center = cost_center
if payment_type == "Receive":
payment_entry.paid_to = company_account
else:
@@ -208,84 +256,111 @@ def create_payment_entry_bts( bank_transaction_name, reference_number=None, refe
payment_entry.insert()
payment_entry.submit()
- vouchers = json.dumps([{
- "payment_doctype":"Payment Entry",
- "payment_name":payment_entry.name,
- "amount":paid_amount}])
+ vouchers = json.dumps(
+ [{"payment_doctype": "Payment Entry", "payment_name": payment_entry.name, "amount": paid_amount}]
+ )
return reconcile_vouchers(bank_transaction.name, vouchers)
+
@frappe.whitelist()
def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
- company_account = frappe.db.get_value('Bank Account', transaction.bank_account, 'account')
+ company_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
total_amount = 0
for voucher in vouchers:
- voucher['payment_entry'] = frappe.get_doc(voucher['payment_doctype'], voucher['payment_name'])
- total_amount += get_paid_amount(frappe._dict({
- 'payment_document': voucher['payment_doctype'],
- 'payment_entry': voucher['payment_name'],
- }), transaction.currency, company_account)
+ voucher["payment_entry"] = frappe.get_doc(voucher["payment_doctype"], voucher["payment_name"])
+ total_amount += get_paid_amount(
+ frappe._dict(
+ {
+ "payment_document": voucher["payment_doctype"],
+ "payment_entry": voucher["payment_name"],
+ }
+ ),
+ transaction.currency,
+ company_account,
+ )
if total_amount > transaction.unallocated_amount:
- frappe.throw(_("The sum total of amounts of all selected vouchers should be less than the unallocated amount of the bank transaction"))
+ frappe.throw(
+ _(
+ "The sum total of amounts of all selected vouchers should be less than the unallocated amount of the bank transaction"
+ )
+ )
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
for voucher in vouchers:
- gl_entry = frappe.db.get_value("GL Entry", dict(account=account, voucher_type=voucher['payment_doctype'], voucher_no=voucher['payment_name']), ['credit', 'debit'], as_dict=1)
- gl_amount, transaction_amount = (gl_entry.credit, transaction.deposit) if gl_entry.credit > 0 else (gl_entry.debit, transaction.withdrawal)
+ gl_entry = frappe.db.get_value(
+ "GL Entry",
+ dict(
+ account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
+ ),
+ ["credit", "debit"],
+ as_dict=1,
+ )
+ gl_amount, transaction_amount = (
+ (gl_entry.credit, transaction.deposit)
+ if gl_entry.credit > 0
+ else (gl_entry.debit, transaction.withdrawal)
+ )
allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
- transaction.append("payment_entries", {
- "payment_document": voucher['payment_entry'].doctype,
- "payment_entry": voucher['payment_entry'].name,
- "allocated_amount": allocated_amount
- })
+ transaction.append(
+ "payment_entries",
+ {
+ "payment_document": voucher["payment_entry"].doctype,
+ "payment_entry": voucher["payment_entry"].name,
+ "allocated_amount": allocated_amount,
+ },
+ )
transaction.save()
transaction.update_allocations()
return frappe.get_doc("Bank Transaction", bank_transaction_name)
+
@frappe.whitelist()
-def get_linked_payments(bank_transaction_name, document_types = None):
+def get_linked_payments(bank_transaction_name, document_types=None):
# get all matching payments for a bank transaction
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
bank_account = frappe.db.get_values(
- "Bank Account",
- transaction.bank_account,
- ["account", "company"],
- as_dict=True)[0]
+ "Bank Account", transaction.bank_account, ["account", "company"], as_dict=True
+ )[0]
(account, company) = (bank_account.account, bank_account.company)
matching = check_matching(account, company, transaction, document_types)
return matching
+
def check_matching(bank_account, company, transaction, document_types):
# combine all types of vouchers
subquery = get_queries(bank_account, company, transaction, document_types)
filters = {
- "amount": transaction.unallocated_amount,
- "payment_type" : "Receive" if transaction.deposit > 0 else "Pay",
- "reference_no": transaction.reference_number,
- "party_type": transaction.party_type,
- "party": transaction.party,
- "bank_account": bank_account
- }
+ "amount": transaction.unallocated_amount,
+ "payment_type": "Receive" if transaction.deposit > 0 else "Pay",
+ "reference_no": transaction.reference_number,
+ "party_type": transaction.party_type,
+ "party": transaction.party,
+ "bank_account": bank_account,
+ }
matching_vouchers = []
- matching_vouchers.extend(get_loan_vouchers(bank_account, transaction,
- document_types, filters))
+ matching_vouchers.extend(get_loan_vouchers(bank_account, transaction, document_types, filters))
for query in subquery:
matching_vouchers.extend(
- frappe.db.sql(query, filters,)
+ frappe.db.sql(
+ query,
+ filters,
+ )
)
- return sorted(matching_vouchers, key = lambda x: x[0], reverse=True) if matching_vouchers else []
+ return sorted(matching_vouchers, key=lambda x: x[0], reverse=True) if matching_vouchers else []
+
def get_queries(bank_account, company, transaction, document_types):
# get queries to get matching vouchers
@@ -302,7 +377,7 @@ def get_queries(bank_account, company, transaction, document_types):
queries.extend([je_amount_matching])
if transaction.deposit > 0 and "sales_invoice" in document_types:
- si_amount_matching = get_si_matching_query(amount_condition)
+ si_amount_matching = get_si_matching_query(amount_condition)
queries.extend([si_amount_matching])
if transaction.withdrawal > 0:
@@ -316,6 +391,7 @@ def get_queries(bank_account, company, transaction, document_types):
return queries
+
def get_loan_vouchers(bank_account, transaction, document_types, filters):
vouchers = []
amount_condition = True if "exact_match" in document_types else False
@@ -328,109 +404,90 @@ def get_loan_vouchers(bank_account, transaction, document_types, filters):
return vouchers
+
def get_ld_matching_query(bank_account, amount_condition, filters):
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
matching_reference = loan_disbursement.reference_number == filters.get("reference_number")
- matching_party = loan_disbursement.applicant_type == filters.get("party_type") and \
- loan_disbursement.applicant == filters.get("party")
+ matching_party = loan_disbursement.applicant_type == filters.get(
+ "party_type"
+ ) and loan_disbursement.applicant == filters.get("party")
- rank = (
- frappe.qb.terms.Case()
- .when(matching_reference, 1)
- .else_(0)
+ rank = frappe.qb.terms.Case().when(matching_reference, 1).else_(0)
+
+ rank1 = frappe.qb.terms.Case().when(matching_party, 1).else_(0)
+
+ query = (
+ frappe.qb.from_(loan_disbursement)
+ .select(
+ rank + rank1 + 1,
+ ConstantColumn("Loan Disbursement").as_("doctype"),
+ loan_disbursement.name,
+ loan_disbursement.disbursed_amount,
+ loan_disbursement.reference_number,
+ loan_disbursement.reference_date,
+ loan_disbursement.applicant_type,
+ loan_disbursement.disbursement_date,
)
-
- rank1 = (
- frappe.qb.terms.Case()
- .when(matching_party, 1)
- .else_(0)
- )
-
- query = frappe.qb.from_(loan_disbursement).select(
- rank + rank1 + 1,
- ConstantColumn("Loan Disbursement").as_("doctype"),
- loan_disbursement.name,
- loan_disbursement.disbursed_amount,
- loan_disbursement.reference_number,
- loan_disbursement.reference_date,
- loan_disbursement.applicant_type,
- loan_disbursement.disbursement_date
- ).where(
- loan_disbursement.docstatus == 1
- ).where(
- loan_disbursement.clearance_date.isnull()
- ).where(
- loan_disbursement.disbursement_account == bank_account
+ .where(loan_disbursement.docstatus == 1)
+ .where(loan_disbursement.clearance_date.isnull())
+ .where(loan_disbursement.disbursement_account == bank_account)
)
if amount_condition:
- query.where(
- loan_disbursement.disbursed_amount == filters.get('amount')
- )
+ query.where(loan_disbursement.disbursed_amount == filters.get("amount"))
else:
- query.where(
- loan_disbursement.disbursed_amount <= filters.get('amount')
- )
+ query.where(loan_disbursement.disbursed_amount <= filters.get("amount"))
vouchers = query.run(as_list=True)
return vouchers
+
def get_lr_matching_query(bank_account, amount_condition, filters):
loan_repayment = frappe.qb.DocType("Loan Repayment")
matching_reference = loan_repayment.reference_number == filters.get("reference_number")
- matching_party = loan_repayment.applicant_type == filters.get("party_type") and \
- loan_repayment.applicant == filters.get("party")
+ matching_party = loan_repayment.applicant_type == filters.get(
+ "party_type"
+ ) and loan_repayment.applicant == filters.get("party")
- rank = (
- frappe.qb.terms.Case()
- .when(matching_reference, 1)
- .else_(0)
+ rank = frappe.qb.terms.Case().when(matching_reference, 1).else_(0)
+
+ rank1 = frappe.qb.terms.Case().when(matching_party, 1).else_(0)
+
+ query = (
+ frappe.qb.from_(loan_repayment)
+ .select(
+ rank + rank1 + 1,
+ ConstantColumn("Loan Repayment").as_("doctype"),
+ loan_repayment.name,
+ loan_repayment.amount_paid,
+ loan_repayment.reference_number,
+ loan_repayment.reference_date,
+ loan_repayment.applicant_type,
+ loan_repayment.posting_date,
)
-
- rank1 = (
- frappe.qb.terms.Case()
- .when(matching_party, 1)
- .else_(0)
- )
-
- query = frappe.qb.from_(loan_repayment).select(
- rank + rank1 + 1,
- ConstantColumn("Loan Repayment").as_("doctype"),
- loan_repayment.name,
- loan_repayment.amount_paid,
- loan_repayment.reference_number,
- loan_repayment.reference_date,
- loan_repayment.applicant_type,
- loan_repayment.posting_date
- ).where(
- loan_repayment.docstatus == 1
- ).where(
- loan_repayment.clearance_date.isnull()
- ).where(
- loan_repayment.payment_account == bank_account
+ .where(loan_repayment.docstatus == 1)
+ .where(loan_repayment.clearance_date.isnull())
+ .where(loan_repayment.payment_account == bank_account)
)
if amount_condition:
- query.where(
- loan_repayment.amount_paid == filters.get('amount')
- )
+ query.where(loan_repayment.amount_paid == filters.get("amount"))
else:
- query.where(
- loan_repayment.amount_paid <= filters.get('amount')
- )
+ query.where(loan_repayment.amount_paid <= filters.get("amount"))
vouchers = query.run()
return vouchers
+
def get_pe_matching_query(amount_condition, account_from_to, transaction):
# get matching payment entries query
if transaction.deposit > 0:
currency_field = "paid_to_account_currency as currency"
else:
currency_field = "paid_from_account_currency as currency"
- return f"""
+ return f"""
SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
@@ -519,6 +576,7 @@ def get_si_matching_query(amount_condition):
AND si.docstatus = 1
"""
+
def get_pi_matching_query(amount_condition):
# get matching purchase invoice query
return f"""
@@ -544,11 +602,16 @@ def get_pi_matching_query(amount_condition):
AND cash_bank_account = %(bank_account)s
"""
+
def get_ec_matching_query(bank_account, company, amount_condition):
# get matching Expense Claim query
- mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account",
- filters={"default_account": bank_account}, fields=["parent"])]
- mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
+ mode_of_payments = [
+ x["parent"]
+ for x in frappe.db.get_all(
+ "Mode of Payment Account", filters={"default_account": bank_account}, fields=["parent"]
+ )
+ ]
+ mode_of_payments = "('" + "', '".join(mode_of_payments) + "' )"
company_currency = get_company_currency(company)
return f"""
SELECT
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index 1403303f53..3540f0b0e0 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -18,6 +18,7 @@ from openpyxl.utils import get_column_letter
INVALID_VALUES = ("", None)
+
class BankStatementImport(DataImport):
def __init__(self, *args, **kwargs):
super(BankStatementImport, self).__init__(*args, **kwargs)
@@ -49,16 +50,14 @@ class BankStatementImport(DataImport):
self.import_file, self.google_sheets_url
)
- if 'Bank Account' not in json.dumps(preview['columns']):
+ if "Bank Account" not in json.dumps(preview["columns"]):
frappe.throw(_("Please add the Bank Account column"))
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
- frappe.throw(
- _("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive")
- )
+ frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
enqueued_jobs = [d.get("job_name") for d in get_info()]
@@ -81,21 +80,25 @@ class BankStatementImport(DataImport):
return False
+
@frappe.whitelist()
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
import_file, google_sheets_url
)
+
@frappe.whitelist()
def form_start_import(data_import):
return frappe.get_doc("Bank Statement Import", data_import).start_import()
+
@frappe.whitelist()
def download_errored_template(data_import_name):
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
data_import.export_errored_rows()
+
def parse_data_from_template(raw_data):
data = []
@@ -108,7 +111,10 @@ def parse_data_from_template(raw_data):
return data
-def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
+
+def start_import(
+ data_import, bank_account, import_file_path, google_sheets_url, bank, template_options
+):
"""This method runs in background job"""
update_mapping_db(bank, template_options)
@@ -116,7 +122,7 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
data_import = frappe.get_doc("Bank Statement Import", data_import)
file = import_file_path if import_file_path else google_sheets_url
- import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
+ import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
data = parse_data_from_template(import_file.raw_data)
@@ -136,16 +142,18 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name})
+
def update_mapping_db(bank, template_options):
bank = frappe.get_doc("Bank", bank)
for d in bank.bank_transaction_mapping:
d.delete()
for d in json.loads(template_options)["column_to_field_map"].items():
- bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1] ,"file_field": d[0]} )
+ bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1], "file_field": d[0]})
bank.save()
+
def add_bank_account(data, bank_account):
bank_account_loc = None
if "Bank Account" not in data[0]:
@@ -161,6 +169,7 @@ def add_bank_account(data, bank_account):
else:
row.append(bank_account)
+
def write_files(import_file, data):
full_file_path = import_file.file_doc.get_full_path()
parts = import_file.file_doc.get_extension()
@@ -168,11 +177,12 @@ def write_files(import_file, data):
extension = extension.lstrip(".")
if extension == "csv":
- with open(full_file_path, 'w', newline='') as file:
+ with open(full_file_path, "w", newline="") as file:
writer = csv.writer(file)
writer.writerows(data)
elif extension == "xlsx" or "xls":
- write_xlsx(data, "trans", file_path = full_file_path)
+ write_xlsx(data, "trans", file_path=full_file_path)
+
def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
# from xlsx utils with changes
@@ -187,19 +197,19 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
ws.column_dimensions[get_column_letter(i + 1)].width = column_width
row1 = ws.row_dimensions[1]
- row1.font = Font(name='Calibri', bold=True)
+ row1.font = Font(name="Calibri", bold=True)
for row in data:
clean_row = []
for item in row:
- if isinstance(item, str) and (sheet_name not in ['Data Import Template', 'Data Export']):
+ if isinstance(item, str) and (sheet_name not in ["Data Import Template", "Data Export"]):
value = handle_html(item)
else:
value = item
if isinstance(item, str) and next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
# Remove illegal characters from the string
- value = re.sub(ILLEGAL_CHARACTERS_RE, '', value)
+ value = re.sub(ILLEGAL_CHARACTERS_RE, "", value)
clean_row.append(value)
@@ -208,19 +218,20 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
wb.save(file_path)
return True
+
@frappe.whitelist()
def upload_bank_statement(**args):
args = frappe._dict(args)
bsi = frappe.new_doc("Bank Statement Import")
if args.company:
- bsi.update({
- "company": args.company,
- })
+ bsi.update(
+ {
+ "company": args.company,
+ }
+ )
if args.bank_account:
- bsi.update({
- "bank_account": args.bank_account
- })
+ bsi.update({"bank_account": args.bank_account})
return bsi
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index a924df7841..9a0891f147 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -29,17 +29,26 @@ class BankTransaction(StatusUpdater):
def update_allocations(self):
if self.payment_entries:
- allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries])
+ allocated_amount = reduce(
+ lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]
+ )
else:
allocated_amount = 0
if allocated_amount:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
- frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount))
+ frappe.db.set_value(
+ self.doctype,
+ self.name,
+ "unallocated_amount",
+ abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount),
+ )
else:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
- frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)))
+ frappe.db.set_value(
+ self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit))
+ )
amount = self.deposit or self.withdrawal
if amount == self.allocated_amount:
@@ -49,8 +58,14 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self, for_cancel=False):
for payment_entry in self.payment_entries:
- if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim", "Loan Repayment",
- "Loan Disbursement"]:
+ if payment_entry.payment_document in [
+ "Payment Entry",
+ "Journal Entry",
+ "Purchase Invoice",
+ "Expense Claim",
+ "Loan Repayment",
+ "Loan Disbursement",
+ ]:
self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
elif payment_entry.payment_document == "Sales Invoice":
@@ -58,38 +73,41 @@ class BankTransaction(StatusUpdater):
def clear_simple_entry(self, payment_entry, for_cancel=False):
if payment_entry.payment_document == "Payment Entry":
- if frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type") == "Internal Transfer":
+ if (
+ frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type")
+ == "Internal Transfer"
+ ):
if len(get_reconciled_bank_transactions(payment_entry)) < 2:
return
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
- payment_entry.payment_document, payment_entry.payment_entry,
- "clearance_date", clearance_date)
+ payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", clearance_date
+ )
def clear_sales_invoice(self, payment_entry, for_cancel=False):
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
"Sales Invoice Payment",
- dict(
- parenttype=payment_entry.payment_document,
- parent=payment_entry.payment_entry
- ),
- "clearance_date", clearance_date)
+ dict(parenttype=payment_entry.payment_document, parent=payment_entry.payment_entry),
+ "clearance_date",
+ clearance_date,
+ )
+
def get_reconciled_bank_transactions(payment_entry):
reconciled_bank_transactions = frappe.get_all(
- 'Bank Transaction Payments',
- filters = {
- 'payment_entry': payment_entry.payment_entry
- },
- fields = ['parent']
+ "Bank Transaction Payments",
+ filters={"payment_entry": payment_entry.payment_entry},
+ fields=["parent"],
)
return reconciled_bank_transactions
+
def get_total_allocated_amount(payment_entry):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
SUM(btp.allocated_amount) as allocated_amount,
bt.name
@@ -102,48 +120,73 @@ def get_total_allocated_amount(payment_entry):
AND
btp.payment_entry = %s
AND
- bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
+ bt.docstatus = 1""",
+ (payment_entry.payment_document, payment_entry.payment_entry),
+ as_dict=True,
+ )
+
def get_paid_amount(payment_entry, currency, bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
- if payment_entry.payment_document == 'Payment Entry':
+ if payment_entry.payment_document == "Payment Entry":
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
- if doc.payment_type == 'Receive':
- paid_amount_field = ("received_amount"
- if doc.paid_to_account_currency == currency else "base_received_amount")
- elif doc.payment_type == 'Pay':
- paid_amount_field = ("paid_amount"
- if doc.paid_to_account_currency == currency else "base_paid_amount")
+ if doc.payment_type == "Receive":
+ paid_amount_field = (
+ "received_amount" if doc.paid_to_account_currency == currency else "base_received_amount"
+ )
+ elif doc.payment_type == "Pay":
+ paid_amount_field = (
+ "paid_amount" if doc.paid_to_account_currency == currency else "base_paid_amount"
+ )
- return frappe.db.get_value(payment_entry.payment_document,
- payment_entry.payment_entry, paid_amount_field)
+ return frappe.db.get_value(
+ payment_entry.payment_document, payment_entry.payment_entry, paid_amount_field
+ )
elif payment_entry.payment_document == "Journal Entry":
- return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account},
- "sum(credit_in_account_currency)")
+ return frappe.db.get_value(
+ "Journal Entry Account",
+ {"parent": payment_entry.payment_entry, "account": bank_account},
+ "sum(credit_in_account_currency)",
+ )
elif payment_entry.payment_document == "Expense Claim":
- return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
+ return frappe.db.get_value(
+ payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed"
+ )
elif payment_entry.payment_document == "Loan Disbursement":
- return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "disbursed_amount")
+ return frappe.db.get_value(
+ payment_entry.payment_document, payment_entry.payment_entry, "disbursed_amount"
+ )
elif payment_entry.payment_document == "Loan Repayment":
- return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
+ return frappe.db.get_value(
+ payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
+ )
else:
- frappe.throw("Please reconcile {0}: {1} manually".format(payment_entry.payment_document, payment_entry.payment_entry))
+ frappe.throw(
+ "Please reconcile {0}: {1} manually".format(
+ payment_entry.payment_document, payment_entry.payment_entry
+ )
+ )
+
@frappe.whitelist()
def unclear_reference_payment(doctype, docname):
if frappe.db.exists(doctype, docname):
doc = frappe.get_doc(doctype, docname)
if doctype == "Sales Invoice":
- frappe.db.set_value("Sales Invoice Payment", dict(parenttype=doc.payment_document,
- parent=doc.payment_entry), "clearance_date", None)
+ frappe.db.set_value(
+ "Sales Invoice Payment",
+ dict(parenttype=doc.payment_document, parent=doc.payment_entry),
+ "clearance_date",
+ None,
+ )
else:
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
index cca8a88c30..9f2731de55 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
@@ -18,12 +18,14 @@ def upload_bank_statement():
fcontent = frappe.local.uploaded_file
fname = frappe.local.uploaded_filename
- if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')):
+ if frappe.safe_encode(fname).lower().endswith("csv".encode("utf-8")):
from frappe.utils.csvutils import read_csv_content
+
rows = read_csv_content(fcontent, False)
- elif frappe.safe_encode(fname).lower().endswith("xlsx".encode('utf-8')):
+ elif frappe.safe_encode(fname).lower().endswith("xlsx".encode("utf-8")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
+
rows = read_xlsx_file_from_attached_file(fcontent=fcontent)
columns = rows[0]
@@ -43,12 +45,10 @@ def create_bank_entries(columns, data, bank_account):
continue
fields = {}
for key, value in header_map.items():
- fields.update({key: d[int(value)-1]})
+ fields.update({key: d[int(value) - 1]})
try:
- bank_transaction = frappe.get_doc({
- "doctype": "Bank Transaction"
- })
+ bank_transaction = frappe.get_doc({"doctype": "Bank Transaction"})
bank_transaction.update(fields)
bank_transaction.date = getdate(parse_date(bank_transaction.date))
bank_transaction.bank_account = bank_account
@@ -61,6 +61,7 @@ def create_bank_entries(columns, data, bank_account):
return {"success": success, "errors": errors}
+
def get_header_mapping(columns, bank_account):
mapping = get_bank_mapping(bank_account)
@@ -71,10 +72,11 @@ def get_header_mapping(columns, bank_account):
return header_map
+
def get_bank_mapping(bank_account):
bank_name = frappe.db.get_value("Bank Account", bank_account, "bank")
bank = frappe.get_doc("Bank", bank_name)
- mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping}
+ mapping = {row.file_field: row.bank_transaction_field for row in bank.bank_transaction_mapping}
return mapping
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index d84b8e07d3..8cbed4c795 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -17,6 +17,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
test_dependencies = ["Item", "Cost Center"]
+
class TestBankTransaction(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -41,21 +42,34 @@ class TestBankTransaction(unittest.TestCase):
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
- bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
- linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
+ bank_transaction = frappe.get_doc(
+ "Bank Transaction",
+ dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"),
+ )
+ linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
self.assertTrue(linked_payments[0][6] == "Conrad Electronic")
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
def test_reconcile(self):
- bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
+ bank_transaction = frappe.get_doc(
+ "Bank Transaction",
+ dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"),
+ )
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
- vouchers = json.dumps([{
- "payment_doctype":"Payment Entry",
- "payment_name":payment.name,
- "amount":bank_transaction.unallocated_amount}])
+ vouchers = json.dumps(
+ [
+ {
+ "payment_doctype": "Payment Entry",
+ "payment_name": payment.name,
+ "amount": bank_transaction.unallocated_amount,
+ }
+ ]
+ )
reconcile_vouchers(bank_transaction.name, vouchers)
- unallocated_amount = frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount")
+ unallocated_amount = frappe.db.get_value(
+ "Bank Transaction", bank_transaction.name, "unallocated_amount"
+ )
self.assertTrue(unallocated_amount == 0)
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
@@ -69,122 +83,177 @@ class TestBankTransaction(unittest.TestCase):
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self):
- bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
- linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
+ bank_transaction = frappe.get_doc(
+ "Bank Transaction",
+ dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"),
+ )
+ linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
self.assertTrue(linked_payments[0][3])
# Check error if already reconciled
def test_already_reconciled(self):
- bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
+ bank_transaction = frappe.get_doc(
+ "Bank Transaction",
+ dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"),
+ )
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
- vouchers = json.dumps([{
- "payment_doctype":"Payment Entry",
- "payment_name":payment.name,
- "amount":bank_transaction.unallocated_amount}])
+ vouchers = json.dumps(
+ [
+ {
+ "payment_doctype": "Payment Entry",
+ "payment_name": payment.name,
+ "amount": bank_transaction.unallocated_amount,
+ }
+ ]
+ )
reconcile_vouchers(bank_transaction.name, vouchers)
- bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
+ bank_transaction = frappe.get_doc(
+ "Bank Transaction",
+ dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"),
+ )
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
- vouchers = json.dumps([{
- "payment_doctype":"Payment Entry",
- "payment_name":payment.name,
- "amount":bank_transaction.unallocated_amount}])
- self.assertRaises(frappe.ValidationError, reconcile_vouchers, bank_transaction_name=bank_transaction.name, vouchers=vouchers)
+ vouchers = json.dumps(
+ [
+ {
+ "payment_doctype": "Payment Entry",
+ "payment_name": payment.name,
+ "amount": bank_transaction.unallocated_amount,
+ }
+ ]
+ )
+ self.assertRaises(
+ frappe.ValidationError,
+ reconcile_vouchers,
+ bank_transaction_name=bank_transaction.name,
+ vouchers=vouchers,
+ )
# Raise an error if debitor transaction vs debitor payment
def test_clear_sales_invoice(self):
- bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"))
+ bank_transaction = frappe.get_doc(
+ "Bank Transaction",
+ dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"),
+ )
payment = frappe.get_doc("Sales Invoice", dict(customer="Fayva", status=["=", "Paid"]))
- vouchers = json.dumps([{
- "payment_doctype":"Sales Invoice",
- "payment_name":payment.name,
- "amount":bank_transaction.unallocated_amount}])
+ vouchers = json.dumps(
+ [
+ {
+ "payment_doctype": "Sales Invoice",
+ "payment_name": payment.name,
+ "amount": bank_transaction.unallocated_amount,
+ }
+ ]
+ )
reconcile_vouchers(bank_transaction.name, vouchers=vouchers)
- self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
- self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
+ self.assertEqual(
+ frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0
+ )
+ self.assertTrue(
+ frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date")
+ is not None
+ )
+
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
try:
- frappe.get_doc({
- "doctype": "Bank",
- "bank_name":bank_name,
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Bank",
+ "bank_name": bank_name,
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
try:
- frappe.get_doc({
- "doctype": "Bank Account",
- "account_name":"Checking Account",
- "bank": bank_name,
- "account": account_name
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Bank Account",
+ "account_name": "Checking Account",
+ "bank": bank_name,
+ "account": account_name,
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
+
def add_transactions():
create_bank_account()
- doc = frappe.get_doc({
- "doctype": "Bank Transaction",
- "description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
- "date": "2018-10-23",
- "deposit": 1200,
- "currency": "INR",
- "bank_account": "Checking Account - Citi Bank"
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Bank Transaction",
+ "description": "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
+ "date": "2018-10-23",
+ "deposit": 1200,
+ "currency": "INR",
+ "bank_account": "Checking Account - Citi Bank",
+ }
+ ).insert()
doc.submit()
- doc = frappe.get_doc({
- "doctype": "Bank Transaction",
- "description":"1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G",
- "date": "2018-10-23",
- "deposit": 1700,
- "currency": "INR",
- "bank_account": "Checking Account - Citi Bank"
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Bank Transaction",
+ "description": "1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G",
+ "date": "2018-10-23",
+ "deposit": 1700,
+ "currency": "INR",
+ "bank_account": "Checking Account - Citi Bank",
+ }
+ ).insert()
doc.submit()
- doc = frappe.get_doc({
- "doctype": "Bank Transaction",
- "description":"Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic",
- "date": "2018-10-26",
- "withdrawal": 690,
- "currency": "INR",
- "bank_account": "Checking Account - Citi Bank"
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Bank Transaction",
+ "description": "Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic",
+ "date": "2018-10-26",
+ "withdrawal": 690,
+ "currency": "INR",
+ "bank_account": "Checking Account - Citi Bank",
+ }
+ ).insert()
doc.submit()
- doc = frappe.get_doc({
- "doctype": "Bank Transaction",
- "description":"Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07",
- "date": "2018-10-27",
- "deposit": 3900,
- "currency": "INR",
- "bank_account": "Checking Account - Citi Bank"
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Bank Transaction",
+ "description": "Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07",
+ "date": "2018-10-27",
+ "deposit": 3900,
+ "currency": "INR",
+ "bank_account": "Checking Account - Citi Bank",
+ }
+ ).insert()
doc.submit()
- doc = frappe.get_doc({
- "doctype": "Bank Transaction",
- "description":"I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio",
- "date": "2018-10-27",
- "withdrawal": 109080,
- "currency": "INR",
- "bank_account": "Checking Account - Citi Bank"
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Bank Transaction",
+ "description": "I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio",
+ "date": "2018-10-27",
+ "withdrawal": 109080,
+ "currency": "INR",
+ "bank_account": "Checking Account - Citi Bank",
+ }
+ ).insert()
doc.submit()
def add_vouchers():
try:
- frappe.get_doc({
- "doctype": "Supplier",
- "supplier_group":"All Supplier Groups",
- "supplier_type": "Company",
- "supplier_name": "Conrad Electronic"
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Supplier",
+ "supplier_group": "All Supplier Groups",
+ "supplier_type": "Company",
+ "supplier_name": "Conrad Electronic",
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
@@ -198,12 +267,14 @@ def add_vouchers():
pe.submit()
try:
- frappe.get_doc({
- "doctype": "Supplier",
- "supplier_group":"All Supplier Groups",
- "supplier_type": "Company",
- "supplier_name": "Mr G"
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Supplier",
+ "supplier_group": "All Supplier Groups",
+ "supplier_type": "Company",
+ "supplier_name": "Mr G",
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
@@ -222,26 +293,30 @@ def add_vouchers():
pe.submit()
try:
- frappe.get_doc({
- "doctype": "Supplier",
- "supplier_group":"All Supplier Groups",
- "supplier_type": "Company",
- "supplier_name": "Poore Simon's"
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Supplier",
+ "supplier_group": "All Supplier Groups",
+ "supplier_type": "Company",
+ "supplier_name": "Poore Simon's",
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
try:
- frappe.get_doc({
- "doctype": "Customer",
- "customer_group":"All Customer Groups",
- "customer_type": "Company",
- "customer_name": "Poore Simon's"
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Customer",
+ "customer_group": "All Customer Groups",
+ "customer_type": "Company",
+ "customer_name": "Poore Simon's",
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
- pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save =1)
+ pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
pi.cash_bank_account = "_Test Bank - _TC"
pi.insert()
pi.submit()
@@ -261,33 +336,31 @@ def add_vouchers():
pe.submit()
try:
- frappe.get_doc({
- "doctype": "Customer",
- "customer_group":"All Customer Groups",
- "customer_type": "Company",
- "customer_name": "Fayva"
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Customer",
+ "customer_group": "All Customer Groups",
+ "customer_type": "Company",
+ "customer_name": "Fayva",
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
- 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('Mode of Payment Account', {'company': "_Test Company", 'parent': "Cash"}):
- mode_of_payment.append("accounts", {
- "company": "_Test Company",
- "default_account": "_Test Bank - _TC"
- })
+ if not frappe.db.get_value(
+ "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
+ ):
+ mode_of_payment.append(
+ "accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
+ )
mode_of_payment.save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1
- si.append("payments", {
- "mode_of_payment": "Cash",
- "account": "_Test Bank - _TC",
- "amount": 109080
- })
+ si.append(
+ "payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
+ )
si.insert()
si.submit()
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 492bb36558..5527f9fb99 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -14,13 +14,19 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
from erpnext.accounts.utils import get_fiscal_year
-class BudgetError(frappe.ValidationError): pass
-class DuplicateBudgetError(frappe.ValidationError): pass
+class BudgetError(frappe.ValidationError):
+ pass
+
+
+class DuplicateBudgetError(frappe.ValidationError):
+ pass
+
class Budget(Document):
def autoname(self):
- self.name = make_autoname(self.get(frappe.scrub(self.budget_against))
- + "/" + self.fiscal_year + "/.###")
+ self.name = make_autoname(
+ self.get(frappe.scrub(self.budget_against)) + "/" + self.fiscal_year + "/.###"
+ )
def validate(self):
if not self.get(frappe.scrub(self.budget_against)):
@@ -35,34 +41,44 @@ class Budget(Document):
budget_against = self.get(budget_against_field)
accounts = [d.account for d in self.accounts] or []
- existing_budget = frappe.db.sql("""
+ existing_budget = frappe.db.sql(
+ """
select
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
where
ba.parent = b.name and b.docstatus < 2 and b.company = %s and %s=%s and
b.fiscal_year=%s and b.name != %s and ba.account in (%s) """
- % ('%s', budget_against_field, '%s', '%s', '%s', ','.join(['%s'] * len(accounts))),
- (self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts), as_dict=1)
+ % ("%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))),
+ (self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts),
+ as_dict=1,
+ )
for d in existing_budget:
- frappe.throw(_("Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4}")
- .format(d.name, self.budget_against, budget_against, d.account, self.fiscal_year), DuplicateBudgetError)
+ frappe.throw(
+ _(
+ "Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4}"
+ ).format(d.name, self.budget_against, budget_against, d.account, self.fiscal_year),
+ DuplicateBudgetError,
+ )
def validate_accounts(self):
account_list = []
- for d in self.get('accounts'):
+ for d in self.get("accounts"):
if d.account:
- account_details = frappe.db.get_value("Account", d.account,
- ["is_group", "company", "report_type"], as_dict=1)
+ account_details = frappe.db.get_value(
+ "Account", d.account, ["is_group", "company", "report_type"], as_dict=1
+ )
if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company:
- frappe.throw(_("Account {0} does not belongs to company {1}")
- .format(d.account, self.company))
+ frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
elif account_details.report_type != "Profit and Loss":
- frappe.throw(_("Budget cannot be assigned against {0}, as it's not an Income or Expense account")
- .format(d.account))
+ frappe.throw(
+ _("Budget cannot be assigned against {0}, as it's not an Income or Expense account").format(
+ d.account
+ )
+ )
if d.account in account_list:
frappe.throw(_("Account {0} has been entered multiple times").format(d.account))
@@ -70,51 +86,66 @@ class Budget(Document):
account_list.append(d.account)
def set_null_value(self):
- if self.budget_against == 'Cost Center':
+ if self.budget_against == "Cost Center":
self.project = None
else:
self.cost_center = None
def validate_applicable_for(self):
- if (self.applicable_on_material_request
- and not (self.applicable_on_purchase_order and self.applicable_on_booking_actual_expenses)):
- frappe.throw(_("Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses"))
+ if self.applicable_on_material_request and not (
+ self.applicable_on_purchase_order and self.applicable_on_booking_actual_expenses
+ ):
+ frappe.throw(
+ _("Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses")
+ )
- elif (self.applicable_on_purchase_order
- and not (self.applicable_on_booking_actual_expenses)):
+ elif self.applicable_on_purchase_order and not (self.applicable_on_booking_actual_expenses):
frappe.throw(_("Please enable Applicable on Booking Actual Expenses"))
- elif not(self.applicable_on_material_request
- or self.applicable_on_purchase_order or self.applicable_on_booking_actual_expenses):
+ elif not (
+ self.applicable_on_material_request
+ or self.applicable_on_purchase_order
+ or self.applicable_on_booking_actual_expenses
+ ):
self.applicable_on_booking_actual_expenses = 1
+
def validate_expense_against_budget(args):
args = frappe._dict(args)
- if args.get('company') and not args.fiscal_year:
- args.fiscal_year = get_fiscal_year(args.get('posting_date'), company=args.get('company'))[0]
- frappe.flags.exception_approver_role = frappe.get_cached_value('Company',
- args.get('company'), 'exception_budget_approver_role')
+ if args.get("company") and not args.fiscal_year:
+ args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
+ frappe.flags.exception_approver_role = frappe.get_cached_value(
+ "Company", args.get("company"), "exception_budget_approver_role"
+ )
if not args.account:
args.account = args.get("expense_account")
- if not (args.get('account') and args.get('cost_center')) and args.item_code:
+ if not (args.get("account") and args.get("cost_center")) and args.item_code:
args.cost_center, args.account = get_item_details(args)
if not args.account:
return
- for budget_against in ['project', 'cost_center'] + get_accounting_dimensions():
- if (args.get(budget_against) and args.account
- and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})):
+ for budget_against in ["project", "cost_center"] + get_accounting_dimensions():
+ if (
+ args.get(budget_against)
+ and args.account
+ and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
+ ):
doctype = frappe.unscrub(budget_against)
- 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"])
condition = """and exists(select name from `tab%s`
- where lft<=%s and rgt>=%s and name=b.%s)""" % (doctype, lft, rgt, budget_against) #nosec
+ where lft<=%s and rgt>=%s and name=b.%s)""" % (
+ doctype,
+ lft,
+ rgt,
+ budget_against,
+ ) # nosec
args.is_tree = True
else:
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
@@ -123,7 +154,8 @@ def validate_expense_against_budget(args):
args.budget_against_field = budget_against
args.budget_against_doctype = doctype
- budget_records = frappe.db.sql("""
+ budget_records = frappe.db.sql(
+ """
select
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request,
@@ -138,11 +170,17 @@ def validate_expense_against_budget(args):
b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1
{condition}
- """.format(condition=condition, budget_against_field=budget_against), (args.fiscal_year, args.account), as_dict=True) #nosec
+ """.format(
+ condition=condition, budget_against_field=budget_against
+ ),
+ (args.fiscal_year, args.account),
+ as_dict=True,
+ ) # nosec
if budget_records:
validate_budget_records(args, budget_records)
+
def validate_budget_records(args, budget_records):
for budget in budget_records:
if flt(budget.budget_amount):
@@ -150,88 +188,118 @@ def validate_budget_records(args, budget_records):
yearly_action, monthly_action = get_actions(args, budget)
if monthly_action in ["Stop", "Warn"]:
- budget_amount = get_accumulated_monthly_budget(budget.monthly_distribution,
- args.posting_date, args.fiscal_year, budget.budget_amount)
+ budget_amount = get_accumulated_monthly_budget(
+ budget.monthly_distribution, args.posting_date, args.fiscal_year, budget.budget_amount
+ )
args["month_end_date"] = get_last_day(args.posting_date)
- compare_expense_with_budget(args, budget_amount,
- _("Accumulated Monthly"), monthly_action, budget.budget_against, amount)
+ compare_expense_with_budget(
+ args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
+ )
+
+ if (
+ yearly_action in ("Stop", "Warn")
+ and monthly_action != "Stop"
+ and yearly_action != monthly_action
+ ):
+ compare_expense_with_budget(
+ args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
+ )
- if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
- and yearly_action != monthly_action:
- compare_expense_with_budget(args, flt(budget.budget_amount),
- _("Annual"), yearly_action, budget.budget_against, amount)
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
actual_expense = amount or get_actual_expense(args)
if actual_expense > budget_amount:
diff = actual_expense - budget_amount
- currency = frappe.get_cached_value('Company', args.company, 'default_currency')
+ currency = frappe.get_cached_value("Company", args.company, "default_currency")
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
- _(action_for), frappe.bold(args.account), args.budget_against_field,
- frappe.bold(budget_against),
- frappe.bold(fmt_money(budget_amount, currency=currency)),
- frappe.bold(fmt_money(diff, currency=currency)))
+ _(action_for),
+ frappe.bold(args.account),
+ args.budget_against_field,
+ frappe.bold(budget_against),
+ frappe.bold(fmt_money(budget_amount, currency=currency)),
+ frappe.bold(fmt_money(diff, currency=currency)),
+ )
- if (frappe.flags.exception_approver_role
- and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)):
+ if (
+ frappe.flags.exception_approver_role
+ and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)
+ ):
action = "Warn"
- if action=="Stop":
+ if action == "Stop":
frappe.throw(msg, BudgetError)
else:
- frappe.msgprint(msg, indicator='orange')
+ frappe.msgprint(msg, indicator="orange")
+
def get_actions(args, budget):
yearly_action = budget.action_if_annual_budget_exceeded
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
- if args.get('doctype') == 'Material Request' and budget.for_material_request:
+ if args.get("doctype") == "Material Request" and budget.for_material_request:
yearly_action = budget.action_if_annual_budget_exceeded_on_mr
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded_on_mr
- elif args.get('doctype') == 'Purchase Order' and budget.for_purchase_order:
+ elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
yearly_action = budget.action_if_annual_budget_exceeded_on_po
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded_on_po
return yearly_action, monthly_action
+
def get_amount(args, budget):
amount = 0
- if args.get('doctype') == 'Material Request' and budget.for_material_request:
- amount = (get_requested_amount(args, budget)
- + get_ordered_amount(args, budget) + get_actual_expense(args))
+ if args.get("doctype") == "Material Request" and budget.for_material_request:
+ amount = (
+ get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args)
+ )
- elif args.get('doctype') == 'Purchase Order' and budget.for_purchase_order:
+ elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
amount = get_ordered_amount(args, budget) + get_actual_expense(args)
return amount
-def get_requested_amount(args, budget):
- item_code = args.get('item_code')
- condition = get_other_condition(args, budget, 'Material Request')
- data = frappe.db.sql(""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
+def get_requested_amount(args, budget):
+ item_code = args.get("item_code")
+ condition = get_other_condition(args, budget, "Material Request")
+
+ data = frappe.db.sql(
+ """ select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {0} and
- parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition), item_code, as_list=1)
+ parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(
+ condition
+ ),
+ item_code,
+ as_list=1,
+ )
return data[0][0] if data else 0
+
def get_ordered_amount(args, budget):
- item_code = args.get('item_code')
- condition = get_other_condition(args, budget, 'Purchase Order')
+ item_code = args.get("item_code")
+ condition = get_other_condition(args, budget, "Purchase Order")
- data = frappe.db.sql(""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
+ data = frappe.db.sql(
+ """ select ifnull(sum(child.amount - child.billed_amt), 0) as amount
from `tabPurchase Order Item` child, `tabPurchase Order` parent where
parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
- and parent.status != 'Closed' and {0}""".format(condition), item_code, as_list=1)
+ and parent.status != 'Closed' and {0}""".format(
+ condition
+ ),
+ item_code,
+ as_list=1,
+ )
return data[0][0] if data else 0
+
def get_other_condition(args, budget, for_doc):
condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = args.get("budget_against_field")
@@ -239,41 +307,51 @@ def get_other_condition(args, budget, for_doc):
if budget_against_field and args.get(budget_against_field):
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
- if args.get('fiscal_year'):
- date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date'
- start_date, end_date = frappe.db.get_value('Fiscal Year', args.get('fiscal_year'),
- ['year_start_date', 'year_end_date'])
+ if args.get("fiscal_year"):
+ date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
+ start_date, end_date = frappe.db.get_value(
+ "Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
+ )
condition += """ and parent.%s
- between '%s' and '%s' """ %(date_field, start_date, end_date)
+ between '%s' and '%s' """ % (
+ date_field,
+ start_date,
+ end_date,
+ )
return condition
+
def get_actual_expense(args):
if not args.budget_against_doctype:
args.budget_against_doctype = frappe.unscrub(args.budget_against_field)
- budget_against_field = args.get('budget_against_field')
- condition1 = " and gle.posting_date <= %(month_end_date)s" \
- if args.get("month_end_date") else ""
+ budget_against_field = args.get("budget_against_field")
+ condition1 = " and gle.posting_date <= %(month_end_date)s" if args.get("month_end_date") else ""
if args.is_tree:
- lft_rgt = frappe.db.get_value(args.budget_against_doctype,
- args.get(budget_against_field), ["lft", "rgt"], as_dict=1)
+ lft_rgt = frappe.db.get_value(
+ args.budget_against_doctype, args.get(budget_against_field), ["lft", "rgt"], as_dict=1
+ )
args.update(lft_rgt)
condition2 = """and exists(select name from `tab{doctype}`
where lft>=%(lft)s and rgt<=%(rgt)s
- and name=gle.{budget_against_field})""".format(doctype=args.budget_against_doctype, #nosec
- budget_against_field=budget_against_field)
+ and name=gle.{budget_against_field})""".format(
+ doctype=args.budget_against_doctype, budget_against_field=budget_against_field # nosec
+ )
else:
condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against} and
- gle.{budget_against} = %({budget_against})s)""".format(doctype=args.budget_against_doctype,
- budget_against = budget_against_field)
+ gle.{budget_against} = %({budget_against})s)""".format(
+ doctype=args.budget_against_doctype, budget_against=budget_against_field
+ )
- amount = flt(frappe.db.sql("""
+ amount = flt(
+ frappe.db.sql(
+ """
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
where gle.account=%(account)s
@@ -282,46 +360,59 @@ def get_actual_expense(args):
and gle.company=%(company)s
and gle.docstatus=1
{condition2}
- """.format(condition1=condition1, condition2=condition2), (args))[0][0]) #nosec
+ """.format(
+ condition1=condition1, condition2=condition2
+ ),
+ (args),
+ )[0][0]
+ ) # nosec
return amount
+
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}
if monthly_distribution:
- for d in frappe.db.sql("""select mdp.month, mdp.percentage_allocation
+ for d in frappe.db.sql(
+ """select mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
- where mdp.parent=md.name and md.fiscal_year=%s""", fiscal_year, as_dict=1):
- distribution.setdefault(d.month, d.percentage_allocation)
+ where mdp.parent=md.name and md.fiscal_year=%s""",
+ fiscal_year,
+ as_dict=1,
+ ):
+ distribution.setdefault(d.month, d.percentage_allocation)
dt = frappe.db.get_value("Fiscal Year", fiscal_year, "year_start_date")
accumulated_percentage = 0.0
- while(dt <= getdate(posting_date)):
+ while dt <= getdate(posting_date):
if monthly_distribution:
accumulated_percentage += distribution.get(getdate(dt).strftime("%B"), 0)
else:
- accumulated_percentage += 100.0/12
+ accumulated_percentage += 100.0 / 12
dt = add_months(dt, 1)
return annual_budget * accumulated_percentage / 100
+
def get_item_details(args):
cost_center, expense_account = None, None
- if not args.get('company'):
+ if not args.get("company"):
return cost_center, expense_account
if args.item_code:
- item_defaults = frappe.db.get_value('Item Default',
- {'parent': args.item_code, 'company': args.get('company')},
- ['buying_cost_center', 'expense_account'])
+ item_defaults = frappe.db.get_value(
+ "Item Default",
+ {"parent": args.item_code, "company": args.get("company")},
+ ["buying_cost_center", "expense_account"],
+ )
if item_defaults:
cost_center, expense_account = item_defaults
if not (cost_center and expense_account):
- for doctype in ['Item Group', 'Company']:
+ for doctype in ["Item Group", "Company"]:
data = get_expense_cost_center(doctype, args)
if not cost_center and data:
@@ -335,11 +426,15 @@ def get_item_details(args):
return cost_center, expense_account
+
def get_expense_cost_center(doctype, args):
- if doctype == 'Item Group':
- return frappe.db.get_value('Item Default',
- {'parent': args.get(frappe.scrub(doctype)), 'company': args.get('company')},
- ['buying_cost_center', 'expense_account'])
+ if doctype == "Item Group":
+ return frappe.db.get_value(
+ "Item Default",
+ {"parent": args.get(frappe.scrub(doctype)), "company": args.get("company")},
+ ["buying_cost_center", "expense_account"],
+ )
else:
- return frappe.db.get_value(doctype, args.get(frappe.scrub(doctype)),\
- ['cost_center', 'default_expense_account'])
+ return frappe.db.get_value(
+ doctype, args.get(frappe.scrub(doctype)), ["cost_center", "default_expense_account"]
+ )
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index 9a83a0aa9a..c48c7d97a2 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -11,7 +11,8 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
-test_dependencies = ['Monthly Distribution']
+test_dependencies = ["Monthly Distribution"]
+
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
@@ -19,11 +20,18 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 40000,
+ "_Test Cost Center - _TC",
+ posting_date=nowdate(),
+ submit=True,
+ )
- self.assertTrue(frappe.db.get_value("GL Entry",
- {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
+ self.assertTrue(
+ frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
+ )
budget.cancel()
jv.cancel()
@@ -33,10 +41,17 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 40000,
+ "_Test Cost Center - _TC",
+ posting_date=nowdate(),
+ )
self.assertRaises(BudgetError, jv.submit)
@@ -48,49 +63,65 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 40000,
+ "_Test Cost Center - _TC",
+ posting_date=nowdate(),
+ )
self.assertRaises(BudgetError, jv.submit)
- frappe.db.set_value('Company', budget.company, 'exception_budget_approver_role', 'Accounts User')
+ frappe.db.set_value("Company", budget.company, "exception_budget_approver_role", "Accounts User")
jv.submit()
- self.assertEqual(frappe.db.get_value('Journal Entry', jv.name, 'docstatus'), 1)
+ self.assertEqual(frappe.db.get_value("Journal Entry", jv.name, "docstatus"), 1)
jv.cancel()
- frappe.db.set_value('Company', budget.company, 'exception_budget_approver_role', '')
+ frappe.db.set_value("Company", budget.company, "exception_budget_approver_role", "")
budget.load_from_db()
budget.cancel()
def test_monthly_budget_crossed_for_mr(self):
- budget = make_budget(applicable_on_material_request=1,
- applicable_on_purchase_order=1, action_if_accumulated_monthly_budget_exceeded_on_mr="Stop",
- budget_against="Cost Center")
+ budget = make_budget(
+ applicable_on_material_request=1,
+ applicable_on_purchase_order=1,
+ action_if_accumulated_monthly_budget_exceeded_on_mr="Stop",
+ budget_against="Cost Center",
+ )
fiscal_year = get_fiscal_year(nowdate())[0]
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
- mr = frappe.get_doc({
- "doctype": "Material Request",
- "material_request_type": "Purchase",
- "transaction_date": nowdate(),
- "company": budget.company,
- "items": [{
- 'item_code': '_Test Item',
- 'qty': 1,
- 'uom': "_Test UOM",
- 'warehouse': '_Test Warehouse - _TC',
- 'schedule_date': nowdate(),
- 'rate': 100000,
- 'expense_account': '_Test Account Cost for Goods Sold - _TC',
- 'cost_center': '_Test Cost Center - _TC'
- }]
- })
+ mr = frappe.get_doc(
+ {
+ "doctype": "Material Request",
+ "material_request_type": "Purchase",
+ "transaction_date": nowdate(),
+ "company": budget.company,
+ "items": [
+ {
+ "item_code": "_Test Item",
+ "qty": 1,
+ "uom": "_Test UOM",
+ "warehouse": "_Test Warehouse - _TC",
+ "schedule_date": nowdate(),
+ "rate": 100000,
+ "expense_account": "_Test Account Cost for Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ }
+ ],
+ }
+ )
mr.set_missing_values()
@@ -100,11 +131,16 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_monthly_budget_crossed_for_po(self):
- budget = make_budget(applicable_on_purchase_order=1,
- action_if_accumulated_monthly_budget_exceeded_on_po="Stop", budget_against="Cost Center")
+ budget = make_budget(
+ applicable_on_purchase_order=1,
+ action_if_accumulated_monthly_budget_exceeded_on_po="Stop",
+ budget_against="Cost Center",
+ )
fiscal_year = get_fiscal_year(nowdate())[0]
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
@@ -122,12 +158,20 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project")
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
project = frappe.get_value("Project", {"project_name": "_Test Project"})
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 40000,
+ "_Test Cost Center - _TC",
+ project=project,
+ posting_date=nowdate(),
+ )
self.assertRaises(BudgetError, jv.submit)
@@ -139,8 +183,13 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 250000, "_Test Cost Center - _TC", posting_date=nowdate())
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 250000,
+ "_Test Cost Center - _TC",
+ posting_date=nowdate(),
+ )
self.assertRaises(BudgetError, jv.submit)
@@ -153,9 +202,14 @@ class TestBudget(unittest.TestCase):
project = frappe.get_value("Project", {"project_name": "_Test Project"})
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
- project=project, posting_date=nowdate())
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 250000,
+ "_Test Cost Center - _TC",
+ project=project,
+ posting_date=nowdate(),
+ )
self.assertRaises(BudgetError, jv.submit)
@@ -169,14 +223,23 @@ class TestBudget(unittest.TestCase):
if month > 9:
month = 9
- for i in range(month+1):
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
+ for i in range(month + 1):
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 20000,
+ "_Test Cost Center - _TC",
+ posting_date=nowdate(),
+ submit=True,
+ )
- self.assertTrue(frappe.db.get_value("GL Entry",
- {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
+ self.assertTrue(
+ frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
+ )
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
self.assertRaises(BudgetError, jv.cancel)
@@ -193,14 +256,23 @@ class TestBudget(unittest.TestCase):
project = frappe.get_value("Project", {"project_name": "_Test Project"})
for i in range(month + 1):
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
- project=project)
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 20000,
+ "_Test Cost Center - _TC",
+ posting_date=nowdate(),
+ submit=True,
+ project=project,
+ )
- self.assertTrue(frappe.db.get_value("GL Entry",
- {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
+ self.assertTrue(
+ frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
+ )
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
self.assertRaises(BudgetError, jv.cancel)
@@ -212,10 +284,17 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date=nowdate())
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 40000,
+ "_Test Cost Center 2 - _TC",
+ posting_date=nowdate(),
+ )
self.assertRaises(BudgetError, jv.submit)
@@ -226,19 +305,28 @@ class TestBudget(unittest.TestCase):
cost_center = "_Test Cost Center 3 - _TC"
if not frappe.db.exists("Cost Center", cost_center):
- frappe.get_doc({
- 'doctype': 'Cost Center',
- 'cost_center_name': '_Test Cost Center 3',
- 'parent_cost_center': "_Test Company - _TC",
- 'company': '_Test Company',
- 'is_group': 0
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Cost Center",
+ "cost_center_name": "_Test Cost Center 3",
+ "parent_cost_center": "_Test Company - _TC",
+ "company": "_Test Company",
+ "is_group": 0,
+ }
+ ).insert(ignore_permissions=True)
budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
- frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+ frappe.db.set_value(
+ "Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
+ )
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 40000, cost_center, posting_date=nowdate())
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 40000,
+ cost_center,
+ posting_date=nowdate(),
+ )
self.assertRaises(BudgetError, jv.submit)
@@ -255,14 +343,16 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
fiscal_year = get_fiscal_year(nowdate())[0]
- args = frappe._dict({
- "account": "_Test Account Cost for Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "monthly_end_date": posting_date,
- "company": "_Test Company",
- "fiscal_year": fiscal_year,
- "budget_against_field": budget_against_field,
- })
+ args = frappe._dict(
+ {
+ "account": "_Test Account Cost for Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "monthly_end_date": posting_date,
+ "company": "_Test Company",
+ "fiscal_year": fiscal_year,
+ "budget_against_field": budget_against_field,
+ }
+ )
if not args.get(budget_against_field):
args[budget_against_field] = budget_against
@@ -271,26 +361,42 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
if existing_expense:
if budget_against_field == "cost_center":
- make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
+ make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ -existing_expense,
+ "_Test Cost Center - _TC",
+ posting_date=nowdate(),
+ submit=True,
+ )
elif budget_against_field == "project":
- make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate())
+ make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ -existing_expense,
+ "_Test Cost Center - _TC",
+ submit=True,
+ project=budget_against,
+ posting_date=nowdate(),
+ )
+
def make_budget(**args):
args = frappe._dict(args)
- budget_against=args.budget_against
- cost_center=args.cost_center
+ budget_against = args.budget_against
+ cost_center = args.cost_center
fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project":
project_name = "{0}%".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:
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
- budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)})
+ budget_list = frappe.get_all(
+ "Budget", fields=["name"], filters={"name": ("like", cost_center_name)}
+ )
for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
@@ -300,7 +406,7 @@ def make_budget(**args):
if budget_against == "Project":
budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
else:
- budget.cost_center =cost_center or "_Test Cost Center - _TC"
+ budget.cost_center = cost_center or "_Test Cost Center - _TC"
monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution")
monthly_distribution.fiscal_year = fiscal_year
@@ -312,20 +418,27 @@ def make_budget(**args):
budget.action_if_annual_budget_exceeded = "Stop"
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
budget.budget_against = budget_against
- budget.append("accounts", {
- "account": "_Test Account Cost for Goods Sold - _TC",
- "budget_amount": 200000
- })
+ budget.append(
+ "accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000}
+ )
if args.applicable_on_material_request:
budget.applicable_on_material_request = 1
- budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or 'Warn'
- budget.action_if_accumulated_monthly_budget_exceeded_on_mr = args.action_if_accumulated_monthly_budget_exceeded_on_mr or 'Warn'
+ budget.action_if_annual_budget_exceeded_on_mr = (
+ args.action_if_annual_budget_exceeded_on_mr or "Warn"
+ )
+ budget.action_if_accumulated_monthly_budget_exceeded_on_mr = (
+ args.action_if_accumulated_monthly_budget_exceeded_on_mr or "Warn"
+ )
if args.applicable_on_purchase_order:
budget.applicable_on_purchase_order = 1
- budget.action_if_annual_budget_exceeded_on_po = args.action_if_annual_budget_exceeded_on_po or 'Warn'
- budget.action_if_accumulated_monthly_budget_exceeded_on_po = args.action_if_accumulated_monthly_budget_exceeded_on_po or 'Warn'
+ budget.action_if_annual_budget_exceeded_on_po = (
+ args.action_if_annual_budget_exceeded_on_po or "Warn"
+ )
+ budget.action_if_accumulated_monthly_budget_exceeded_on_po = (
+ args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
+ )
budget.insert()
budget.submit()
diff --git a/erpnext/accounts/doctype/c_form/c_form.py b/erpnext/accounts/doctype/c_form/c_form.py
index 61331d32d8..0de75c7869 100644
--- a/erpnext/accounts/doctype/c_form/c_form.py
+++ b/erpnext/accounts/doctype/c_form/c_form.py
@@ -11,28 +11,42 @@ from frappe.utils import flt
class CForm(Document):
def validate(self):
"""Validate invoice that c-form is applicable
- and no other c-form is received for that"""
+ and no other c-form is received for that"""
- for d in self.get('invoices'):
+ for d in self.get("invoices"):
if d.invoice_no:
- inv = frappe.db.sql("""select c_form_applicable, c_form_no from
- `tabSales Invoice` where name = %s and docstatus = 1""", d.invoice_no)
+ inv = frappe.db.sql(
+ """select c_form_applicable, c_form_no from
+ `tabSales Invoice` where name = %s and docstatus = 1""",
+ d.invoice_no,
+ )
- if inv and inv[0][0] != 'Yes':
+ if inv and inv[0][0] != "Yes":
frappe.throw(_("C-form is not applicable for Invoice: {0}").format(d.invoice_no))
elif inv and inv[0][1] and inv[0][1] != self.name:
- frappe.throw(_("""Invoice {0} is tagged in another C-form: {1}.
+ frappe.throw(
+ _(
+ """Invoice {0} is tagged in another C-form: {1}.
If you want to change C-form no for this invoice,
- please remove invoice no from the previous c-form and then try again"""\
- .format(d.invoice_no, inv[0][1])))
+ please remove invoice no from the previous c-form and then try again""".format(
+ d.invoice_no, inv[0][1]
+ )
+ )
+ )
elif not inv:
- frappe.throw(_("Row {0}: Invoice {1} is invalid, it might be cancelled / does not exist. \
- Please enter a valid Invoice".format(d.idx, d.invoice_no)))
+ frappe.throw(
+ _(
+ "Row {0}: Invoice {1} is invalid, it might be cancelled / does not exist. \
+ Please enter a valid Invoice".format(
+ d.idx, d.invoice_no
+ )
+ )
+ )
def on_update(self):
- """ Update C-Form No on invoices"""
+ """Update C-Form No on invoices"""
self.set_total_invoiced_amount()
def on_submit(self):
@@ -43,30 +57,40 @@ class CForm(Document):
frappe.db.sql("""update `tabSales Invoice` set c_form_no=null where c_form_no=%s""", self.name)
def set_cform_in_sales_invoices(self):
- inv = [d.invoice_no for d in self.get('invoices')]
+ inv = [d.invoice_no for d in self.get("invoices")]
if inv:
- frappe.db.sql("""update `tabSales Invoice` set c_form_no=%s, modified=%s where name in (%s)""" %
- ('%s', '%s', ', '.join(['%s'] * len(inv))), tuple([self.name, self.modified] + inv))
+ frappe.db.sql(
+ """update `tabSales Invoice` set c_form_no=%s, modified=%s where name in (%s)"""
+ % ("%s", "%s", ", ".join(["%s"] * len(inv))),
+ tuple([self.name, self.modified] + inv),
+ )
- frappe.db.sql("""update `tabSales Invoice` set c_form_no = null, modified = %s
- where name not in (%s) and ifnull(c_form_no, '') = %s""" %
- ('%s', ', '.join(['%s']*len(inv)), '%s'), tuple([self.modified] + inv + [self.name]))
+ frappe.db.sql(
+ """update `tabSales Invoice` set c_form_no = null, modified = %s
+ where name not in (%s) and ifnull(c_form_no, '') = %s"""
+ % ("%s", ", ".join(["%s"] * len(inv)), "%s"),
+ tuple([self.modified] + inv + [self.name]),
+ )
else:
frappe.throw(_("Please enter atleast 1 invoice in the table"))
def set_total_invoiced_amount(self):
- total = sum(flt(d.grand_total) for d in self.get('invoices'))
- frappe.db.set(self, 'total_invoiced_amount', total)
+ total = sum(flt(d.grand_total) for d in self.get("invoices"))
+ frappe.db.set(self, "total_invoiced_amount", total)
@frappe.whitelist()
def get_invoice_details(self, invoice_no):
- """ Pull details from invoices for referrence """
+ """Pull details from invoices for referrence"""
if invoice_no:
- inv = frappe.db.get_value("Sales Invoice", invoice_no,
- ["posting_date", "territory", "base_net_total", "base_grand_total"], as_dict=True)
+ inv = frappe.db.get_value(
+ "Sales Invoice",
+ invoice_no,
+ ["posting_date", "territory", "base_net_total", "base_grand_total"],
+ as_dict=True,
+ )
return {
- 'invoice_date' : inv.posting_date,
- 'territory' : inv.territory,
- 'net_total' : inv.base_net_total,
- 'grand_total' : inv.base_grand_total
+ "invoice_date": inv.posting_date,
+ "territory": inv.territory,
+ "net_total": inv.base_net_total,
+ "grand_total": inv.base_grand_total,
}
diff --git a/erpnext/accounts/doctype/c_form/test_c_form.py b/erpnext/accounts/doctype/c_form/test_c_form.py
index fa34c255c6..87ad60fdda 100644
--- a/erpnext/accounts/doctype/c_form/test_c_form.py
+++ b/erpnext/accounts/doctype/c_form/test_c_form.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('C-Form')
+
class TestCForm(unittest.TestCase):
pass
diff --git a/erpnext/accounts/doctype/cash_flow_mapper/default_cash_flow_mapper.py b/erpnext/accounts/doctype/cash_flow_mapper/default_cash_flow_mapper.py
index 6e7b687c04..79feb2dae2 100644
--- a/erpnext/accounts/doctype/cash_flow_mapper/default_cash_flow_mapper.py
+++ b/erpnext/accounts/doctype/cash_flow_mapper/default_cash_flow_mapper.py
@@ -1,25 +1,25 @@
DEFAULT_MAPPERS = [
- {
- 'doctype': 'Cash Flow Mapper',
- 'section_footer': 'Net cash generated by operating activities',
- 'section_header': 'Cash flows from operating activities',
- 'section_leader': 'Adjustments for',
- 'section_name': 'Operating Activities',
- 'position': 0,
- 'section_subtotal': 'Cash generated from operations',
- },
- {
- 'doctype': 'Cash Flow Mapper',
- 'position': 1,
- 'section_footer': 'Net cash used in investing activities',
- 'section_header': 'Cash flows from investing activities',
- 'section_name': 'Investing Activities'
- },
- {
- 'doctype': 'Cash Flow Mapper',
- 'position': 2,
- 'section_footer': 'Net cash used in financing activites',
- 'section_header': 'Cash flows from financing activities',
- 'section_name': 'Financing Activities',
- }
+ {
+ "doctype": "Cash Flow Mapper",
+ "section_footer": "Net cash generated by operating activities",
+ "section_header": "Cash flows from operating activities",
+ "section_leader": "Adjustments for",
+ "section_name": "Operating Activities",
+ "position": 0,
+ "section_subtotal": "Cash generated from operations",
+ },
+ {
+ "doctype": "Cash Flow Mapper",
+ "position": 1,
+ "section_footer": "Net cash used in investing activities",
+ "section_header": "Cash flows from investing activities",
+ "section_name": "Investing Activities",
+ },
+ {
+ "doctype": "Cash Flow Mapper",
+ "position": 2,
+ "section_footer": "Net cash used in financing activites",
+ "section_header": "Cash flows from financing activities",
+ "section_name": "Financing Activities",
+ },
]
diff --git a/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py b/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py
index cd8381a4bd..3bce4d51c7 100644
--- a/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py
+++ b/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py
@@ -11,9 +11,11 @@ class CashFlowMapping(Document):
self.validate_checked_options()
def validate_checked_options(self):
- checked_fields = [d for d in self.meta.fields if d.fieldtype == 'Check' and self.get(d.fieldname) == 1]
+ checked_fields = [
+ d for d in self.meta.fields if d.fieldtype == "Check" and self.get(d.fieldname) == 1
+ ]
if len(checked_fields) > 1:
frappe.throw(
- frappe._('You can only select a maximum of one option from the list of check boxes.'),
- title='Error'
+ frappe._("You can only select a maximum of one option from the list of check boxes."),
+ title="Error",
)
diff --git a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.py b/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.py
index abb2567046..19f2425b4c 100644
--- a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.py
+++ b/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.py
@@ -9,19 +9,16 @@ import frappe
class TestCashFlowMapping(unittest.TestCase):
def setUp(self):
if frappe.db.exists("Cash Flow Mapping", "Test Mapping"):
- frappe.delete_doc('Cash Flow Mappping', 'Test Mapping')
+ frappe.delete_doc("Cash Flow Mappping", "Test Mapping")
def tearDown(self):
- frappe.delete_doc('Cash Flow Mapping', 'Test Mapping')
+ frappe.delete_doc("Cash Flow Mapping", "Test Mapping")
def test_multiple_selections_not_allowed(self):
- doc = frappe.new_doc('Cash Flow Mapping')
- doc.mapping_name = 'Test Mapping'
- doc.label = 'Test label'
- doc.append(
- 'accounts',
- {'account': 'Accounts Receivable - _TC'}
- )
+ doc = frappe.new_doc("Cash Flow Mapping")
+ doc.mapping_name = "Test Mapping"
+ doc.label = "Test label"
+ doc.append("accounts", {"account": "Accounts Receivable - _TC"})
doc.is_working_capital = 1
doc.is_finance_cost = 1
diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
index 9fbd0c97c1..6013807728 100644
--- a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
+++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
@@ -17,11 +17,14 @@ class CashierClosing(Document):
self.make_calculations()
def get_outstanding(self):
- values = frappe.db.sql("""
+ values = frappe.db.sql(
+ """
select sum(outstanding_amount)
from `tabSales Invoice`
where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s
- """, (self.date, self.from_time, self.time, self.user))
+ """,
+ (self.date, self.from_time, self.time, self.user),
+ )
self.outstanding_amount = flt(values[0][0] if values else 0)
def make_calculations(self):
@@ -29,7 +32,9 @@ class CashierClosing(Document):
for i in self.payments:
total += flt(i.amount)
- self.net_amount = total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
+ self.net_amount = (
+ total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
+ )
def validate_time(self):
if self.from_time >= self.time:
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index aaacce4eb9..01bf1c23e9 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -25,33 +25,41 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
class ChartofAccountsImporter(Document):
def validate(self):
if self.import_file:
- get_coa('Chart of Accounts Importer', 'All Accounts', file_name=self.import_file, for_validate=1)
+ get_coa(
+ "Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1
+ )
+
def validate_columns(data):
if not data:
- frappe.throw(_('No data found. Seems like you uploaded a blank file'))
+ frappe.throw(_("No data found. Seems like you uploaded a blank file"))
no_of_columns = max([len(d) for d in data])
if no_of_columns > 7:
- frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'),
- title=(_("Wrong Template")))
+ frappe.throw(
+ _("More columns found than expected. Please compare the uploaded file with standard template"),
+ title=(_("Wrong Template")),
+ )
+
@frappe.whitelist()
def validate_company(company):
- parent_company, allow_account_creation_against_child_company = frappe.db.get_value('Company',
- {'name': company}, ['parent_company',
- 'allow_account_creation_against_child_company'])
+ parent_company, allow_account_creation_against_child_company = frappe.db.get_value(
+ "Company", {"name": company}, ["parent_company", "allow_account_creation_against_child_company"]
+ )
if parent_company and (not allow_account_creation_against_child_company):
msg = _("{} is a child company.").format(frappe.bold(company)) + " "
msg += _("Please import accounts against parent company or enable {} in company master.").format(
- frappe.bold('Allow Account Creation Against Child Company'))
- frappe.throw(msg, title=_('Wrong Company'))
+ frappe.bold("Allow Account Creation Against Child Company")
+ )
+ frappe.throw(msg, title=_("Wrong Company"))
- if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1):
+ if frappe.db.get_all("GL Entry", {"company": company}, "name", limit=1):
return False
+
@frappe.whitelist()
def import_coa(file_name, company):
# delete existing data for accounts
@@ -60,7 +68,7 @@ def import_coa(file_name, company):
# create accounts
file_doc, extension = get_file(file_name)
- if extension == 'csv':
+ if extension == "csv":
data = generate_data_from_csv(file_doc)
else:
data = generate_data_from_excel(file_doc, extension)
@@ -72,27 +80,33 @@ def import_coa(file_name, company):
# trigger on_update for company to reset default accounts
set_default_accounts(company)
+
def get_file(file_name):
file_doc = frappe.get_doc("File", {"file_url": file_name})
parts = file_doc.get_extension()
extension = parts[1]
extension = extension.lstrip(".")
- if extension not in ('csv', 'xlsx', 'xls'):
- frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"))
+ if extension not in ("csv", "xlsx", "xls"):
+ frappe.throw(
+ _(
+ "Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"
+ )
+ )
+
+ return file_doc, extension
- return file_doc, extension
def generate_data_from_csv(file_doc, as_dict=False):
- ''' read csv file and return the generated nested tree '''
+ """read csv file and return the generated nested tree"""
file_path = file_doc.get_full_path()
data = []
- with open(file_path, 'r') as in_file:
+ with open(file_path, "r") as in_file:
csv_reader = list(csv.reader(in_file))
headers = csv_reader[0]
- del csv_reader[0] # delete top row and headers row
+ del csv_reader[0] # delete top row and headers row
for row in csv_reader:
if as_dict:
@@ -106,6 +120,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
# convert csv data
return data
+
def generate_data_from_excel(file_doc, extension, as_dict=False):
content = file_doc.get_content()
@@ -123,20 +138,21 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else:
if not row[1]:
- row[1] = row[0]
- row[3] = row[2]
+ row[1] = row[0]
+ row[3] = row[2]
data.append(row)
return data
+
@frappe.whitelist()
def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
- ''' called by tree view (to fetch node's children) '''
+ """called by tree view (to fetch node's children)"""
file_doc, extension = get_file(file_name)
- parent = None if parent==_('All Accounts') else parent
+ parent = None if parent == _("All Accounts") else parent
- if extension == 'csv':
+ if extension == "csv":
data = generate_data_from_csv(file_doc)
else:
data = generate_data_from_excel(file_doc, extension)
@@ -146,32 +162,33 @@ def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
if not for_validate:
forest = build_forest(data)
- accounts = build_tree_from_json("", chart_data=forest, from_coa_importer=True) # returns a list of dict in a tree render-able form
+ accounts = build_tree_from_json(
+ "", chart_data=forest, from_coa_importer=True
+ ) # returns a list of dict in a tree render-able form
# filter out to show data for the selected node only
- accounts = [d for d in accounts if d['parent_account']==parent]
+ accounts = [d for d in accounts if d["parent_account"] == parent]
return accounts
else:
- return {
- 'show_import_button': 1
- }
+ return {"show_import_button": 1}
+
def build_forest(data):
- '''
- converts list of list into a nested tree
- if a = [[1,1], [1,2], [3,2], [4,4], [5,4]]
- tree = {
- 1: {
- 2: {
- 3: {}
- }
- },
- 4: {
- 5: {}
- }
- }
- '''
+ """
+ converts list of list into a nested tree
+ if a = [[1,1], [1,2], [3,2], [4,4], [5,4]]
+ tree = {
+ 1: {
+ 2: {
+ 3: {}
+ }
+ },
+ 4: {
+ 5: {}
+ }
+ }
+ """
# set the value of nested dictionary
def set_nested(d, path, value):
@@ -195,8 +212,11 @@ def build_forest(data):
elif account_name == child:
parent_account_list = return_parent(data, parent_account)
if not parent_account_list and parent_account:
- frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
- frappe.bold(parent_account)))
+ frappe.throw(
+ _("The parent account {0} does not exists in the uploaded template").format(
+ frappe.bold(parent_account)
+ )
+ )
return [child] + parent_account_list
charts_map, paths = {}, []
@@ -205,7 +225,15 @@ def build_forest(data):
error_messages = []
for i in data:
- account_name, parent_account, account_number, parent_account_number, is_group, account_type, root_type = i
+ (
+ account_name,
+ parent_account,
+ account_number,
+ parent_account_number,
+ is_group,
+ account_type,
+ root_type,
+ ) = i
if not account_name:
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
@@ -216,13 +244,17 @@ def build_forest(data):
account_name = "{} - {}".format(account_number, account_name)
charts_map[account_name] = {}
- charts_map[account_name]['account_name'] = name
- if account_number: charts_map[account_name]["account_number"] = account_number
- if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group
- if account_type: charts_map[account_name]["account_type"] = account_type
- if root_type: charts_map[account_name]["root_type"] = root_type
+ charts_map[account_name]["account_name"] = name
+ if account_number:
+ charts_map[account_name]["account_number"] = account_number
+ if cint(is_group) == 1:
+ charts_map[account_name]["is_group"] = is_group
+ if account_type:
+ charts_map[account_name]["account_type"] = account_type
+ if root_type:
+ charts_map[account_name]["root_type"] = root_type
path = return_parent(data, account_name)[::-1]
- paths.append(path) # List of path is created
+ paths.append(path) # List of path is created
line_no += 1
if error_messages:
@@ -231,27 +263,32 @@ def build_forest(data):
out = {}
for path in paths:
for n, account_name in enumerate(path):
- set_nested(out, path[:n+1], charts_map[account_name]) # setting the value of nested dictionary.
+ set_nested(
+ out, path[: n + 1], charts_map[account_name]
+ ) # setting the value of nested dictionary.
return out
+
def build_response_as_excel(writer):
filename = frappe.generate_hash("", 10)
- with open(filename, 'wb') as f:
- f.write(cstr(writer.getvalue()).encode('utf-8'))
+ with open(filename, "wb") as f:
+ f.write(cstr(writer.getvalue()).encode("utf-8"))
f = open(filename)
reader = csv.reader(f)
from frappe.utils.xlsxutils import make_xlsx
+
xlsx_file = make_xlsx(reader, "Chart of Accounts Importer Template")
f.close()
os.remove(filename)
# write out response as a xlsx type
- frappe.response['filename'] = 'coa_importer_template.xlsx'
- frappe.response['filecontent'] = xlsx_file.getvalue()
- frappe.response['type'] = 'binary'
+ frappe.response["filename"] = "coa_importer_template.xlsx"
+ frappe.response["filecontent"] = xlsx_file.getvalue()
+ frappe.response["type"] = "binary"
+
@frappe.whitelist()
def download_template(file_type, template_type):
@@ -259,34 +296,46 @@ def download_template(file_type, template_type):
writer = get_template(template_type)
- if file_type == 'CSV':
+ if file_type == "CSV":
# download csv file
- frappe.response['result'] = cstr(writer.getvalue())
- frappe.response['type'] = 'csv'
- frappe.response['doctype'] = 'Chart of Accounts Importer'
+ frappe.response["result"] = cstr(writer.getvalue())
+ frappe.response["type"] = "csv"
+ frappe.response["doctype"] = "Chart of Accounts Importer"
else:
build_response_as_excel(writer)
+
def get_template(template_type):
- fields = ["Account Name", "Parent Account", "Account Number", "Parent Account Number", "Is Group", "Account Type", "Root Type"]
+ fields = [
+ "Account Name",
+ "Parent Account",
+ "Account Number",
+ "Parent Account Number",
+ "Is Group",
+ "Account Type",
+ "Root Type",
+ ]
writer = UnicodeWriter()
writer.writerow(fields)
- if template_type == 'Blank Template':
- for root_type in get_root_types():
- writer.writerow(['', '', '', 1, '', root_type])
+ if template_type == "Blank Template":
+ for root_type in get_root_types():
+ writer.writerow(["", "", "", 1, "", root_type])
for account in get_mandatory_group_accounts():
- writer.writerow(['', '', '', 1, account, "Asset"])
+ writer.writerow(["", "", "", 1, account, "Asset"])
for account_type in get_mandatory_account_types():
- writer.writerow(['', '', '', 0, account_type.get('account_type'), account_type.get('root_type')])
+ writer.writerow(
+ ["", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
+ )
else:
writer = get_sample_template(writer)
return writer
+
def get_sample_template(writer):
template = [
["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"],
@@ -316,7 +365,7 @@ def get_sample_template(writer):
@frappe.whitelist()
def validate_accounts(file_doc, extension):
- if extension == 'csv':
+ if extension == "csv":
accounts = generate_data_from_csv(file_doc, as_dict=True)
else:
accounts = generate_data_from_excel(file_doc, extension, as_dict=True)
@@ -325,7 +374,9 @@ def validate_accounts(file_doc, extension):
for account in accounts:
accounts_dict.setdefault(account["account_name"], account)
if "parent_account" not in account:
- msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
+ msg = _(
+ "Please make sure the file you are using has 'Parent Account' column present in the header."
+ )
msg += "
"
msg += _("Alternatively, you can download the template and fill your data in.")
frappe.throw(msg, title=_("Parent Account Missing"))
@@ -336,77 +387,106 @@ def validate_accounts(file_doc, extension):
return [True, len(accounts)]
+
def validate_root(accounts):
- roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
+ roots = [accounts[d] for d in accounts if not accounts[d].get("parent_account")]
error_messages = []
for account in roots:
if not account.get("root_type") and account.get("account_name"):
- error_messages.append(_("Please enter Root Type for account- {0}").format(account.get("account_name")))
+ error_messages.append(
+ _("Please enter Root Type for account- {0}").format(account.get("account_name"))
+ )
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
- error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name")))
+ error_messages.append(
+ _("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(
+ account.get("account_name")
+ )
+ )
validate_missing_roots(roots)
if error_messages:
frappe.throw("
".join(error_messages))
+
def validate_missing_roots(roots):
- root_types_added = set(d.get('root_type') for d in roots)
+ root_types_added = set(d.get("root_type") for d in roots)
missing = list(set(get_root_types()) - root_types_added)
if missing:
- frappe.throw(_("Please add Root Account for - {0}").format(' , '.join(missing)))
+ frappe.throw(_("Please add Root Account for - {0}").format(" , ".join(missing)))
+
def get_root_types():
- return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
+ return ("Asset", "Liability", "Expense", "Income", "Equity")
+
def get_report_type(root_type):
- if root_type in ('Asset', 'Liability', 'Equity'):
- return 'Balance Sheet'
+ if root_type in ("Asset", "Liability", "Equity"):
+ return "Balance Sheet"
else:
- return 'Profit and Loss'
+ return "Profit and Loss"
+
def get_mandatory_group_accounts():
- return ('Bank', 'Cash', 'Stock')
+ return ("Bank", "Cash", "Stock")
+
def get_mandatory_account_types():
return [
- {'account_type': 'Cost of Goods Sold', 'root_type': 'Expense'},
- {'account_type': 'Depreciation', 'root_type': 'Expense'},
- {'account_type': 'Fixed Asset', 'root_type': 'Asset'},
- {'account_type': 'Payable', 'root_type': 'Liability'},
- {'account_type': 'Receivable', 'root_type': 'Asset'},
- {'account_type': 'Stock Adjustment', 'root_type': 'Expense'},
- {'account_type': 'Bank', 'root_type': 'Asset'},
- {'account_type': 'Cash', 'root_type': 'Asset'},
- {'account_type': 'Stock', 'root_type': 'Asset'}
+ {"account_type": "Cost of Goods Sold", "root_type": "Expense"},
+ {"account_type": "Depreciation", "root_type": "Expense"},
+ {"account_type": "Fixed Asset", "root_type": "Asset"},
+ {"account_type": "Payable", "root_type": "Liability"},
+ {"account_type": "Receivable", "root_type": "Asset"},
+ {"account_type": "Stock Adjustment", "root_type": "Expense"},
+ {"account_type": "Bank", "root_type": "Asset"},
+ {"account_type": "Cash", "root_type": "Asset"},
+ {"account_type": "Stock", "root_type": "Asset"},
]
+
def unset_existing_data(company):
- linked = frappe.db.sql('''select fieldname from tabDocField
- where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True)
+ linked = frappe.db.sql(
+ '''select fieldname from tabDocField
+ where fieldtype="Link" and options="Account" and parent="Company"''',
+ as_dict=True,
+ )
# remove accounts data from company
- update_values = {d.fieldname: '' for d in linked}
- frappe.db.set_value('Company', company, update_values, update_values)
+ update_values = {d.fieldname: "" for d in linked}
+ frappe.db.set_value("Company", company, update_values, update_values)
# remove accounts data from various doctypes
- for doctype in ["Account", "Party Account", "Mode of Payment Account", "Tax Withholding Account",
- "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
- frappe.db.sql('''delete from `tab{0}` where `company`="%s"''' # nosec
- .format(doctype) % (company))
+ for doctype in [
+ "Account",
+ "Party Account",
+ "Mode of Payment Account",
+ "Tax Withholding Account",
+ "Sales Taxes and Charges Template",
+ "Purchase Taxes and Charges Template",
+ ]:
+ frappe.db.sql(
+ '''delete from `tab{0}` where `company`="%s"'''.format(doctype) % (company) # nosec
+ )
+
def set_default_accounts(company):
from erpnext.setup.doctype.company.company import install_country_fixtures
- company = frappe.get_doc('Company', company)
- company.update({
- "default_receivable_account": frappe.db.get_value("Account",
- {"company": company.name, "account_type": "Receivable", "is_group": 0}),
- "default_payable_account": frappe.db.get_value("Account",
- {"company": company.name, "account_type": "Payable", "is_group": 0})
- })
+
+ company = frappe.get_doc("Company", company)
+ company.update(
+ {
+ "default_receivable_account": frappe.db.get_value(
+ "Account", {"company": company.name, "account_type": "Receivable", "is_group": 0}
+ ),
+ "default_payable_account": frappe.db.get_value(
+ "Account", {"company": company.name, "account_type": "Payable", "is_group": 0}
+ ),
+ }
+ )
company.save()
install_country_fixtures(company.name, company.country)
diff --git a/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.py b/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.py
index 20cb42c109..f8ac66444b 100644
--- a/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.py
+++ b/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.py
@@ -10,17 +10,20 @@ from frappe.model.document import Document
class ChequePrintTemplate(Document):
pass
+
@frappe.whitelist()
def create_or_update_cheque_print_format(template_name):
if not frappe.db.exists("Print Format", template_name):
cheque_print = frappe.new_doc("Print Format")
- cheque_print.update({
- "doc_type": "Payment Entry",
- "standard": "No",
- "custom_format": 1,
- "print_format_type": "Jinja",
- "name": template_name
- })
+ cheque_print.update(
+ {
+ "doc_type": "Payment Entry",
+ "standard": "No",
+ "custom_format": 1,
+ "print_format_type": "Jinja",
+ "name": template_name,
+ }
+ )
else:
cheque_print = frappe.get_doc("Print Format", template_name)
@@ -69,10 +72,12 @@ def create_or_update_cheque_print_format(template_name):
{{doc.company}}
-"""%{
- "starting_position_from_top_edge": doc.starting_position_from_top_edge \
- if doc.cheque_size == "A4" else 0.0,
- "cheque_width": doc.cheque_width, "cheque_height": doc.cheque_height,
+""" % {
+ "starting_position_from_top_edge": doc.starting_position_from_top_edge
+ if doc.cheque_size == "A4"
+ else 0.0,
+ "cheque_width": doc.cheque_width,
+ "cheque_height": doc.cheque_height,
"acc_pay_dist_from_top_edge": doc.acc_pay_dist_from_top_edge,
"acc_pay_dist_from_left_edge": doc.acc_pay_dist_from_left_edge,
"message_to_show": doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
@@ -89,7 +94,7 @@ def create_or_update_cheque_print_format(template_name):
"amt_in_figures_from_top_edge": doc.amt_in_figures_from_top_edge,
"amt_in_figures_from_left_edge": doc.amt_in_figures_from_left_edge,
"signatory_from_top_edge": doc.signatory_from_top_edge,
- "signatory_from_left_edge": doc.signatory_from_left_edge
+ "signatory_from_left_edge": doc.signatory_from_left_edge,
}
cheque_print.save(ignore_permissions=True)
diff --git a/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py b/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py
index 2b323a9bf6..9b003ceaa3 100644
--- a/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py
+++ b/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Cheque Print Template')
+
class TestChequePrintTemplate(unittest.TestCase):
pass
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 07cc0764e3..31055c3fb4 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -10,11 +10,14 @@ from erpnext.accounts.utils import validate_field_number
class CostCenter(NestedSet):
- nsm_parent_field = 'parent_cost_center'
+ nsm_parent_field = "parent_cost_center"
def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number
- self.name = get_autoname_with_number(self.cost_center_number, self.cost_center_name, None, self.company)
+
+ self.name = get_autoname_with_number(
+ self.cost_center_number, self.cost_center_name, None, self.company
+ )
def validate(self):
self.validate_mandatory()
@@ -28,9 +31,12 @@ class CostCenter(NestedSet):
def validate_parent_cost_center(self):
if self.parent_cost_center:
- if not frappe.db.get_value('Cost Center', self.parent_cost_center, 'is_group'):
- frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
- frappe.bold(self.parent_cost_center)))
+ if not frappe.db.get_value("Cost Center", self.parent_cost_center, "is_group"):
+ frappe.throw(
+ _("{0} is not a group node. Please select a group node as parent cost center").format(
+ frappe.bold(self.parent_cost_center)
+ )
+ )
@frappe.whitelist()
def convert_group_to_ledger(self):
@@ -48,7 +54,9 @@ class CostCenter(NestedSet):
if self.if_allocation_exists_against_cost_center():
frappe.throw(_("Cost Center with Allocation records can not be converted to a group"))
if self.check_if_part_of_cost_center_allocation():
- frappe.throw(_("Cost Center is a part of Cost Center Allocation, hence cannot be converted to a group"))
+ frappe.throw(
+ _("Cost Center is a part of Cost Center Allocation, hence cannot be converted to a group")
+ )
if self.check_gle_exists():
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
self.is_group = 1
@@ -59,24 +67,26 @@ class CostCenter(NestedSet):
return frappe.db.get_value("GL Entry", {"cost_center": self.name})
def check_if_child_exists(self):
- return frappe.db.sql("select name from `tabCost Center` where \
- parent_cost_center = %s and docstatus != 2", self.name)
+ return frappe.db.sql(
+ "select name from `tabCost Center` where \
+ parent_cost_center = %s and docstatus != 2",
+ self.name,
+ )
def if_allocation_exists_against_cost_center(self):
- return frappe.db.get_value("Cost Center Allocation", filters = {
- "main_cost_center": self.name,
- "docstatus": 1
- })
+ return frappe.db.get_value(
+ "Cost Center Allocation", filters={"main_cost_center": self.name, "docstatus": 1}
+ )
def check_if_part_of_cost_center_allocation(self):
- return frappe.db.get_value("Cost Center Allocation Percentage", filters = {
- "cost_center": self.name,
- "docstatus": 1
- })
+ return frappe.db.get_value(
+ "Cost Center Allocation Percentage", filters={"cost_center": self.name, "docstatus": 1}
+ )
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
from erpnext.setup.doctype.company.company import get_name_with_abbr
+
new_cost_center = get_name_with_abbr(newdn, self.company)
# Validate properties before merging
@@ -90,7 +100,9 @@ class CostCenter(NestedSet):
super(CostCenter, self).after_rename(olddn, newdn, merge)
if not merge:
- new_cost_center = frappe.db.get_value("Cost Center", newdn, ["cost_center_name", "cost_center_number"], as_dict=1)
+ new_cost_center = frappe.db.get_value(
+ "Cost Center", newdn, ["cost_center_name", "cost_center_number"], as_dict=1
+ )
# exclude company abbr
new_parts = newdn.split(" - ")[:-1]
@@ -99,7 +111,9 @@ class CostCenter(NestedSet):
if len(new_parts) == 1:
new_parts = newdn.split(" ")
if new_cost_center.cost_center_number != new_parts[0]:
- validate_field_number("Cost Center", self.name, new_parts[0], self.company, "cost_center_number")
+ validate_field_number(
+ "Cost Center", self.name, new_parts[0], self.company, "cost_center_number"
+ )
self.cost_center_number = new_parts[0]
self.db_set("cost_center_number", new_parts[0])
new_parts = new_parts[1:]
@@ -110,10 +124,12 @@ class CostCenter(NestedSet):
self.cost_center_name = cost_center_name
self.db_set("cost_center_name", cost_center_name)
+
def on_doctype_update():
frappe.db.add_index("Cost Center", ["lft", "rgt"])
+
def get_name_with_number(new_account, account_number):
if account_number and not new_account[0].isdigit():
new_account = account_number + " - " + new_account
- return new_account
\ No newline at end of file
+ return new_account
diff --git a/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py b/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py
index f524803f64..5059dc3cc0 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py
@@ -3,11 +3,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'cost_center',
- 'reports': [
- {
- 'label': _('Reports'),
- 'items': ['Budget Variance Report', 'General Ledger']
- }
- ]
+ "fieldname": "cost_center",
+ "reports": [{"label": _("Reports"), "items": ["Budget Variance Report", "General Ledger"]}],
}
diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py
index ff50a21124..2ec16092d4 100644
--- a/erpnext/accounts/doctype/cost_center/test_cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py
@@ -5,24 +5,28 @@ import unittest
import frappe
-test_records = frappe.get_test_records('Cost Center')
+test_records = frappe.get_test_records("Cost Center")
+
class TestCostCenter(unittest.TestCase):
def test_cost_center_creation_against_child_node(self):
- if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
+ if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
frappe.get_doc(test_records[1]).insert()
- cost_center = frappe.get_doc({
- 'doctype': 'Cost Center',
- 'cost_center_name': '_Test Cost Center 3',
- 'parent_cost_center': '_Test Cost Center 2 - _TC',
- 'is_group': 0,
- 'company': '_Test Company'
- })
+ cost_center = frappe.get_doc(
+ {
+ "doctype": "Cost Center",
+ "cost_center_name": "_Test Cost Center 3",
+ "parent_cost_center": "_Test Cost Center 2 - _TC",
+ "is_group": 0,
+ "company": "_Test Company",
+ }
+ )
self.assertRaises(frappe.ValidationError, cost_center.save)
+
def create_cost_center(**args):
args = frappe._dict(args)
if args.cost_center_name:
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
index bad3fb4f96..d25016fe59 100644
--- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
@@ -9,15 +9,24 @@ from frappe.utils import add_days, format_date, getdate
class MainCostCenterCantBeChild(frappe.ValidationError):
pass
+
+
class InvalidMainCostCenter(frappe.ValidationError):
pass
+
+
class InvalidChildCostCenter(frappe.ValidationError):
pass
+
+
class WrongPercentageAllocation(frappe.ValidationError):
pass
+
+
class InvalidDateError(frappe.ValidationError):
pass
+
class CostCenterAllocation(Document):
def validate(self):
self.validate_total_allocation_percentage()
@@ -30,61 +39,96 @@ class CostCenterAllocation(Document):
total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
if total_percentage != 100:
- frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
+ frappe.throw(
+ _("Total percentage against cost centers should be 100"), WrongPercentageAllocation
+ )
def validate_from_date_based_on_existing_gle(self):
# Check if GLE exists against the main cost center
# If exists ensure from date is set after posting date of last GLE
- last_gle_date = frappe.db.get_value("GL Entry",
+ last_gle_date = frappe.db.get_value(
+ "GL Entry",
{"cost_center": self.main_cost_center, "is_cancelled": 0},
- "posting_date", order_by="posting_date desc")
+ "posting_date",
+ order_by="posting_date desc",
+ )
if last_gle_date:
if getdate(self.valid_from) <= getdate(last_gle_date):
- frappe.throw(_("Valid From must be after {0} as last GL Entry against the cost center {1} posted on this date")
- .format(last_gle_date, self.main_cost_center), InvalidDateError)
+ frappe.throw(
+ _(
+ "Valid From must be after {0} as last GL Entry against the cost center {1} posted on this date"
+ ).format(last_gle_date, self.main_cost_center),
+ InvalidDateError,
+ )
def validate_backdated_allocation(self):
# Check if there are any future existing allocation records against the main cost center
# If exists, warn the user about it
- future_allocation = frappe.db.get_value("Cost Center Allocation", filters = {
- "main_cost_center": self.main_cost_center,
- "valid_from": (">=", self.valid_from),
- "name": ("!=", self.name),
- "docstatus": 1
- }, fieldname=['valid_from', 'name'], order_by='valid_from', as_dict=1)
+ future_allocation = frappe.db.get_value(
+ "Cost Center Allocation",
+ filters={
+ "main_cost_center": self.main_cost_center,
+ "valid_from": (">=", self.valid_from),
+ "name": ("!=", self.name),
+ "docstatus": 1,
+ },
+ fieldname=["valid_from", "name"],
+ order_by="valid_from",
+ as_dict=1,
+ )
if future_allocation:
- frappe.msgprint(_("Another Cost Center Allocation record {0} applicable from {1}, hence this allocation will be applicable upto {2}")
- .format(frappe.bold(future_allocation.name), frappe.bold(format_date(future_allocation.valid_from)),
- frappe.bold(format_date(add_days(future_allocation.valid_from, -1)))),
- title=_("Warning!"), indicator="orange", alert=1
+ frappe.msgprint(
+ _(
+ "Another Cost Center Allocation record {0} applicable from {1}, hence this allocation will be applicable upto {2}"
+ ).format(
+ frappe.bold(future_allocation.name),
+ frappe.bold(format_date(future_allocation.valid_from)),
+ frappe.bold(format_date(add_days(future_allocation.valid_from, -1))),
+ ),
+ title=_("Warning!"),
+ indicator="orange",
+ alert=1,
)
def validate_main_cost_center(self):
# Main cost center itself cannot be entered in child table
if self.main_cost_center in [d.cost_center for d in self.allocation_percentages]:
- frappe.throw(_("Main Cost Center {0} cannot be entered in the child table")
- .format(self.main_cost_center), MainCostCenterCantBeChild)
+ frappe.throw(
+ _("Main Cost Center {0} cannot be entered in the child table").format(self.main_cost_center),
+ MainCostCenterCantBeChild,
+ )
# If main cost center is used for allocation under any other cost center,
# allocation cannot be done against it
- parent = frappe.db.get_value("Cost Center Allocation Percentage", filters = {
- "cost_center": self.main_cost_center,
- "docstatus": 1
- }, fieldname='parent')
+ parent = frappe.db.get_value(
+ "Cost Center Allocation Percentage",
+ filters={"cost_center": self.main_cost_center, "docstatus": 1},
+ fieldname="parent",
+ )
if parent:
- frappe.throw(_("{0} cannot be used as a Main Cost Center because it has been used as child in Cost Center Allocation {1}")
- .format(self.main_cost_center, parent), InvalidMainCostCenter)
+ frappe.throw(
+ _(
+ "{0} cannot be used as a Main Cost Center because it has been used as child in Cost Center Allocation {1}"
+ ).format(self.main_cost_center, parent),
+ InvalidMainCostCenter,
+ )
def validate_child_cost_centers(self):
# Check if child cost center is used as main cost center in any existing allocation
- main_cost_centers = [d.main_cost_center for d in
- frappe.get_all("Cost Center Allocation", {'docstatus': 1}, 'main_cost_center')]
+ main_cost_centers = [
+ d.main_cost_center
+ for d in frappe.get_all("Cost Center Allocation", {"docstatus": 1}, "main_cost_center")
+ ]
for d in self.allocation_percentages:
if d.cost_center in main_cost_centers:
- frappe.throw(_("Cost Center {0} cannot be used for allocation as it is used as main cost center in other allocation record.")
- .format(d.cost_center), InvalidChildCostCenter)
\ No newline at end of file
+ frappe.throw(
+ _(
+ "Cost Center {0} cannot be used for allocation as it is used as main cost center in other allocation record."
+ ).format(d.cost_center),
+ InvalidChildCostCenter,
+ )
diff --git a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
index 9cf4c00217..65784dbb6c 100644
--- a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
+++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
@@ -19,33 +19,35 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestCostCenterAllocation(unittest.TestCase):
def setUp(self):
- cost_centers = ["Main Cost Center 1", "Main Cost Center 2", "Sub Cost Center 1", "Sub Cost Center 2"]
+ cost_centers = [
+ "Main Cost Center 1",
+ "Main Cost Center 2",
+ "Sub Cost Center 1",
+ "Sub Cost Center 2",
+ ]
for cc in cost_centers:
create_cost_center(cost_center_name=cc, company="_Test Company")
def test_gle_based_on_cost_center_allocation(self):
- cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
- {
- "Sub Cost Center 1 - _TC": 60,
- "Sub Cost Center 2 - _TC": 40
- }
+ cca = create_cost_center_allocation(
+ "_Test Company",
+ "Main Cost Center 1 - _TC",
+ {"Sub Cost Center 1 - _TC": 60, "Sub Cost Center 2 - _TC": 40},
)
- jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
- cost_center = "Main Cost Center 1 - _TC", submit=True)
+ jv = make_journal_entry(
+ "_Test Cash - _TC", "Sales - _TC", 100, cost_center="Main Cost Center 1 - _TC", submit=True
+ )
- expected_values = [
- ["Sub Cost Center 1 - _TC", 0.0, 60],
- ["Sub Cost Center 2 - _TC", 0.0, 40]
- ]
+ expected_values = [["Sub Cost Center 1 - _TC", 0.0, 60], ["Sub Cost Center 2 - _TC", 0.0, 40]]
gle = frappe.qb.DocType("GL Entry")
gl_entries = (
frappe.qb.from_(gle)
.select(gle.cost_center, gle.debit, gle.credit)
- .where(gle.voucher_type == 'Journal Entry')
+ .where(gle.voucher_type == "Journal Entry")
.where(gle.voucher_no == jv.name)
- .where(gle.account == 'Sales - _TC')
+ .where(gle.account == "Sales - _TC")
.orderby(gle.cost_center)
).run(as_dict=1)
@@ -61,11 +63,11 @@ class TestCostCenterAllocation(unittest.TestCase):
def test_main_cost_center_cant_be_child(self):
# Main cost center itself cannot be entered in child table
- cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
- {
- "Sub Cost Center 1 - _TC": 60,
- "Main Cost Center 1 - _TC": 40
- }, save=False
+ cca = create_cost_center_allocation(
+ "_Test Company",
+ "Main Cost Center 1 - _TC",
+ {"Sub Cost Center 1 - _TC": 60, "Main Cost Center 1 - _TC": 40},
+ save=False,
)
self.assertRaises(MainCostCenterCantBeChild, cca.save)
@@ -73,17 +75,14 @@ class TestCostCenterAllocation(unittest.TestCase):
def test_invalid_main_cost_center(self):
# If main cost center is used for allocation under any other cost center,
# allocation cannot be done against it
- cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
- {
- "Sub Cost Center 1 - _TC": 60,
- "Sub Cost Center 2 - _TC": 40
- }
+ cca1 = create_cost_center_allocation(
+ "_Test Company",
+ "Main Cost Center 1 - _TC",
+ {"Sub Cost Center 1 - _TC": 60, "Sub Cost Center 2 - _TC": 40},
)
- cca2 = create_cost_center_allocation("_Test Company", "Sub Cost Center 1 - _TC",
- {
- "Sub Cost Center 2 - _TC": 100
- }, save=False
+ cca2 = create_cost_center_allocation(
+ "_Test Company", "Sub Cost Center 1 - _TC", {"Sub Cost Center 2 - _TC": 100}, save=False
)
self.assertRaises(InvalidMainCostCenter, cca2.save)
@@ -92,18 +91,17 @@ class TestCostCenterAllocation(unittest.TestCase):
def test_if_child_cost_center_has_any_allocation_record(self):
# Check if any child cost center is used as main cost center in any other existing allocation
- cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
- {
- "Sub Cost Center 1 - _TC": 60,
- "Sub Cost Center 2 - _TC": 40
- }
+ cca1 = create_cost_center_allocation(
+ "_Test Company",
+ "Main Cost Center 1 - _TC",
+ {"Sub Cost Center 1 - _TC": 60, "Sub Cost Center 2 - _TC": 40},
)
- cca2 = create_cost_center_allocation("_Test Company", "Main Cost Center 2 - _TC",
- {
- "Main Cost Center 1 - _TC": 60,
- "Sub Cost Center 1 - _TC": 40
- }, save=False
+ cca2 = create_cost_center_allocation(
+ "_Test Company",
+ "Main Cost Center 2 - _TC",
+ {"Main Cost Center 1 - _TC": 60, "Sub Cost Center 1 - _TC": 40},
+ save=False,
)
self.assertRaises(InvalidChildCostCenter, cca2.save)
@@ -111,46 +109,58 @@ class TestCostCenterAllocation(unittest.TestCase):
cca1.cancel()
def test_total_percentage(self):
- cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
- {
- "Sub Cost Center 1 - _TC": 40,
- "Sub Cost Center 2 - _TC": 40
- }, save=False
+ cca = create_cost_center_allocation(
+ "_Test Company",
+ "Main Cost Center 1 - _TC",
+ {"Sub Cost Center 1 - _TC": 40, "Sub Cost Center 2 - _TC": 40},
+ save=False,
)
self.assertRaises(WrongPercentageAllocation, cca.save)
def test_valid_from_based_on_existing_gle(self):
# GLE posted against Sub Cost Center 1 on today
- jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
- cost_center = "Main Cost Center 1 - _TC", posting_date=today(), submit=True)
+ jv = make_journal_entry(
+ "_Test Cash - _TC",
+ "Sales - _TC",
+ 100,
+ cost_center="Main Cost Center 1 - _TC",
+ posting_date=today(),
+ submit=True,
+ )
# try to set valid from as yesterday
- cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
- {
- "Sub Cost Center 1 - _TC": 60,
- "Sub Cost Center 2 - _TC": 40
- }, valid_from=add_days(today(), -1), save=False
+ cca = create_cost_center_allocation(
+ "_Test Company",
+ "Main Cost Center 1 - _TC",
+ {"Sub Cost Center 1 - _TC": 60, "Sub Cost Center 2 - _TC": 40},
+ valid_from=add_days(today(), -1),
+ save=False,
)
self.assertRaises(InvalidDateError, cca.save)
jv.cancel()
-def create_cost_center_allocation(company, main_cost_center, allocation_percentages,
- valid_from=None, valid_upto=None, save=True, submit=True):
+
+def create_cost_center_allocation(
+ company,
+ main_cost_center,
+ allocation_percentages,
+ valid_from=None,
+ valid_upto=None,
+ save=True,
+ submit=True,
+):
doc = frappe.new_doc("Cost Center Allocation")
doc.main_cost_center = main_cost_center
doc.company = company
doc.valid_from = valid_from or today()
doc.valid_upto = valid_upto
for cc, percentage in allocation_percentages.items():
- doc.append("allocation_percentages", {
- "cost_center": cc,
- "percentage": percentage
- })
+ doc.append("allocation_percentages", {"cost_center": cc, "percentage": percentage})
if save:
doc.save()
if submit:
doc.submit()
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.py b/erpnext/accounts/doctype/coupon_code/coupon_code.py
index ee32de1cd2..6a0cdf91c0 100644
--- a/erpnext/accounts/doctype/coupon_code/coupon_code.py
+++ b/erpnext/accounts/doctype/coupon_code/coupon_code.py
@@ -15,7 +15,7 @@ class CouponCode(Document):
if not self.coupon_code:
if self.coupon_type == "Promotional":
- self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
+ self.coupon_code = "".join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
elif self.coupon_type == "Gift Card":
self.coupon_code = frappe.generate_hash()[:10].upper()
diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
index ca482c8c4e..b897546b03 100644
--- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
+++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
@@ -7,92 +7,110 @@ import frappe
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
-test_dependencies = ['Item']
+test_dependencies = ["Item"]
+
def test_create_test_data():
frappe.set_user("Administrator")
# create test item
- if not frappe.db.exists("Item","_Test Tesla Car"):
- item = frappe.get_doc({
- "description": "_Test Tesla Car",
- "doctype": "Item",
- "has_batch_no": 0,
- "has_serial_no": 0,
- "inspection_required": 0,
- "is_stock_item": 1,
- "opening_stock":100,
- "is_sub_contracted_item": 0,
- "item_code": "_Test Tesla Car",
- "item_group": "_Test Item Group",
- "item_name": "_Test Tesla Car",
- "apply_warehouse_wise_reorder_level": 0,
- "warehouse":"Stores - _TC",
- "gst_hsn_code": "999800",
- "valuation_rate": 5000,
- "standard_rate":5000,
- "item_defaults": [{
- "company": "_Test Company",
- "default_warehouse": "Stores - _TC",
- "default_price_list":"_Test Price List",
- "expense_account": "Cost of Goods Sold - _TC",
- "buying_cost_center": "Main - _TC",
- "selling_cost_center": "Main - _TC",
- "income_account": "Sales - _TC"
- }],
- })
+ if not frappe.db.exists("Item", "_Test Tesla Car"):
+ item = frappe.get_doc(
+ {
+ "description": "_Test Tesla Car",
+ "doctype": "Item",
+ "has_batch_no": 0,
+ "has_serial_no": 0,
+ "inspection_required": 0,
+ "is_stock_item": 1,
+ "opening_stock": 100,
+ "is_sub_contracted_item": 0,
+ "item_code": "_Test Tesla Car",
+ "item_group": "_Test Item Group",
+ "item_name": "_Test Tesla Car",
+ "apply_warehouse_wise_reorder_level": 0,
+ "warehouse": "Stores - _TC",
+ "gst_hsn_code": "999800",
+ "valuation_rate": 5000,
+ "standard_rate": 5000,
+ "item_defaults": [
+ {
+ "company": "_Test Company",
+ "default_warehouse": "Stores - _TC",
+ "default_price_list": "_Test Price List",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "buying_cost_center": "Main - _TC",
+ "selling_cost_center": "Main - _TC",
+ "income_account": "Sales - _TC",
+ }
+ ],
+ }
+ )
item.insert()
# create test item price
- item_price = frappe.get_list('Item Price', filters={'item_code': '_Test Tesla Car', 'price_list': '_Test Price List'}, fields=['name'])
- if len(item_price)==0:
- item_price = frappe.get_doc({
- "doctype": "Item Price",
- "item_code": "_Test Tesla Car",
- "price_list": "_Test Price List",
- "price_list_rate": 5000
- })
+ item_price = frappe.get_list(
+ "Item Price",
+ filters={"item_code": "_Test Tesla Car", "price_list": "_Test Price List"},
+ fields=["name"],
+ )
+ if len(item_price) == 0:
+ item_price = frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "item_code": "_Test Tesla Car",
+ "price_list": "_Test Price List",
+ "price_list_rate": 5000,
+ }
+ )
item_price.insert()
# create test item pricing rule
if not frappe.db.exists("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}):
- item_pricing_rule = frappe.get_doc({
- "doctype": "Pricing Rule",
- "title": "_Test Pricing Rule for _Test Item",
- "apply_on": "Item Code",
- "items": [{
- "item_code": "_Test Tesla Car"
- }],
- "warehouse":"Stores - _TC",
- "coupon_code_based":1,
- "selling": 1,
- "rate_or_discount": "Discount Percentage",
- "discount_percentage": 30,
- "company": "_Test Company",
- "currency":"INR",
- "for_price_list":"_Test Price List"
- })
+ item_pricing_rule = frappe.get_doc(
+ {
+ "doctype": "Pricing Rule",
+ "title": "_Test Pricing Rule for _Test Item",
+ "apply_on": "Item Code",
+ "items": [{"item_code": "_Test Tesla Car"}],
+ "warehouse": "Stores - _TC",
+ "coupon_code_based": 1,
+ "selling": 1,
+ "rate_or_discount": "Discount Percentage",
+ "discount_percentage": 30,
+ "company": "_Test Company",
+ "currency": "INR",
+ "for_price_list": "_Test Price List",
+ }
+ )
item_pricing_rule.insert()
# create test item sales partner
- if not frappe.db.exists("Sales Partner","_Test Coupon Partner"):
- sales_partner = frappe.get_doc({
- "doctype": "Sales Partner",
- "partner_name":"_Test Coupon Partner",
- "commission_rate":2,
- "referral_code": "COPART"
- })
+ if not frappe.db.exists("Sales Partner", "_Test Coupon Partner"):
+ sales_partner = frappe.get_doc(
+ {
+ "doctype": "Sales Partner",
+ "partner_name": "_Test Coupon Partner",
+ "commission_rate": 2,
+ "referral_code": "COPART",
+ }
+ )
sales_partner.insert()
# create test item coupon code
if not frappe.db.exists("Coupon Code", "SAVE30"):
- pricing_rule = frappe.db.get_value("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}, ['name'])
- coupon_code = frappe.get_doc({
- "doctype": "Coupon Code",
- "coupon_name":"SAVE30",
- "coupon_code":"SAVE30",
- "pricing_rule": pricing_rule,
- "valid_from": "2014-01-01",
- "maximum_use":1,
- "used":0
- })
+ pricing_rule = frappe.db.get_value(
+ "Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}, ["name"]
+ )
+ coupon_code = frappe.get_doc(
+ {
+ "doctype": "Coupon Code",
+ "coupon_name": "SAVE30",
+ "coupon_code": "SAVE30",
+ "pricing_rule": pricing_rule,
+ "valid_from": "2014-01-01",
+ "maximum_use": 1,
+ "used": 0,
+ }
+ )
coupon_code.insert()
+
class TestCouponCode(unittest.TestCase):
def setUp(self):
test_create_test_data()
@@ -103,15 +121,21 @@ class TestCouponCode(unittest.TestCase):
def test_sales_order_with_coupon_code(self):
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
- so = make_sales_order(company='_Test Company', warehouse='Stores - _TC',
- customer="_Test Customer", selling_price_list="_Test Price List",
- item_code="_Test Tesla Car", rate=5000, qty=1,
- do_not_submit=True)
+ so = make_sales_order(
+ company="_Test Company",
+ warehouse="Stores - _TC",
+ customer="_Test Customer",
+ selling_price_list="_Test Price List",
+ item_code="_Test Tesla Car",
+ rate=5000,
+ qty=1,
+ do_not_submit=True,
+ )
self.assertEqual(so.items[0].rate, 5000)
- so.coupon_code='SAVE30'
- so.sales_partner='_Test Coupon Partner'
+ so.coupon_code = "SAVE30"
+ so.sales_partner = "_Test Coupon Partner"
so.save()
# check item price after coupon code is applied
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
index e16ff3aa7e..04a8e8ea92 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
@@ -15,24 +15,24 @@ class CurrencyExchangeSettings(Document):
self.validate_result(response, value)
def set_parameters_and_result(self):
- if self.service_provider == 'exchangerate.host':
- self.set('result_key', [])
- self.set('req_params', [])
+ if self.service_provider == "exchangerate.host":
+ self.set("result_key", [])
+ self.set("req_params", [])
self.api_endpoint = "https://api.exchangerate.host/convert"
- self.append('result_key', {'key': 'result'})
- self.append('req_params', {'key': 'date', 'value': '{transaction_date}'})
- self.append('req_params', {'key': 'from', 'value': '{from_currency}'})
- self.append('req_params', {'key': 'to', 'value': '{to_currency}'})
- elif self.service_provider == 'frankfurter.app':
- self.set('result_key', [])
- self.set('req_params', [])
+ self.append("result_key", {"key": "result"})
+ self.append("req_params", {"key": "date", "value": "{transaction_date}"})
+ self.append("req_params", {"key": "from", "value": "{from_currency}"})
+ self.append("req_params", {"key": "to", "value": "{to_currency}"})
+ elif self.service_provider == "frankfurter.app":
+ self.set("result_key", [])
+ self.set("req_params", [])
self.api_endpoint = "https://frankfurter.app/{transaction_date}"
- self.append('result_key', {'key': 'rates'})
- self.append('result_key', {'key': '{to_currency}'})
- self.append('req_params', {'key': 'base', 'value': '{from_currency}'})
- self.append('req_params', {'key': 'symbols', 'value': '{to_currency}'})
+ self.append("result_key", {"key": "rates"})
+ self.append("result_key", {"key": "{to_currency}"})
+ self.append("req_params", {"key": "base", "value": "{from_currency}"})
+ self.append("req_params", {"key": "symbols", "value": "{to_currency}"})
def validate_parameters(self):
if frappe.flags.in_test:
@@ -41,15 +41,11 @@ class CurrencyExchangeSettings(Document):
params = {}
for row in self.req_params:
params[row.key] = row.value.format(
- transaction_date=nowdate(),
- to_currency='INR',
- from_currency='USD'
+ transaction_date=nowdate(), to_currency="INR", from_currency="USD"
)
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:
@@ -68,11 +64,9 @@ class CurrencyExchangeSettings(Document):
try:
for key in self.result_key:
- value = value[str(key.key).format(
- transaction_date=nowdate(),
- to_currency='INR',
- from_currency='USD'
- )]
+ value = value[
+ str(key.key).format(transaction_date=nowdate(), to_currency="INR", from_currency="USD")
+ ]
except Exception:
frappe.throw("Invalid result key. Response: " + response.text)
if not isinstance(value, (int, float)):
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index 5da0077a2b..9874d66fa5 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -19,78 +19,99 @@ class Dunning(AccountsController):
self.validate_overdue_days()
self.validate_amount()
if not self.income_account:
- self.income_account = frappe.db.get_value('Company', self.company, 'default_income_account')
+ self.income_account = frappe.db.get_value("Company", self.company, "default_income_account")
def validate_overdue_days(self):
self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
def validate_amount(self):
amounts = calculate_interest_and_amount(
- self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
- if self.interest_amount != amounts.get('interest_amount'):
- self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
- if self.dunning_amount != amounts.get('dunning_amount'):
- self.dunning_amount = flt(amounts.get('dunning_amount'), self.precision('dunning_amount'))
- if self.grand_total != amounts.get('grand_total'):
- self.grand_total = flt(amounts.get('grand_total'), self.precision('grand_total'))
+ self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days
+ )
+ if self.interest_amount != amounts.get("interest_amount"):
+ self.interest_amount = flt(amounts.get("interest_amount"), self.precision("interest_amount"))
+ if self.dunning_amount != amounts.get("dunning_amount"):
+ self.dunning_amount = flt(amounts.get("dunning_amount"), self.precision("dunning_amount"))
+ if self.grand_total != amounts.get("grand_total"):
+ self.grand_total = flt(amounts.get("grand_total"), self.precision("grand_total"))
def on_submit(self):
self.make_gl_entries()
def on_cancel(self):
if self.dunning_amount:
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def make_gl_entries(self):
if not self.dunning_amount:
return
gl_entries = []
- invoice_fields = ["project", "cost_center", "debit_to", "party_account_currency", "conversion_rate", "cost_center"]
+ invoice_fields = [
+ "project",
+ "cost_center",
+ "debit_to",
+ "party_account_currency",
+ "conversion_rate",
+ "cost_center",
+ ]
inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
accounting_dimensions = get_accounting_dimensions()
invoice_fields.extend(accounting_dimensions)
dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
- default_cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
+ default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
gl_entries.append(
- self.get_gl_dict({
- "account": inv.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "due_date": self.due_date,
- "against": self.income_account,
- "debit": dunning_in_company_currency,
- "debit_in_account_currency": self.dunning_amount,
- "against_voucher": self.name,
- "against_voucher_type": "Dunning",
- "cost_center": inv.cost_center or default_cost_center,
- "project": inv.project
- }, inv.party_account_currency, item=inv)
+ self.get_gl_dict(
+ {
+ "account": inv.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "due_date": self.due_date,
+ "against": self.income_account,
+ "debit": dunning_in_company_currency,
+ "debit_in_account_currency": self.dunning_amount,
+ "against_voucher": self.name,
+ "against_voucher_type": "Dunning",
+ "cost_center": inv.cost_center or default_cost_center,
+ "project": inv.project,
+ },
+ inv.party_account_currency,
+ item=inv,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": self.income_account,
- "against": self.customer,
- "credit": dunning_in_company_currency,
- "cost_center": inv.cost_center or default_cost_center,
- "credit_in_account_currency": self.dunning_amount,
- "project": inv.project
- }, item=inv)
+ self.get_gl_dict(
+ {
+ "account": self.income_account,
+ "against": self.customer,
+ "credit": dunning_in_company_currency,
+ "cost_center": inv.cost_center or default_cost_center,
+ "credit_in_account_currency": self.dunning_amount,
+ "project": inv.project,
+ },
+ item=inv,
+ )
+ )
+ make_gl_entries(
+ gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False
)
- make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False)
def resolve_dunning(doc, state):
for reference in doc.references:
- if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
- dunnings = frappe.get_list('Dunning', filters={
- 'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')}, ignore_permissions=True)
+ if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0:
+ dunnings = frappe.get_list(
+ "Dunning",
+ filters={"sales_invoice": reference.reference_name, "status": ("!=", "Resolved")},
+ ignore_permissions=True,
+ )
for dunning in dunnings:
- frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
+ frappe.db.set_value("Dunning", dunning.name, "status", "Resolved")
+
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
interest_amount = 0
@@ -101,23 +122,26 @@ def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_
grand_total += flt(interest_amount)
dunning_amount = flt(interest_amount) + flt(dunning_fee)
return {
- 'interest_amount': interest_amount,
- 'grand_total': grand_total,
- 'dunning_amount': dunning_amount}
+ "interest_amount": interest_amount,
+ "grand_total": grand_total,
+ "dunning_amount": dunning_amount,
+ }
+
@frappe.whitelist()
def get_dunning_letter_text(dunning_type, doc, language=None):
if isinstance(doc, str):
doc = json.loads(doc)
if language:
- filters = {'parent': dunning_type, 'language': language}
+ filters = {"parent": dunning_type, "language": language}
else:
- filters = {'parent': dunning_type, 'is_default_language': 1}
- letter_text = frappe.db.get_value('Dunning Letter Text', filters,
- ['body_text', 'closing_text', 'language'], as_dict=1)
+ filters = {"parent": dunning_type, "is_default_language": 1}
+ letter_text = frappe.db.get_value(
+ "Dunning Letter Text", filters, ["body_text", "closing_text", "language"], as_dict=1
+ )
if letter_text:
return {
- 'body_text': frappe.render_template(letter_text.body_text, doc),
- 'closing_text': frappe.render_template(letter_text.closing_text, doc),
- 'language': letter_text.language
+ "body_text": frappe.render_template(letter_text.body_text, doc),
+ "closing_text": frappe.render_template(letter_text.closing_text, doc),
+ "language": letter_text.language,
}
diff --git a/erpnext/accounts/doctype/dunning/dunning_dashboard.py b/erpnext/accounts/doctype/dunning/dunning_dashboard.py
index a891bd2917..d1d4031410 100644
--- a/erpnext/accounts/doctype/dunning/dunning_dashboard.py
+++ b/erpnext/accounts/doctype/dunning/dunning_dashboard.py
@@ -3,15 +3,10 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'dunning',
- 'non_standard_fieldnames': {
- 'Journal Entry': 'reference_name',
- 'Payment Entry': 'reference_name'
+ "fieldname": "dunning",
+ "non_standard_fieldnames": {
+ "Journal Entry": "reference_name",
+ "Payment Entry": "reference_name",
},
- 'transactions': [
- {
- 'label': _('Payment'),
- 'items': ['Payment Entry', 'Journal Entry']
- }
- ]
+ "transactions": [{"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]}],
}
diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py
index b043c5ba19..e1fd1e984f 100644
--- a/erpnext/accounts/doctype/dunning/test_dunning.py
+++ b/erpnext/accounts/doctype/dunning/test_dunning.py
@@ -30,30 +30,35 @@ class TestDunning(unittest.TestCase):
def test_dunning(self):
dunning = create_dunning()
amounts = calculate_interest_and_amount(
- dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
- self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
- self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
- self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
+ dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
+ )
+ self.assertEqual(round(amounts.get("interest_amount"), 2), 0.44)
+ self.assertEqual(round(amounts.get("dunning_amount"), 2), 20.44)
+ self.assertEqual(round(amounts.get("grand_total"), 2), 120.44)
def test_dunning_with_zero_interest_rate(self):
dunning = create_dunning_with_zero_interest_rate()
amounts = calculate_interest_and_amount(
- dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
- self.assertEqual(round(amounts.get('interest_amount'), 2), 0)
- self.assertEqual(round(amounts.get('dunning_amount'), 2), 20)
- self.assertEqual(round(amounts.get('grand_total'), 2), 120)
+ dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
+ )
+ self.assertEqual(round(amounts.get("interest_amount"), 2), 0)
+ self.assertEqual(round(amounts.get("dunning_amount"), 2), 20)
+ self.assertEqual(round(amounts.get("grand_total"), 2), 120)
def test_gl_entries(self):
dunning = create_dunning()
dunning.submit()
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s
- order by account asc""", dunning.name, as_dict=1)
+ order by account asc""",
+ dunning.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- ['Debtors - _TC', 20.44, 0.0],
- ['Sales - _TC', 0.0, 20.44]
- ])
+ expected_values = dict(
+ (d[0], d) for d in [["Debtors - _TC", 20.44, 0.0], ["Sales - _TC", 0.0, 20.44]]
+ )
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
@@ -71,7 +76,7 @@ class TestDunning(unittest.TestCase):
pe.target_exchange_rate = 1
pe.insert()
pe.submit()
- si_doc = frappe.get_doc('Sales Invoice', dunning.sales_invoice)
+ si_doc = frappe.get_doc("Sales Invoice", dunning.sales_invoice)
self.assertEqual(si_doc.outstanding_amount, 0)
@@ -79,8 +84,9 @@ def create_dunning():
posting_date = add_days(today(), -20)
due_date = add_days(today(), -15)
sales_invoice = create_sales_invoice_against_cost_center(
- posting_date=posting_date, due_date=due_date, status='Overdue')
- dunning_type = frappe.get_doc("Dunning Type", 'First Notice')
+ posting_date=posting_date, due_date=due_date, status="Overdue"
+ )
+ dunning_type = frappe.get_doc("Dunning Type", "First Notice")
dunning = frappe.new_doc("Dunning")
dunning.sales_invoice = sales_invoice.name
dunning.customer_name = sales_invoice.customer_name
@@ -90,18 +96,20 @@ def create_dunning():
dunning.company = sales_invoice.company
dunning.posting_date = nowdate()
dunning.due_date = sales_invoice.due_date
- dunning.dunning_type = 'First Notice'
+ dunning.dunning_type = "First Notice"
dunning.rate_of_interest = dunning_type.rate_of_interest
dunning.dunning_fee = dunning_type.dunning_fee
dunning.save()
return dunning
+
def create_dunning_with_zero_interest_rate():
posting_date = add_days(today(), -20)
due_date = add_days(today(), -15)
sales_invoice = create_sales_invoice_against_cost_center(
- posting_date=posting_date, due_date=due_date, status='Overdue')
- dunning_type = frappe.get_doc("Dunning Type", 'First Notice with 0% Rate of Interest')
+ posting_date=posting_date, due_date=due_date, status="Overdue"
+ )
+ dunning_type = frappe.get_doc("Dunning Type", "First Notice with 0% Rate of Interest")
dunning = frappe.new_doc("Dunning")
dunning.sales_invoice = sales_invoice.name
dunning.customer_name = sales_invoice.customer_name
@@ -111,40 +119,44 @@ def create_dunning_with_zero_interest_rate():
dunning.company = sales_invoice.company
dunning.posting_date = nowdate()
dunning.due_date = sales_invoice.due_date
- dunning.dunning_type = 'First Notice with 0% Rate of Interest'
+ dunning.dunning_type = "First Notice with 0% Rate of Interest"
dunning.rate_of_interest = dunning_type.rate_of_interest
dunning.dunning_fee = dunning_type.dunning_fee
dunning.save()
return dunning
+
def create_dunning_type():
dunning_type = frappe.new_doc("Dunning Type")
- dunning_type.dunning_type = 'First Notice'
+ dunning_type.dunning_type = "First Notice"
dunning_type.start_day = 10
dunning_type.end_day = 20
dunning_type.dunning_fee = 20
dunning_type.rate_of_interest = 8
dunning_type.append(
- "dunning_letter_text", {
- 'language': 'en',
- 'body_text': 'We have still not received payment for our invoice ',
- 'closing_text': 'We kindly request that you pay the outstanding amount immediately, including interest and late fees.'
- }
+ "dunning_letter_text",
+ {
+ "language": "en",
+ "body_text": "We have still not received payment for our invoice ",
+ "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.",
+ },
)
dunning_type.save()
+
def create_dunning_type_with_zero_interest_rate():
dunning_type = frappe.new_doc("Dunning Type")
- dunning_type.dunning_type = 'First Notice with 0% Rate of Interest'
+ dunning_type.dunning_type = "First Notice with 0% Rate of Interest"
dunning_type.start_day = 10
dunning_type.end_day = 20
dunning_type.dunning_fee = 20
dunning_type.rate_of_interest = 0
dunning_type.append(
- "dunning_letter_text", {
- 'language': 'en',
- 'body_text': 'We have still not received payment for our invoice ',
- 'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.'
- }
+ "dunning_letter_text",
+ {
+ "language": "en",
+ "body_text": "We have still not received payment for our invoice ",
+ "closing_text": "We kindly request that you pay the outstanding amount immediately, and late fees.",
+ },
)
dunning_type.save()
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 1b13195ce9..2f81c5fb75 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -20,8 +20,9 @@ class ExchangeRateRevaluation(Document):
def set_total_gain_loss(self):
total_gain_loss = 0
for d in self.accounts:
- d.gain_loss = flt(d.new_balance_in_base_currency, d.precision("new_balance_in_base_currency")) \
- - flt(d.balance_in_base_currency, d.precision("balance_in_base_currency"))
+ d.gain_loss = flt(
+ d.new_balance_in_base_currency, d.precision("new_balance_in_base_currency")
+ ) - flt(d.balance_in_base_currency, d.precision("balance_in_base_currency"))
total_gain_loss += flt(d.gain_loss, d.precision("gain_loss"))
self.total_gain_loss = flt(total_gain_loss, self.precision("total_gain_loss"))
@@ -30,15 +31,15 @@ class ExchangeRateRevaluation(Document):
frappe.throw(_("Please select Company and Posting Date to getting entries"))
def on_cancel(self):
- self.ignore_linked_doctypes = ('GL Entry')
+ self.ignore_linked_doctypes = "GL Entry"
@frappe.whitelist()
def check_journal_entry_condition(self):
- total_debit = frappe.db.get_value("Journal Entry Account", {
- 'reference_type': 'Exchange Rate Revaluation',
- 'reference_name': self.name,
- 'docstatus': 1
- }, "sum(debit) as sum")
+ total_debit = frappe.db.get_value(
+ "Journal Entry Account",
+ {"reference_type": "Exchange Rate Revaluation", "reference_name": self.name, "docstatus": 1},
+ "sum(debit) as sum",
+ )
total_amt = 0
for d in self.accounts:
@@ -54,28 +55,33 @@ class ExchangeRateRevaluation(Document):
accounts = []
self.validate_mandatory()
company_currency = erpnext.get_company_currency(self.company)
- precision = get_field_precision(frappe.get_meta("Exchange Rate Revaluation Account")
- .get_field("new_balance_in_base_currency"), company_currency)
+ precision = get_field_precision(
+ frappe.get_meta("Exchange Rate Revaluation Account").get_field("new_balance_in_base_currency"),
+ company_currency,
+ )
account_details = self.get_accounts_from_gle()
for d in account_details:
- current_exchange_rate = d.balance / d.balance_in_account_currency \
- if d.balance_in_account_currency else 0
+ current_exchange_rate = (
+ d.balance / d.balance_in_account_currency if d.balance_in_account_currency else 0
+ )
new_exchange_rate = get_exchange_rate(d.account_currency, company_currency, self.posting_date)
new_balance_in_base_currency = flt(d.balance_in_account_currency * new_exchange_rate)
gain_loss = flt(new_balance_in_base_currency, precision) - flt(d.balance, precision)
if gain_loss:
- accounts.append({
- "account": d.account,
- "party_type": d.party_type,
- "party": d.party,
- "account_currency": d.account_currency,
- "balance_in_base_currency": d.balance,
- "balance_in_account_currency": d.balance_in_account_currency,
- "current_exchange_rate": current_exchange_rate,
- "new_exchange_rate": new_exchange_rate,
- "new_balance_in_base_currency": new_balance_in_base_currency
- })
+ accounts.append(
+ {
+ "account": d.account,
+ "party_type": d.party_type,
+ "party": d.party,
+ "account_currency": d.account_currency,
+ "balance_in_base_currency": d.balance,
+ "balance_in_account_currency": d.balance_in_account_currency,
+ "current_exchange_rate": current_exchange_rate,
+ "new_exchange_rate": new_exchange_rate,
+ "new_balance_in_base_currency": new_balance_in_base_currency,
+ }
+ )
if not accounts:
self.throw_invalid_response_message(account_details)
@@ -84,7 +90,8 @@ class ExchangeRateRevaluation(Document):
def get_accounts_from_gle(self):
company_currency = erpnext.get_company_currency(self.company)
- accounts = frappe.db.sql_list("""
+ accounts = frappe.db.sql_list(
+ """
select name
from tabAccount
where is_group = 0
@@ -93,11 +100,14 @@ class ExchangeRateRevaluation(Document):
and account_type != 'Stock'
and company=%s
and account_currency != %s
- order by name""",(self.company, company_currency))
+ order by name""",
+ (self.company, company_currency),
+ )
account_details = []
if accounts:
- account_details = frappe.db.sql("""
+ account_details = frappe.db.sql(
+ """
select
account, party_type, party, account_currency,
sum(debit_in_account_currency) - sum(credit_in_account_currency) as balance_in_account_currency,
@@ -109,7 +119,11 @@ class ExchangeRateRevaluation(Document):
group by account, NULLIF(party_type,''), NULLIF(party,'')
having sum(debit) != sum(credit)
order by account
- """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
+ """
+ % (", ".join(["%s"] * len(accounts)), "%s"),
+ tuple(accounts + [self.posting_date]),
+ as_dict=1,
+ )
return account_details
@@ -125,77 +139,107 @@ class ExchangeRateRevaluation(Document):
if self.total_gain_loss == 0:
return
- unrealized_exchange_gain_loss_account = frappe.get_cached_value('Company', self.company,
- "unrealized_exchange_gain_loss_account")
+ unrealized_exchange_gain_loss_account = frappe.get_cached_value(
+ "Company", self.company, "unrealized_exchange_gain_loss_account"
+ )
if not unrealized_exchange_gain_loss_account:
- frappe.throw(_("Please set Unrealized Exchange Gain/Loss Account in Company {0}")
- .format(self.company))
+ frappe.throw(
+ _("Please set Unrealized Exchange Gain/Loss Account in Company {0}").format(self.company)
+ )
- journal_entry = frappe.new_doc('Journal Entry')
- journal_entry.voucher_type = 'Exchange Rate Revaluation'
+ journal_entry = frappe.new_doc("Journal Entry")
+ journal_entry.voucher_type = "Exchange Rate Revaluation"
journal_entry.company = self.company
journal_entry.posting_date = self.posting_date
journal_entry.multi_currency = 1
journal_entry_accounts = []
for d in self.accounts:
- dr_or_cr = "debit_in_account_currency" \
- if d.get("balance_in_account_currency") > 0 else "credit_in_account_currency"
+ dr_or_cr = (
+ "debit_in_account_currency"
+ if d.get("balance_in_account_currency") > 0
+ else "credit_in_account_currency"
+ )
- reverse_dr_or_cr = "debit_in_account_currency" \
- if dr_or_cr=="credit_in_account_currency" else "credit_in_account_currency"
+ reverse_dr_or_cr = (
+ "debit_in_account_currency"
+ if dr_or_cr == "credit_in_account_currency"
+ else "credit_in_account_currency"
+ )
- journal_entry_accounts.append({
- "account": d.get("account"),
- "party_type": d.get("party_type"),
- "party": d.get("party"),
- "account_currency": d.get("account_currency"),
- "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
- dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
- "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
+ journal_entry_accounts.append(
+ {
+ "account": d.get("account"),
+ "party_type": d.get("party_type"),
+ "party": d.get("party"),
+ "account_currency": d.get("account_currency"),
+ "balance": flt(
+ d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")
+ ),
+ dr_or_cr: flt(
+ abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
+ ),
+ "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
+ "reference_type": "Exchange Rate Revaluation",
+ "reference_name": self.name,
+ }
+ )
+ journal_entry_accounts.append(
+ {
+ "account": d.get("account"),
+ "party_type": d.get("party_type"),
+ "party": d.get("party"),
+ "account_currency": d.get("account_currency"),
+ "balance": flt(
+ d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")
+ ),
+ reverse_dr_or_cr: flt(
+ abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
+ ),
+ "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
+ "reference_type": "Exchange Rate Revaluation",
+ "reference_name": self.name,
+ }
+ )
+
+ journal_entry_accounts.append(
+ {
+ "account": unrealized_exchange_gain_loss_account,
+ "balance": get_balance_on(unrealized_exchange_gain_loss_account),
+ "debit_in_account_currency": abs(self.total_gain_loss) if self.total_gain_loss < 0 else 0,
+ "credit_in_account_currency": self.total_gain_loss if self.total_gain_loss > 0 else 0,
+ "exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
- })
- journal_entry_accounts.append({
- "account": d.get("account"),
- "party_type": d.get("party_type"),
- "party": d.get("party"),
- "account_currency": d.get("account_currency"),
- "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
- reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
- "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
- "reference_type": "Exchange Rate Revaluation",
- "reference_name": self.name
- })
-
- journal_entry_accounts.append({
- "account": unrealized_exchange_gain_loss_account,
- "balance": get_balance_on(unrealized_exchange_gain_loss_account),
- "debit_in_account_currency": abs(self.total_gain_loss) if self.total_gain_loss < 0 else 0,
- "credit_in_account_currency": self.total_gain_loss if self.total_gain_loss > 0 else 0,
- "exchange_rate": 1,
- "reference_type": "Exchange Rate Revaluation",
- "reference_name": self.name,
- })
+ }
+ )
journal_entry.set("accounts", journal_entry_accounts)
journal_entry.set_amounts_in_company_currency()
journal_entry.set_total_debit_credit()
return journal_entry.as_dict()
+
@frappe.whitelist()
def get_account_details(account, company, posting_date, party_type=None, party=None):
- account_currency, account_type = frappe.db.get_value("Account", account,
- ["account_currency", "account_type"])
+ account_currency, account_type = frappe.db.get_value(
+ "Account", account, ["account_currency", "account_type"]
+ )
if account_type in ["Receivable", "Payable"] and not (party_type and party):
frappe.throw(_("Party Type and Party is mandatory for {0} account").format(account_type))
account_details = {}
company_currency = erpnext.get_company_currency(company)
- balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
+ balance = get_balance_on(
+ account, date=posting_date, party_type=party_type, party=party, in_account_currency=False
+ )
if balance:
- balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
- current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
+ balance_in_account_currency = get_balance_on(
+ account, date=posting_date, party_type=party_type, party=party
+ )
+ current_exchange_rate = (
+ balance / balance_in_account_currency if balance_in_account_currency else 0
+ )
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
account_details = {
@@ -204,7 +248,7 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
"balance_in_account_currency": balance_in_account_currency,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
- "new_balance_in_base_currency": new_balance_in_base_currency
+ "new_balance_in_base_currency": new_balance_in_base_currency,
}
return account_details
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation_dashboard.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation_dashboard.py
index fe862507fb..7eca9703c2 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation_dashboard.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'reference_name',
- 'transactions': [
- {
- 'items': ['Journal Entry']
- }
- ]
- }
+ return {"fieldname": "reference_name", "transactions": [{"items": ["Journal Entry"]}]}
diff --git a/erpnext/accounts/doctype/finance_book/finance_book_dashboard.py b/erpnext/accounts/doctype/finance_book/finance_book_dashboard.py
index 57b039d022..24e6c0c872 100644
--- a/erpnext/accounts/doctype/finance_book/finance_book_dashboard.py
+++ b/erpnext/accounts/doctype/finance_book/finance_book_dashboard.py
@@ -3,21 +3,11 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'finance_book',
- 'non_standard_fieldnames': {
- 'Asset': 'default_finance_book',
- 'Company': 'default_finance_book'
- },
- 'transactions': [
- {
- 'label': _('Assets'),
- 'items': ['Asset', 'Asset Value Adjustment']
- },
- {
- 'items': ['Company']
- },
- {
- 'items': ['Journal Entry']
- }
- ]
+ "fieldname": "finance_book",
+ "non_standard_fieldnames": {"Asset": "default_finance_book", "Company": "default_finance_book"},
+ "transactions": [
+ {"label": _("Assets"), "items": ["Asset", "Asset Value Adjustment"]},
+ {"items": ["Company"]},
+ {"items": ["Journal Entry"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py
index 5fb3d0a96c..7b2575d2c3 100644
--- a/erpnext/accounts/doctype/finance_book/test_finance_book.py
+++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py
@@ -13,30 +13,29 @@ class TestFinanceBook(unittest.TestCase):
finance_book = create_finance_book()
# create jv entry
- jv = make_journal_entry("_Test Bank - _TC",
- "_Test Receivable - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
- jv.accounts[1].update({
- "party_type": "Customer",
- "party": "_Test Customer"
- })
+ jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer"})
jv.finance_book = finance_book.finance_book_name
jv.submit()
# check the Finance Book in the GL Entry
- gl_entries = frappe.get_all("GL Entry", fields=["name", "finance_book"],
- filters={"voucher_type": "Journal Entry", "voucher_no": jv.name})
+ gl_entries = frappe.get_all(
+ "GL Entry",
+ fields=["name", "finance_book"],
+ filters={"voucher_type": "Journal Entry", "voucher_no": jv.name},
+ )
for gl_entry in gl_entries:
self.assertEqual(gl_entry.finance_book, finance_book.name)
+
def create_finance_book():
if not frappe.db.exists("Finance Book", "_Test Finance Book"):
- finance_book = frappe.get_doc({
- "doctype": "Finance Book",
- "finance_book_name": "_Test Finance Book"
- }).insert()
+ finance_book = frappe.get_doc(
+ {"doctype": "Finance Book", "finance_book_name": "_Test Finance Book"}
+ ).insert()
else:
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
index dd893f9fc8..069ab5ea84 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
@@ -9,7 +9,9 @@ from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate
-class FiscalYearIncorrectDate(frappe.ValidationError): pass
+class FiscalYearIncorrectDate(frappe.ValidationError):
+ pass
+
class FiscalYear(Document):
@frappe.whitelist()
@@ -22,19 +24,33 @@ class FiscalYear(Document):
# clear cache
frappe.clear_cache()
- msgprint(_("{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect.").format(self.name))
+ msgprint(
+ _(
+ "{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect."
+ ).format(self.name)
+ )
def validate(self):
self.validate_dates()
self.validate_overlap()
if not self.is_new():
- year_start_end_dates = frappe.db.sql("""select year_start_date, year_end_date
- from `tabFiscal Year` where name=%s""", (self.name))
+ year_start_end_dates = frappe.db.sql(
+ """select year_start_date, year_end_date
+ from `tabFiscal Year` where name=%s""",
+ (self.name),
+ )
if year_start_end_dates:
- if getdate(self.year_start_date) != year_start_end_dates[0][0] or getdate(self.year_end_date) != year_start_end_dates[0][1]:
- frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."))
+ if (
+ getdate(self.year_start_date) != year_start_end_dates[0][0]
+ or getdate(self.year_end_date) != year_start_end_dates[0][1]
+ ):
+ frappe.throw(
+ _(
+ "Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."
+ )
+ )
def validate_dates(self):
if self.is_short_year:
@@ -43,14 +59,18 @@ class FiscalYear(Document):
return
if getdate(self.year_start_date) > getdate(self.year_end_date):
- frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
- FiscalYearIncorrectDate)
+ frappe.throw(
+ _("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
+ FiscalYearIncorrectDate,
+ )
date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1)
if getdate(self.year_end_date) != date:
- frappe.throw(_("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
- FiscalYearIncorrectDate)
+ frappe.throw(
+ _("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
+ FiscalYearIncorrectDate,
+ )
def on_update(self):
check_duplicate_fiscal_year(self)
@@ -59,11 +79,16 @@ class FiscalYear(Document):
def on_trash(self):
global_defaults = frappe.get_doc("Global Defaults")
if global_defaults.current_fiscal_year == self.name:
- frappe.throw(_("You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings").format(self.name))
+ frappe.throw(
+ _(
+ "You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings"
+ ).format(self.name)
+ )
frappe.cache().delete_value("fiscal_years")
def validate_overlap(self):
- existing_fiscal_years = frappe.db.sql("""select name from `tabFiscal Year`
+ existing_fiscal_years = frappe.db.sql(
+ """select name from `tabFiscal Year`
where (
(%(year_start_date)s between year_start_date and year_end_date)
or (%(year_end_date)s between year_start_date and year_end_date)
@@ -73,13 +98,18 @@ class FiscalYear(Document):
{
"year_start_date": self.year_start_date,
"year_end_date": self.year_end_date,
- "name": self.name or "No Name"
- }, as_dict=True)
+ "name": self.name or "No Name",
+ },
+ as_dict=True,
+ )
if existing_fiscal_years:
for existing in existing_fiscal_years:
- company_for_existing = frappe.db.sql_list("""select company from `tabFiscal Year Company`
- where parent=%s""", existing.name)
+ company_for_existing = frappe.db.sql_list(
+ """select company from `tabFiscal Year Company`
+ where parent=%s""",
+ existing.name,
+ )
overlap = False
if not self.get("companies") or not company_for_existing:
@@ -90,20 +120,36 @@ class FiscalYear(Document):
overlap = True
if overlap:
- frappe.throw(_("Year start date or end date is overlapping with {0}. To avoid please set company")
- .format(existing.name), frappe.NameError)
+ frappe.throw(
+ _("Year start date or end date is overlapping with {0}. To avoid please set company").format(
+ existing.name
+ ),
+ frappe.NameError,
+ )
+
@frappe.whitelist()
def check_duplicate_fiscal_year(doc):
- year_start_end_dates = frappe.db.sql("""select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""", (doc.name))
+ year_start_end_dates = frappe.db.sql(
+ """select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""",
+ (doc.name),
+ )
for fiscal_year, ysd, yed in year_start_end_dates:
- if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (not frappe.flags.in_test):
- frappe.throw(_("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(fiscal_year))
+ if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
+ not frappe.flags.in_test
+ ):
+ frappe.throw(
+ _("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(
+ fiscal_year
+ )
+ )
@frappe.whitelist()
def auto_create_fiscal_year():
- for d in frappe.db.sql("""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""):
+ for d in frappe.db.sql(
+ """select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""
+ ):
try:
current_fy = frappe.get_doc("Fiscal Year", d[0])
@@ -114,16 +160,14 @@ def auto_create_fiscal_year():
start_year = cstr(new_fy.year_start_date.year)
end_year = cstr(new_fy.year_end_date.year)
- new_fy.year = start_year if start_year==end_year else (start_year + "-" + end_year)
+ new_fy.year = start_year if start_year == end_year else (start_year + "-" + end_year)
new_fy.auto_created = 1
new_fy.insert(ignore_permissions=True)
except frappe.NameError:
pass
+
def get_from_and_to_date(fiscal_year):
- fields = [
- "year_start_date as from_date",
- "year_end_date as to_date"
- ]
+ fields = ["year_start_date as from_date", "year_end_date as to_date"]
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py
index 892a2c62cf..bc966916ef 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py
@@ -3,19 +3,13 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'fiscal_year',
- 'transactions': [
+ "fieldname": "fiscal_year",
+ "transactions": [
+ {"label": _("Budgets"), "items": ["Budget"]},
+ {"label": _("References"), "items": ["Period Closing Voucher"]},
{
- 'label': _('Budgets'),
- 'items': ['Budget']
+ "label": _("Target Details"),
+ "items": ["Sales Person", "Sales Partner", "Territory", "Monthly Distribution"],
},
- {
- 'label': _('References'),
- 'items': ['Period Closing Voucher']
- },
- {
- 'label': _('Target Details'),
- 'items': ['Sales Person', 'Sales Partner', 'Territory', 'Monthly Distribution']
- }
- ]
+ ],
}
diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
index 69e13a407d..6e946f7466 100644
--- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
@@ -11,43 +11,48 @@ from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrect
test_ignore = ["Company"]
-class TestFiscalYear(unittest.TestCase):
+class TestFiscalYear(unittest.TestCase):
def test_extra_year(self):
if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"):
frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000")
- fy = frappe.get_doc({
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2000",
- "year_end_date": "2002-12-31",
- "year_start_date": "2000-04-01"
- })
+ fy = frappe.get_doc(
+ {
+ "doctype": "Fiscal Year",
+ "year": "_Test Fiscal Year 2000",
+ "year_end_date": "2002-12-31",
+ "year_start_date": "2000-04-01",
+ }
+ )
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
def test_record_generator():
test_records = [
- {
- "doctype": "Fiscal Year",
- "year": "_Test Short Fiscal Year 2011",
- "is_short_year": 1,
- "year_end_date": "2011-04-01",
- "year_start_date": "2011-12-31"
- }
+ {
+ "doctype": "Fiscal Year",
+ "year": "_Test Short Fiscal Year 2011",
+ "is_short_year": 1,
+ "year_end_date": "2011-04-01",
+ "year_start_date": "2011-12-31",
+ }
]
start = 2012
end = now_datetime().year + 5
for year in range(start, end):
- test_records.append({
- "doctype": "Fiscal Year",
- "year": f"_Test Fiscal Year {year}",
- "year_start_date": f"{year}-01-01",
- "year_end_date": f"{year}-12-31"
- })
+ test_records.append(
+ {
+ "doctype": "Fiscal Year",
+ "year": f"_Test Fiscal Year {year}",
+ "year_start_date": f"{year}-01-01",
+ "year_end_date": f"{year}-12-31",
+ }
+ )
return test_records
+
test_records = test_record_generator()
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 6f7a0b29e8..aee7f0e0f9 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -25,6 +25,8 @@ from erpnext.exceptions import (
)
exclude_from_linked_with = True
+
+
class GLEntry(Document):
def autoname(self):
"""
@@ -57,14 +59,18 @@ class GLEntry(Document):
validate_frozen_account(self.account, adv_adj)
# Update outstanding amt on against voucher
- if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees']
- and self.against_voucher and self.flags.update_outstanding == 'Yes'
- and not frappe.flags.is_reverse_depr_entry):
- update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
- self.against_voucher)
+ if (
+ self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
+ and self.against_voucher
+ and self.flags.update_outstanding == "Yes"
+ and not frappe.flags.is_reverse_depr_entry
+ ):
+ update_outstanding_amt(
+ self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
+ )
def check_mandatory(self):
- mandatory = ['account','voucher_type','voucher_no','company']
+ mandatory = ["account", "voucher_type", "voucher_no", "company"]
for k in mandatory:
if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
@@ -72,29 +78,40 @@ class GLEntry(Document):
if not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type == "Receivable":
- frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
- .format(self.voucher_type, self.voucher_no, self.account))
+ frappe.throw(
+ _("{0} {1}: Customer is required against Receivable account {2}").format(
+ self.voucher_type, self.voucher_no, self.account
+ )
+ )
elif account_type == "Payable":
- frappe.throw(_("{0} {1}: Supplier is required against Payable account {2}")
- .format(self.voucher_type, self.voucher_no, self.account))
+ frappe.throw(
+ _("{0} {1}: Supplier is required against Payable account {2}").format(
+ self.voucher_type, self.voucher_no, self.account
+ )
+ )
# Zero value transaction is not allowed
if not (flt(self.debit, self.precision("debit")) or flt(self.credit, self.precision("credit"))):
- frappe.throw(_("{0} {1}: Either debit or credit amount is required for {2}")
- .format(self.voucher_type, self.voucher_no, self.account))
+ frappe.throw(
+ _("{0} {1}: Either debit or credit amount is required for {2}").format(
+ self.voucher_type, self.voucher_no, self.account
+ )
+ )
def pl_must_have_cost_center(self):
"""Validate that profit and loss type account GL entries have a cost center."""
- if self.cost_center or self.voucher_type == 'Period Closing Voucher':
+ if self.cost_center or self.voucher_type == "Period Closing Voucher":
return
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
- self.voucher_type, self.voucher_no, self.account)
+ self.voucher_type, self.voucher_no, self.account
+ )
msg += " "
- msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
- self.voucher_type)
+ msg += _(
+ "Please set the cost center field in {0} or setup a default Cost Center for the Company."
+ ).format(self.voucher_type)
frappe.throw(msg, title=_("Missing Cost Center"))
@@ -102,17 +119,31 @@ class GLEntry(Document):
account_type = frappe.db.get_value("Account", self.account, "report_type")
for dimension in get_checks_for_pl_and_bs_accounts():
- if account_type == "Profit and Loss" \
- and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
+ if (
+ account_type == "Profit and Loss"
+ and self.company == dimension.company
+ and dimension.mandatory_for_pl
+ and not dimension.disabled
+ ):
if not self.get(dimension.fieldname):
- frappe.throw(_("Accounting Dimension {0} is required for 'Profit and Loss' account {1}.")
- .format(dimension.label, self.account))
+ frappe.throw(
+ _("Accounting Dimension {0} is required for 'Profit and Loss' account {1}.").format(
+ dimension.label, self.account
+ )
+ )
- if account_type == "Balance Sheet" \
- and self.company == dimension.company and dimension.mandatory_for_bs and not dimension.disabled:
+ if (
+ account_type == "Balance Sheet"
+ and self.company == dimension.company
+ and dimension.mandatory_for_bs
+ and not dimension.disabled
+ ):
if not self.get(dimension.fieldname):
- frappe.throw(_("Accounting Dimension {0} is required for 'Balance Sheet' account {1}.")
- .format(dimension.label, self.account))
+ frappe.throw(
+ _("Accounting Dimension {0} is required for 'Balance Sheet' account {1}.").format(
+ dimension.label, self.account
+ )
+ )
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
@@ -121,56 +152,97 @@ class GLEntry(Document):
account = key[1]
if self.account == account:
- if value['is_mandatory'] and not self.get(dimension):
- frappe.throw(_("{0} is mandatory for account {1}").format(
- frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
+ if value["is_mandatory"] and not self.get(dimension):
+ frappe.throw(
+ _("{0} is mandatory for account {1}").format(
+ frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
+ ),
+ MandatoryAccountDimensionError,
+ )
- if value['allow_or_restrict'] == 'Allow':
- if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
- frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
- frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
+ if value["allow_or_restrict"] == "Allow":
+ if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
+ frappe.throw(
+ _("Invalid value {0} for {1} against account {2}").format(
+ frappe.bold(self.get(dimension)),
+ frappe.bold(frappe.unscrub(dimension)),
+ frappe.bold(self.account),
+ ),
+ InvalidAccountDimensionError,
+ )
else:
- if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
- frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
- frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
+ if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
+ frappe.throw(
+ _("Invalid value {0} for {1} against account {2}").format(
+ frappe.bold(self.get(dimension)),
+ frappe.bold(frappe.unscrub(dimension)),
+ frappe.bold(self.account),
+ ),
+ InvalidAccountDimensionError,
+ )
def check_pl_account(self):
- if self.is_opening=='Yes' and \
- frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss" and not self.is_cancelled:
- frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry")
- .format(self.voucher_type, self.voucher_no, self.account))
+ if (
+ self.is_opening == "Yes"
+ and frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss"
+ and not self.is_cancelled
+ ):
+ frappe.throw(
+ _("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry").format(
+ self.voucher_type, self.voucher_no, self.account
+ )
+ )
def validate_account_details(self, adv_adj):
"""Account must be ledger, active and not freezed"""
- ret = frappe.db.sql("""select is_group, docstatus, company
- from tabAccount where name=%s""", self.account, as_dict=1)[0]
+ ret = frappe.db.sql(
+ """select is_group, docstatus, company
+ from tabAccount where name=%s""",
+ self.account,
+ as_dict=1,
+ )[0]
- if ret.is_group==1:
- frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''')
- .format(self.voucher_type, self.voucher_no, self.account))
+ if ret.is_group == 1:
+ frappe.throw(
+ _(
+ """{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
+ ).format(self.voucher_type, self.voucher_no, self.account)
+ )
- if ret.docstatus==2:
- frappe.throw(_("{0} {1}: Account {2} is inactive")
- .format(self.voucher_type, self.voucher_no, self.account))
+ if ret.docstatus == 2:
+ frappe.throw(
+ _("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
+ )
if ret.company != self.company:
- frappe.throw(_("{0} {1}: Account {2} does not belong to Company {3}")
- .format(self.voucher_type, self.voucher_no, self.account, self.company))
+ frappe.throw(
+ _("{0} {1}: Account {2} does not belong to Company {3}").format(
+ self.voucher_type, self.voucher_no, self.account, self.company
+ )
+ )
def validate_cost_center(self):
- if not self.cost_center: return
+ if not self.cost_center:
+ return
- is_group, company = frappe.get_cached_value('Cost Center',
- self.cost_center, ['is_group', 'company'])
+ is_group, company = frappe.get_cached_value(
+ "Cost Center", self.cost_center, ["is_group", "company"]
+ )
if company != self.company:
- frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
- .format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
+ frappe.throw(
+ _("{0} {1}: Cost Center {2} does not belong to Company {3}").format(
+ self.voucher_type, self.voucher_no, self.cost_center, self.company
+ )
+ )
- if (self.voucher_type != 'Period Closing Voucher' and is_group):
- frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
- self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
+ if self.voucher_type != "Period Closing Voucher" and is_group:
+ frappe.throw(
+ _(
+ """{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions"""
+ ).format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))
+ )
def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party)
@@ -183,9 +255,12 @@ class GLEntry(Document):
self.account_currency = account_currency or company_currency
if account_currency != self.account_currency:
- frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")
- .format(self.voucher_type, self.voucher_no, self.account,
- (account_currency or company_currency)), InvalidAccountCurrency)
+ frappe.throw(
+ _("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}").format(
+ self.voucher_type, self.voucher_no, self.account, (account_currency or company_currency)
+ ),
+ InvalidAccountCurrency,
+ )
if self.party_type and self.party:
validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency)
@@ -194,51 +269,80 @@ class GLEntry(Document):
if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
+
def validate_balance_type(account, adv_adj=False):
if not adv_adj and account:
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
if balance_must_be:
- balance = frappe.db.sql("""select sum(debit) - sum(credit)
- from `tabGL Entry` where account = %s""", account)[0][0]
+ balance = frappe.db.sql(
+ """select sum(debit) - sum(credit)
+ from `tabGL Entry` where account = %s""",
+ account,
+ )[0][0]
- if (balance_must_be=="Debit" and flt(balance) < 0) or \
- (balance_must_be=="Credit" and flt(balance) > 0):
- frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be)))
+ if (balance_must_be == "Debit" and flt(balance) < 0) or (
+ balance_must_be == "Credit" and flt(balance) > 0
+ ):
+ frappe.throw(
+ _("Balance for Account {0} must always be {1}").format(account, _(balance_must_be))
+ )
-def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False):
+
+def update_outstanding_amt(
+ account, party_type, party, against_voucher_type, against_voucher, on_cancel=False
+):
if party_type and party:
- party_condition = " and party_type={0} and party={1}"\
- .format(frappe.db.escape(party_type), frappe.db.escape(party))
+ party_condition = " and party_type={0} and party={1}".format(
+ frappe.db.escape(party_type), frappe.db.escape(party)
+ )
else:
party_condition = ""
if against_voucher_type == "Sales Invoice":
party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to")
- account_condition = "and account in ({0}, {1})".format(frappe.db.escape(account), frappe.db.escape(party_account))
+ account_condition = "and account in ({0}, {1})".format(
+ frappe.db.escape(account), frappe.db.escape(party_account)
+ )
else:
account_condition = " and account = {0}".format(frappe.db.escape(account))
# get final outstanding amt
- bal = flt(frappe.db.sql("""
+ bal = flt(
+ frappe.db.sql(
+ """
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry`
where against_voucher_type=%s and against_voucher=%s
and voucher_type != 'Invoice Discounting'
- {0} {1}""".format(party_condition, account_condition),
- (against_voucher_type, against_voucher))[0][0] or 0.0)
+ {0} {1}""".format(
+ party_condition, account_condition
+ ),
+ (against_voucher_type, against_voucher),
+ )[0][0]
+ or 0.0
+ )
- if against_voucher_type == 'Purchase Invoice':
+ if against_voucher_type == "Purchase Invoice":
bal = -bal
elif against_voucher_type == "Journal Entry":
- against_voucher_amount = flt(frappe.db.sql("""
+ against_voucher_amount = flt(
+ frappe.db.sql(
+ """
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
- and account = %s and (against_voucher is null or against_voucher='') {0}"""
- .format(party_condition), (against_voucher, account))[0][0])
+ and account = %s and (against_voucher is null or against_voucher='') {0}""".format(
+ party_condition
+ ),
+ (against_voucher, account),
+ )[0][0]
+ )
if not against_voucher_amount:
- frappe.throw(_("Against Journal Entry {0} is already adjusted against some other voucher")
- .format(against_voucher))
+ frappe.throw(
+ _("Against Journal Entry {0} is already adjusted against some other voucher").format(
+ against_voucher
+ )
+ )
bal = against_voucher_amount + bal
if against_voucher_amount < 0:
@@ -246,44 +350,51 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
# Validation : Outstanding can not be negative for JV
if bal < 0 and not on_cancel:
- frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
+ frappe.throw(
+ _("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"]:
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
# Didn't use db_set for optimisation purpose
ref_doc.outstanding_amount = bal
- frappe.db.set_value(against_voucher_type, against_voucher, 'outstanding_amount', bal)
+ frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)
ref_doc.set_status(update=True)
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
- if frozen_account == 'Yes' and not adv_adj:
- frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,
- 'frozen_accounts_modifier')
+ if frozen_account == "Yes" and not adv_adj:
+ frozen_accounts_modifier = frappe.db.get_value(
+ "Accounts Settings", None, "frozen_accounts_modifier"
+ )
if not frozen_accounts_modifier:
frappe.throw(_("Account {0} is frozen").format(account))
elif frozen_accounts_modifier not in frappe.get_roles():
frappe.throw(_("Not authorized to edit frozen Account {0}").format(account))
+
def update_against_account(voucher_type, voucher_no):
- entries = frappe.db.get_all("GL Entry",
+ entries = frappe.db.get_all(
+ "GL Entry",
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
- fields=["name", "party", "against", "debit", "credit", "account", "company"])
+ fields=["name", "party", "against", "debit", "credit", "account", "company"],
+ )
if not entries:
return
company_currency = erpnext.get_company_currency(entries[0].company)
- precision = get_field_precision(frappe.get_meta("GL Entry")
- .get_field("debit"), company_currency)
+ precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
accounts_debited, accounts_credited = [], []
for d in entries:
- if flt(d.debit, precision) > 0: accounts_debited.append(d.party or d.account)
- if flt(d.credit, precision) > 0: accounts_credited.append(d.party or d.account)
+ if flt(d.debit, precision) > 0:
+ accounts_debited.append(d.party or d.account)
+ if flt(d.credit, precision) > 0:
+ accounts_credited.append(d.party or d.account)
for d in entries:
if flt(d.debit, precision) > 0:
@@ -294,14 +405,17 @@ def update_against_account(voucher_type, voucher_no):
if d.against != new_against:
frappe.db.set_value("GL Entry", d.name, "against", new_against)
+
def on_doctype_update():
frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
+
def rename_gle_sle_docs():
for doctype in ["GL Entry", "Stock Ledger Entry"]:
rename_temporarily_named_docs(doctype)
+
def rename_temporarily_named_docs(doctype):
"""Rename temporarily named docs using autoname options"""
docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation", limit=50000)
@@ -312,5 +426,5 @@ def rename_temporarily_named_docs(doctype):
frappe.db.sql(
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
(newname, oldname),
- auto_commit=True
+ auto_commit=True,
)
diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
index 3de2394689..b188b09843 100644
--- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
@@ -14,48 +14,68 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestGLEntry(unittest.TestCase):
def test_round_off_entry(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC")
- frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC")
+ frappe.db.set_value(
+ "Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC"
+ )
- jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", 100, "_Test Cost Center - _TC", submit=False)
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 100,
+ "_Test Cost Center - _TC",
+ submit=False,
+ )
jv.get("accounts")[0].debit = 100.01
jv.flags.ignore_validate = True
jv.submit()
- round_off_entry = frappe.db.sql("""select name from `tabGL Entry`
+ round_off_entry = frappe.db.sql(
+ """select name from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_no = %s
and account='_Test Write Off - _TC' and cost_center='_Test Cost Center - _TC'
- and debit = 0 and credit = '.01'""", jv.name)
+ and debit = 0 and credit = '.01'""",
+ jv.name,
+ )
self.assertTrue(round_off_entry)
def test_rename_entries(self):
- je = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True)
+ je = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True
+ )
rename_gle_sle_docs()
naming_series = parse_naming_series(parts=frappe.get_meta("GL Entry").autoname.split(".")[:-1])
- je = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True)
+ je = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True
+ )
- gl_entries = frappe.get_all("GL Entry",
+ gl_entries = frappe.get_all(
+ "GL Entry",
fields=["name", "to_rename"],
filters={"voucher_type": "Journal Entry", "voucher_no": je.name},
- order_by="creation"
+ order_by="creation",
)
self.assertTrue(all(entry.to_rename == 1 for entry in gl_entries))
- old_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
+ old_naming_series_current_value = frappe.db.sql(
+ "SELECT current from tabSeries where name = %s", naming_series
+ )[0][0]
rename_gle_sle_docs()
- new_gl_entries = frappe.get_all("GL Entry",
+ new_gl_entries = frappe.get_all(
+ "GL Entry",
fields=["name", "to_rename"],
filters={"voucher_type": "Journal Entry", "voucher_no": je.name},
- order_by="creation"
+ order_by="creation",
)
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)))
- new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
+ new_naming_series_current_value = frappe.db.sql(
+ "SELECT current from tabSeries where name = %s", naming_series
+ )[0][0]
self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index 09c389de73..5bd4585a9a 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -33,19 +33,32 @@ class InvoiceDiscounting(AccountsController):
frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting"))
def validate_invoices(self):
- discounted_invoices = [record.sales_invoice for record in
- frappe.get_all("Discounted Invoice",fields=["sales_invoice"], filters={"docstatus":1})]
+ discounted_invoices = [
+ record.sales_invoice
+ for record in frappe.get_all(
+ "Discounted Invoice", fields=["sales_invoice"], filters={"docstatus": 1}
+ )
+ ]
for record in self.invoices:
if record.sales_invoice in discounted_invoices:
- frappe.throw(_("Row({0}): {1} is already discounted in {2}")
- .format(record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent)))
+ frappe.throw(
+ _("Row({0}): {1} is already discounted in {2}").format(
+ record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent)
+ )
+ )
- actual_outstanding = frappe.db.get_value("Sales Invoice", record.sales_invoice,"outstanding_amount")
- if record.outstanding_amount > actual_outstanding :
- frappe.throw(_
- ("Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}").format(
- record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)))
+ actual_outstanding = frappe.db.get_value(
+ "Sales Invoice", record.sales_invoice, "outstanding_amount"
+ )
+ if record.outstanding_amount > actual_outstanding:
+ frappe.throw(
+ _(
+ "Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}"
+ ).format(
+ record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)
+ )
+ )
def calculate_total_amount(self):
self.total_amount = sum(flt(d.outstanding_amount) for d in self.invoices)
@@ -73,24 +86,21 @@ class InvoiceDiscounting(AccountsController):
self.status = "Cancelled"
if cancel:
- self.db_set('status', self.status, update_modified = True)
+ self.db_set("status", self.status, update_modified=True)
def update_sales_invoice(self):
for d in self.invoices:
if self.docstatus == 1:
is_discounted = 1
else:
- discounted_invoice = frappe.db.exists({
- "doctype": "Discounted Invoice",
- "sales_invoice": d.sales_invoice,
- "docstatus": 1
- })
+ discounted_invoice = frappe.db.exists(
+ {"doctype": "Discounted Invoice", "sales_invoice": d.sales_invoice, "docstatus": 1}
+ )
is_discounted = 1 if discounted_invoice else 0
frappe.db.set_value("Sales Invoice", d.sales_invoice, "is_discounted", is_discounted)
def make_gl_entries(self):
- company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
-
+ company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
gl_entries = []
invoice_fields = ["debit_to", "party_account_currency", "conversion_rate", "cost_center"]
@@ -102,135 +112,182 @@ class InvoiceDiscounting(AccountsController):
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1)
if d.outstanding_amount:
- outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate,
- d.precision("outstanding_amount"))
- ar_credit_account_currency = frappe.get_cached_value("Account", self.accounts_receivable_credit, "currency")
+ outstanding_in_company_currency = flt(
+ d.outstanding_amount * inv.conversion_rate, d.precision("outstanding_amount")
+ )
+ ar_credit_account_currency = frappe.get_cached_value(
+ "Account", self.accounts_receivable_credit, "currency"
+ )
- gl_entries.append(self.get_gl_dict({
- "account": inv.debit_to,
- "party_type": "Customer",
- "party": d.customer,
- "against": self.accounts_receivable_credit,
- "credit": outstanding_in_company_currency,
- "credit_in_account_currency": outstanding_in_company_currency \
- if inv.party_account_currency==company_currency else d.outstanding_amount,
- "cost_center": inv.cost_center,
- "against_voucher": d.sales_invoice,
- "against_voucher_type": "Sales Invoice"
- }, inv.party_account_currency, item=inv))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": inv.debit_to,
+ "party_type": "Customer",
+ "party": d.customer,
+ "against": self.accounts_receivable_credit,
+ "credit": outstanding_in_company_currency,
+ "credit_in_account_currency": outstanding_in_company_currency
+ if inv.party_account_currency == company_currency
+ else d.outstanding_amount,
+ "cost_center": inv.cost_center,
+ "against_voucher": d.sales_invoice,
+ "against_voucher_type": "Sales Invoice",
+ },
+ inv.party_account_currency,
+ item=inv,
+ )
+ )
- gl_entries.append(self.get_gl_dict({
- "account": self.accounts_receivable_credit,
- "party_type": "Customer",
- "party": d.customer,
- "against": inv.debit_to,
- "debit": outstanding_in_company_currency,
- "debit_in_account_currency": outstanding_in_company_currency \
- if ar_credit_account_currency==company_currency else d.outstanding_amount,
- "cost_center": inv.cost_center,
- "against_voucher": d.sales_invoice,
- "against_voucher_type": "Sales Invoice"
- }, ar_credit_account_currency, item=inv))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": self.accounts_receivable_credit,
+ "party_type": "Customer",
+ "party": d.customer,
+ "against": inv.debit_to,
+ "debit": outstanding_in_company_currency,
+ "debit_in_account_currency": outstanding_in_company_currency
+ if ar_credit_account_currency == company_currency
+ else d.outstanding_amount,
+ "cost_center": inv.cost_center,
+ "against_voucher": d.sales_invoice,
+ "against_voucher_type": "Sales Invoice",
+ },
+ ar_credit_account_currency,
+ item=inv,
+ )
+ )
- make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
+ make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding="No")
@frappe.whitelist()
def create_disbursement_entry(self):
je = frappe.new_doc("Journal Entry")
- je.voucher_type = 'Journal Entry'
+ je.voucher_type = "Journal Entry"
je.company = self.company
- je.remark = 'Loan Disbursement entry against Invoice Discounting: ' + self.name
+ je.remark = "Loan Disbursement entry against Invoice Discounting: " + self.name
- je.append("accounts", {
- "account": self.bank_account,
- "debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges),
- "cost_center": erpnext.get_default_cost_center(self.company)
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.bank_account,
+ "debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ },
+ )
if self.bank_charges:
- je.append("accounts", {
- "account": self.bank_charges_account,
- "debit_in_account_currency": flt(self.bank_charges),
- "cost_center": erpnext.get_default_cost_center(self.company)
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.bank_charges_account,
+ "debit_in_account_currency": flt(self.bank_charges),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ },
+ )
- je.append("accounts", {
- "account": self.short_term_loan,
- "credit_in_account_currency": flt(self.total_amount),
- "cost_center": erpnext.get_default_cost_center(self.company),
- "reference_type": "Invoice Discounting",
- "reference_name": self.name
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.short_term_loan,
+ "credit_in_account_currency": flt(self.total_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "reference_type": "Invoice Discounting",
+ "reference_name": self.name,
+ },
+ )
for d in self.invoices:
- je.append("accounts", {
- "account": self.accounts_receivable_discounted,
- "debit_in_account_currency": flt(d.outstanding_amount),
- "cost_center": erpnext.get_default_cost_center(self.company),
- "reference_type": "Invoice Discounting",
- "reference_name": self.name,
- "party_type": "Customer",
- "party": d.customer
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.accounts_receivable_discounted,
+ "debit_in_account_currency": flt(d.outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "reference_type": "Invoice Discounting",
+ "reference_name": self.name,
+ "party_type": "Customer",
+ "party": d.customer,
+ },
+ )
- je.append("accounts", {
- "account": self.accounts_receivable_credit,
- "credit_in_account_currency": flt(d.outstanding_amount),
- "cost_center": erpnext.get_default_cost_center(self.company),
- "reference_type": "Invoice Discounting",
- "reference_name": self.name,
- "party_type": "Customer",
- "party": d.customer
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.accounts_receivable_credit,
+ "credit_in_account_currency": flt(d.outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "reference_type": "Invoice Discounting",
+ "reference_name": self.name,
+ "party_type": "Customer",
+ "party": d.customer,
+ },
+ )
return je
@frappe.whitelist()
def close_loan(self):
je = frappe.new_doc("Journal Entry")
- je.voucher_type = 'Journal Entry'
+ je.voucher_type = "Journal Entry"
je.company = self.company
- je.remark = 'Loan Settlement entry against Invoice Discounting: ' + self.name
+ je.remark = "Loan Settlement entry against Invoice Discounting: " + self.name
- je.append("accounts", {
- "account": self.short_term_loan,
- "debit_in_account_currency": flt(self.total_amount),
- "cost_center": erpnext.get_default_cost_center(self.company),
- "reference_type": "Invoice Discounting",
- "reference_name": self.name,
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.short_term_loan,
+ "debit_in_account_currency": flt(self.total_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "reference_type": "Invoice Discounting",
+ "reference_name": self.name,
+ },
+ )
- je.append("accounts", {
- "account": self.bank_account,
- "credit_in_account_currency": flt(self.total_amount),
- "cost_center": erpnext.get_default_cost_center(self.company)
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.bank_account,
+ "credit_in_account_currency": flt(self.total_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ },
+ )
if getdate(self.loan_end_date) > getdate(nowdate()):
for d in self.invoices:
- outstanding_amount = frappe.db.get_value("Sales Invoice", d.sales_invoice, "outstanding_amount")
+ outstanding_amount = frappe.db.get_value(
+ "Sales Invoice", d.sales_invoice, "outstanding_amount"
+ )
if flt(outstanding_amount) > 0:
- je.append("accounts", {
- "account": self.accounts_receivable_discounted,
- "credit_in_account_currency": flt(outstanding_amount),
- "cost_center": erpnext.get_default_cost_center(self.company),
- "reference_type": "Invoice Discounting",
- "reference_name": self.name,
- "party_type": "Customer",
- "party": d.customer
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.accounts_receivable_discounted,
+ "credit_in_account_currency": flt(outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "reference_type": "Invoice Discounting",
+ "reference_name": self.name,
+ "party_type": "Customer",
+ "party": d.customer,
+ },
+ )
- je.append("accounts", {
- "account": self.accounts_receivable_unpaid,
- "debit_in_account_currency": flt(outstanding_amount),
- "cost_center": erpnext.get_default_cost_center(self.company),
- "reference_type": "Invoice Discounting",
- "reference_name": self.name,
- "party_type": "Customer",
- "party": d.customer
- })
+ je.append(
+ "accounts",
+ {
+ "account": self.accounts_receivable_unpaid,
+ "debit_in_account_currency": flt(outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "reference_type": "Invoice Discounting",
+ "reference_name": self.name,
+ "party_type": "Customer",
+ "party": d.customer,
+ },
+ )
return je
+
@frappe.whitelist()
def get_invoices(filters):
filters = frappe._dict(json.loads(filters))
@@ -250,7 +307,8 @@ def get_invoices(filters):
if cond:
where_condition += " and " + " and ".join(cond)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
name as sales_invoice,
customer,
@@ -264,17 +322,26 @@ def get_invoices(filters):
%s
and not exists(select di.name from `tabDiscounted Invoice` di
where di.docstatus=1 and di.sales_invoice=si.name)
- """ % where_condition, filters, as_dict=1)
+ """
+ % where_condition,
+ filters,
+ as_dict=1,
+ )
+
def get_party_account_based_on_invoice_discounting(sales_invoice):
party_account = None
- invoice_discounting = frappe.db.sql("""
+ invoice_discounting = frappe.db.sql(
+ """
select par.accounts_receivable_discounted, par.accounts_receivable_unpaid, par.status
from `tabInvoice Discounting` par, `tabDiscounted Invoice` ch
where par.name=ch.parent
and par.docstatus=1
and ch.sales_invoice = %s
- """, (sales_invoice), as_dict=1)
+ """,
+ (sales_invoice),
+ as_dict=1,
+ )
if invoice_discounting:
if invoice_discounting[0].status == "Disbursed":
party_account = invoice_discounting[0].accounts_receivable_discounted
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py
index b748429302..a442231d9a 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py
@@ -3,18 +3,10 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'reference_name',
- 'internal_links': {
- 'Sales Invoice': ['invoices', 'sales_invoice']
- },
- 'transactions': [
- {
- 'label': _('Reference'),
- 'items': ['Sales Invoice']
- },
- {
- 'label': _('Payment'),
- 'items': ['Payment Entry', 'Journal Entry']
- }
- ]
+ "fieldname": "reference_name",
+ "internal_links": {"Sales Invoice": ["invoices", "sales_invoice"]},
+ "transactions": [
+ {"label": _("Reference"), "items": ["Sales Invoice"]},
+ {"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
index d1d4be36f1..a85fdfcad7 100644
--- a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
@@ -14,52 +14,74 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_
class TestInvoiceDiscounting(unittest.TestCase):
def setUp(self):
- self.ar_credit = create_account(account_name="_Test Accounts Receivable Credit", parent_account = "Accounts Receivable - _TC", company="_Test Company")
- self.ar_discounted = create_account(account_name="_Test Accounts Receivable Discounted", parent_account = "Accounts Receivable - _TC", company="_Test Company")
- self.ar_unpaid = create_account(account_name="_Test Accounts Receivable Unpaid", parent_account = "Accounts Receivable - _TC", company="_Test Company")
- self.short_term_loan = create_account(account_name="_Test Short Term Loan", parent_account = "Source of Funds (Liabilities) - _TC", company="_Test Company")
- self.bank_account = create_account(account_name="_Test Bank 2", parent_account = "Bank Accounts - _TC", company="_Test Company")
- self.bank_charges_account = create_account(account_name="_Test Bank Charges Account", parent_account = "Expenses - _TC", company="_Test Company")
+ self.ar_credit = create_account(
+ account_name="_Test Accounts Receivable Credit",
+ parent_account="Accounts Receivable - _TC",
+ company="_Test Company",
+ )
+ self.ar_discounted = create_account(
+ account_name="_Test Accounts Receivable Discounted",
+ parent_account="Accounts Receivable - _TC",
+ company="_Test Company",
+ )
+ self.ar_unpaid = create_account(
+ account_name="_Test Accounts Receivable Unpaid",
+ parent_account="Accounts Receivable - _TC",
+ company="_Test Company",
+ )
+ self.short_term_loan = create_account(
+ account_name="_Test Short Term Loan",
+ parent_account="Source of Funds (Liabilities) - _TC",
+ company="_Test Company",
+ )
+ self.bank_account = create_account(
+ account_name="_Test Bank 2", parent_account="Bank Accounts - _TC", company="_Test Company"
+ )
+ self.bank_charges_account = create_account(
+ account_name="_Test Bank Charges Account",
+ parent_account="Expenses - _TC",
+ company="_Test Company",
+ )
frappe.db.set_value("Company", "_Test Company", "default_bank_account", self.bank_account)
def test_total_amount(self):
inv1 = create_sales_invoice(rate=200)
inv2 = create_sales_invoice(rate=500)
- inv_disc = create_invoice_discounting([inv1.name, inv2.name],
+ inv_disc = create_invoice_discounting(
+ [inv1.name, inv2.name],
do_not_submit=True,
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
- bank_account=self.bank_account
- )
+ bank_account=self.bank_account,
+ )
self.assertEqual(inv_disc.total_amount, 700)
def test_gl_entries_in_base_currency(self):
inv = create_sales_invoice(rate=200)
- inv_disc = create_invoice_discounting([inv.name],
+ inv_disc = create_invoice_discounting(
+ [inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
- bank_account=self.bank_account
- )
+ bank_account=self.bank_account,
+ )
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):
self.assertEqual([gle.debit, gle.credit], expected_gle.get(gle.account))
def test_loan_on_submit(self):
inv = create_sales_invoice(rate=300)
- inv_disc = create_invoice_discounting([inv.name],
+ inv_disc = create_invoice_discounting(
+ [inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -67,28 +89,33 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=nowdate(),
- period=60
- )
+ period=60,
+ )
self.assertEqual(inv_disc.status, "Sanctioned")
- self.assertEqual(inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period))
-
+ self.assertEqual(
+ inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period)
+ )
def test_on_disbursed(self):
inv = create_sales_invoice(rate=500)
- inv_disc = create_invoice_discounting([inv.name],
+ inv_disc = create_invoice_discounting(
+ [inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
- bank_charges=100
- )
+ bank_charges=100,
+ )
je = inv_disc.create_disbursement_entry()
self.assertEqual(je.accounts[0].account, self.bank_account)
- self.assertEqual(je.accounts[0].debit_in_account_currency, flt(inv_disc.total_amount) - flt(inv_disc.bank_charges))
+ self.assertEqual(
+ je.accounts[0].debit_in_account_currency,
+ flt(inv_disc.total_amount) - flt(inv_disc.bank_charges),
+ )
self.assertEqual(je.accounts[1].account, self.bank_charges_account)
self.assertEqual(je.accounts[1].debit_in_account_currency, flt(inv_disc.bank_charges))
@@ -102,7 +129,6 @@ class TestInvoiceDiscounting(unittest.TestCase):
self.assertEqual(je.accounts[4].account, self.ar_credit)
self.assertEqual(je.accounts[4].credit_in_account_currency, flt(inv.outstanding_amount))
-
je.posting_date = nowdate()
je.submit()
@@ -114,7 +140,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
def test_on_close_after_loan_period(self):
inv = create_sales_invoice(rate=600)
- inv_disc = create_invoice_discounting([inv.name],
+ inv_disc = create_invoice_discounting(
+ [inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -122,8 +149,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=nowdate(),
- period=60
- )
+ period=60,
+ )
je1 = inv_disc.create_disbursement_entry()
je1.posting_date = nowdate()
@@ -151,7 +178,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
def test_on_close_after_loan_period_after_inv_payment(self):
inv = create_sales_invoice(rate=600)
- inv_disc = create_invoice_discounting([inv.name],
+ inv_disc = create_invoice_discounting(
+ [inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -159,8 +187,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=nowdate(),
- period=60
- )
+ period=60,
+ )
je1 = inv_disc.create_disbursement_entry()
je1.posting_date = nowdate()
@@ -183,7 +211,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
def test_on_close_before_loan_period(self):
inv = create_sales_invoice(rate=700)
- inv_disc = create_invoice_discounting([inv.name],
+ inv_disc = create_invoice_discounting(
+ [inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -191,7 +220,7 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=add_days(nowdate(), -80),
- period=60
+ period=60,
)
je1 = inv_disc.create_disbursement_entry()
@@ -209,16 +238,17 @@ class TestInvoiceDiscounting(unittest.TestCase):
self.assertEqual(je2.accounts[1].credit_in_account_currency, flt(inv_disc.total_amount))
def test_make_payment_before_loan_period(self):
- #it has problem
+ # it has problem
inv = create_sales_invoice(rate=700)
- inv_disc = create_invoice_discounting([inv.name],
- accounts_receivable_credit=self.ar_credit,
- accounts_receivable_discounted=self.ar_discounted,
- accounts_receivable_unpaid=self.ar_unpaid,
- short_term_loan=self.short_term_loan,
- bank_charges_account=self.bank_charges_account,
- bank_account=self.bank_account
- )
+ inv_disc = create_invoice_discounting(
+ [inv.name],
+ accounts_receivable_credit=self.ar_credit,
+ accounts_receivable_discounted=self.ar_discounted,
+ accounts_receivable_unpaid=self.ar_unpaid,
+ short_term_loan=self.short_term_loan,
+ bank_charges_account=self.bank_charges_account,
+ bank_account=self.bank_account,
+ )
je = inv_disc.create_disbursement_entry()
inv_disc.reload()
je.posting_date = nowdate()
@@ -232,26 +262,31 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_discounted)
- self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
+ self.assertEqual(
+ je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
+ )
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
- self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
+ self.assertEqual(
+ je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
+ )
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)
def test_make_payment_before_after_period(self):
- #it has problem
+ # it has problem
inv = create_sales_invoice(rate=700)
- inv_disc = create_invoice_discounting([inv.name],
- accounts_receivable_credit=self.ar_credit,
- accounts_receivable_discounted=self.ar_discounted,
- accounts_receivable_unpaid=self.ar_unpaid,
- short_term_loan=self.short_term_loan,
- bank_charges_account=self.bank_charges_account,
- bank_account=self.bank_account,
- loan_start_date=add_days(nowdate(), -10),
- period=5
- )
+ inv_disc = create_invoice_discounting(
+ [inv.name],
+ accounts_receivable_credit=self.ar_credit,
+ accounts_receivable_discounted=self.ar_discounted,
+ accounts_receivable_unpaid=self.ar_unpaid,
+ short_term_loan=self.short_term_loan,
+ bank_charges_account=self.bank_charges_account,
+ bank_account=self.bank_account,
+ loan_start_date=add_days(nowdate(), -10),
+ period=5,
+ )
je = inv_disc.create_disbursement_entry()
inv_disc.reload()
je.posting_date = nowdate()
@@ -269,9 +304,13 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_unpaid)
- self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
+ self.assertEqual(
+ je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
+ )
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
- self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
+ self.assertEqual(
+ je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
+ )
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)
@@ -287,17 +326,15 @@ def create_invoice_discounting(invoices, **args):
inv_disc.accounts_receivable_credit = args.accounts_receivable_credit
inv_disc.accounts_receivable_discounted = args.accounts_receivable_discounted
inv_disc.accounts_receivable_unpaid = args.accounts_receivable_unpaid
- inv_disc.short_term_loan=args.short_term_loan
- inv_disc.bank_charges_account=args.bank_charges_account
- inv_disc.bank_account=args.bank_account
+ inv_disc.short_term_loan = args.short_term_loan
+ inv_disc.bank_charges_account = args.bank_charges_account
+ inv_disc.bank_account = args.bank_account
inv_disc.loan_start_date = args.start or nowdate()
inv_disc.loan_period = args.period or 30
inv_disc.bank_charges = flt(args.bank_charges)
for d in invoices:
- inv_disc.append("invoices", {
- "sales_invoice": d
- })
+ inv_disc.append("invoices", {"sales_invoice": d})
inv_disc.insert()
if not args.do_not_submit:
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.py b/erpnext/accounts/doctype/item_tax_template/item_tax_template.py
index 0ceb6a0bc2..23f36ec6d8 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.py
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.py
@@ -13,20 +13,28 @@ class ItemTaxTemplate(Document):
def autoname(self):
if self.company and self.title:
- abbr = frappe.get_cached_value('Company', self.company, 'abbr')
- self.name = '{0} - {1}'.format(self.title, abbr)
+ abbr = frappe.get_cached_value("Company", self.company, "abbr")
+ self.name = "{0} - {1}".format(self.title, abbr)
def validate_tax_accounts(self):
"""Check whether Tax Rate is not entered twice for same Tax Type"""
check_list = []
- for d in self.get('taxes'):
+ for d in self.get("taxes"):
if d.tax_type:
account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
- if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation']:
+ if account_type not in [
+ "Tax",
+ "Chargeable",
+ "Income Account",
+ "Expense Account",
+ "Expenses Included In Valuation",
+ ]:
frappe.throw(
- _("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(
- d.idx))
+ _(
+ "Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable"
+ ).format(d.idx)
+ )
else:
if d.tax_type in check_list:
frappe.throw(_("{0} entered twice in Item Tax").format(d.tax_type))
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py
index af01c57560..5a2bd720dd 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py
@@ -3,23 +3,11 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'item_tax_template',
- 'transactions': [
- {
- 'label': _('Pre Sales'),
- 'items': ['Quotation', 'Supplier Quotation']
- },
- {
- 'label': _('Sales'),
- 'items': ['Sales Invoice', 'Sales Order', 'Delivery Note']
- },
- {
- 'label': _('Purchase'),
- 'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
- },
- {
- 'label': _('Stock'),
- 'items': ['Item Groups', 'Item']
- }
- ]
+ "fieldname": "item_tax_template",
+ "transactions": [
+ {"label": _("Pre Sales"), "items": ["Quotation", "Supplier Quotation"]},
+ {"label": _("Sales"), "items": ["Sales Invoice", "Sales Order", "Delivery Note"]},
+ {"label": _("Purchase"), "items": ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]},
+ {"label": _("Stock"), "items": ["Item Groups", "Item"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index ac8ab31024..920db5b19c 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -28,7 +28,9 @@ from erpnext.controllers.accounts_controller import AccountsController
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
-class StockAccountInvalidTransaction(frappe.ValidationError): pass
+class StockAccountInvalidTransaction(frappe.ValidationError):
+ pass
+
class JournalEntry(AccountsController):
def __init__(self, *args, **kwargs):
@@ -38,11 +40,11 @@ class JournalEntry(AccountsController):
return self.voucher_type
def validate(self):
- if self.voucher_type == 'Opening Entry':
- self.is_opening = 'Yes'
+ if self.voucher_type == "Opening Entry":
+ self.is_opening = "Yes"
if not self.is_opening:
- self.is_opening='No'
+ self.is_opening = "No"
self.clearance_date = None
@@ -86,15 +88,17 @@ class JournalEntry(AccountsController):
self.update_inter_company_jv()
self.update_invoice_discounting()
self.update_status_for_full_and_final_statement()
- check_if_stock_and_account_balance_synced(self.posting_date,
- self.company, self.doctype, self.name)
+ check_if_stock_and_account_balance_synced(
+ self.posting_date, self.company, self.doctype, self.name
+ )
def on_cancel(self):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
from erpnext.payroll.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip
+
unlink_ref_doc_from_payment_entries(self)
unlink_ref_doc_from_salary_slip(self.name)
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(1)
self.update_advance_paid()
self.update_expense_claim()
@@ -127,12 +131,14 @@ class JournalEntry(AccountsController):
elif self.docstatus == 2:
frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Unpaid")
-
def validate_inter_company_accounts(self):
- if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
+ if (
+ self.voucher_type == "Inter Company Journal Entry"
+ and self.inter_company_journal_entry_reference
+ ):
doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
- account_currency = frappe.get_cached_value('Company', self.company, "default_currency")
- previous_account_currency = frappe.get_cached_value('Company', doc.company, "default_currency")
+ account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
+ previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
if account_currency == previous_account_currency:
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
@@ -140,45 +146,63 @@ class JournalEntry(AccountsController):
def validate_stock_accounts(self):
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
for account in stock_accounts:
- account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
- self.posting_date, self.company)
+ account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
+ account, self.posting_date, self.company
+ )
if account_bal == stock_bal:
- frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
- .format(account), StockAccountInvalidTransaction)
+ frappe.throw(
+ _("Account: {0} can only be updated via Stock Transactions").format(account),
+ StockAccountInvalidTransaction,
+ )
def apply_tax_withholding(self):
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map
- if not self.apply_tds or self.voucher_type not in ('Debit Note', 'Credit Note'):
+ if not self.apply_tds or self.voucher_type not in ("Debit Note", "Credit Note"):
return
- parties = [d.party for d in self.get('accounts') if d.party]
+ parties = [d.party for d in self.get("accounts") if d.party]
parties = list(set(parties))
if len(parties) > 1:
frappe.throw(_("Cannot apply TDS against multiple parties in one entry"))
account_type_map = get_account_type_map(self.company)
- party_type = 'supplier' if self.voucher_type == 'Credit Note' else 'customer'
- doctype = 'Purchase Invoice' if self.voucher_type == 'Credit Note' else 'Sales Invoice'
- debit_or_credit = 'debit_in_account_currency' if self.voucher_type == 'Credit Note' else 'credit_in_account_currency'
- rev_debit_or_credit = 'credit_in_account_currency' if debit_or_credit == 'debit_in_account_currency' else 'debit_in_account_currency'
+ party_type = "supplier" if self.voucher_type == "Credit Note" else "customer"
+ doctype = "Purchase Invoice" if self.voucher_type == "Credit Note" else "Sales Invoice"
+ debit_or_credit = (
+ "debit_in_account_currency"
+ if self.voucher_type == "Credit Note"
+ else "credit_in_account_currency"
+ )
+ rev_debit_or_credit = (
+ "credit_in_account_currency"
+ if debit_or_credit == "debit_in_account_currency"
+ else "debit_in_account_currency"
+ )
party_account = get_party_account(party_type.title(), parties[0], self.company)
- net_total = sum(d.get(debit_or_credit) for d in self.get('accounts') if account_type_map.get(d.account)
- not in ('Tax', 'Chargeable'))
+ net_total = sum(
+ d.get(debit_or_credit)
+ for d in self.get("accounts")
+ if account_type_map.get(d.account) not in ("Tax", "Chargeable")
+ )
- party_amount = sum(d.get(rev_debit_or_credit) for d in self.get('accounts') if d.account == party_account)
+ party_amount = sum(
+ d.get(rev_debit_or_credit) for d in self.get("accounts") if d.account == party_account
+ )
- inv = frappe._dict({
- party_type: parties[0],
- 'doctype': doctype,
- 'company': self.company,
- 'posting_date': self.posting_date,
- 'net_total': net_total
- })
+ inv = frappe._dict(
+ {
+ party_type: parties[0],
+ "doctype": doctype,
+ "company": self.company,
+ "posting_date": self.posting_date,
+ "net_total": net_total,
+ }
+ )
tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category)
@@ -186,45 +210,64 @@ class JournalEntry(AccountsController):
return
accounts = []
- for d in self.get('accounts'):
- if d.get('account') == tax_withholding_details.get("account_head"):
- d.update({
- 'account': tax_withholding_details.get("account_head"),
- debit_or_credit: tax_withholding_details.get('tax_amount')
- })
+ for d in self.get("accounts"):
+ if d.get("account") == tax_withholding_details.get("account_head"):
+ d.update(
+ {
+ "account": tax_withholding_details.get("account_head"),
+ debit_or_credit: tax_withholding_details.get("tax_amount"),
+ }
+ )
- accounts.append(d.get('account'))
+ accounts.append(d.get("account"))
- if d.get('account') == party_account:
- d.update({
- rev_debit_or_credit: party_amount - tax_withholding_details.get('tax_amount')
- })
+ if d.get("account") == party_account:
+ d.update({rev_debit_or_credit: party_amount - tax_withholding_details.get("tax_amount")})
if not accounts or tax_withholding_details.get("account_head") not in accounts:
- self.append("accounts", {
- 'account': tax_withholding_details.get("account_head"),
- rev_debit_or_credit: tax_withholding_details.get('tax_amount'),
- 'against_account': parties[0]
- })
+ self.append(
+ "accounts",
+ {
+ "account": tax_withholding_details.get("account_head"),
+ rev_debit_or_credit: tax_withholding_details.get("tax_amount"),
+ "against_account": parties[0],
+ },
+ )
- to_remove = [d for d in self.get('accounts')
- if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")]
+ to_remove = [
+ d
+ for d in self.get("accounts")
+ if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")
+ ]
for d in to_remove:
self.remove(d)
def update_inter_company_jv(self):
- if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
- frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\
- "inter_company_journal_entry_reference", self.name)
+ if (
+ self.voucher_type == "Inter Company Journal Entry"
+ and self.inter_company_journal_entry_reference
+ ):
+ frappe.db.set_value(
+ "Journal Entry",
+ self.inter_company_journal_entry_reference,
+ "inter_company_journal_entry_reference",
+ self.name,
+ )
def update_invoice_discounting(self):
def _validate_invoice_discounting_status(inv_disc, id_status, expected_status, row_id):
id_link = get_link_to_form("Invoice Discounting", inv_disc)
if id_status != expected_status:
- frappe.throw(_("Row #{0}: Status must be {1} for Invoice Discounting {2}").format(d.idx, expected_status, id_link))
+ frappe.throw(
+ _("Row #{0}: Status must be {1} for Invoice Discounting {2}").format(
+ d.idx, expected_status, id_link
+ )
+ )
- invoice_discounting_list = list(set([d.reference_name for d in self.accounts if d.reference_type=="Invoice Discounting"]))
+ invoice_discounting_list = list(
+ set([d.reference_name for d in self.accounts if d.reference_type == "Invoice Discounting"])
+ )
for inv_disc in invoice_discounting_list:
inv_disc_doc = frappe.get_doc("Invoice Discounting", inv_disc)
status = None
@@ -248,104 +291,147 @@ class JournalEntry(AccountsController):
if status:
inv_disc_doc.set_status(status=status)
-
def unlink_advance_entry_reference(self):
for d in self.get("accounts"):
if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
doc = frappe.get_doc(d.reference_type, d.reference_name)
doc.delink_advance_entries(self.name)
- d.reference_type = ''
- d.reference_name = ''
+ d.reference_type = ""
+ d.reference_name = ""
d.db_update()
def unlink_asset_reference(self):
for d in self.get("accounts"):
- if d.reference_type=="Asset" and d.reference_name:
+ if d.reference_type == "Asset" and d.reference_name:
asset = frappe.get_doc("Asset", d.reference_name)
for s in asset.get("schedules"):
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
idx = cint(s.finance_book_id) or 1
- finance_books = asset.get('finance_books')[idx - 1]
+ finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation += s.depreciation_amount
finance_books.db_update()
asset.set_status()
def unlink_inter_company_jv(self):
- if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
- frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\
- "inter_company_journal_entry_reference", "")
- frappe.db.set_value("Journal Entry", self.name,\
- "inter_company_journal_entry_reference", "")
+ if (
+ self.voucher_type == "Inter Company Journal Entry"
+ and self.inter_company_journal_entry_reference
+ ):
+ frappe.db.set_value(
+ "Journal Entry",
+ self.inter_company_journal_entry_reference,
+ "inter_company_journal_entry_reference",
+ "",
+ )
+ frappe.db.set_value("Journal Entry", self.name, "inter_company_journal_entry_reference", "")
def unlink_asset_adjustment_entry(self):
- frappe.db.sql(""" update `tabAsset Value Adjustment`
- set journal_entry = null where journal_entry = %s""", self.name)
+ frappe.db.sql(
+ """ update `tabAsset Value Adjustment`
+ set journal_entry = null where journal_entry = %s""",
+ self.name,
+ )
def validate_party(self):
for d in self.get("accounts"):
account_type = frappe.db.get_value("Account", d.account, "account_type")
if account_type in ["Receivable", "Payable"]:
if not (d.party_type and d.party):
- frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account))
+ frappe.throw(
+ _("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(
+ d.idx, d.account
+ )
+ )
def check_credit_limit(self):
- customers = list(set(d.party for d in self.get("accounts")
- if d.party_type=="Customer" and d.party and flt(d.debit) > 0))
+ customers = list(
+ set(
+ d.party
+ for d in self.get("accounts")
+ if d.party_type == "Customer" and d.party and flt(d.debit) > 0
+ )
+ )
if customers:
from erpnext.selling.doctype.customer.customer import check_credit_limit
+
for customer in customers:
check_credit_limit(customer, self.company)
def validate_cheque_info(self):
- if self.voucher_type in ['Bank Entry']:
+ if self.voucher_type in ["Bank Entry"]:
if not self.cheque_no or not self.cheque_date:
- msgprint(_("Reference No & Reference Date is required for {0}").format(self.voucher_type),
- raise_exception=1)
+ msgprint(
+ _("Reference No & Reference Date is required for {0}").format(self.voucher_type),
+ raise_exception=1,
+ )
if self.cheque_date and not self.cheque_no:
msgprint(_("Reference No is mandatory if you entered Reference Date"), raise_exception=1)
def validate_entries_for_advance(self):
- for d in self.get('accounts'):
+ for d in self.get("accounts"):
if d.reference_type not in ("Sales Invoice", "Purchase Invoice", "Journal Entry"):
- if (d.party_type == 'Customer' and flt(d.credit) > 0) or \
- (d.party_type == 'Supplier' and flt(d.debit) > 0):
- if d.is_advance=="No":
- msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.").format(d.idx, d.account), alert=True)
+ if (d.party_type == "Customer" and flt(d.credit) > 0) or (
+ d.party_type == "Supplier" and flt(d.debit) > 0
+ ):
+ if d.is_advance == "No":
+ msgprint(
+ _(
+ "Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry."
+ ).format(d.idx, d.account),
+ alert=True,
+ )
elif d.reference_type in ("Sales Order", "Purchase Order") and d.is_advance != "Yes":
- frappe.throw(_("Row {0}: Payment against Sales/Purchase Order should always be marked as advance").format(d.idx))
+ frappe.throw(
+ _(
+ "Row {0}: Payment against Sales/Purchase Order should always be marked as advance"
+ ).format(d.idx)
+ )
if d.is_advance == "Yes":
- if d.party_type == 'Customer' and flt(d.debit) > 0:
+ if d.party_type == "Customer" and flt(d.debit) > 0:
frappe.throw(_("Row {0}: Advance against Customer must be credit").format(d.idx))
- elif d.party_type == 'Supplier' and flt(d.credit) > 0:
+ elif d.party_type == "Supplier" and flt(d.credit) > 0:
frappe.throw(_("Row {0}: Advance against Supplier must be debit").format(d.idx))
def validate_against_jv(self):
- for d in self.get('accounts'):
- if d.reference_type=="Journal Entry":
+ for d in self.get("accounts"):
+ if d.reference_type == "Journal Entry":
account_root_type = frappe.db.get_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0:
- frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets credited")
- .format(d.idx, d.account))
+ frappe.throw(
+ _(
+ "Row #{0}: For {1}, you can select reference document only if account gets credited"
+ ).format(d.idx, d.account)
+ )
elif account_root_type == "Liability" and flt(d.credit) > 0:
- frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets debited")
- .format(d.idx, d.account))
+ frappe.throw(
+ _(
+ "Row #{0}: For {1}, you can select reference document only if account gets debited"
+ ).format(d.idx, d.account)
+ )
if d.reference_name == self.name:
frappe.throw(_("You can not enter current voucher in 'Against Journal Entry' column"))
- against_entries = frappe.db.sql("""select * from `tabJournal Entry Account`
+ against_entries = frappe.db.sql(
+ """select * from `tabJournal Entry Account`
where account = %s and docstatus = 1 and parent = %s
and (reference_type is null or reference_type in ("", "Sales Order", "Purchase Order"))
- """, (d.account, d.reference_name), as_dict=True)
+ """,
+ (d.account, d.reference_name),
+ as_dict=True,
+ )
if not against_entries:
- frappe.throw(_("Journal Entry {0} does not have account {1} or already matched against other voucher")
- .format(d.reference_name, d.account))
+ frappe.throw(
+ _(
+ "Journal Entry {0} does not have account {1} or already matched against other voucher"
+ ).format(d.reference_name, d.account)
+ )
else:
dr_or_cr = "debit" if d.credit > 0 else "credit"
valid = False
@@ -353,16 +439,19 @@ class JournalEntry(AccountsController):
if flt(jvd[dr_or_cr]) > 0:
valid = True
if not valid:
- frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
- .format(d.reference_name, dr_or_cr))
+ frappe.throw(
+ _("Against Journal Entry {0} does not have any unmatched {1} entry").format(
+ d.reference_name, dr_or_cr
+ )
+ )
def validate_reference_doc(self):
"""Validates reference document"""
field_dict = {
- 'Sales Invoice': ["Customer", "Debit To"],
- 'Purchase Invoice': ["Supplier", "Credit To"],
- 'Sales Order': ["Customer"],
- 'Purchase Order': ["Supplier"]
+ "Sales Invoice": ["Customer", "Debit To"],
+ "Purchase Invoice": ["Supplier", "Credit To"],
+ "Sales Order": ["Customer"],
+ "Purchase Order": ["Supplier"],
}
self.reference_totals = {}
@@ -375,56 +464,76 @@ class JournalEntry(AccountsController):
if not d.reference_name:
d.reference_type = None
if d.reference_type and d.reference_name and (d.reference_type in list(field_dict)):
- dr_or_cr = "credit_in_account_currency" \
- if d.reference_type in ("Sales Order", "Sales Invoice") else "debit_in_account_currency"
+ dr_or_cr = (
+ "credit_in_account_currency"
+ if d.reference_type in ("Sales Order", "Sales Invoice")
+ else "debit_in_account_currency"
+ )
# check debit or credit type Sales / Purchase Order
- if d.reference_type=="Sales Order" and flt(d.debit) > 0:
- frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, d.reference_type))
+ if d.reference_type == "Sales Order" and flt(d.debit) > 0:
+ frappe.throw(
+ _("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, d.reference_type)
+ )
if d.reference_type == "Purchase Order" and flt(d.credit) > 0:
- frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, d.reference_type))
+ frappe.throw(
+ _("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, d.reference_type)
+ )
# set totals
if not d.reference_name in self.reference_totals:
self.reference_totals[d.reference_name] = 0.0
- if self.voucher_type not in ('Deferred Revenue', 'Deferred Expense'):
+ if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
self.reference_types[d.reference_name] = d.reference_type
self.reference_accounts[d.reference_name] = d.account
- against_voucher = frappe.db.get_value(d.reference_type, d.reference_name,
- [scrub(dt) for dt in field_dict.get(d.reference_type)])
+ against_voucher = frappe.db.get_value(
+ d.reference_type, d.reference_name, [scrub(dt) for dt in field_dict.get(d.reference_type)]
+ )
if not against_voucher:
frappe.throw(_("Row {0}: Invalid reference {1}").format(d.idx, d.reference_name))
# check if party and account match
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
- if self.voucher_type in ('Deferred Revenue', 'Deferred Expense') and d.reference_detail_no:
- debit_or_credit = 'Debit' if d.debit else 'Credit'
- party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
- debit_or_credit)
- against_voucher = ['', against_voucher[1]]
+ if self.voucher_type in ("Deferred Revenue", "Deferred Expense") and d.reference_detail_no:
+ debit_or_credit = "Debit" if d.debit else "Credit"
+ party_account = get_deferred_booking_accounts(
+ d.reference_type, d.reference_detail_no, debit_or_credit
+ )
+ against_voucher = ["", against_voucher[1]]
else:
if d.reference_type == "Sales Invoice":
- party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
+ party_account = (
+ get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
+ )
else:
party_account = against_voucher[1]
- if (against_voucher[0] != cstr(d.party) or party_account != d.account):
- frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
- .format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
- d.reference_type, d.reference_name))
+ if against_voucher[0] != cstr(d.party) or party_account != d.account:
+ frappe.throw(
+ _("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}").format(
+ d.idx,
+ field_dict.get(d.reference_type)[0],
+ field_dict.get(d.reference_type)[1],
+ d.reference_type,
+ d.reference_name,
+ )
+ )
# check if party matches for Sales / Purchase Order
if d.reference_type in ("Sales Order", "Purchase Order"):
# set totals
if against_voucher != d.party:
- frappe.throw(_("Row {0}: {1} {2} does not match with {3}") \
- .format(d.idx, d.party_type, d.party, d.reference_type))
+ frappe.throw(
+ _("Row {0}: {1} {2} does not match with {3}").format(
+ d.idx, d.party_type, d.party, d.reference_type
+ )
+ )
self.validate_orders()
self.validate_invoices()
@@ -450,62 +559,79 @@ class JournalEntry(AccountsController):
account_currency = get_account_currency(account)
if account_currency == self.company_currency:
voucher_total = order.base_grand_total
- formatted_voucher_total = fmt_money(voucher_total, order.precision("base_grand_total"),
- currency=account_currency)
+ formatted_voucher_total = fmt_money(
+ voucher_total, order.precision("base_grand_total"), currency=account_currency
+ )
else:
voucher_total = order.grand_total
- formatted_voucher_total = fmt_money(voucher_total, order.precision("grand_total"),
- currency=account_currency)
+ formatted_voucher_total = fmt_money(
+ voucher_total, order.precision("grand_total"), currency=account_currency
+ )
if flt(voucher_total) < (flt(order.advance_paid) + total):
- frappe.throw(_("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
+ frappe.throw(
+ _("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(
+ reference_type, reference_name, formatted_voucher_total
+ )
+ )
def validate_invoices(self):
"""Validate totals and docstatus for invoices"""
for reference_name, total in self.reference_totals.items():
reference_type = self.reference_types[reference_name]
- if (reference_type in ("Sales Invoice", "Purchase Invoice") and
- self.voucher_type not in ['Debit Note', 'Credit Note']):
- invoice = frappe.db.get_value(reference_type, reference_name,
- ["docstatus", "outstanding_amount"], as_dict=1)
+ if reference_type in ("Sales Invoice", "Purchase Invoice") and self.voucher_type not in [
+ "Debit Note",
+ "Credit Note",
+ ]:
+ invoice = frappe.db.get_value(
+ reference_type, reference_name, ["docstatus", "outstanding_amount"], as_dict=1
+ )
if invoice.docstatus != 1:
frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name))
if total and flt(invoice.outstanding_amount) < total:
- frappe.throw(_("Payment against {0} {1} cannot be greater than Outstanding Amount {2}")
- .format(reference_type, reference_name, invoice.outstanding_amount))
+ frappe.throw(
+ _("Payment against {0} {1} cannot be greater than Outstanding Amount {2}").format(
+ reference_type, reference_name, invoice.outstanding_amount
+ )
+ )
def set_against_account(self):
accounts_debited, accounts_credited = [], []
- if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
- for d in self.get('accounts'):
- if d.reference_type == 'Sales Invoice':
- field = 'customer'
+ if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
+ for d in self.get("accounts"):
+ if d.reference_type == "Sales Invoice":
+ field = "customer"
else:
- field = 'supplier'
+ field = "supplier"
d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
else:
for d in self.get("accounts"):
- if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
- if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
+ if flt(d.debit > 0):
+ accounts_debited.append(d.party or d.account)
+ if flt(d.credit) > 0:
+ accounts_credited.append(d.party or d.account)
for d in self.get("accounts"):
- if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
- if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
+ if flt(d.debit > 0):
+ d.against_account = ", ".join(list(set(accounts_credited)))
+ if flt(d.credit > 0):
+ d.against_account = ", ".join(list(set(accounts_debited)))
def validate_debit_credit_amount(self):
- for d in self.get('accounts'):
+ for d in self.get("accounts"):
if not flt(d.debit) and not flt(d.credit):
frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
def validate_total_debit_and_credit(self):
self.set_total_debit_credit()
if self.difference:
- frappe.throw(_("Total Debit must be equal to Total Credit. The difference is {0}")
- .format(self.difference))
+ frappe.throw(
+ _("Total Debit must be equal to Total Credit. The difference is {0}").format(self.difference)
+ )
def set_total_debit_credit(self):
self.total_debit, self.total_credit, self.difference = 0, 0, 0
@@ -516,13 +642,16 @@ class JournalEntry(AccountsController):
self.total_debit = flt(self.total_debit) + flt(d.debit, d.precision("debit"))
self.total_credit = flt(self.total_credit) + flt(d.credit, d.precision("credit"))
- self.difference = flt(self.total_debit, self.precision("total_debit")) - \
- flt(self.total_credit, self.precision("total_credit"))
+ self.difference = flt(self.total_debit, self.precision("total_debit")) - flt(
+ self.total_credit, self.precision("total_credit")
+ )
def validate_multi_currency(self):
alternate_currency = []
for d in self.get("accounts"):
- account = frappe.db.get_value("Account", d.account, ["account_currency", "account_type"], as_dict=1)
+ account = frappe.db.get_value(
+ "Account", d.account, ["account_currency", "account_type"], as_dict=1
+ )
if account:
d.account_currency = account.account_currency
d.account_type = account.account_type
@@ -541,8 +670,12 @@ class JournalEntry(AccountsController):
def set_amounts_in_company_currency(self):
for d in self.get("accounts"):
- d.debit_in_account_currency = flt(d.debit_in_account_currency, d.precision("debit_in_account_currency"))
- d.credit_in_account_currency = flt(d.credit_in_account_currency, d.precision("credit_in_account_currency"))
+ d.debit_in_account_currency = flt(
+ d.debit_in_account_currency, d.precision("debit_in_account_currency")
+ )
+ d.credit_in_account_currency = flt(
+ d.credit_in_account_currency, d.precision("credit_in_account_currency")
+ )
d.debit = flt(d.debit_in_account_currency * flt(d.exchange_rate), d.precision("debit"))
d.credit = flt(d.credit_in_account_currency * flt(d.exchange_rate), d.precision("credit"))
@@ -551,13 +684,28 @@ class JournalEntry(AccountsController):
for d in self.get("accounts"):
if d.account_currency == self.company_currency:
d.exchange_rate = 1
- elif not d.exchange_rate or d.exchange_rate == 1 or \
- (d.reference_type in ("Sales Invoice", "Purchase Invoice")
- and d.reference_name and self.posting_date):
+ elif (
+ not d.exchange_rate
+ or d.exchange_rate == 1
+ or (
+ d.reference_type in ("Sales Invoice", "Purchase Invoice")
+ and d.reference_name
+ and self.posting_date
+ )
+ ):
- # Modified to include the posting date for which to retreive the exchange rate
- d.exchange_rate = get_exchange_rate(self.posting_date, d.account, d.account_currency,
- self.company, d.reference_type, d.reference_name, d.debit, d.credit, d.exchange_rate)
+ # Modified to include the posting date for which to retreive the exchange rate
+ d.exchange_rate = get_exchange_rate(
+ self.posting_date,
+ d.account,
+ d.account_currency,
+ self.company,
+ d.reference_type,
+ d.reference_name,
+ d.debit,
+ d.credit,
+ d.exchange_rate,
+ )
if not d.exchange_rate:
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
@@ -570,55 +718,76 @@ class JournalEntry(AccountsController):
if self.cheque_no:
if self.cheque_date:
- r.append(_('Reference #{0} dated {1}').format(self.cheque_no, formatdate(self.cheque_date)))
+ r.append(_("Reference #{0} dated {1}").format(self.cheque_no, formatdate(self.cheque_date)))
else:
msgprint(_("Please enter Reference date"), raise_exception=frappe.MandatoryError)
- for d in self.get('accounts'):
- if d.reference_type=="Sales Invoice" and d.credit:
- r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = self.company_currency), \
- d.reference_name))
+ for d in self.get("accounts"):
+ if d.reference_type == "Sales Invoice" and d.credit:
+ r.append(
+ _("{0} against Sales Invoice {1}").format(
+ fmt_money(flt(d.credit), currency=self.company_currency), d.reference_name
+ )
+ )
- if d.reference_type=="Sales Order" and d.credit:
- r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = self.company_currency), \
- d.reference_name))
+ if d.reference_type == "Sales Order" and d.credit:
+ r.append(
+ _("{0} against Sales Order {1}").format(
+ fmt_money(flt(d.credit), currency=self.company_currency), d.reference_name
+ )
+ )
if d.reference_type == "Purchase Invoice" and d.debit:
- bill_no = frappe.db.sql("""select bill_no, bill_date
- from `tabPurchase Invoice` where name=%s""", d.reference_name)
- if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \
- not in ['na', 'not applicable', 'none']:
- r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=self.company_currency), bill_no[0][0],
- bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d'))))
+ bill_no = frappe.db.sql(
+ """select bill_no, bill_date
+ from `tabPurchase Invoice` where name=%s""",
+ d.reference_name,
+ )
+ if (
+ bill_no
+ and bill_no[0][0]
+ and bill_no[0][0].lower().strip() not in ["na", "not applicable", "none"]
+ ):
+ r.append(
+ _("{0} against Bill {1} dated {2}").format(
+ fmt_money(flt(d.debit), currency=self.company_currency),
+ bill_no[0][0],
+ bill_no[0][1] and formatdate(bill_no[0][1].strftime("%Y-%m-%d")),
+ )
+ )
if d.reference_type == "Purchase Order" and d.debit:
- r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = self.company_currency), \
- d.reference_name))
+ r.append(
+ _("{0} against Purchase Order {1}").format(
+ fmt_money(flt(d.credit), currency=self.company_currency), d.reference_name
+ )
+ )
if r:
- self.remark = ("\n").join(r) #User Remarks is not mandatory
+ self.remark = ("\n").join(r) # User Remarks is not mandatory
def set_print_format_fields(self):
bank_amount = party_amount = total_amount = 0.0
- currency = bank_account_currency = party_account_currency = pay_to_recd_from= None
+ currency = bank_account_currency = party_account_currency = pay_to_recd_from = None
party_type = None
- for d in self.get('accounts'):
- if d.party_type in ['Customer', 'Supplier'] and d.party:
+ for d in self.get("accounts"):
+ if d.party_type in ["Customer", "Supplier"] and d.party:
party_type = d.party_type
if not pay_to_recd_from:
pay_to_recd_from = d.party
if pay_to_recd_from and pay_to_recd_from == d.party:
- party_amount += (d.debit_in_account_currency or d.credit_in_account_currency)
+ party_amount += d.debit_in_account_currency or d.credit_in_account_currency
party_account_currency = d.account_currency
elif frappe.db.get_value("Account", d.account, "account_type") in ["Bank", "Cash"]:
- bank_amount += (d.debit_in_account_currency or d.credit_in_account_currency)
+ bank_amount += d.debit_in_account_currency or d.credit_in_account_currency
bank_account_currency = d.account_currency
if party_type and pay_to_recd_from:
- self.pay_to_recd_from = frappe.db.get_value(party_type, pay_to_recd_from,
- "customer_name" if party_type=="Customer" else "supplier_name")
+ self.pay_to_recd_from = frappe.db.get_value(
+ party_type, pay_to_recd_from, "customer_name" if party_type == "Customer" else "supplier_name"
+ )
if bank_amount:
total_amount = bank_amount
currency = bank_account_currency
@@ -632,6 +801,7 @@ class JournalEntry(AccountsController):
self.total_amount = amt
self.total_amount_currency = currency
from frappe.utils import money_in_words
+
self.total_amount_in_words = money_in_words(amt, currency)
def make_gl_entries(self, cancel=0, adv_adj=0):
@@ -645,38 +815,45 @@ class JournalEntry(AccountsController):
remarks = "\n".join(r)
gl_map.append(
- self.get_gl_dict({
- "account": d.account,
- "party_type": d.party_type,
- "due_date": self.due_date,
- "party": d.party,
- "against": d.against_account,
- "debit": flt(d.debit, d.precision("debit")),
- "credit": flt(d.credit, d.precision("credit")),
- "account_currency": d.account_currency,
- "debit_in_account_currency": flt(d.debit_in_account_currency, d.precision("debit_in_account_currency")),
- "credit_in_account_currency": flt(d.credit_in_account_currency, d.precision("credit_in_account_currency")),
- "against_voucher_type": d.reference_type,
- "against_voucher": d.reference_name,
- "remarks": remarks,
- "voucher_detail_no": d.reference_detail_no,
- "cost_center": d.cost_center,
- "project": d.project,
- "finance_book": self.finance_book
- }, item=d)
+ self.get_gl_dict(
+ {
+ "account": d.account,
+ "party_type": d.party_type,
+ "due_date": self.due_date,
+ "party": d.party,
+ "against": d.against_account,
+ "debit": flt(d.debit, d.precision("debit")),
+ "credit": flt(d.credit, d.precision("credit")),
+ "account_currency": d.account_currency,
+ "debit_in_account_currency": flt(
+ d.debit_in_account_currency, d.precision("debit_in_account_currency")
+ ),
+ "credit_in_account_currency": flt(
+ d.credit_in_account_currency, d.precision("credit_in_account_currency")
+ ),
+ "against_voucher_type": d.reference_type,
+ "against_voucher": d.reference_name,
+ "remarks": remarks,
+ "voucher_detail_no": d.reference_detail_no,
+ "cost_center": d.cost_center,
+ "project": d.project,
+ "finance_book": self.finance_book,
+ },
+ item=d,
+ )
)
- if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
- update_outstanding = 'No'
+ if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
+ update_outstanding = "No"
else:
- update_outstanding = 'Yes'
+ update_outstanding = "Yes"
if gl_map:
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
@frappe.whitelist()
def get_balance(self):
- if not self.get('accounts'):
+ if not self.get("accounts"):
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
else:
self.total_debit, self.total_credit = 0, 0
@@ -685,18 +862,18 @@ class JournalEntry(AccountsController):
# If any row without amount, set the diff on that row
if diff:
blank_row = None
- for d in self.get('accounts'):
+ for d in self.get("accounts"):
if not d.credit_in_account_currency and not d.debit_in_account_currency and diff != 0:
blank_row = d
if not blank_row:
- blank_row = self.append('accounts', {})
+ blank_row = self.append("accounts", {})
blank_row.exchange_rate = 1
- if diff>0:
+ if diff > 0:
blank_row.credit_in_account_currency = diff
blank_row.credit = diff
- elif diff<0:
+ elif diff < 0:
blank_row.debit_in_account_currency = abs(diff)
blank_row.debit = abs(diff)
@@ -704,76 +881,100 @@ class JournalEntry(AccountsController):
@frappe.whitelist()
def get_outstanding_invoices(self):
- self.set('accounts', [])
+ self.set("accounts", [])
total = 0
for d in self.get_values():
total += flt(d.outstanding_amount, self.precision("credit", "accounts"))
- jd1 = self.append('accounts', {})
+ jd1 = self.append("accounts", {})
jd1.account = d.account
jd1.party = d.party
- if self.write_off_based_on == 'Accounts Receivable':
+ if self.write_off_based_on == "Accounts Receivable":
jd1.party_type = "Customer"
- jd1.credit_in_account_currency = flt(d.outstanding_amount, self.precision("credit", "accounts"))
+ jd1.credit_in_account_currency = flt(
+ d.outstanding_amount, self.precision("credit", "accounts")
+ )
jd1.reference_type = "Sales Invoice"
jd1.reference_name = cstr(d.name)
- elif self.write_off_based_on == 'Accounts Payable':
+ elif self.write_off_based_on == "Accounts Payable":
jd1.party_type = "Supplier"
jd1.debit_in_account_currency = flt(d.outstanding_amount, self.precision("debit", "accounts"))
jd1.reference_type = "Purchase Invoice"
jd1.reference_name = cstr(d.name)
- jd2 = self.append('accounts', {})
- if self.write_off_based_on == 'Accounts Receivable':
+ jd2 = self.append("accounts", {})
+ if self.write_off_based_on == "Accounts Receivable":
jd2.debit_in_account_currency = total
- elif self.write_off_based_on == 'Accounts Payable':
+ elif self.write_off_based_on == "Accounts Payable":
jd2.credit_in_account_currency = total
self.validate_total_debit_and_credit()
-
def get_values(self):
- cond = " and outstanding_amount <= {0}".format(self.write_off_amount) \
- if flt(self.write_off_amount) > 0 else ""
+ cond = (
+ " and outstanding_amount <= {0}".format(self.write_off_amount)
+ if flt(self.write_off_amount) > 0
+ else ""
+ )
- if self.write_off_based_on == 'Accounts Receivable':
- return frappe.db.sql("""select name, debit_to as account, customer as party, outstanding_amount
+ if self.write_off_based_on == "Accounts Receivable":
+ return frappe.db.sql(
+ """select name, debit_to as account, customer as party, outstanding_amount
from `tabSales Invoice` where docstatus = 1 and company = %s
- and outstanding_amount > 0 %s""" % ('%s', cond), self.company, as_dict=True)
- elif self.write_off_based_on == 'Accounts Payable':
- return frappe.db.sql("""select name, credit_to as account, supplier as party, outstanding_amount
+ and outstanding_amount > 0 %s"""
+ % ("%s", cond),
+ self.company,
+ as_dict=True,
+ )
+ elif self.write_off_based_on == "Accounts Payable":
+ return frappe.db.sql(
+ """select name, credit_to as account, supplier as party, outstanding_amount
from `tabPurchase Invoice` where docstatus = 1 and company = %s
- and outstanding_amount > 0 %s""" % ('%s', cond), self.company, as_dict=True)
+ and outstanding_amount > 0 %s"""
+ % ("%s", cond),
+ self.company,
+ as_dict=True,
+ )
def update_expense_claim(self):
for d in self.accounts:
- if d.reference_type=="Expense Claim" and d.reference_name:
+ if d.reference_type == "Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
if self.docstatus == 2:
update_reimbursed_amount(doc, -1 * d.debit)
else:
update_reimbursed_amount(doc, d.debit)
-
def validate_expense_claim(self):
for d in self.accounts:
- if d.reference_type=="Expense Claim":
- sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim",
- d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed"))
+ if d.reference_type == "Expense Claim":
+ sanctioned_amount, reimbursed_amount = frappe.db.get_value(
+ "Expense Claim", d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed")
+ )
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
if d.debit > pending_amount:
- frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}").format(d.idx, d.reference_name, pending_amount))
+ frappe.throw(
+ _(
+ "Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}"
+ ).format(d.idx, d.reference_name, pending_amount)
+ )
def validate_credit_debit_note(self):
if self.stock_entry:
if frappe.db.get_value("Stock Entry", self.stock_entry, "docstatus") != 1:
frappe.throw(_("Stock Entry {0} is not submitted").format(self.stock_entry))
- if frappe.db.exists({"doctype": "Journal Entry", "stock_entry": self.stock_entry, "docstatus":1}):
- frappe.msgprint(_("Warning: Another {0} # {1} exists against stock entry {2}").format(self.voucher_type, self.name, self.stock_entry))
+ if frappe.db.exists(
+ {"doctype": "Journal Entry", "stock_entry": self.stock_entry, "docstatus": 1}
+ ):
+ frappe.msgprint(
+ _("Warning: Another {0} # {1} exists against stock entry {2}").format(
+ self.voucher_type, self.name, self.stock_entry
+ )
+ )
def validate_empty_accounts_table(self):
- if not self.get('accounts'):
+ if not self.get("accounts"):
frappe.throw(_("Accounts table cannot be blank."))
def set_account_and_party_balance(self):
@@ -784,54 +985,66 @@ class JournalEntry(AccountsController):
account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date)
if (d.party_type, d.party) not in party_balance:
- party_balance[(d.party_type, d.party)] = get_balance_on(party_type=d.party_type,
- party=d.party, date=self.posting_date, company=self.company)
+ party_balance[(d.party_type, d.party)] = get_balance_on(
+ party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company
+ )
d.account_balance = account_balance[d.account]
d.party_balance = party_balance[(d.party_type, d.party)]
+
@frappe.whitelist()
def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
+
if mode_of_payment:
account = get_bank_cash_account(mode_of_payment, company).get("account")
if not account:
- '''
- Set the default account first. If the user hasn't set any default account then, he doesn't
- want us to set any random account. In this case set the account only if there is single
- account (of that type), otherwise return empty dict.
- '''
- if account_type=="Bank":
- account = frappe.get_cached_value('Company', company, "default_bank_account")
+ """
+ Set the default account first. If the user hasn't set any default account then, he doesn't
+ want us to set any random account. In this case set the account only if there is single
+ account (of that type), otherwise return empty dict.
+ """
+ if account_type == "Bank":
+ account = frappe.get_cached_value("Company", company, "default_bank_account")
if not account:
- account_list = frappe.get_all("Account", filters = {"company": company,
- "account_type": "Bank", "is_group": 0})
+ account_list = frappe.get_all(
+ "Account", filters={"company": company, "account_type": "Bank", "is_group": 0}
+ )
if len(account_list) == 1:
account = account_list[0].name
- elif account_type=="Cash":
- account = frappe.get_cached_value('Company', company, "default_cash_account")
+ elif account_type == "Cash":
+ account = frappe.get_cached_value("Company", company, "default_cash_account")
if not account:
- account_list = frappe.get_all("Account", filters = {"company": company,
- "account_type": "Cash", "is_group": 0})
+ account_list = frappe.get_all(
+ "Account", filters={"company": company, "account_type": "Cash", "is_group": 0}
+ )
if len(account_list) == 1:
account = account_list[0].name
if account:
- account_details = frappe.db.get_value("Account", account,
- ["account_currency", "account_type"], as_dict=1)
+ account_details = frappe.db.get_value(
+ "Account", account, ["account_currency", "account_type"], as_dict=1
+ )
+
+ return frappe._dict(
+ {
+ "account": account,
+ "balance": get_balance_on(account),
+ "account_currency": account_details.account_currency,
+ "account_type": account_details.account_type,
+ }
+ )
+ else:
+ return frappe._dict()
- return frappe._dict({
- "account": account,
- "balance": get_balance_on(account),
- "account_currency": account_details.account_currency,
- "account_type": account_details.account_type
- })
- else: return frappe._dict()
@frappe.whitelist()
-def get_payment_entry_against_order(dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None):
+def get_payment_entry_against_order(
+ dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
+):
ref_doc = frappe.get_doc(dt, dn)
if flt(ref_doc.per_billed, 2) > 0:
@@ -855,22 +1068,28 @@ def get_payment_entry_against_order(dt, dn, amount=None, debit_in_account_curren
else:
amount = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
- return get_payment_entry(ref_doc, {
- "party_type": party_type,
- "party_account": party_account,
- "party_account_currency": party_account_currency,
- "amount_field_party": amount_field_party,
- "amount_field_bank": amount_field_bank,
- "amount": amount,
- "debit_in_account_currency": debit_in_account_currency,
- "remarks": 'Advance Payment received against {0} {1}'.format(dt, dn),
- "is_advance": "Yes",
- "bank_account": bank_account,
- "journal_entry": journal_entry
- })
+ return get_payment_entry(
+ ref_doc,
+ {
+ "party_type": party_type,
+ "party_account": party_account,
+ "party_account_currency": party_account_currency,
+ "amount_field_party": amount_field_party,
+ "amount_field_bank": amount_field_bank,
+ "amount": amount,
+ "debit_in_account_currency": debit_in_account_currency,
+ "remarks": "Advance Payment received against {0} {1}".format(dt, dn),
+ "is_advance": "Yes",
+ "bank_account": bank_account,
+ "journal_entry": journal_entry,
+ },
+ )
+
@frappe.whitelist()
-def get_payment_entry_against_invoice(dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None):
+def get_payment_entry_against_invoice(
+ dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
+):
ref_doc = frappe.get_doc(dt, dn)
if dt == "Sales Invoice":
party_type = "Customer"
@@ -879,73 +1098,91 @@ def get_payment_entry_against_invoice(dt, dn, amount=None, debit_in_account_cur
party_type = "Supplier"
party_account = ref_doc.credit_to
- if (dt == "Sales Invoice" and ref_doc.outstanding_amount > 0) \
- or (dt == "Purchase Invoice" and ref_doc.outstanding_amount < 0):
- amount_field_party = "credit_in_account_currency"
- amount_field_bank = "debit_in_account_currency"
+ if (dt == "Sales Invoice" and ref_doc.outstanding_amount > 0) or (
+ dt == "Purchase Invoice" and ref_doc.outstanding_amount < 0
+ ):
+ amount_field_party = "credit_in_account_currency"
+ amount_field_bank = "debit_in_account_currency"
else:
amount_field_party = "debit_in_account_currency"
amount_field_bank = "credit_in_account_currency"
- return get_payment_entry(ref_doc, {
- "party_type": party_type,
- "party_account": party_account,
- "party_account_currency": ref_doc.party_account_currency,
- "amount_field_party": amount_field_party,
- "amount_field_bank": amount_field_bank,
- "amount": amount if amount else abs(ref_doc.outstanding_amount),
- "debit_in_account_currency": debit_in_account_currency,
- "remarks": 'Payment received against {0} {1}. {2}'.format(dt, dn, ref_doc.remarks),
- "is_advance": "No",
- "bank_account": bank_account,
- "journal_entry": journal_entry
- })
+ return get_payment_entry(
+ ref_doc,
+ {
+ "party_type": party_type,
+ "party_account": party_account,
+ "party_account_currency": ref_doc.party_account_currency,
+ "amount_field_party": amount_field_party,
+ "amount_field_bank": amount_field_bank,
+ "amount": amount if amount else abs(ref_doc.outstanding_amount),
+ "debit_in_account_currency": debit_in_account_currency,
+ "remarks": "Payment received against {0} {1}. {2}".format(dt, dn, ref_doc.remarks),
+ "is_advance": "No",
+ "bank_account": bank_account,
+ "journal_entry": journal_entry,
+ },
+ )
+
def get_payment_entry(ref_doc, args):
- cost_center = ref_doc.get("cost_center") or frappe.get_cached_value('Company', ref_doc.company, "cost_center")
+ cost_center = ref_doc.get("cost_center") or frappe.get_cached_value(
+ "Company", ref_doc.company, "cost_center"
+ )
exchange_rate = 1
if args.get("party_account"):
# Modified to include the posting date for which the exchange rate is required.
# Assumed to be the posting date in the reference document
- exchange_rate = get_exchange_rate(ref_doc.get("posting_date") or ref_doc.get("transaction_date"),
- args.get("party_account"), args.get("party_account_currency"),
- ref_doc.company, ref_doc.doctype, ref_doc.name)
+ exchange_rate = get_exchange_rate(
+ ref_doc.get("posting_date") or ref_doc.get("transaction_date"),
+ args.get("party_account"),
+ args.get("party_account_currency"),
+ ref_doc.company,
+ ref_doc.doctype,
+ ref_doc.name,
+ )
je = frappe.new_doc("Journal Entry")
- je.update({
- "voucher_type": "Bank Entry",
- "company": ref_doc.company,
- "remark": args.get("remarks")
- })
+ je.update(
+ {"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")}
+ )
- party_row = je.append("accounts", {
- "account": args.get("party_account"),
- "party_type": args.get("party_type"),
- "party": ref_doc.get(args.get("party_type").lower()),
- "cost_center": cost_center,
- "account_type": frappe.db.get_value("Account", args.get("party_account"), "account_type"),
- "account_currency": args.get("party_account_currency") or \
- get_account_currency(args.get("party_account")),
- "balance": get_balance_on(args.get("party_account")),
- "party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")),
- "exchange_rate": exchange_rate,
- args.get("amount_field_party"): args.get("amount"),
- "is_advance": args.get("is_advance"),
- "reference_type": ref_doc.doctype,
- "reference_name": ref_doc.name
- })
+ party_row = je.append(
+ "accounts",
+ {
+ "account": args.get("party_account"),
+ "party_type": args.get("party_type"),
+ "party": ref_doc.get(args.get("party_type").lower()),
+ "cost_center": cost_center,
+ "account_type": frappe.db.get_value("Account", args.get("party_account"), "account_type"),
+ "account_currency": args.get("party_account_currency")
+ or get_account_currency(args.get("party_account")),
+ "balance": get_balance_on(args.get("party_account")),
+ "party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")),
+ "exchange_rate": exchange_rate,
+ args.get("amount_field_party"): args.get("amount"),
+ "is_advance": args.get("is_advance"),
+ "reference_type": ref_doc.doctype,
+ "reference_name": ref_doc.name,
+ },
+ )
bank_row = je.append("accounts")
# Make it bank_details
- bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account"))
+ bank_account = get_default_bank_cash_account(
+ ref_doc.company, "Bank", account=args.get("bank_account")
+ )
if bank_account:
bank_row.update(bank_account)
# Modified to include the posting date for which the exchange rate is required.
# Assumed to be the posting date of the reference date
- bank_row.exchange_rate = get_exchange_rate(ref_doc.get("posting_date")
- or ref_doc.get("transaction_date"), bank_account["account"],
- bank_account["account_currency"], ref_doc.company)
+ bank_row.exchange_rate = get_exchange_rate(
+ ref_doc.get("posting_date") or ref_doc.get("transaction_date"),
+ bank_account["account"],
+ bank_account["account_currency"],
+ ref_doc.company,
+ )
bank_row.cost_center = cost_center
@@ -957,9 +1194,10 @@ def get_payment_entry(ref_doc, args):
bank_row.set(args.get("amount_field_bank"), amount * exchange_rate)
# Multi currency check again
- if party_row.account_currency != ref_doc.company_currency \
- or (bank_row.account_currency and bank_row.account_currency != ref_doc.company_currency):
- je.multi_currency = 1
+ if party_row.account_currency != ref_doc.company_currency or (
+ bank_row.account_currency and bank_row.account_currency != ref_doc.company_currency
+ ):
+ je.multi_currency = 1
je.set_amounts_in_company_currency()
je.set_total_debit_credit()
@@ -970,13 +1208,17 @@ def get_payment_entry(ref_doc, args):
@frappe.whitelist()
def get_opening_accounts(company):
"""get all balance sheet accounts for opening entry"""
- accounts = frappe.db.sql_list("""select
+ accounts = frappe.db.sql_list(
+ """select
name from tabAccount
where
is_group=0 and report_type='Balance Sheet' and company={0} and
name not in (select distinct account from tabWarehouse where
account is not null and account != '')
- order by name asc""".format(frappe.db.escape(company)))
+ order by name asc""".format(
+ frappe.db.escape(company)
+ )
+ )
return [{"account": a, "balance": get_balance_on(a)} for a in accounts]
@@ -984,10 +1226,11 @@ def get_opening_accounts(company):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
- if not frappe.db.has_column('Journal Entry', searchfield):
+ if not frappe.db.has_column("Journal Entry", searchfield):
return []
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT jv.name, jv.posting_date, jv.user_remark
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
WHERE jv_detail.parent = jv.name
@@ -1001,14 +1244,17 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
AND jv.`{0}` LIKE %(txt)s
ORDER BY jv.name DESC
LIMIT %(offset)s, %(limit)s
- """.format(searchfield), dict(
- account=filters.get("account"),
- party=cstr(filters.get("party")),
- txt="%{0}%".format(txt),
- offset=start,
- limit=page_len
- )
- )
+ """.format(
+ searchfield
+ ),
+ dict(
+ account=filters.get("account"),
+ party=cstr(filters.get("party")),
+ txt="%{0}%".format(txt),
+ offset=start,
+ limit=page_len,
+ ),
+ )
@frappe.whitelist()
@@ -1024,37 +1270,55 @@ def get_outstanding(args):
if args.get("doctype") == "Journal Entry":
condition = " and party=%(party)s" if args.get("party") else ""
- against_jv_amount = frappe.db.sql("""
+ against_jv_amount = frappe.db.sql(
+ """
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0}
- and (reference_type is null or reference_type = '')""".format(condition), args)
+ and (reference_type is null or reference_type = '')""".format(
+ condition
+ ),
+ args,
+ )
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
- amount_field = "credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
- return {
- amount_field: abs(against_jv_amount)
- }
+ amount_field = (
+ "credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
+ )
+ return {amount_field: abs(against_jv_amount)}
elif args.get("doctype") in ("Sales Invoice", "Purchase Invoice"):
party_type = "Customer" if args.get("doctype") == "Sales Invoice" else "Supplier"
- invoice = frappe.db.get_value(args["doctype"], args["docname"],
- ["outstanding_amount", "conversion_rate", scrub(party_type)], as_dict=1)
+ invoice = frappe.db.get_value(
+ args["doctype"],
+ args["docname"],
+ ["outstanding_amount", "conversion_rate", scrub(party_type)],
+ as_dict=1,
+ )
- exchange_rate = invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
+ exchange_rate = (
+ invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
+ )
if args["doctype"] == "Sales Invoice":
- amount_field = "credit_in_account_currency" \
- if flt(invoice.outstanding_amount) > 0 else "debit_in_account_currency"
+ amount_field = (
+ "credit_in_account_currency"
+ if flt(invoice.outstanding_amount) > 0
+ else "debit_in_account_currency"
+ )
else:
- amount_field = "debit_in_account_currency" \
- if flt(invoice.outstanding_amount) > 0 else "credit_in_account_currency"
+ amount_field = (
+ "debit_in_account_currency"
+ if flt(invoice.outstanding_amount) > 0
+ else "credit_in_account_currency"
+ )
return {
amount_field: abs(flt(invoice.outstanding_amount)),
"exchange_rate": exchange_rate,
"party_type": party_type,
- "party": invoice.get(scrub(party_type))
+ "party": invoice.get(scrub(party_type)),
}
+
@frappe.whitelist()
def get_party_account_and_balance(company, party_type, party, cost_center=None):
if not frappe.has_permission("Account"):
@@ -1063,24 +1327,30 @@ def get_party_account_and_balance(company, party_type, party, cost_center=None):
account = get_party_account(party_type, party, company)
account_balance = get_balance_on(account=account, cost_center=cost_center)
- party_balance = get_balance_on(party_type=party_type, party=party, company=company, cost_center=cost_center)
+ party_balance = get_balance_on(
+ party_type=party_type, party=party, company=company, cost_center=cost_center
+ )
return {
"account": account,
"balance": account_balance,
"party_balance": party_balance,
- "account_currency": frappe.db.get_value("Account", account, "account_currency")
+ "account_currency": frappe.db.get_value("Account", account, "account_currency"),
}
@frappe.whitelist()
-def get_account_balance_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None):
+def get_account_balance_and_party_type(
+ account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None
+):
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
company_currency = erpnext.get_company_currency(company)
- account_details = frappe.db.get_value("Account", account, ["account_type", "account_currency"], as_dict=1)
+ account_details = frappe.db.get_value(
+ "Account", account, ["account_type", "account_currency"], as_dict=1
+ )
if not account_details:
return
@@ -1097,11 +1367,17 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi
"party_type": party_type,
"account_type": account_details.account_type,
"account_currency": account_details.account_currency or company_currency,
-
# The date used to retreive the exchange rate here is the date passed in
# as an argument to this function. It is assumed to be the date on which the balance is sought
- "exchange_rate": get_exchange_rate(date, account, account_details.account_currency,
- company, debit=debit, credit=credit, exchange_rate=exchange_rate)
+ "exchange_rate": get_exchange_rate(
+ date,
+ account,
+ account_details.account_currency,
+ company,
+ debit=debit,
+ credit=credit,
+ exchange_rate=exchange_rate,
+ ),
}
# un-set party if not party type
@@ -1112,11 +1388,22 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi
@frappe.whitelist()
-def get_exchange_rate(posting_date, account=None, account_currency=None, company=None,
- reference_type=None, reference_name=None, debit=None, credit=None, exchange_rate=None):
+def get_exchange_rate(
+ posting_date,
+ account=None,
+ account_currency=None,
+ company=None,
+ reference_type=None,
+ reference_name=None,
+ debit=None,
+ credit=None,
+ exchange_rate=None,
+):
from erpnext.setup.utils import get_exchange_rate
- account_details = frappe.db.get_value("Account", account,
- ["account_type", "root_type", "account_currency", "company"], as_dict=1)
+
+ account_details = frappe.db.get_value(
+ "Account", account, ["account_type", "root_type", "account_currency", "company"], as_dict=1
+ )
if not account_details:
frappe.throw(_("Please select correct account"))
@@ -1135,7 +1422,7 @@ def get_exchange_rate(posting_date, account=None, account_currency=None, company
# The date used to retreive the exchange rate here is the date passed
# in as an argument to this function.
- elif (not exchange_rate or flt(exchange_rate)==1) and account_currency and posting_date:
+ elif (not exchange_rate or flt(exchange_rate) == 1) and account_currency and posting_date:
exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
else:
exchange_rate = 1
@@ -1154,15 +1441,17 @@ def get_average_exchange_rate(account):
return exchange_rate
+
@frappe.whitelist()
def make_inter_company_journal_entry(name, voucher_type, company):
- journal_entry = frappe.new_doc('Journal Entry')
+ journal_entry = frappe.new_doc("Journal Entry")
journal_entry.voucher_type = voucher_type
journal_entry.company = company
journal_entry.posting_date = nowdate()
journal_entry.inter_company_journal_entry_reference = name
return journal_entry.as_dict()
+
@frappe.whitelist()
def make_reverse_journal_entry(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
@@ -1170,24 +1459,25 @@ def make_reverse_journal_entry(source_name, target_doc=None):
def post_process(source, target):
target.reversal_of = source.name
- doclist = get_mapped_doc("Journal Entry", source_name, {
- "Journal Entry": {
- "doctype": "Journal Entry",
- "validation": {
- "docstatus": ["=", 1]
- }
+ doclist = get_mapped_doc(
+ "Journal Entry",
+ source_name,
+ {
+ "Journal Entry": {"doctype": "Journal Entry", "validation": {"docstatus": ["=", 1]}},
+ "Journal Entry Account": {
+ "doctype": "Journal Entry Account",
+ "field_map": {
+ "account_currency": "account_currency",
+ "exchange_rate": "exchange_rate",
+ "debit_in_account_currency": "credit_in_account_currency",
+ "debit": "credit",
+ "credit_in_account_currency": "debit_in_account_currency",
+ "credit": "debit",
+ },
+ },
},
- "Journal Entry Account": {
- "doctype": "Journal Entry Account",
- "field_map": {
- "account_currency": "account_currency",
- "exchange_rate": "exchange_rate",
- "debit_in_account_currency": "credit_in_account_currency",
- "debit": "credit",
- "credit_in_account_currency": "debit_in_account_currency",
- "credit": "debit",
- }
- },
- }, target_doc, post_process)
+ target_doc,
+ post_process,
+ )
return doclist
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 481462b2ab..2cc5378e92 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -39,14 +39,25 @@ class TestJournalEntry(unittest.TestCase):
test_voucher.submit()
if test_voucher.doctype == "Journal Entry":
- self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
+ self.assertTrue(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
where account = %s and docstatus = 1 and parent = %s""",
- ("_Test Receivable - _TC", test_voucher.name)))
+ ("_Test Receivable - _TC", test_voucher.name),
+ )
+ )
- self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
- where reference_type = %s and reference_name = %s""", (test_voucher.doctype, test_voucher.name)))
+ self.assertFalse(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
+ where reference_type = %s and reference_name = %s""",
+ (test_voucher.doctype, test_voucher.name),
+ )
+ )
- base_jv.get("accounts")[0].is_advance = "Yes" if (test_voucher.doctype in ["Sales Order", "Purchase Order"]) else "No"
+ base_jv.get("accounts")[0].is_advance = (
+ "Yes" if (test_voucher.doctype in ["Sales Order", "Purchase Order"]) else "No"
+ )
base_jv.get("accounts")[0].set("reference_type", test_voucher.doctype)
base_jv.get("accounts")[0].set("reference_name", test_voucher.name)
base_jv.insert()
@@ -54,18 +65,28 @@ class TestJournalEntry(unittest.TestCase):
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
- self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
- where reference_type = %s and reference_name = %s and {0}=400""".format(dr_or_cr),
- (submitted_voucher.doctype, submitted_voucher.name)))
+ self.assertTrue(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
+ where reference_type = %s and reference_name = %s and {0}=400""".format(
+ dr_or_cr
+ ),
+ (submitted_voucher.doctype, submitted_voucher.name),
+ )
+ )
if base_jv.get("accounts")[0].is_advance == "Yes":
self.advance_paid_testcase(base_jv, submitted_voucher, dr_or_cr)
self.cancel_against_voucher_testcase(submitted_voucher)
def advance_paid_testcase(self, base_jv, test_voucher, dr_or_cr):
- #Test advance paid field
- advance_paid = frappe.db.sql("""select advance_paid from `tab%s`
- where name=%s""" % (test_voucher.doctype, '%s'), (test_voucher.name))
+ # Test advance paid field
+ advance_paid = frappe.db.sql(
+ """select advance_paid from `tab%s`
+ where name=%s"""
+ % (test_voucher.doctype, "%s"),
+ (test_voucher.name),
+ )
payment_against_order = base_jv.get("accounts")[0].get(dr_or_cr)
self.assertTrue(flt(advance_paid[0][0]) == flt(payment_against_order))
@@ -74,13 +95,19 @@ class TestJournalEntry(unittest.TestCase):
if test_voucher.doctype == "Journal Entry":
# if test_voucher is a Journal Entry, test cancellation of test_voucher
test_voucher.cancel()
- self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
- where reference_type='Journal Entry' and reference_name=%s""", test_voucher.name))
+ self.assertFalse(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
+ where reference_type='Journal Entry' and reference_name=%s""",
+ test_voucher.name,
+ )
+ )
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
- frappe.db.set_value("Accounts Settings", "Accounts Settings",
- "unlink_advance_payment_on_cancelation_of_order", 0)
+ frappe.db.set_value(
+ "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0
+ )
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
@@ -89,7 +116,10 @@ class TestJournalEntry(unittest.TestCase):
stock_account = get_inventory_account(company)
from erpnext.accounts.utils import get_stock_and_account_balance
- account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
+
+ account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
+ stock_account, nowdate(), company
+ )
diff = flt(account_bal) - flt(stock_bal)
if not diff:
@@ -98,19 +128,25 @@ class TestJournalEntry(unittest.TestCase):
jv = frappe.new_doc("Journal Entry")
jv.company = company
jv.posting_date = nowdate()
- jv.append("accounts", {
- "account": stock_account,
- "cost_center": "Main - TCP1",
- "debit_in_account_currency": 0 if diff > 0 else abs(diff),
- "credit_in_account_currency": diff if diff > 0 else 0
- })
+ jv.append(
+ "accounts",
+ {
+ "account": stock_account,
+ "cost_center": "Main - TCP1",
+ "debit_in_account_currency": 0 if diff > 0 else abs(diff),
+ "credit_in_account_currency": diff if diff > 0 else 0,
+ },
+ )
- jv.append("accounts", {
- "account": "Stock Adjustment - TCP1",
- "cost_center": "Main - TCP1",
- "debit_in_account_currency": diff if diff > 0 else 0,
- "credit_in_account_currency": 0 if diff > 0 else abs(diff)
- })
+ jv.append(
+ "accounts",
+ {
+ "account": "Stock Adjustment - TCP1",
+ "cost_center": "Main - TCP1",
+ "debit_in_account_currency": diff if diff > 0 else 0,
+ "credit_in_account_currency": 0 if diff > 0 else abs(diff),
+ },
+ )
jv.insert()
if account_bal == stock_bal:
@@ -121,16 +157,21 @@ class TestJournalEntry(unittest.TestCase):
jv.cancel()
def test_multi_currency(self):
- jv = make_journal_entry("_Test Bank USD - _TC",
- "_Test Bank - _TC", 100, exchange_rate=50, save=False)
+ jv = make_journal_entry(
+ "_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False
+ )
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""", jv.name, as_dict=1)
+ order by account asc""",
+ jv.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -140,33 +181,42 @@ class TestJournalEntry(unittest.TestCase):
"debit": 5000,
"debit_in_account_currency": 100,
"credit": 0,
- "credit_in_account_currency": 0
+ "credit_in_account_currency": 0,
},
"_Test Bank - _TC": {
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
- "credit_in_account_currency": 5000
- }
+ "credit_in_account_currency": 5000,
+ },
}
- for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
+ for field in (
+ "account_currency",
+ "debit",
+ "debit_in_account_currency",
+ "credit",
+ "credit_in_account_currency",
+ ):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
# cancel
jv.cancel()
- gle = frappe.db.sql("""select name from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s""", jv.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry`
+ where voucher_type='Sales Invoice' and voucher_no=%s""",
+ jv.name,
+ )
self.assertFalse(gle)
def test_reverse_journal_entry(self):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
- jv = make_journal_entry("_Test Bank USD - _TC",
- "Sales - _TC", 100, exchange_rate=50, save=False)
+
+ jv = make_journal_entry("_Test Bank USD - _TC", "Sales - _TC", 100, exchange_rate=50, save=False)
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.get("accounts")[1].exchange_rate = 1
@@ -176,15 +226,17 @@ class TestJournalEntry(unittest.TestCase):
rjv.posting_date = nowdate()
rjv.submit()
-
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""", rjv.name, as_dict=1)
+ order by account asc""",
+ rjv.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
-
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
@@ -199,44 +251,38 @@ class TestJournalEntry(unittest.TestCase):
"debit_in_account_currency": 5000,
"credit": 0,
"credit_in_account_currency": 0,
- }
+ },
}
- for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
+ for field in (
+ "account_currency",
+ "debit",
+ "debit_in_account_currency",
+ "credit",
+ "credit_in_account_currency",
+ ):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD
- jv = make_journal_entry("_Test Bank USD - _TC",
- "_Test Receivable USD - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank USD - _TC", "_Test Receivable USD - _TC", 100, save=False)
- jv.accounts[1].update({
- "party_type": "Customer",
- "party": "_Test Customer USD"
- })
+ jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
jv.submit()
# create jv in USD, but account currency in INR
- jv = make_journal_entry("_Test Bank - _TC",
- "_Test Receivable - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
- jv.accounts[1].update({
- "party_type": "Customer",
- "party": "_Test Customer USD"
- })
+ jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
self.assertRaises(InvalidAccountCurrency, jv.submit)
# back in USD
- jv = make_journal_entry("_Test Bank USD - _TC",
- "_Test Receivable USD - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank USD - _TC", "_Test Receivable USD - _TC", 100, save=False)
- jv.accounts[1].update({
- "party_type": "Customer",
- "party": "_Test Customer USD"
- })
+ jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
jv.submit()
@@ -245,13 +291,27 @@ class TestJournalEntry(unittest.TestCase):
frappe.db.set_value("Account", "Buildings - _TC", "inter_company_account", 1)
frappe.db.set_value("Account", "Sales Expenses - _TC1", "inter_company_account", 1)
frappe.db.set_value("Account", "Buildings - _TC1", "inter_company_account", 1)
- jv = make_journal_entry("Sales Expenses - _TC", "Buildings - _TC", 100, posting_date=nowdate(), cost_center = "Main - _TC", save=False)
+ jv = make_journal_entry(
+ "Sales Expenses - _TC",
+ "Buildings - _TC",
+ 100,
+ posting_date=nowdate(),
+ cost_center="Main - _TC",
+ save=False,
+ )
jv.voucher_type = "Inter Company Journal Entry"
jv.multi_currency = 0
jv.insert()
jv.submit()
- jv1 = make_journal_entry("Sales Expenses - _TC1", "Buildings - _TC1", 100, posting_date=nowdate(), cost_center = "Main - _TC1", save=False)
+ jv1 = make_journal_entry(
+ "Sales Expenses - _TC1",
+ "Buildings - _TC1",
+ 100,
+ posting_date=nowdate(),
+ cost_center="Main - _TC1",
+ save=False,
+ )
jv1.inter_company_journal_entry_reference = jv.name
jv1.company = "_Test Company 1"
jv1.voucher_type = "Inter Company Journal Entry"
@@ -273,9 +333,12 @@ class TestJournalEntry(unittest.TestCase):
def test_jv_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
- jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
+ jv = make_journal_entry(
+ "_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center=cost_center, save=False
+ )
jv.voucher_type = "Bank Entry"
jv.multi_currency = 0
jv.cheque_no = "112233"
@@ -284,17 +347,17 @@ class TestJournalEntry(unittest.TestCase):
jv.submit()
expected_values = {
- "_Test Cash - _TC": {
- "cost_center": cost_center
- },
- "_Test Bank - _TC": {
- "cost_center": cost_center
- }
+ "_Test Cash - _TC": {"cost_center": cost_center},
+ "_Test Bank - _TC": {"cost_center": cost_center},
}
- gl_entries = frappe.db.sql("""select account, cost_center, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""", jv.name, as_dict=1)
+ order by account asc""",
+ jv.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -305,11 +368,13 @@ class TestJournalEntry(unittest.TestCase):
from erpnext.projects.doctype.project.test_project import make_project
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
- project = make_project({
- 'project_name': 'Journal Entry Project',
- 'project_template_name': 'Test Project Template',
- 'start_date': '2020-01-01'
- })
+ project = make_project(
+ {
+ "project_name": "Journal Entry Project",
+ "project_template_name": "Test Project Template",
+ "start_date": "2020-01-01",
+ }
+ )
project_name = project.name
else:
project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
@@ -325,17 +390,17 @@ class TestJournalEntry(unittest.TestCase):
jv.submit()
expected_values = {
- "_Test Cash - _TC": {
- "project": project_name
- },
- "_Test Bank - _TC": {
- "project": project_name
- }
+ "_Test Cash - _TC": {"project": project_name},
+ "_Test Bank - _TC": {"project": project_name},
}
- gl_entries = frappe.db.sql("""select account, project, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, project, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""", jv.name, as_dict=1)
+ order by account asc""",
+ jv.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -345,9 +410,12 @@ class TestJournalEntry(unittest.TestCase):
def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on
+
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
- jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
+ jv = make_journal_entry(
+ "_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center=cost_center, save=False
+ )
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
jv.voucher_type = "Bank Entry"
jv.multi_currency = 0
@@ -360,7 +428,18 @@ class TestJournalEntry(unittest.TestCase):
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
self.assertEqual(expected_account_balance, account_balance)
-def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None):
+
+def make_journal_entry(
+ account1,
+ account2,
+ amount,
+ cost_center=None,
+ posting_date=None,
+ exchange_rate=1,
+ save=True,
+ submit=False,
+ project=None,
+):
if not cost_center:
cost_center = "_Test Cost Center - _TC"
@@ -369,23 +448,27 @@ def make_journal_entry(account1, account2, amount, cost_center=None, posting_dat
jv.company = "_Test Company"
jv.user_remark = "test"
jv.multi_currency = 1
- jv.set("accounts", [
- {
- "account": account1,
- "cost_center": cost_center,
- "project": project,
- "debit_in_account_currency": amount if amount > 0 else 0,
- "credit_in_account_currency": abs(amount) if amount < 0 else 0,
- "exchange_rate": exchange_rate
- }, {
- "account": account2,
- "cost_center": cost_center,
- "project": project,
- "credit_in_account_currency": amount if amount > 0 else 0,
- "debit_in_account_currency": abs(amount) if amount < 0 else 0,
- "exchange_rate": exchange_rate
- }
- ])
+ jv.set(
+ "accounts",
+ [
+ {
+ "account": account1,
+ "cost_center": cost_center,
+ "project": project,
+ "debit_in_account_currency": amount if amount > 0 else 0,
+ "credit_in_account_currency": abs(amount) if amount < 0 else 0,
+ "exchange_rate": exchange_rate,
+ },
+ {
+ "account": account2,
+ "cost_center": cost_center,
+ "project": project,
+ "credit_in_account_currency": amount if amount > 0 else 0,
+ "debit_in_account_currency": abs(amount) if amount < 0 else 0,
+ "exchange_rate": exchange_rate,
+ },
+ ],
+ )
if save or submit:
jv.insert()
@@ -394,4 +477,5 @@ def make_journal_entry(account1, account2, amount, cost_center=None, posting_dat
return jv
-test_records = frappe.get_test_records('Journal Entry')
+
+test_records = frappe.get_test_records("Journal Entry")
diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py
index 2da72c20ad..b8ef3545d3 100644
--- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py
+++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py
@@ -9,6 +9,7 @@ from frappe.model.document import Document
class JournalEntryTemplate(Document):
pass
+
@frappe.whitelist()
def get_naming_series():
return frappe.get_meta("Journal Entry").get_field("naming_series").options
diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
index 830ad370d7..c1e2975fbc 100644
--- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
+++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
@@ -15,9 +15,7 @@ class LedgerMerge(Document):
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
- frappe.throw(
- _("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive")
- )
+ frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
enqueued_jobs = [d.get("job_name") for d in get_info()]
@@ -35,10 +33,12 @@ class LedgerMerge(Document):
return False
+
@frappe.whitelist()
def form_start_merge(docname):
return frappe.get_doc("Ledger Merge", docname).start_merge()
+
def start_merge(docname):
ledger_merge = frappe.get_doc("Ledger Merge", docname)
successful_merges = 0
@@ -51,26 +51,24 @@ def start_merge(docname):
ledger_merge.account,
ledger_merge.is_group,
ledger_merge.root_type,
- ledger_merge.company
+ ledger_merge.company,
)
- row.db_set('merged', 1)
+ row.db_set("merged", 1)
frappe.db.commit()
successful_merges += 1
- frappe.publish_realtime("ledger_merge_progress", {
- "ledger_merge": ledger_merge.name,
- "current": successful_merges,
- "total": total
- }
+ frappe.publish_realtime(
+ "ledger_merge_progress",
+ {"ledger_merge": ledger_merge.name, "current": successful_merges, "total": total},
)
except Exception:
frappe.db.rollback()
frappe.log_error(title=ledger_merge.name)
finally:
if successful_merges == total:
- ledger_merge.db_set('status', 'Success')
+ ledger_merge.db_set("status", "Success")
elif successful_merges > 0:
- ledger_merge.db_set('status', 'Partial Success')
+ ledger_merge.db_set("status", "Partial Success")
else:
- ledger_merge.db_set('status', 'Error')
+ ledger_merge.db_set("status", "Error")
frappe.publish_realtime("ledger_merge_refresh", {"ledger_merge": ledger_merge.name})
diff --git a/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py
index f7315362b7..992ce9ede5 100644
--- a/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py
+++ b/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py
@@ -31,18 +31,17 @@ class TestLedgerMerge(unittest.TestCase):
acc.company = "_Test Company"
acc.insert()
- doc = frappe.get_doc({
- "doctype": "Ledger Merge",
- "company": "_Test Company",
- "root_type": frappe.db.get_value("Account", "Indirect Test Expenses - _TC", "root_type"),
- "account": "Indirect Expenses - _TC",
- "merge_accounts": [
- {
- "account": "Indirect Test Expenses - _TC",
- "account_name": "Indirect Expenses"
- }
- ]
- }).insert(ignore_permissions=True)
+ doc = frappe.get_doc(
+ {
+ "doctype": "Ledger Merge",
+ "company": "_Test Company",
+ "root_type": frappe.db.get_value("Account", "Indirect Test Expenses - _TC", "root_type"),
+ "account": "Indirect Expenses - _TC",
+ "merge_accounts": [
+ {"account": "Indirect Test Expenses - _TC", "account_name": "Indirect Expenses"}
+ ],
+ }
+ ).insert(ignore_permissions=True)
parent = frappe.db.get_value("Account", "Administrative Test Expenses - _TC", "parent_account")
self.assertEqual(parent, "Indirect Test Expenses - _TC")
@@ -76,22 +75,18 @@ class TestLedgerMerge(unittest.TestCase):
acc.company = "_Test Company"
acc.insert()
- doc = frappe.get_doc({
- "doctype": "Ledger Merge",
- "company": "_Test Company",
- "root_type": frappe.db.get_value("Account", "Indirect Income - _TC", "root_type"),
- "account": "Indirect Income - _TC",
- "merge_accounts": [
- {
- "account": "Indirect Test Income - _TC",
- "account_name": "Indirect Test Income"
- },
- {
- "account": "Administrative Test Income - _TC",
- "account_name": "Administrative Test Income"
- }
- ]
- }).insert(ignore_permissions=True)
+ doc = frappe.get_doc(
+ {
+ "doctype": "Ledger Merge",
+ "company": "_Test Company",
+ "root_type": frappe.db.get_value("Account", "Indirect Income - _TC", "root_type"),
+ "account": "Indirect Income - _TC",
+ "merge_accounts": [
+ {"account": "Indirect Test Income - _TC", "account_name": "Indirect Test Income"},
+ {"account": "Administrative Test Income - _TC", "account_name": "Administrative Test Income"},
+ ],
+ }
+ ).insert(ignore_permissions=True)
parent = frappe.db.get_value("Account", "Administrative Test Income - _TC", "parent_account")
self.assertEqual(parent, "Indirect Test Income - _TC")
@@ -112,7 +107,7 @@ class TestLedgerMerge(unittest.TestCase):
"Indirect Test Expenses - _TC",
"Administrative Test Expenses - _TC",
"Indirect Test Income - _TC",
- "Administrative Test Income - _TC"
+ "Administrative Test Income - _TC",
]
for account in test_accounts:
frappe.delete_doc_if_exists("Account", account)
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
index f460b9f795..dcb43fb2cb 100644
--- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
@@ -8,6 +8,7 @@ from frappe.utils import today
exclude_from_linked_with = True
+
class LoyaltyPointEntry(Document):
pass
@@ -16,18 +17,28 @@ def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=No
if not expiry_date:
expiry_date = today()
- return frappe.db.sql('''
+ return frappe.db.sql(
+ """
select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice
from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s
and expiry_date>=%s and loyalty_points>0 and company=%s
order by expiry_date
- ''', (customer, loyalty_program, expiry_date, company), as_dict=1)
+ """,
+ (customer, loyalty_program, expiry_date, company),
+ as_dict=1,
+ )
+
def get_redemption_details(customer, loyalty_program, company):
- return frappe._dict(frappe.db.sql('''
+ return frappe._dict(
+ frappe.db.sql(
+ """
select redeem_against, sum(loyalty_points)
from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and loyalty_points<0 and company=%s
group by redeem_against
- ''', (customer, loyalty_program, company)))
+ """,
+ (customer, loyalty_program, company),
+ )
+ )
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
index 70da03b27f..48a25ad6b8 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
@@ -12,39 +12,61 @@ class LoyaltyProgram(Document):
pass
-def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=None, include_expired_entry=False):
+def get_loyalty_details(
+ customer, loyalty_program, expiry_date=None, company=None, include_expired_entry=False
+):
if not expiry_date:
expiry_date = today()
- condition = ''
+ condition = ""
if company:
condition = " and company=%s " % frappe.db.escape(company)
if not include_expired_entry:
condition += " and expiry_date>='%s' " % expiry_date
- loyalty_point_details = frappe.db.sql('''select sum(loyalty_points) as loyalty_points,
+ loyalty_point_details = frappe.db.sql(
+ """select sum(loyalty_points) as loyalty_points,
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and posting_date <= %s
{condition}
- group by customer'''.format(condition=condition),
- (customer, loyalty_program, expiry_date), as_dict=1)
+ group by customer""".format(
+ condition=condition
+ ),
+ (customer, loyalty_program, expiry_date),
+ as_dict=1,
+ )
if loyalty_point_details:
return loyalty_point_details[0]
else:
return {"loyalty_points": 0, "total_spent": 0}
-@frappe.whitelist()
-def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, \
- silent=False, include_expired_entry=False, current_transaction_amount=0):
- lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
- loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
- lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
- tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
- key=lambda rule:rule.min_spent, reverse=True)
+@frappe.whitelist()
+def get_loyalty_program_details_with_points(
+ customer,
+ loyalty_program=None,
+ expiry_date=None,
+ company=None,
+ silent=False,
+ include_expired_entry=False,
+ current_transaction_amount=0,
+):
+ lp_details = get_loyalty_program_details(
+ customer, loyalty_program, company=company, silent=silent
+ )
+ loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
+ lp_details.update(
+ get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)
+ )
+
+ tier_spent_level = sorted(
+ [d.as_dict() for d in loyalty_program.collection_rules],
+ key=lambda rule: rule.min_spent,
+ reverse=True,
+ )
for i, d in enumerate(tier_spent_level):
- if i==0 or (lp_details.total_spent+current_transaction_amount) <= d.min_spent:
+ if i == 0 or (lp_details.total_spent + current_transaction_amount) <= d.min_spent:
lp_details.tier_name = d.tier_name
lp_details.collection_factor = d.collection_factor
else:
@@ -52,8 +74,16 @@ def get_loyalty_program_details_with_points(customer, loyalty_program=None, expi
return lp_details
+
@frappe.whitelist()
-def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False):
+def get_loyalty_program_details(
+ customer,
+ loyalty_program=None,
+ expiry_date=None,
+ company=None,
+ silent=False,
+ include_expired_entry=False,
+):
lp_details = frappe._dict()
if not loyalty_program:
@@ -72,6 +102,7 @@ def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None
lp_details.update(loyalty_program.as_dict())
return lp_details
+
@frappe.whitelist()
def get_redeemption_factor(loyalty_program=None, customer=None):
customer_loyalty_program = None
@@ -98,13 +129,16 @@ def validate_loyalty_points(ref_doc, points_to_redeem):
else:
loyalty_program = frappe.db.get_value("Customer", ref_doc.customer, ["loyalty_program"])
- if loyalty_program and frappe.db.get_value("Loyalty Program", loyalty_program, ["company"]) !=\
- ref_doc.company:
+ if (
+ loyalty_program
+ and frappe.db.get_value("Loyalty Program", loyalty_program, ["company"]) != ref_doc.company
+ ):
frappe.throw(_("The Loyalty Program isn't valid for the selected company"))
if loyalty_program and points_to_redeem:
- loyalty_program_details = get_loyalty_program_details_with_points(ref_doc.customer, loyalty_program,
- posting_date, ref_doc.company)
+ loyalty_program_details = get_loyalty_program_details_with_points(
+ ref_doc.customer, loyalty_program, posting_date, ref_doc.company
+ )
if points_to_redeem > loyalty_program_details.loyalty_points:
frappe.throw(_("You don't have enought Loyalty Points to redeem"))
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program_dashboard.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program_dashboard.py
index 25328e501e..3a4f908a1e 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program_dashboard.py
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
return {
- 'fieldname': 'loyalty_program',
- 'transactions': [
- {
- 'items': ['Sales Invoice', 'Customer']
- }
- ]
+ "fieldname": "loyalty_program",
+ "transactions": [{"items": ["Sales Invoice", "Customer"]}],
}
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 82c14324f5..3641ac4428 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -19,19 +19,28 @@ class TestLoyaltyProgram(unittest.TestCase):
create_records()
def test_loyalty_points_earned_single_tier(self):
- frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
+ frappe.db.set_value(
+ "Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
+ )
# create a new sales invoice
si_original = create_sales_invoice_record()
si_original.insert()
si_original.submit()
- customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
+ customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
earned_points = get_points_earned(si_original)
- lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
+ lpe = frappe.get_doc(
+ "Loyalty Point Entry",
+ {
+ "invoice_type": "Sales Invoice",
+ "invoice": si_original.name,
+ "customer": si_original.customer,
+ },
+ )
- self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
- self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
+ self.assertEqual(si_original.get("loyalty_program"), customer.loyalty_program)
+ self.assertEqual(lpe.get("loyalty_program_tier"), customer.loyalty_program_tier)
self.assertEqual(lpe.loyalty_points, earned_points)
# add redemption point
@@ -43,21 +52,31 @@ class TestLoyaltyProgram(unittest.TestCase):
earned_after_redemption = get_points_earned(si_redeem)
- lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name})
- lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
+ lpe_redeem = frappe.get_doc(
+ "Loyalty Point Entry",
+ {"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "redeem_against": lpe.name},
+ )
+ lpe_earn = frappe.get_doc(
+ "Loyalty Point Entry",
+ {"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "name": ["!=", lpe_redeem.name]},
+ )
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
- self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
+ self.assertEqual(lpe_redeem.loyalty_points, (-1 * earned_points))
# cancel and delete
for d in [si_redeem, si_original]:
d.cancel()
def test_loyalty_points_earned_multiple_tier(self):
- frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
+ frappe.db.set_value(
+ "Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty"
+ )
# assign multiple tier program to the customer
- customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
- customer.loyalty_program = frappe.get_doc('Loyalty Program', {'loyalty_program_name': 'Test Multiple Loyalty'}).name
+ customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
+ customer.loyalty_program = frappe.get_doc(
+ "Loyalty Program", {"loyalty_program_name": "Test Multiple Loyalty"}
+ ).name
customer.save()
# create a new sales invoice
@@ -67,10 +86,17 @@ class TestLoyaltyProgram(unittest.TestCase):
earned_points = get_points_earned(si_original)
- lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
+ lpe = frappe.get_doc(
+ "Loyalty Point Entry",
+ {
+ "invoice_type": "Sales Invoice",
+ "invoice": si_original.name,
+ "customer": si_original.customer,
+ },
+ )
- self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
- self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
+ self.assertEqual(si_original.get("loyalty_program"), customer.loyalty_program)
+ self.assertEqual(lpe.get("loyalty_program_tier"), customer.loyalty_program_tier)
self.assertEqual(lpe.loyalty_points, earned_points)
# add redemption point
@@ -80,14 +106,20 @@ class TestLoyaltyProgram(unittest.TestCase):
si_redeem.insert()
si_redeem.submit()
- customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
+ customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
earned_after_redemption = get_points_earned(si_redeem)
- lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name})
- lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
+ lpe_redeem = frappe.get_doc(
+ "Loyalty Point Entry",
+ {"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "redeem_against": lpe.name},
+ )
+ lpe_earn = frappe.get_doc(
+ "Loyalty Point Entry",
+ {"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "name": ["!=", lpe_redeem.name]},
+ )
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
- self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
+ self.assertEqual(lpe_redeem.loyalty_points, (-1 * earned_points))
self.assertEqual(lpe_earn.loyalty_program_tier, customer.loyalty_program_tier)
# cancel and delete
@@ -95,23 +127,30 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel()
def test_cancel_sales_invoice(self):
- ''' cancelling the sales invoice should cancel the earned points'''
- frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
+ """cancelling the sales invoice should cancel the earned points"""
+ frappe.db.set_value(
+ "Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
+ )
# create a new sales invoice
si = create_sales_invoice_record()
si.insert()
si.submit()
- lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si.name, 'customer': si.customer})
+ lpe = frappe.get_doc(
+ "Loyalty Point Entry",
+ {"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
+ )
self.assertEqual(True, not (lpe is None))
# cancelling sales invoice
si.cancel()
- lpe = frappe.db.exists('Loyalty Point Entry', lpe.name)
+ lpe = frappe.db.exists("Loyalty Point Entry", lpe.name)
self.assertEqual(True, (lpe is None))
def test_sales_invoice_return(self):
- frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
+ frappe.db.set_value(
+ "Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
+ )
# create a new sales invoice
si_original = create_sales_invoice_record(2)
si_original.conversion_rate = flt(1)
@@ -119,7 +158,14 @@ class TestLoyaltyProgram(unittest.TestCase):
si_original.submit()
earned_points = get_points_earned(si_original)
- lpe_original = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
+ lpe_original = frappe.get_doc(
+ "Loyalty Point Entry",
+ {
+ "invoice_type": "Sales Invoice",
+ "invoice": si_original.name,
+ "customer": si_original.customer,
+ },
+ )
self.assertEqual(lpe_original.loyalty_points, earned_points)
# create sales invoice return
@@ -131,10 +177,17 @@ class TestLoyaltyProgram(unittest.TestCase):
si_return.submit()
# fetch original invoice again as its status would have been updated
- si_original = frappe.get_doc('Sales Invoice', lpe_original.invoice)
+ si_original = frappe.get_doc("Sales Invoice", lpe_original.invoice)
earned_points = get_points_earned(si_original)
- lpe_after_return = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
+ lpe_after_return = frappe.get_doc(
+ "Loyalty Point Entry",
+ {
+ "invoice_type": "Sales Invoice",
+ "invoice": si_original.name,
+ "customer": si_original.customer,
+ },
+ )
self.assertEqual(lpe_after_return.loyalty_points, earned_points)
self.assertEqual(True, (lpe_original.loyalty_points > lpe_after_return.loyalty_points))
@@ -143,144 +196,164 @@ class TestLoyaltyProgram(unittest.TestCase):
try:
d.cancel()
except frappe.TimestampMismatchError:
- frappe.get_doc('Sales Invoice', d.name).cancel()
+ frappe.get_doc("Sales Invoice", d.name).cancel()
def test_loyalty_points_for_dashboard(self):
- doc = frappe.get_doc('Customer', 'Test Loyalty Customer')
+ doc = frappe.get_doc("Customer", "Test Loyalty Customer")
company_wise_info = get_dashboard_info("Customer", doc.name, doc.loyalty_program)
for d in company_wise_info:
self.assertTrue(d.get("loyalty_points"))
+
def get_points_earned(self):
def get_returned_amount():
- returned_amount = frappe.db.sql("""
+ returned_amount = frappe.db.sql(
+ """
select sum(grand_total)
from `tabSales Invoice`
where docstatus=1 and is_return=1 and ifnull(return_against, '')=%s
- """, self.name)
+ """,
+ self.name,
+ )
return abs(flt(returned_amount[0][0])) if returned_amount else 0
- lp_details = get_loyalty_program_details_with_points(self.customer, company=self.company,
- loyalty_program=self.loyalty_program, expiry_date=self.posting_date, include_expired_entry=True)
- if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
- (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
+ lp_details = get_loyalty_program_details_with_points(
+ self.customer,
+ company=self.company,
+ loyalty_program=self.loyalty_program,
+ expiry_date=self.posting_date,
+ include_expired_entry=True,
+ )
+ if (
+ lp_details
+ and getdate(lp_details.from_date) <= getdate(self.posting_date)
+ and (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date))
+ ):
returned_amount = get_returned_amount()
eligible_amount = flt(self.grand_total) - cint(self.loyalty_amount) - returned_amount
- points_earned = cint(eligible_amount/lp_details.collection_factor)
+ points_earned = cint(eligible_amount / lp_details.collection_factor)
return points_earned or 0
+
def create_sales_invoice_record(qty=1):
# return sales invoice doc object
- return frappe.get_doc({
- "doctype": "Sales Invoice",
- "customer": frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"}).name,
- "company": '_Test Company',
- "due_date": today(),
- "posting_date": today(),
- "currency": "INR",
- "taxes_and_charges": "",
- "debit_to": "Debtors - _TC",
- "taxes": [],
- "items": [{
- 'doctype': 'Sales Invoice Item',
- 'item_code': frappe.get_doc('Item', {'item_name': 'Loyal Item'}).name,
- 'qty': qty,
- "rate": 10000,
- 'income_account': 'Sales - _TC',
- 'cost_center': 'Main - _TC',
- 'expense_account': 'Cost of Goods Sold - _TC'
- }]
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Sales Invoice",
+ "customer": frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"}).name,
+ "company": "_Test Company",
+ "due_date": today(),
+ "posting_date": today(),
+ "currency": "INR",
+ "taxes_and_charges": "",
+ "debit_to": "Debtors - _TC",
+ "taxes": [],
+ "items": [
+ {
+ "doctype": "Sales Invoice Item",
+ "item_code": frappe.get_doc("Item", {"item_name": "Loyal Item"}).name,
+ "qty": qty,
+ "rate": 10000,
+ "income_account": "Sales - _TC",
+ "cost_center": "Main - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ }
+ ],
+ }
+ )
+
def create_records():
# create a new loyalty Account
if not frappe.db.exists("Account", "Loyalty - _TC"):
- frappe.get_doc({
- "doctype": "Account",
- "account_name": "Loyalty",
- "parent_account": "Direct Expenses - _TC",
- "company": "_Test Company",
- "is_group": 0,
- "account_type": "Expense Account",
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": "Loyalty",
+ "parent_account": "Direct Expenses - _TC",
+ "company": "_Test Company",
+ "is_group": 0,
+ "account_type": "Expense Account",
+ }
+ ).insert()
# create a new loyalty program Single tier
- if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
- frappe.get_doc({
- "doctype": "Loyalty Program",
- "loyalty_program_name": "Test Single Loyalty",
- "auto_opt_in": 1,
- "from_date": today(),
- "loyalty_program_type": "Single Tier Program",
- "conversion_factor": 1,
- "expiry_duration": 10,
- "company": "_Test Company",
- "cost_center": "Main - _TC",
- "expense_account": "Loyalty - _TC",
- "collection_rules": [{
- 'tier_name': 'Silver',
- 'collection_factor': 1000,
- 'min_spent': 1000
- }]
- }).insert()
+ if not frappe.db.exists("Loyalty Program", "Test Single Loyalty"):
+ frappe.get_doc(
+ {
+ "doctype": "Loyalty Program",
+ "loyalty_program_name": "Test Single Loyalty",
+ "auto_opt_in": 1,
+ "from_date": today(),
+ "loyalty_program_type": "Single Tier Program",
+ "conversion_factor": 1,
+ "expiry_duration": 10,
+ "company": "_Test Company",
+ "cost_center": "Main - _TC",
+ "expense_account": "Loyalty - _TC",
+ "collection_rules": [{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 1000}],
+ }
+ ).insert()
# create a new customer
- if not frappe.db.exists("Customer","Test Loyalty Customer"):
- frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": "Test Loyalty Customer",
- "customer_type": "Individual",
- "doctype": "Customer",
- "territory": "_Test Territory"
- }).insert()
+ if not frappe.db.exists("Customer", "Test Loyalty Customer"):
+ frappe.get_doc(
+ {
+ "customer_group": "_Test Customer Group",
+ "customer_name": "Test Loyalty Customer",
+ "customer_type": "Individual",
+ "doctype": "Customer",
+ "territory": "_Test Territory",
+ }
+ ).insert()
# create a new loyalty program Multiple tier
- if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
- frappe.get_doc({
- "doctype": "Loyalty Program",
- "loyalty_program_name": "Test Multiple Loyalty",
- "auto_opt_in": 1,
- "from_date": today(),
- "loyalty_program_type": "Multiple Tier Program",
- "conversion_factor": 1,
- "expiry_duration": 10,
- "company": "_Test Company",
- "cost_center": "Main - _TC",
- "expense_account": "Loyalty - _TC",
- "collection_rules": [
- {
- 'tier_name': 'Silver',
- 'collection_factor': 1000,
- 'min_spent': 10000
- },
- {
- 'tier_name': 'Gold',
- 'collection_factor': 1000,
- 'min_spent': 19000
- }
- ]
- }).insert()
+ if not frappe.db.exists("Loyalty Program", "Test Multiple Loyalty"):
+ frappe.get_doc(
+ {
+ "doctype": "Loyalty Program",
+ "loyalty_program_name": "Test Multiple Loyalty",
+ "auto_opt_in": 1,
+ "from_date": today(),
+ "loyalty_program_type": "Multiple Tier Program",
+ "conversion_factor": 1,
+ "expiry_duration": 10,
+ "company": "_Test Company",
+ "cost_center": "Main - _TC",
+ "expense_account": "Loyalty - _TC",
+ "collection_rules": [
+ {"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
+ {"tier_name": "Gold", "collection_factor": 1000, "min_spent": 19000},
+ ],
+ }
+ ).insert()
# create an item
if not frappe.db.exists("Item", "Loyal Item"):
- frappe.get_doc({
- "doctype": "Item",
- "item_code": "Loyal Item",
- "item_name": "Loyal Item",
- "item_group": "All Item Groups",
- "company": "_Test Company",
- "is_stock_item": 1,
- "opening_stock": 100,
- "valuation_rate": 10000,
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": "Loyal Item",
+ "item_name": "Loyal Item",
+ "item_group": "All Item Groups",
+ "company": "_Test Company",
+ "is_stock_item": 1,
+ "opening_stock": 100,
+ "valuation_rate": 10000,
+ }
+ ).insert()
# create item price
- if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
- frappe.get_doc({
- "doctype": "Item Price",
- "price_list": "Standard Selling",
- "item_code": "Loyal Item",
- "price_list_rate": 10000
- }).insert()
+ if not frappe.db.exists(
+ "Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}
+ ):
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": "Standard Selling",
+ "item_code": "Loyal Item",
+ "price_list_rate": 10000,
+ }
+ ).insert()
diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py
index f21d1b9baa..d0373021a6 100644
--- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py
+++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py
@@ -19,23 +19,35 @@ class ModeofPayment(Document):
for entry in self.accounts:
accounts_list.append(entry.company)
- if len(accounts_list)!= len(set(accounts_list)):
+ if len(accounts_list) != len(set(accounts_list)):
frappe.throw(_("Same Company is entered more than once"))
def validate_accounts(self):
for entry in self.accounts:
"""Error when Company of Ledger account doesn't match with Company Selected"""
if frappe.db.get_value("Account", entry.default_account, "company") != entry.company:
- frappe.throw(_("Account {0} does not match with Company {1} in Mode of Account: {2}")
- .format(entry.default_account, entry.company, self.name))
+ frappe.throw(
+ _("Account {0} does not match with Company {1} in Mode of Account: {2}").format(
+ entry.default_account, entry.company, self.name
+ )
+ )
def validate_pos_mode_of_payment(self):
if not self.enabled:
- pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip
- WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""", (self.name))
+ pos_profiles = frappe.db.sql(
+ """SELECT sip.parent FROM `tabSales Invoice Payment` sip
+ WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""",
+ (self.name),
+ )
pos_profiles = list(map(lambda x: x[0], pos_profiles))
if pos_profiles:
- message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \
- Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode."
+ message = (
+ "POS Profile "
+ + frappe.bold(", ".join(pos_profiles))
+ + " contains \
+ Mode of Payment "
+ + frappe.bold(str(self.name))
+ + ". Please remove them to disable this mode."
+ )
frappe.throw(_(message), title="Not Allowed")
diff --git a/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py
index 2ff02a7c4d..9733fb89e2 100644
--- a/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py
+++ b/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Mode of Payment')
+
class TestModeofPayment(unittest.TestCase):
pass
diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
index a8c5f68c11..1d19708edd 100644
--- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
+++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
@@ -11,13 +11,25 @@ from frappe.utils import add_months, flt
class MonthlyDistribution(Document):
@frappe.whitelist()
def get_months(self):
- month_list = ['January','February','March','April','May','June','July','August','September',
- 'October','November','December']
- idx =1
+ month_list = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ]
+ idx = 1
for m in month_list:
- mnth = self.append('percentages')
+ mnth = self.append("percentages")
mnth.month = m
- mnth.percentage_allocation = 100.0/12
+ mnth.percentage_allocation = 100.0 / 12
mnth.idx = idx
idx += 1
@@ -25,18 +37,15 @@ class MonthlyDistribution(Document):
total = sum(flt(d.percentage_allocation) for d in self.get("percentages"))
if flt(total, 2) != 100.0:
- frappe.throw(_("Percentage Allocation should be equal to 100%") + \
- " ({0}%)".format(str(flt(total, 2))))
+ frappe.throw(
+ _("Percentage Allocation should be equal to 100%") + " ({0}%)".format(str(flt(total, 2)))
+ )
+
def get_periodwise_distribution_data(distribution_id, period_list, periodicity):
- doc = frappe.get_doc('Monthly Distribution', distribution_id)
+ doc = frappe.get_doc("Monthly Distribution", distribution_id)
- months_to_add = {
- "Yearly": 12,
- "Half-Yearly": 6,
- "Quarterly": 3,
- "Monthly": 1
- }[periodicity]
+ months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_dict = {}
@@ -45,6 +54,7 @@ def get_periodwise_distribution_data(distribution_id, period_list, periodicity):
return period_dict
+
def get_percentage(doc, start_date, period):
percentage = 0
months = [start_date.strftime("%B").title()]
diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py
index 96008c4733..ba2cb671d4 100644
--- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py
+++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py
@@ -3,19 +3,14 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'monthly_distribution',
- 'non_standard_fieldnames': {
- 'Sales Person': 'distribution_id',
- 'Territory': 'distribution_id',
- 'Sales Partner': 'distribution_id',
+ "fieldname": "monthly_distribution",
+ "non_standard_fieldnames": {
+ "Sales Person": "distribution_id",
+ "Territory": "distribution_id",
+ "Sales Partner": "distribution_id",
},
- 'transactions': [
- {
- 'label': _('Target Details'),
- 'items': ['Sales Person', 'Territory', 'Sales Partner']
- },
- {
- 'items': ['Budget']
- }
- ]
+ "transactions": [
+ {"label": _("Target Details"), "items": ["Sales Person", "Territory", "Sales Partner"]},
+ {"items": ["Budget"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py
index 4a878b2aaf..848b1f9de7 100644
--- a/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py
+++ b/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py
@@ -6,7 +6,8 @@ import unittest
import frappe
-test_records = frappe.get_test_records('Monthly Distribution')
+test_records = frappe.get_test_records("Monthly Distribution")
+
class TestMonthlyDistribution(unittest.TestCase):
pass
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index 6e7b80e731..9d4d76b8f6 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -20,9 +20,9 @@ class OpeningInvoiceCreationTool(Document):
def onload(self):
"""Load the Opening Invoice summary"""
summary, max_count = self.get_opening_invoice_summary()
- self.set_onload('opening_invoices_summary', summary)
- self.set_onload('max_count', max_count)
- self.set_onload('temporary_opening_account', get_temporary_opening_account(self.company))
+ self.set_onload("opening_invoices_summary", summary)
+ self.set_onload("max_count", max_count)
+ self.set_onload("temporary_opening_account", get_temporary_opening_account(self.company))
def get_opening_invoice_summary(self):
def prepare_invoice_summary(doctype, invoices):
@@ -32,10 +32,7 @@ class OpeningInvoiceCreationTool(Document):
for invoice in invoices:
company = invoice.pop("company")
_summary = invoices_summary.get(company, {})
- _summary.update({
- "currency": company_wise_currency.get(company),
- doctype: invoice
- })
+ _summary.update({"currency": company_wise_currency.get(company), doctype: invoice})
invoices_summary.update({company: _summary})
if invoice.paid_amount:
@@ -44,17 +41,21 @@ class OpeningInvoiceCreationTool(Document):
outstanding_amount.append(invoice.outstanding_amount)
if paid_amount or outstanding_amount:
- max_count.update({
- doctype: {
- "max_paid": max(paid_amount) if paid_amount else 0.0,
- "max_due": max(outstanding_amount) if outstanding_amount else 0.0
+ max_count.update(
+ {
+ doctype: {
+ "max_paid": max(paid_amount) if paid_amount else 0.0,
+ "max_due": max(outstanding_amount) if outstanding_amount else 0.0,
+ }
}
- })
+ )
invoices_summary = {}
max_count = {}
fields = [
- "company", "count(name) as total_invoices", "sum(outstanding_amount) as outstanding_amount"
+ "company",
+ "count(name) as total_invoices",
+ "sum(outstanding_amount) as outstanding_amount",
]
companies = frappe.get_all("Company", fields=["name as company", "default_currency as currency"])
if not companies:
@@ -62,8 +63,9 @@ class OpeningInvoiceCreationTool(Document):
company_wise_currency = {row.company: row.currency for row in companies}
for doctype in ["Sales Invoice", "Purchase Invoice"]:
- invoices = frappe.get_all(doctype, filters=dict(is_opening="Yes", docstatus=1),
- fields=fields, group_by="company")
+ invoices = frappe.get_all(
+ doctype, filters=dict(is_opening="Yes", docstatus=1), fields=fields, group_by="company"
+ )
prepare_invoice_summary(doctype, invoices)
return invoices_summary, max_count
@@ -74,7 +76,9 @@ class OpeningInvoiceCreationTool(Document):
def set_missing_values(self, row):
row.qty = row.qty or 1.0
- row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
+ row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(
+ self.company
+ )
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
row.item_name = row.item_name or _("Opening Invoice Item")
row.posting_date = row.posting_date or nowdate()
@@ -85,7 +89,11 @@ class OpeningInvoiceCreationTool(Document):
if self.create_missing_party:
self.add_party(row.party_type, row.party)
else:
- frappe.throw(_("Row #{}: {} {} does not exist.").format(row.idx, frappe.bold(row.party_type), frappe.bold(row.party)))
+ frappe.throw(
+ _("Row #{}: {} {} does not exist.").format(
+ row.idx, frappe.bold(row.party_type), frappe.bold(row.party)
+ )
+ )
mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices")
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
@@ -100,12 +108,19 @@ class OpeningInvoiceCreationTool(Document):
self.set_missing_values(row)
self.validate_mandatory_invoice_fields(row)
invoice = self.get_invoice_dict(row)
- company_details = frappe.get_cached_value('Company', self.company, ["default_currency", "default_letter_head"], as_dict=1) or {}
+ company_details = (
+ frappe.get_cached_value(
+ "Company", self.company, ["default_currency", "default_letter_head"], as_dict=1
+ )
+ or {}
+ )
if company_details:
- invoice.update({
- "currency": company_details.get("default_currency"),
- "letter_head": company_details.get("default_letter_head")
- })
+ invoice.update(
+ {
+ "currency": company_details.get("default_currency"),
+ "letter_head": company_details.get("default_letter_head"),
+ }
+ )
invoices.append(invoice)
return invoices
@@ -127,55 +142,61 @@ class OpeningInvoiceCreationTool(Document):
def get_invoice_dict(self, row=None):
def get_item_dict():
- cost_center = row.get('cost_center') or frappe.get_cached_value('Company', self.company, "cost_center")
+ cost_center = row.get("cost_center") or frappe.get_cached_value(
+ "Company", self.company, "cost_center"
+ )
if not cost_center:
- frappe.throw(_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company)))
+ frappe.throw(
+ _("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
+ )
- income_expense_account_field = "income_account" if row.party_type == "Customer" else "expense_account"
+ income_expense_account_field = (
+ "income_account" if row.party_type == "Customer" else "expense_account"
+ )
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
rate = flt(row.outstanding_amount) / flt(row.qty)
- item_dict = frappe._dict({
- "uom": default_uom,
- "rate": rate or 0.0,
- "qty": row.qty,
- "conversion_factor": 1.0,
- "item_name": row.item_name or "Opening Invoice Item",
- "description": row.item_name or "Opening Invoice Item",
- income_expense_account_field: row.temporary_opening_account,
- "cost_center": cost_center
- })
+ item_dict = frappe._dict(
+ {
+ "uom": default_uom,
+ "rate": rate or 0.0,
+ "qty": row.qty,
+ "conversion_factor": 1.0,
+ "item_name": row.item_name or "Opening Invoice Item",
+ "description": row.item_name or "Opening Invoice Item",
+ income_expense_account_field: row.temporary_opening_account,
+ "cost_center": cost_center,
+ }
+ )
for dimension in get_accounting_dimensions():
- item_dict.update({
- dimension: row.get(dimension)
- })
+ item_dict.update({dimension: row.get(dimension)})
return item_dict
item = get_item_dict()
- invoice = frappe._dict({
- "items": [item],
- "is_opening": "Yes",
- "set_posting_time": 1,
- "company": self.company,
- "cost_center": self.cost_center,
- "due_date": row.due_date,
- "posting_date": row.posting_date,
- frappe.scrub(row.party_type): row.party,
- "is_pos": 0,
- "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
- "update_stock": 0, # important: https://github.com/frappe/erpnext/pull/23559
- "invoice_number": row.invoice_number,
- "disable_rounded_total": 1
- })
+ invoice = frappe._dict(
+ {
+ "items": [item],
+ "is_opening": "Yes",
+ "set_posting_time": 1,
+ "company": self.company,
+ "cost_center": self.cost_center,
+ "due_date": row.due_date,
+ "posting_date": row.posting_date,
+ frappe.scrub(row.party_type): row.party,
+ "is_pos": 0,
+ "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
+ "update_stock": 0, # important: https://github.com/frappe/erpnext/pull/23559
+ "invoice_number": row.invoice_number,
+ "disable_rounded_total": 1,
+ }
+ )
accounting_dimension = get_accounting_dimensions()
for dimension in accounting_dimension:
- invoice.update({
- dimension: self.get(dimension) or item.get(dimension)
- })
+ invoice.update({dimension: self.get(dimension) or item.get(dimension)})
return invoice
@@ -201,9 +222,10 @@ class OpeningInvoiceCreationTool(Document):
event="opening_invoice_creation",
job_name=self.name,
invoices=invoices,
- now=frappe.conf.developer_mode or frappe.flags.in_test
+ now=frappe.conf.developer_mode or frappe.flags.in_test,
)
+
def start_import(invoices):
errors = 0
names = []
@@ -222,14 +244,22 @@ def start_import(invoices):
except Exception:
errors += 1
frappe.db.rollback()
- message = "\n".join(["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
+ message = "\n".join(
+ ["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()]
+ )
frappe.log_error(title="Error while creating Opening Invoice", message=message)
frappe.db.commit()
if errors:
- frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
- .format(errors, "Error Log"), indicator="red", title=_("Error Occured"))
+ frappe.msgprint(
+ _("You had {} errors while creating opening invoices. Check {} for more details").format(
+ errors, "Error Log"
+ ),
+ indicator="red",
+ title=_("Error Occured"),
+ )
return names
+
def publish(index, total, doctype):
if total < 50:
return
@@ -237,21 +267,20 @@ def publish(index, total, doctype):
"opening_invoice_creation_progress",
dict(
title=_("Opening Invoice Creation In Progress"),
- message=_('Creating {} out of {} {}').format(index + 1, total, doctype),
+ message=_("Creating {} out of {} {}").format(index + 1, total, doctype),
user=frappe.session.user,
- count=index+1,
- total=total
- ))
+ count=index + 1,
+ total=total,
+ ),
+ )
+
@frappe.whitelist()
def get_temporary_opening_account(company=None):
if not company:
return
- accounts = frappe.get_all("Account", filters={
- 'company': company,
- 'account_type': 'Temporary'
- })
+ accounts = frappe.get_all("Account", filters={"company": company, "account_type": "Temporary"})
if not accounts:
frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts"))
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index 77d54a605e..1e22c64c8f 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -14,6 +14,7 @@ from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_crea
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
+
class TestOpeningInvoiceCreationTool(FrappeTestCase):
@classmethod
def setUpClass(self):
@@ -22,10 +23,24 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
create_dimension()
return super().setUpClass()
- def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None):
+ def make_invoices(
+ self,
+ invoice_type="Sales",
+ company=None,
+ party_1=None,
+ party_2=None,
+ invoice_number=None,
+ department=None,
+ ):
doc = frappe.get_single("Opening Invoice Creation Tool")
- args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
- party_1=party_1, party_2=party_2, invoice_number=invoice_number, department=department)
+ args = get_opening_invoice_creation_dict(
+ invoice_type=invoice_type,
+ company=company,
+ party_1=party_1,
+ party_2=party_2,
+ invoice_number=invoice_number,
+ department=department,
+ )
doc.update(args)
return doc.make_invoices()
@@ -68,15 +83,30 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
- old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
+ old_default_receivable_account = frappe.db.get_value(
+ "Company", company, "default_receivable_account"
+ )
frappe.db.set_value("Company", company, "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
- cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
- "is_group": 1, "company": "_Test Opening Invoice Company"})
+ cc = frappe.get_doc(
+ {
+ "doctype": "Cost Center",
+ "cost_center_name": "_Test Opening Invoice Company",
+ "is_group": 1,
+ "company": "_Test Opening Invoice Company",
+ }
+ )
cc.insert(ignore_mandatory=True)
- cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
- "company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
+ cc2 = frappe.get_doc(
+ {
+ "doctype": "Cost Center",
+ "cost_center_name": "Main",
+ "is_group": 0,
+ "company": "_Test Opening Invoice Company",
+ "parent_cost_center": cc.name,
+ }
+ )
cc2.insert()
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
@@ -84,28 +114,37 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
# Check if missing debit account error raised
- error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]})
+ error_log = frappe.db.exists(
+ "Error Log",
+ {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]},
+ )
self.assertTrue(error_log)
# teardown
- frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
+ frappe.db.set_value(
+ "Company", company, "default_receivable_account", old_default_receivable_account
+ )
def test_renaming_of_invoice_using_invoice_number_field(self):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
- self.make_invoices(company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11")
+ self.make_invoices(
+ company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11"
+ )
- sales_inv1 = frappe.get_all('Sales Invoice', filters={'customer':'Customer A'})[0].get("name")
- sales_inv2 = frappe.get_all('Sales Invoice', filters={'customer':'Customer B'})[0].get("name")
+ sales_inv1 = frappe.get_all("Sales Invoice", filters={"customer": "Customer A"})[0].get("name")
+ sales_inv2 = frappe.get_all("Sales Invoice", filters={"customer": "Customer B"})[0].get("name")
self.assertEqual(sales_inv1, "TEST-NEW-INV-11")
- #teardown
+ # teardown
for inv in [sales_inv1, sales_inv2]:
- doc = frappe.get_doc('Sales Invoice', inv)
+ doc = frappe.get_doc("Sales Invoice", inv)
doc.cancel()
def test_opening_invoice_with_accounting_dimension(self):
- invoices = self.make_invoices(invoice_type="Sales", company="_Test Opening Invoice Company", department='Sales - _TOIC')
+ invoices = self.make_invoices(
+ invoice_type="Sales", company="_Test Opening Invoice Company", department="Sales - _TOIC"
+ )
expected_value = {
"keys": ["customer", "outstanding_amount", "status", "department"],
@@ -117,40 +156,44 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
def tearDown(self):
disable_dimension()
+
def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company")
- invoice_dict = frappe._dict({
- "company": company,
- "invoice_type": args.get("invoice_type", "Sales"),
- "invoices": [
- {
- "qty": 1.0,
- "outstanding_amount": 300,
- "party": args.get("party_1") or "_Test {0}".format(party),
- "item_name": "Opening Item",
- "due_date": "2016-09-10",
- "posting_date": "2016-09-05",
- "temporary_opening_account": get_temporary_opening_account(company),
- "invoice_number": args.get("invoice_number")
- },
- {
- "qty": 2.0,
- "outstanding_amount": 250,
- "party": args.get("party_2") or "_Test {0} 1".format(party),
- "item_name": "Opening Item",
- "due_date": "2016-09-10",
- "posting_date": "2016-09-05",
- "temporary_opening_account": get_temporary_opening_account(company),
- "invoice_number": None
- }
- ]
- })
+ invoice_dict = frappe._dict(
+ {
+ "company": company,
+ "invoice_type": args.get("invoice_type", "Sales"),
+ "invoices": [
+ {
+ "qty": 1.0,
+ "outstanding_amount": 300,
+ "party": args.get("party_1") or "_Test {0}".format(party),
+ "item_name": "Opening Item",
+ "due_date": "2016-09-10",
+ "posting_date": "2016-09-05",
+ "temporary_opening_account": get_temporary_opening_account(company),
+ "invoice_number": args.get("invoice_number"),
+ },
+ {
+ "qty": 2.0,
+ "outstanding_amount": 250,
+ "party": args.get("party_2") or "_Test {0} 1".format(party),
+ "item_name": "Opening Item",
+ "due_date": "2016-09-10",
+ "posting_date": "2016-09-05",
+ "temporary_opening_account": get_temporary_opening_account(company),
+ "invoice_number": None,
+ },
+ ],
+ }
+ )
invoice_dict.update(args)
return invoice_dict
+
def make_company():
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
return frappe.get_doc("Company", "_Test Opening Invoice Company")
@@ -163,15 +206,18 @@ def make_company():
company.insert()
return company
+
def make_customer(customer=None):
customer_name = customer or "Opening Customer"
- customer = frappe.get_doc({
- "doctype": "Customer",
- "customer_name": customer_name,
- "customer_group": "All Customer Groups",
- "customer_type": "Company",
- "territory": "All Territories"
- })
+ customer = frappe.get_doc(
+ {
+ "doctype": "Customer",
+ "customer_name": customer_name,
+ "customer_group": "All Customer Groups",
+ "customer_type": "Company",
+ "territory": "All Territories",
+ }
+ )
if not frappe.db.exists("Customer", customer_name):
customer.insert(ignore_permissions=True)
return customer.name
diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py
index 031a9fa4db..312cfd2c0a 100644
--- a/erpnext/accounts/doctype/party_link/party_link.py
+++ b/erpnext/accounts/doctype/party_link/party_link.py
@@ -8,45 +8,55 @@ from frappe.model.document import Document
class PartyLink(Document):
def validate(self):
- if self.primary_role not in ['Customer', 'Supplier']:
- frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."),
- title=_("Invalid Primary Role"))
+ if self.primary_role not in ["Customer", "Supplier"]:
+ frappe.throw(
+ _(
+ "Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."
+ ),
+ title=_("Invalid Primary Role"),
+ )
- existing_party_link = frappe.get_all('Party Link', {
- 'primary_party': self.primary_party,
- 'secondary_party': self.secondary_party
- }, pluck="primary_role")
+ existing_party_link = frappe.get_all(
+ "Party Link",
+ {"primary_party": self.primary_party, "secondary_party": self.secondary_party},
+ pluck="primary_role",
+ )
if existing_party_link:
- frappe.throw(_('{} {} is already linked with {} {}')
- .format(
- self.primary_role, bold(self.primary_party),
- self.secondary_role, bold(self.secondary_party)
- ))
+ frappe.throw(
+ _("{} {} is already linked with {} {}").format(
+ self.primary_role, bold(self.primary_party), self.secondary_role, bold(self.secondary_party)
+ )
+ )
- existing_party_link = frappe.get_all('Party Link', {
- 'primary_party': self.secondary_party
- }, pluck="primary_role")
+ existing_party_link = frappe.get_all(
+ "Party Link", {"primary_party": self.secondary_party}, pluck="primary_role"
+ )
if existing_party_link:
- frappe.throw(_('{} {} is already linked with another {}')
- .format(self.secondary_role, self.secondary_party, existing_party_link[0]))
+ frappe.throw(
+ _("{} {} is already linked with another {}").format(
+ self.secondary_role, self.secondary_party, existing_party_link[0]
+ )
+ )
- existing_party_link = frappe.get_all('Party Link', {
- 'secondary_party': self.primary_party
- }, pluck="primary_role")
+ existing_party_link = frappe.get_all(
+ "Party Link", {"secondary_party": self.primary_party}, pluck="primary_role"
+ )
if existing_party_link:
- frappe.throw(_('{} {} is already linked with another {}')
- .format(self.primary_role, self.primary_party, existing_party_link[0]))
+ frappe.throw(
+ _("{} {} is already linked with another {}").format(
+ self.primary_role, self.primary_party, existing_party_link[0]
+ )
+ )
@frappe.whitelist()
def create_party_link(primary_role, primary_party, secondary_party):
- party_link = frappe.new_doc('Party Link')
+ party_link = frappe.new_doc("Party Link")
party_link.primary_role = primary_role
party_link.primary_party = primary_party
- party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier'
+ party_link.secondary_role = "Customer" if primary_role == "Supplier" else "Supplier"
party_link.secondary_party = secondary_party
party_link.save(ignore_permissions=True)
return party_link
-
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index f9f33502d3..e6a554c00a 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -95,7 +95,7 @@ class PaymentEntry(AccountsController):
self.set_status()
def on_cancel(self):
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=1)
self.update_expense_claim()
self.update_outstanding_amounts()
@@ -107,6 +107,7 @@ class PaymentEntry(AccountsController):
def set_payment_req_status(self):
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
+
update_payment_req_status(self, None)
def update_outstanding_amounts(self):
@@ -116,8 +117,11 @@ class PaymentEntry(AccountsController):
reference_names = []
for d in self.get("references"):
if (d.reference_doctype, d.reference_name, d.payment_term) in reference_names:
- frappe.throw(_("Row #{0}: Duplicate entry in References {1} {2}")
- .format(d.idx, d.reference_doctype, d.reference_name))
+ frappe.throw(
+ _("Row #{0}: Duplicate entry in References {1} {2}").format(
+ d.idx, d.reference_doctype, d.reference_name
+ )
+ )
reference_names.append((d.reference_doctype, d.reference_name, d.payment_term))
def set_bank_account_data(self):
@@ -133,20 +137,27 @@ class PaymentEntry(AccountsController):
self.set(field, bank_data.account)
def validate_payment_type_with_outstanding(self):
- total_outstanding = sum(d.allocated_amount for d in self.get('references'))
- if total_outstanding < 0 and self.party_type == 'Customer' and self.payment_type == 'Receive':
- frappe.throw(_("Cannot receive from customer against negative outstanding"), title=_("Incorrect Payment Type"))
+ total_outstanding = sum(d.allocated_amount for d in self.get("references"))
+ if total_outstanding < 0 and self.party_type == "Customer" and self.payment_type == "Receive":
+ frappe.throw(
+ _("Cannot receive from customer against negative outstanding"),
+ title=_("Incorrect Payment Type"),
+ )
def validate_allocated_amount(self):
for d in self.get("references"):
- if (flt(d.allocated_amount))> 0:
+ if (flt(d.allocated_amount)) > 0:
if flt(d.allocated_amount) > flt(d.outstanding_amount):
- frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
+ frappe.throw(
+ _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
+ )
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0:
if flt(d.allocated_amount) < flt(d.outstanding_amount):
- frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
+ frappe.throw(
+ _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
+ )
def delink_advance_entry_references(self):
for reference in self.references:
@@ -156,9 +167,14 @@ class PaymentEntry(AccountsController):
def set_missing_values(self):
if self.payment_type == "Internal Transfer":
- for field in ("party", "party_balance", "total_allocated_amount",
- "base_total_allocated_amount", "unallocated_amount"):
- self.set(field, None)
+ for field in (
+ "party",
+ "party_balance",
+ "total_allocated_amount",
+ "base_total_allocated_amount",
+ "unallocated_amount",
+ ):
+ self.set(field, None)
self.references = []
else:
if not self.party_type:
@@ -167,13 +183,16 @@ class PaymentEntry(AccountsController):
if not self.party:
frappe.throw(_("Party is mandatory"))
- _party_name = "title" if self.party_type in ("Student", "Shareholder") else self.party_type.lower() + "_name"
+ _party_name = (
+ "title" if self.party_type in ("Student", "Shareholder") else self.party_type.lower() + "_name"
+ )
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
if self.party:
if not self.party_balance:
- self.party_balance = get_balance_on(party_type=self.party_type,
- party=self.party, date=self.posting_date, company=self.company)
+ self.party_balance = get_balance_on(
+ party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
+ )
if not self.party_account:
party_account = get_party_account(self.party_type, self.party, self.company)
@@ -190,16 +209,20 @@ class PaymentEntry(AccountsController):
self.paid_to_account_currency = acc.account_currency
self.paid_to_account_balance = acc.account_balance
- self.party_account_currency = self.paid_from_account_currency \
- if self.payment_type=="Receive" else self.paid_to_account_currency
+ self.party_account_currency = (
+ self.paid_from_account_currency
+ if self.payment_type == "Receive"
+ else self.paid_to_account_currency
+ )
self.set_missing_ref_details()
def set_missing_ref_details(self, force=False):
for d in self.get("references"):
if d.allocated_amount:
- ref_details = get_reference_details(d.reference_doctype,
- d.reference_name, self.party_account_currency)
+ ref_details = get_reference_details(
+ d.reference_doctype, d.reference_name, self.party_account_currency
+ )
for field, value in ref_details.items():
if d.exchange_gain_loss:
@@ -209,7 +232,7 @@ class PaymentEntry(AccountsController):
# refer -> `update_reference_in_payment_entry()` in utils.py
continue
- if field == 'exchange_rate' or not d.get(field) or force:
+ if field == "exchange_rate" or not d.get(field) or force:
d.db_set(field, value)
def validate_payment_type(self):
@@ -222,8 +245,9 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
if self.party_account and self.party_type in ("Customer", "Supplier"):
- self.validate_account_type(self.party_account,
- [erpnext.get_party_account_type(self.party_type)])
+ self.validate_account_type(
+ self.party_account, [erpnext.get_party_account_type(self.party_type)]
+ )
def validate_bank_accounts(self):
if self.payment_type in ("Pay", "Internal Transfer"):
@@ -251,8 +275,9 @@ class PaymentEntry(AccountsController):
self.source_exchange_rate = ref_doc.get("exchange_rate")
if not self.source_exchange_rate:
- self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
- self.company_currency, self.posting_date)
+ self.source_exchange_rate = get_exchange_rate(
+ self.paid_from_account_currency, self.company_currency, self.posting_date
+ )
def set_target_exchange_rate(self, ref_doc=None):
if self.paid_from_account_currency == self.paid_to_account_currency:
@@ -263,8 +288,9 @@ class PaymentEntry(AccountsController):
self.target_exchange_rate = ref_doc.get("exchange_rate")
if not self.target_exchange_rate:
- self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
- self.company_currency, self.posting_date)
+ self.target_exchange_rate = get_exchange_rate(
+ self.paid_to_account_currency, self.company_currency, self.posting_date
+ )
def validate_mandatory(self):
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
@@ -273,7 +299,7 @@ class PaymentEntry(AccountsController):
def validate_reference_documents(self):
if self.party_type == "Student":
- valid_reference_doctypes = ("Fees")
+ valid_reference_doctypes = "Fees"
elif self.party_type == "Customer":
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
@@ -281,14 +307,15 @@ class PaymentEntry(AccountsController):
elif self.party_type == "Employee":
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
elif self.party_type == "Shareholder":
- valid_reference_doctypes = ("Journal Entry")
+ valid_reference_doctypes = "Journal Entry"
for d in self.get("references"):
if not d.allocated_amount:
continue
if d.reference_doctype not in valid_reference_doctypes:
- frappe.throw(_("Reference Doctype must be one of {0}")
- .format(comma_or(valid_reference_doctypes)))
+ frappe.throw(
+ _("Reference Doctype must be one of {0}").format(comma_or(valid_reference_doctypes))
+ )
elif d.reference_name:
if not frappe.db.exists(d.reference_doctype, d.reference_name):
@@ -298,28 +325,35 @@ class PaymentEntry(AccountsController):
if d.reference_doctype != "Journal Entry":
if self.party != ref_doc.get(scrub(self.party_type)):
- frappe.throw(_("{0} {1} is not associated with {2} {3}")
- .format(d.reference_doctype, d.reference_name, self.party_type, self.party))
+ frappe.throw(
+ _("{0} {1} is not associated with {2} {3}").format(
+ d.reference_doctype, d.reference_name, self.party_type, self.party
+ )
+ )
else:
self.validate_journal_entry()
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim", "Fees"):
if self.party_type == "Customer":
- ref_party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
+ ref_party_account = (
+ get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
+ )
elif self.party_type == "Student":
ref_party_account = ref_doc.receivable_account
- elif self.party_type=="Supplier":
+ elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to
- elif self.party_type=="Employee":
+ elif self.party_type == "Employee":
ref_party_account = ref_doc.payable_account
if ref_party_account != self.party_account:
- frappe.throw(_("{0} {1} is associated with {2}, but Party Account is {3}")
- .format(d.reference_doctype, d.reference_name, ref_party_account, self.party_account))
+ frappe.throw(
+ _("{0} {1} is associated with {2}, but Party Account is {3}").format(
+ d.reference_doctype, d.reference_name, ref_party_account, self.party_account
+ )
+ )
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 validate_paid_invoices(self):
no_oustanding_refs = {}
@@ -329,28 +363,45 @@ class PaymentEntry(AccountsController):
continue
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
- outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"])
+ outstanding_amount, is_return = frappe.get_cached_value(
+ d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]
+ )
if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items():
frappe.msgprint(
- _("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
- .format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount")))
- + "
" + _("If this is undesirable please cancel the corresponding Payment Entry."),
- title=_("Warning"), indicator="orange")
+ _(
+ "{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry."
+ ).format(
+ _(k),
+ frappe.bold(", ".join(d.reference_name for d in v)),
+ frappe.bold(_("negative outstanding amount")),
+ )
+ + "
"
+ + _("If this is undesirable please cancel the corresponding Payment Entry."),
+ title=_("Warning"),
+ indicator="orange",
+ )
def validate_journal_entry(self):
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype == "Journal Entry":
- je_accounts = frappe.db.sql("""select debit, credit from `tabJournal Entry Account`
+ je_accounts = frappe.db.sql(
+ """select debit, credit from `tabJournal Entry Account`
where account = %s and party=%s and docstatus = 1 and parent = %s
and (reference_type is null or reference_type in ("", "Sales Order", "Purchase Order"))
- """, (self.party_account, self.party, d.reference_name), as_dict=True)
+ """,
+ (self.party_account, self.party, d.reference_name),
+ as_dict=True,
+ )
if not je_accounts:
- frappe.throw(_("Row #{0}: Journal Entry {1} does not have account {2} or already matched against another voucher")
- .format(d.idx, d.reference_name, self.party_account))
+ frappe.throw(
+ _(
+ "Row #{0}: Journal Entry {1} does not have account {2} or already matched against another voucher"
+ ).format(d.idx, d.reference_name, self.party_account)
+ )
else:
dr_or_cr = "debit" if self.payment_type == "Receive" else "credit"
valid = False
@@ -358,14 +409,17 @@ class PaymentEntry(AccountsController):
if flt(jvd[dr_or_cr]) > 0:
valid = True
if not valid:
- frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
- .format(d.reference_name, dr_or_cr))
+ frappe.throw(
+ _("Against Journal Entry {0} does not have any unmatched {1} entry").format(
+ d.reference_name, dr_or_cr
+ )
+ )
def update_payment_schedule(self, cancel=0):
invoice_payment_amount_map = {}
invoice_paid_amount_map = {}
- for ref in self.get('references'):
+ for ref in self.get("references"):
if ref.payment_term and ref.reference_name:
key = (ref.payment_term, ref.reference_name)
invoice_payment_amount_map.setdefault(key, 0.0)
@@ -373,58 +427,68 @@ class PaymentEntry(AccountsController):
if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all(
- 'Payment Schedule',
- filters={'parent': ref.reference_name},
- fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding']
+ "Payment Schedule",
+ filters={"parent": ref.reference_name},
+ fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name)
invoice_paid_amount_map.setdefault(invoice_key, {})
- invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
- invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
+ invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
+ invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
+ term.discount / 100
+ )
for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1):
if not invoice_paid_amount_map.get(key):
- frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1]))
+ frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1]))
- outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
- discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt'))
+ outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
+ discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
if cancel:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabPayment Schedule`
SET
paid_amount = `paid_amount` - %s,
discounted_amount = `discounted_amount` - %s,
outstanding = `outstanding` + %s
WHERE parent = %s and payment_term = %s""",
- (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
+ (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
+ )
else:
if allocated_amount > outstanding:
- frappe.throw(_('Row #{0}: Cannot allocate more than {1} against payment term {2}').format(idx, outstanding, key[0]))
+ frappe.throw(
+ _("Row #{0}: Cannot allocate more than {1} against payment term {2}").format(
+ idx, outstanding, key[0]
+ )
+ )
if allocated_amount and outstanding:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabPayment Schedule`
SET
paid_amount = `paid_amount` + %s,
discounted_amount = `discounted_amount` + %s,
outstanding = `outstanding` - %s
WHERE parent = %s and payment_term = %s""",
- (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
+ (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
+ )
def set_status(self):
if self.docstatus == 2:
- self.status = 'Cancelled'
+ self.status = "Cancelled"
elif self.docstatus == 1:
- self.status = 'Submitted'
+ self.status = "Submitted"
else:
- self.status = 'Draft'
+ self.status = "Draft"
- self.db_set('status', self.status, update_modified = True)
+ self.db_set("status", self.status, update_modified=True)
def set_tax_withholding(self):
- if not self.party_type == 'Supplier':
+ if not self.party_type == "Supplier":
return
if not self.apply_tax_withholding_amount:
@@ -433,22 +497,24 @@ class PaymentEntry(AccountsController):
net_total = self.paid_amount
# Adding args as purchase invoice to get TDS amount
- args = frappe._dict({
- 'company': self.company,
- 'doctype': 'Payment Entry',
- 'supplier': self.party,
- 'posting_date': self.posting_date,
- 'net_total': net_total
- })
+ args = frappe._dict(
+ {
+ "company": self.company,
+ "doctype": "Payment Entry",
+ "supplier": self.party,
+ "posting_date": self.posting_date,
+ "net_total": net_total,
+ }
+ )
tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
if not tax_withholding_details:
return
- tax_withholding_details.update({
- 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
- })
+ tax_withholding_details.update(
+ {"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company)}
+ )
accounts = []
for d in self.taxes:
@@ -456,7 +522,7 @@ class PaymentEntry(AccountsController):
# Preserve user updated included in paid amount
if d.included_in_paid_amount:
- tax_withholding_details.update({'included_in_paid_amount': d.included_in_paid_amount})
+ tax_withholding_details.update({"included_in_paid_amount": d.included_in_paid_amount})
d.update(tax_withholding_details)
accounts.append(d.account_head)
@@ -464,8 +530,11 @@ class PaymentEntry(AccountsController):
if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("taxes", tax_withholding_details)
- to_remove = [d for d in self.taxes
- if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
+ to_remove = [
+ d
+ for d in self.taxes
+ if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")
+ ]
for d in to_remove:
self.remove(d)
@@ -492,40 +561,53 @@ class PaymentEntry(AccountsController):
def set_received_amount(self):
self.base_received_amount = self.base_paid_amount
- if self.paid_from_account_currency == self.paid_to_account_currency \
- and not self.payment_type == 'Internal Transfer':
+ if (
+ self.paid_from_account_currency == self.paid_to_account_currency
+ and not self.payment_type == "Internal Transfer"
+ ):
self.received_amount = self.paid_amount
def set_amounts_after_tax(self):
applicable_tax = 0
base_applicable_tax = 0
- for tax in self.get('taxes'):
+ for tax in self.get("taxes"):
if not tax.included_in_paid_amount:
- amount = -1 * tax.tax_amount if tax.add_deduct_tax == 'Deduct' else tax.tax_amount
- base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == 'Deduct' else tax.base_tax_amount
+ amount = -1 * tax.tax_amount if tax.add_deduct_tax == "Deduct" else tax.tax_amount
+ base_amount = (
+ -1 * tax.base_tax_amount if tax.add_deduct_tax == "Deduct" else tax.base_tax_amount
+ )
applicable_tax += amount
base_applicable_tax += base_amount
- self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(applicable_tax),
- self.precision("paid_amount_after_tax"))
- self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate),
- self.precision("base_paid_amount_after_tax"))
+ self.paid_amount_after_tax = flt(
+ flt(self.paid_amount) + flt(applicable_tax), self.precision("paid_amount_after_tax")
+ )
+ self.base_paid_amount_after_tax = flt(
+ flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate),
+ self.precision("base_paid_amount_after_tax"),
+ )
- self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax),
- self.precision("paid_amount_after_tax"))
- self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.target_exchange_rate),
- self.precision("base_paid_amount_after_tax"))
+ self.received_amount_after_tax = flt(
+ flt(self.received_amount) + flt(applicable_tax), self.precision("paid_amount_after_tax")
+ )
+ self.base_received_amount_after_tax = flt(
+ flt(self.received_amount_after_tax) * flt(self.target_exchange_rate),
+ self.precision("base_paid_amount_after_tax"),
+ )
def set_amounts_in_company_currency(self):
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
if self.paid_amount:
- self.base_paid_amount = flt(flt(self.paid_amount) * flt(self.source_exchange_rate),
- self.precision("base_paid_amount"))
+ self.base_paid_amount = flt(
+ flt(self.paid_amount) * flt(self.source_exchange_rate), self.precision("base_paid_amount")
+ )
if self.received_amount:
- self.base_received_amount = flt(flt(self.received_amount) * flt(self.target_exchange_rate),
- self.precision("base_received_amount"))
+ self.base_received_amount = flt(
+ flt(self.received_amount) * flt(self.target_exchange_rate),
+ self.precision("base_received_amount"),
+ )
def set_total_allocated_amount(self):
if self.payment_type == "Internal Transfer":
@@ -535,8 +617,9 @@ class PaymentEntry(AccountsController):
for d in self.get("references"):
if d.allocated_amount:
total_allocated_amount += flt(d.allocated_amount)
- base_total_allocated_amount += flt(flt(d.allocated_amount) * flt(d.exchange_rate),
- self.precision("base_paid_amount"))
+ base_total_allocated_amount += flt(
+ flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
+ )
self.total_allocated_amount = abs(total_allocated_amount)
self.base_total_allocated_amount = abs(base_total_allocated_amount)
@@ -546,22 +629,33 @@ class PaymentEntry(AccountsController):
if self.party:
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
included_taxes = self.get_included_taxes()
- if self.payment_type == "Receive" \
- and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
- and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
- self.unallocated_amount = (self.base_received_amount + total_deductions -
- self.base_total_allocated_amount) / self.source_exchange_rate
+ if (
+ self.payment_type == "Receive"
+ and self.base_total_allocated_amount < self.base_received_amount + total_deductions
+ and self.total_allocated_amount
+ < self.paid_amount + (total_deductions / self.source_exchange_rate)
+ ):
+ self.unallocated_amount = (
+ self.base_received_amount + total_deductions - self.base_total_allocated_amount
+ ) / self.source_exchange_rate
self.unallocated_amount -= included_taxes
- elif self.payment_type == "Pay" \
- and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
- and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
- self.unallocated_amount = (self.base_paid_amount - (total_deductions +
- self.base_total_allocated_amount)) / self.target_exchange_rate
+ elif (
+ self.payment_type == "Pay"
+ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions)
+ and self.total_allocated_amount
+ < self.received_amount + (total_deductions / self.target_exchange_rate)
+ ):
+ self.unallocated_amount = (
+ self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)
+ ) / self.target_exchange_rate
self.unallocated_amount -= included_taxes
def set_difference_amount(self):
- base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
- if self.payment_type == "Receive" else flt(self.target_exchange_rate))
+ base_unallocated_amount = flt(self.unallocated_amount) * (
+ flt(self.source_exchange_rate)
+ if self.payment_type == "Receive"
+ else flt(self.target_exchange_rate)
+ )
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
@@ -575,14 +669,15 @@ class PaymentEntry(AccountsController):
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
included_taxes = self.get_included_taxes()
- self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes,
- self.precision("difference_amount"))
+ self.difference_amount = flt(
+ self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")
+ )
def get_included_taxes(self):
included_taxes = 0
- for tax in self.get('taxes'):
+ for tax in self.get("taxes"):
if tax.included_in_paid_amount:
- if tax.add_deduct_tax == 'Add':
+ if tax.add_deduct_tax == "Add":
included_taxes += tax.base_tax_amount
else:
included_taxes -= tax.base_tax_amount
@@ -593,27 +688,41 @@ class PaymentEntry(AccountsController):
# Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast
def clear_unallocated_reference_document_rows(self):
self.set("references", self.get("references", {"allocated_amount": ["not in", [0, None, ""]]}))
- frappe.db.sql("""delete from `tabPayment Entry Reference`
- where parent = %s and allocated_amount = 0""", self.name)
+ frappe.db.sql(
+ """delete from `tabPayment Entry Reference`
+ where parent = %s and allocated_amount = 0""",
+ self.name,
+ )
def validate_payment_against_negative_invoice(self):
- if ((self.payment_type=="Pay" and self.party_type=="Customer")
- or (self.payment_type=="Receive" and self.party_type=="Supplier")):
+ if (self.payment_type == "Pay" and self.party_type == "Customer") or (
+ self.payment_type == "Receive" and self.party_type == "Supplier"
+ ):
- total_negative_outstanding = sum(abs(flt(d.outstanding_amount))
- for d in self.get("references") if flt(d.outstanding_amount) < 0)
+ total_negative_outstanding = sum(
+ abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
+ )
- paid_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
+ paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
additional_charges = sum([flt(d.amount) for d in self.deductions])
if not total_negative_outstanding:
- frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice")
- .format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")),
- self.party_type), InvalidPaymentEntry)
+ frappe.throw(
+ _("Cannot {0} {1} {2} without any negative outstanding invoice").format(
+ _(self.payment_type),
+ (_("to") if self.party_type == "Customer" else _("from")),
+ self.party_type,
+ ),
+ InvalidPaymentEntry,
+ )
elif paid_amount - additional_charges > total_negative_outstanding:
- frappe.throw(_("Paid Amount cannot be greater than total negative outstanding amount {0}")
- .format(total_negative_outstanding), InvalidPaymentEntry)
+ frappe.throw(
+ _("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
+ total_negative_outstanding
+ ),
+ InvalidPaymentEntry,
+ )
def set_title(self):
if frappe.flags.in_import and self.title:
@@ -634,33 +743,45 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
def set_remarks(self):
- if self.custom_remarks: return
+ if self.custom_remarks:
+ return
- if self.payment_type=="Internal Transfer":
- remarks = [_("Amount {0} {1} transferred from {2} to {3}")
- .format(self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to)]
+ if self.payment_type == "Internal Transfer":
+ remarks = [
+ _("Amount {0} {1} transferred from {2} to {3}").format(
+ self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to
+ )
+ ]
else:
- remarks = [_("Amount {0} {1} {2} {3}").format(
- self.party_account_currency,
- self.paid_amount if self.payment_type=="Receive" else self.received_amount,
- _("received from") if self.payment_type=="Receive" else _("to"), self.party
- )]
+ remarks = [
+ _("Amount {0} {1} {2} {3}").format(
+ self.party_account_currency,
+ self.paid_amount if self.payment_type == "Receive" else self.received_amount,
+ _("received from") if self.payment_type == "Receive" else _("to"),
+ self.party,
+ )
+ ]
if self.reference_no:
- remarks.append(_("Transaction reference no {0} dated {1}")
- .format(self.reference_no, self.reference_date))
+ remarks.append(
+ _("Transaction reference no {0} dated {1}").format(self.reference_no, self.reference_date)
+ )
if self.payment_type in ["Receive", "Pay"]:
for d in self.get("references"):
if d.allocated_amount:
- remarks.append(_("Amount {0} {1} against {2} {3}").format(self.party_account_currency,
- d.allocated_amount, d.reference_doctype, d.reference_name))
+ remarks.append(
+ _("Amount {0} {1} against {2} {3}").format(
+ self.party_account_currency, d.allocated_amount, d.reference_doctype, d.reference_name
+ )
+ )
for d in self.get("deductions"):
if d.amount:
- remarks.append(_("Amount {0} {1} deducted against {2}")
- .format(self.company_currency, d.amount, d.account))
+ remarks.append(
+ _("Amount {0} {1} deducted against {2}").format(self.company_currency, d.amount, d.account)
+ )
self.set("remarks", "\n".join(remarks))
@@ -679,92 +800,110 @@ class PaymentEntry(AccountsController):
def add_party_gl_entries(self, gl_entries):
if self.party_account:
- if self.payment_type=="Receive":
+ if self.payment_type == "Receive":
against_account = self.paid_to
else:
against_account = self.paid_from
- party_gl_dict = self.get_gl_dict({
- "account": self.party_account,
- "party_type": self.party_type,
- "party": self.party,
- "against": against_account,
- "account_currency": self.party_account_currency,
- "cost_center": self.cost_center
- }, item=self)
+ party_gl_dict = self.get_gl_dict(
+ {
+ "account": self.party_account,
+ "party_type": self.party_type,
+ "party": self.party,
+ "against": against_account,
+ "account_currency": self.party_account_currency,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
- dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
+ dr_or_cr = (
+ "credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit"
+ )
for d in self.get("references"):
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
gle = party_gl_dict.copy()
- gle.update({
- "against_voucher_type": d.reference_doctype,
- "against_voucher": d.reference_name,
- "cost_center": cost_center
- })
+ gle.update(
+ {
+ "against_voucher_type": d.reference_doctype,
+ "against_voucher": d.reference_name,
+ "cost_center": cost_center,
+ }
+ )
- allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate),
- self.precision("paid_amount"))
+ allocated_amount_in_company_currency = flt(
+ flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("paid_amount")
+ )
- gle.update({
- dr_or_cr + "_in_account_currency": d.allocated_amount,
- dr_or_cr: allocated_amount_in_company_currency
- })
+ gle.update(
+ {
+ dr_or_cr + "_in_account_currency": d.allocated_amount,
+ dr_or_cr: allocated_amount_in_company_currency,
+ }
+ )
gl_entries.append(gle)
if self.unallocated_amount:
exchange_rate = self.get_exchange_rate()
- base_unallocated_amount = (self.unallocated_amount * exchange_rate)
+ base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
- gle.update({
- dr_or_cr + "_in_account_currency": self.unallocated_amount,
- dr_or_cr: base_unallocated_amount
- })
+ gle.update(
+ {
+ dr_or_cr + "_in_account_currency": self.unallocated_amount,
+ dr_or_cr: base_unallocated_amount,
+ }
+ )
gl_entries.append(gle)
def add_bank_gl_entries(self, gl_entries):
if self.payment_type in ("Pay", "Internal Transfer"):
gl_entries.append(
- self.get_gl_dict({
- "account": self.paid_from,
- "account_currency": self.paid_from_account_currency,
- "against": self.party if self.payment_type=="Pay" else self.paid_to,
- "credit_in_account_currency": self.paid_amount,
- "credit": self.base_paid_amount,
- "cost_center": self.cost_center,
- "post_net_value": True
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.paid_from,
+ "account_currency": self.paid_from_account_currency,
+ "against": self.party if self.payment_type == "Pay" else self.paid_to,
+ "credit_in_account_currency": self.paid_amount,
+ "credit": self.base_paid_amount,
+ "cost_center": self.cost_center,
+ "post_net_value": True,
+ },
+ item=self,
+ )
)
if self.payment_type in ("Receive", "Internal Transfer"):
gl_entries.append(
- self.get_gl_dict({
- "account": self.paid_to,
- "account_currency": self.paid_to_account_currency,
- "against": self.party if self.payment_type=="Receive" else self.paid_from,
- "debit_in_account_currency": self.received_amount,
- "debit": self.base_received_amount,
- "cost_center": self.cost_center
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.paid_to,
+ "account_currency": self.paid_to_account_currency,
+ "against": self.party if self.payment_type == "Receive" else self.paid_from,
+ "debit_in_account_currency": self.received_amount,
+ "debit": self.base_received_amount,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
)
def add_tax_gl_entries(self, gl_entries):
- for d in self.get('taxes'):
+ for d in self.get("taxes"):
account_currency = get_account_currency(d.account_head)
if account_currency != self.company_currency:
frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
- if self.payment_type in ('Pay', 'Internal Transfer'):
+ if self.payment_type in ("Pay", "Internal Transfer"):
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_from
- elif self.payment_type == 'Receive':
+ elif self.payment_type == "Receive":
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to
@@ -774,29 +913,39 @@ class PaymentEntry(AccountsController):
base_tax_amount = d.base_tax_amount
gl_entries.append(
- self.get_gl_dict({
- "account": d.account_head,
- "against": against,
- dr_or_cr: tax_amount,
- dr_or_cr + "_in_account_currency": base_tax_amount
- if account_currency==self.company_currency
- else d.tax_amount,
- "cost_center": d.cost_center,
- "post_net_value": True,
- }, account_currency, item=d))
+ self.get_gl_dict(
+ {
+ "account": d.account_head,
+ "against": against,
+ dr_or_cr: tax_amount,
+ dr_or_cr + "_in_account_currency": base_tax_amount
+ if account_currency == self.company_currency
+ else d.tax_amount,
+ "cost_center": d.cost_center,
+ "post_net_value": True,
+ },
+ account_currency,
+ item=d,
+ )
+ )
if not d.included_in_paid_amount:
gl_entries.append(
- self.get_gl_dict({
- "account": payment_account,
- "against": against,
- rev_dr_or_cr: tax_amount,
- rev_dr_or_cr + "_in_account_currency": base_tax_amount
- if account_currency==self.company_currency
- else d.tax_amount,
- "cost_center": self.cost_center,
- "post_net_value": True,
- }, account_currency, item=d))
+ self.get_gl_dict(
+ {
+ "account": payment_account,
+ "against": against,
+ rev_dr_or_cr: tax_amount,
+ rev_dr_or_cr + "_in_account_currency": base_tax_amount
+ if account_currency == self.company_currency
+ else d.tax_amount,
+ "cost_center": self.cost_center,
+ "post_net_value": True,
+ },
+ account_currency,
+ item=d,
+ )
+ )
def add_deductions_gl_entries(self, gl_entries):
for d in self.get("deductions"):
@@ -806,33 +955,40 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
gl_entries.append(
- self.get_gl_dict({
- "account": d.account,
- "account_currency": account_currency,
- "against": self.party or self.paid_from,
- "debit_in_account_currency": d.amount,
- "debit": d.amount,
- "cost_center": d.cost_center
- }, item=d)
+ self.get_gl_dict(
+ {
+ "account": d.account,
+ "account_currency": account_currency,
+ "against": self.party or self.paid_from,
+ "debit_in_account_currency": d.amount,
+ "debit": d.amount,
+ "cost_center": d.cost_center,
+ },
+ item=d,
+ )
)
def get_party_account_for_taxes(self):
- if self.payment_type == 'Receive':
+ if self.payment_type == "Receive":
return self.paid_to
- elif self.payment_type in ('Pay', 'Internal Transfer'):
+ elif self.payment_type in ("Pay", "Internal Transfer"):
return self.paid_from
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"):
- if d.allocated_amount \
- and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance", "Gratuity"):
- frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
+ if d.allocated_amount and d.reference_doctype in (
+ "Sales Order",
+ "Purchase Order",
+ "Employee Advance",
+ "Gratuity",
+ ):
+ frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
def update_expense_claim(self):
if self.payment_type in ("Pay") and self.party:
for d in self.get("references"):
- if d.reference_doctype=="Expense Claim" and d.reference_name:
+ if d.reference_doctype == "Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
if self.docstatus == 2:
update_reimbursed_amount(doc, -1 * d.allocated_amount)
@@ -845,31 +1001,29 @@ class PaymentEntry(AccountsController):
def calculate_deductions(self, tax_details):
return {
- "account": tax_details['tax']['account_head'],
- "cost_center": frappe.get_cached_value('Company', self.company, "cost_center"),
- "amount": self.total_allocated_amount * (tax_details['tax']['rate'] / 100)
+ "account": tax_details["tax"]["account_head"],
+ "cost_center": frappe.get_cached_value("Company", self.company, "cost_center"),
+ "amount": self.total_allocated_amount * (tax_details["tax"]["rate"] / 100),
}
def set_gain_or_loss(self, account_details=None):
if not self.difference_amount:
self.set_difference_amount()
- row = {
- 'amount': self.difference_amount
- }
+ row = {"amount": self.difference_amount}
if account_details:
row.update(account_details)
- if not row.get('amount'):
+ if not row.get("amount"):
# if no difference amount
return
- self.append('deductions', row)
+ self.append("deductions", row)
self.set_unallocated_amount()
def get_exchange_rate(self):
- return self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate
+ return self.source_exchange_rate if self.payment_type == "Receive" else self.target_exchange_rate
def initialize_taxes(self):
for tax in self.get("taxes"):
@@ -893,25 +1047,31 @@ class PaymentEntry(AccountsController):
cumulated_tax_fraction = 0
for i, tax in enumerate(self.get("taxes")):
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax)
- if i==0:
+ if i == 0:
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
else:
- tax.grand_total_fraction_for_current_item = \
- self.get("taxes")[i-1].grand_total_fraction_for_current_item \
+ tax.grand_total_fraction_for_current_item = (
+ self.get("taxes")[i - 1].grand_total_fraction_for_current_item
+ tax.tax_fraction_for_current_item
+ )
cumulated_tax_fraction += tax.tax_fraction_for_current_item
- self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction))
+ self.paid_amount_after_tax = flt(self.paid_amount / (1 + cumulated_tax_fraction))
def calculate_taxes(self):
self.total_taxes_and_charges = 0.0
self.base_total_taxes_and_charges = 0.0
- actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
- for tax in self.get("taxes") if tax.charge_type == "Actual"])
+ actual_tax_dict = dict(
+ [
+ [tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
+ for tax in self.get("taxes")
+ if tax.charge_type == "Actual"
+ ]
+ )
- for i, tax in enumerate(self.get('taxes')):
+ for i, tax in enumerate(self.get("taxes")):
current_tax_amount = self.get_current_tax_amount(tax)
if tax.charge_type == "Actual":
@@ -930,19 +1090,21 @@ class PaymentEntry(AccountsController):
if i == 0:
tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax))
else:
- tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax))
+ tax.total = flt(
+ self.get("taxes")[i - 1].total + current_tax_amount, self.precision("total", tax)
+ )
tax.base_total = tax.total * self.source_exchange_rate
- if self.payment_type == 'Pay':
+ if self.payment_type == "Pay":
self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
else:
self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
- if self.get('taxes'):
- self.paid_amount_after_tax = self.get('taxes')[-1].base_total
+ if self.get("taxes"):
+ self.paid_amount_after_tax = self.get("taxes")[-1].base_total
def get_current_tax_amount(self, tax):
tax_rate = tax.rate
@@ -950,7 +1112,11 @@ class PaymentEntry(AccountsController):
# To set row_id by default as previous row.
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
if tax.idx == 1:
- frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
+ frappe.throw(
+ _(
+ "Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"
+ )
+ )
if not tax.row_id:
tax.row_id = tax.idx - 1
@@ -960,12 +1126,10 @@ class PaymentEntry(AccountsController):
elif tax.charge_type == "On Paid Amount":
current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax
elif tax.charge_type == "On Previous Row Amount":
- current_tax_amount = (tax_rate / 100.0) * \
- self.get('taxes')[cint(tax.row_id) - 1].tax_amount
+ current_tax_amount = (tax_rate / 100.0) * self.get("taxes")[cint(tax.row_id) - 1].tax_amount
elif tax.charge_type == "On Previous Row Total":
- current_tax_amount = (tax_rate / 100.0) * \
- self.get('taxes')[cint(tax.row_id) - 1].total
+ current_tax_amount = (tax_rate / 100.0) * self.get("taxes")[cint(tax.row_id) - 1].total
return current_tax_amount
@@ -978,83 +1142,106 @@ class PaymentEntry(AccountsController):
if tax.charge_type == "On Paid Amount":
current_tax_fraction = tax_rate / 100.0
elif tax.charge_type == "On Previous Row Amount":
- current_tax_fraction = (tax_rate / 100.0) * \
- self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
+ current_tax_fraction = (tax_rate / 100.0) * self.get("taxes")[
+ cint(tax.row_id) - 1
+ ].tax_fraction_for_current_item
elif tax.charge_type == "On Previous Row Total":
- current_tax_fraction = (tax_rate / 100.0) * \
- self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
+ current_tax_fraction = (tax_rate / 100.0) * self.get("taxes")[
+ cint(tax.row_id) - 1
+ ].grand_total_fraction_for_current_item
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
current_tax_fraction *= -1.0
return current_tax_fraction
+
def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range):
- throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
+ throw(
+ _("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(
+ tax.idx, row_range
+ )
+ )
if cint(getattr(tax, "included_in_paid_amount", None)):
if tax.charge_type == "Actual":
# inclusive tax cannot be of type Actual
- throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
- elif tax.charge_type == "On Previous Row Amount" and \
- not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_paid_amount):
+ throw(
+ _("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(
+ tax.idx
+ )
+ )
+ elif tax.charge_type == "On Previous Row Amount" and not cint(
+ doc.get("taxes")[cint(tax.row_id) - 1].included_in_paid_amount
+ ):
# referred row should also be inclusive
_on_previous_row_error(tax.row_id)
- elif tax.charge_type == "On Previous Row Total" and \
- not all([cint(t.included_in_paid_amount for t in doc.get("taxes")[:cint(tax.row_id) - 1])]):
+ elif tax.charge_type == "On Previous Row Total" and not all(
+ [cint(t.included_in_paid_amount for t in doc.get("taxes")[: cint(tax.row_id) - 1])]
+ ):
# all rows about the referred tax should be inclusive
_on_previous_row_error("1 - %d" % (cint(tax.row_id),))
elif tax.get("category") == "Valuation":
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
+
@frappe.whitelist()
def get_outstanding_reference_documents(args):
if isinstance(args, str):
args = json.loads(args)
- if args.get('party_type') == 'Member':
+ if args.get("party_type") == "Member":
return
# confirm that Supplier is not blocked
- if args.get('party_type') == 'Supplier':
- supplier_status = get_supplier_block_status(args['party'])
- if supplier_status['on_hold']:
- if supplier_status['hold_type'] == 'All':
+ if args.get("party_type") == "Supplier":
+ supplier_status = get_supplier_block_status(args["party"])
+ if supplier_status["on_hold"]:
+ if supplier_status["hold_type"] == "All":
return []
- elif supplier_status['hold_type'] == 'Payments':
- if not supplier_status['release_date'] or getdate(nowdate()) <= supplier_status['release_date']:
+ elif supplier_status["hold_type"] == "Payments":
+ if (
+ not supplier_status["release_date"] or getdate(nowdate()) <= supplier_status["release_date"]
+ ):
return []
party_account_currency = get_account_currency(args.get("party_account"))
- company_currency = frappe.get_cached_value('Company', args.get("company"), "default_currency")
+ company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency")
# Get positive outstanding sales /purchase invoices/ Fees
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
- condition = " and voucher_type={0} and voucher_no={1}"\
- .format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"]))
+ condition = " and voucher_type={0} and voucher_no={1}".format(
+ frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])
+ )
# Add cost center condition
if args.get("cost_center"):
condition += " and cost_center='%s'" % args.get("cost_center")
date_fields_dict = {
- 'posting_date': ['from_posting_date', 'to_posting_date'],
- 'due_date': ['from_due_date', 'to_due_date']
+ "posting_date": ["from_posting_date", "to_posting_date"],
+ "due_date": ["from_due_date", "to_due_date"],
}
for fieldname, date_fields in date_fields_dict.items():
if args.get(date_fields[0]) and args.get(date_fields[1]):
- condition += " and {0} between '{1}' and '{2}'".format(fieldname,
- args.get(date_fields[0]), args.get(date_fields[1]))
+ condition += " and {0} between '{1}' and '{2}'".format(
+ fieldname, args.get(date_fields[0]), args.get(date_fields[1])
+ )
if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
- outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
- args.get("party_account"), filters=args, condition=condition)
+ outstanding_invoices = get_outstanding_invoices(
+ args.get("party_type"),
+ args.get("party"),
+ args.get("party_account"),
+ filters=args,
+ condition=condition,
+ )
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
@@ -1065,28 +1252,44 @@ def get_outstanding_reference_documents(args):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
- party_account_currency, company_currency, d.posting_date
+ party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
- if (args.get("party_type") != "Student"):
- orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
- args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args)
+ if args.get("party_type") != "Student":
+ orders_to_be_billed = get_orders_to_be_billed(
+ args.get("posting_date"),
+ args.get("party_type"),
+ args.get("party"),
+ args.get("company"),
+ party_account_currency,
+ company_currency,
+ filters=args,
+ )
# Get negative outstanding sales /purchase invoices
negative_outstanding_invoices = []
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
- negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"),
- args.get("party_account"), party_account_currency, company_currency, condition=condition)
+ negative_outstanding_invoices = get_negative_outstanding_invoices(
+ args.get("party_type"),
+ args.get("party"),
+ args.get("party_account"),
+ party_account_currency,
+ company_currency,
+ condition=condition,
+ )
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data:
- frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
- .format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))))
+ frappe.msgprint(
+ _(
+ "No outstanding invoices found for the {0} {1} which qualify the filters you have specified."
+ ).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))
+ )
return data
@@ -1094,53 +1297,75 @@ def get_outstanding_reference_documents(args):
def split_invoices_based_on_payment_terms(outstanding_invoices):
invoice_ref_based_on_payment_terms = {}
for idx, d in enumerate(outstanding_invoices):
- if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']:
- payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template')
+ if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
+ payment_term_template = frappe.db.get_value(
+ d.voucher_type, d.voucher_no, "payment_terms_template"
+ )
if payment_term_template:
allocate_payment_based_on_payment_terms = frappe.db.get_value(
- 'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms')
+ "Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
+ )
if allocate_payment_based_on_payment_terms:
- payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"])
+ payment_schedule = frappe.get_all(
+ "Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"]
+ )
for payment_term in payment_schedule:
if payment_term.outstanding > 0.1:
invoice_ref_based_on_payment_terms.setdefault(idx, [])
- invoice_ref_based_on_payment_terms[idx].append(frappe._dict({
- 'due_date': d.due_date,
- 'currency': d.currency,
- 'voucher_no': d.voucher_no,
- 'voucher_type': d.voucher_type,
- 'posting_date': d.posting_date,
- 'invoice_amount': flt(d.invoice_amount),
- 'outstanding_amount': flt(d.outstanding_amount),
- 'payment_amount': payment_term.payment_amount,
- 'payment_term': payment_term.payment_term
- }))
+ invoice_ref_based_on_payment_terms[idx].append(
+ frappe._dict(
+ {
+ "due_date": d.due_date,
+ "currency": d.currency,
+ "voucher_no": d.voucher_no,
+ "voucher_type": d.voucher_type,
+ "posting_date": d.posting_date,
+ "invoice_amount": flt(d.invoice_amount),
+ "outstanding_amount": flt(d.outstanding_amount),
+ "payment_amount": payment_term.payment_amount,
+ "payment_term": payment_term.payment_term,
+ }
+ )
+ )
outstanding_invoices_after_split = []
if invoice_ref_based_on_payment_terms:
for idx, ref in invoice_ref_based_on_payment_terms.items():
- voucher_no = ref[0]['voucher_no']
- voucher_type = ref[0]['voucher_type']
+ voucher_no = ref[0]["voucher_no"]
+ voucher_type = ref[0]["voucher_type"]
- frappe.msgprint(_("Spliting {} {} into {} row(s) as per Payment Terms").format(
- voucher_type, voucher_no, len(ref)), alert=True)
+ frappe.msgprint(
+ _("Spliting {} {} into {} row(s) as per Payment Terms").format(
+ voucher_type, voucher_no, len(ref)
+ ),
+ alert=True,
+ )
outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
- existing_row = list(filter(lambda x: x.get('voucher_no') == voucher_no, outstanding_invoices))
+ existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices))
index = outstanding_invoices.index(existing_row[0])
outstanding_invoices.pop(index)
outstanding_invoices_after_split += outstanding_invoices
return outstanding_invoices_after_split
-def get_orders_to_be_billed(posting_date, party_type, party,
- company, party_account_currency, company_currency, cost_center=None, filters=None):
+
+def get_orders_to_be_billed(
+ posting_date,
+ party_type,
+ party,
+ company,
+ party_account_currency,
+ company_currency,
+ cost_center=None,
+ filters=None,
+):
if party_type == "Customer":
- voucher_type = 'Sales Order'
+ voucher_type = "Sales Order"
elif party_type == "Supplier":
- voucher_type = 'Purchase Order'
+ voucher_type = "Purchase Order"
elif party_type == "Employee":
voucher_type = None
@@ -1148,7 +1373,7 @@ def get_orders_to_be_billed(posting_date, party_type, party,
if voucher_type:
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
- if doc and hasattr(doc, 'cost_center'):
+ if doc and hasattr(doc, "cost_center"):
condition = " and cost_center='%s'" % cost_center
orders = []
@@ -1160,7 +1385,8 @@ def get_orders_to_be_billed(posting_date, party_type, party,
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
- orders = frappe.db.sql("""
+ orders = frappe.db.sql(
+ """
select
name as voucher_no,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
@@ -1178,18 +1404,25 @@ def get_orders_to_be_billed(posting_date, party_type, party,
{condition}
order by
transaction_date, name
- """.format(**{
- "rounded_total_field": rounded_total_field,
- "grand_total_field": grand_total_field,
- "voucher_type": voucher_type,
- "party_type": scrub(party_type),
- "condition": condition
- }), (party, company), as_dict=True)
+ """.format(
+ **{
+ "rounded_total_field": rounded_total_field,
+ "grand_total_field": grand_total_field,
+ "voucher_type": voucher_type,
+ "party_type": scrub(party_type),
+ "condition": condition,
+ }
+ ),
+ (party, company),
+ as_dict=True,
+ )
order_list = []
for d in orders:
- if not (flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
- and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))):
+ if not (
+ flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
+ and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))
+ ):
continue
d["voucher_type"] = voucher_type
@@ -1199,8 +1432,16 @@ def get_orders_to_be_billed(posting_date, party_type, party,
return order_list
-def get_negative_outstanding_invoices(party_type, party, party_account,
- party_account_currency, company_currency, cost_center=None, condition=None):
+
+def get_negative_outstanding_invoices(
+ party_type,
+ party,
+ party_account,
+ party_account_currency,
+ company_currency,
+ cost_center=None,
+ condition=None,
+):
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
@@ -1212,7 +1453,8 @@ def get_negative_outstanding_invoices(party_type, party, party_account,
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
"{voucher_type}" as voucher_type, name as voucher_no,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
@@ -1227,21 +1469,26 @@ def get_negative_outstanding_invoices(party_type, party, party_account,
{condition}
order by
posting_date, name
- """.format(**{
- "supplier_condition": supplier_condition,
- "condition": condition,
- "rounded_total_field": rounded_total_field,
- "grand_total_field": grand_total_field,
- "voucher_type": voucher_type,
- "party_type": scrub(party_type),
- "party_account": "debit_to" if party_type == "Customer" else "credit_to",
- "cost_center": cost_center
- }), (party, party_account), as_dict=True)
+ """.format(
+ **{
+ "supplier_condition": supplier_condition,
+ "condition": condition,
+ "rounded_total_field": rounded_total_field,
+ "grand_total_field": grand_total_field,
+ "voucher_type": voucher_type,
+ "party_type": scrub(party_type),
+ "party_account": "debit_to" if party_type == "Customer" else "credit_to",
+ "cost_center": cost_center,
+ }
+ ),
+ (party, party_account),
+ as_dict=True,
+ )
@frappe.whitelist()
def get_party_details(company, party_type, party, date, cost_center=None):
- bank_account = ''
+ bank_account = ""
if not frappe.db.exists(party_type, party):
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
@@ -1249,7 +1496,9 @@ def get_party_details(company, party_type, party, date, cost_center=None):
account_currency = get_account_currency(party_account)
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
- _party_name = "title" if party_type in ("Student", "Shareholder") else party_type.lower() + "_name"
+ _party_name = (
+ "title" if party_type in ("Student", "Shareholder") else party_type.lower() + "_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)
if party_type in ["Customer", "Supplier"]:
@@ -1261,61 +1510,68 @@ def get_party_details(company, party_type, party, date, cost_center=None):
"party_account_currency": account_currency,
"party_balance": party_balance,
"account_balance": account_balance,
- "bank_account": bank_account
+ "bank_account": bank_account,
}
@frappe.whitelist()
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
- account_list = frappe.get_list('Account', {
- 'name': account
- }, reference_doctype='Payment Entry', limit=1)
+ account_list = frappe.get_list(
+ "Account", {"name": account}, reference_doctype="Payment Entry", limit=1
+ )
# There might be some user permissions which will allow account under certain doctypes
# except for Payment Entry, only in such case we should throw permission error
if not account_list:
- frappe.throw(_('Account: {0} is not permitted under Payment Entry').format(account))
+ frappe.throw(_("Account: {0} is not permitted under Payment Entry").format(account))
- account_balance = get_balance_on(account, date, cost_center=cost_center,
- ignore_account_permission=True)
+ account_balance = get_balance_on(
+ account, date, cost_center=cost_center, ignore_account_permission=True
+ )
- return frappe._dict({
- "account_currency": get_account_currency(account),
- "account_balance": account_balance,
- "account_type": frappe.db.get_value("Account", account, "account_type")
- })
+ return frappe._dict(
+ {
+ "account_currency": get_account_currency(account),
+ "account_balance": account_balance,
+ "account_type": frappe.db.get_value("Account", account, "account_type"),
+ }
+ )
@frappe.whitelist()
def get_company_defaults(company):
fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"]
- ret = frappe.get_cached_value('Company', company, fields, as_dict=1)
+ ret = frappe.get_cached_value("Company", company, fields, as_dict=1)
for fieldname in fields:
if not ret[fieldname]:
- frappe.throw(_("Please set default {0} in Company {1}")
- .format(frappe.get_meta("Company").get_label(fieldname), company))
+ frappe.throw(
+ _("Please set default {0} in Company {1}").format(
+ frappe.get_meta("Company").get_label(fieldname), company
+ )
+ )
return ret
def get_outstanding_on_journal_entry(name):
res = frappe.db.sql(
- 'SELECT '
- 'CASE WHEN party_type IN ("Customer", "Student") '
- 'THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) '
- 'ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) '
- 'END as outstanding_amount '
- 'FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) '
- 'AND party_type IS NOT NULL '
- 'AND party_type != ""',
- (name, name), as_dict=1
- )
+ "SELECT "
+ 'CASE WHEN party_type IN ("Customer", "Student") '
+ "THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) "
+ "ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) "
+ "END as outstanding_amount "
+ "FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) "
+ "AND party_type IS NOT NULL "
+ 'AND party_type != ""',
+ (name, name),
+ as_dict=1,
+ )
- outstanding_amount = res[0].get('outstanding_amount', 0) if res else 0
+ outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
return outstanding_amount
@@ -1324,7 +1580,9 @@ def get_outstanding_on_journal_entry(name):
def get_reference_details(reference_doctype, reference_name, party_account_currency):
total_amount = outstanding_amount = exchange_rate = bill_no = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
- company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
+ company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
+ ref_doc.company
+ )
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
@@ -1337,20 +1595,22 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
- exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+ exchange_rate = get_exchange_rate(
+ party_account_currency, company_currency, ref_doc.posting_date
+ )
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
elif reference_doctype != "Journal Entry":
if ref_doc.doctype == "Expense Claim":
- total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
+ total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
elif ref_doc.doctype == "Gratuity":
- total_amount = ref_doc.amount
+ total_amount = ref_doc.amount
if not total_amount:
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
@@ -1360,16 +1620,21 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc.
- exchange_rate = ref_doc.get("conversion_rate") or \
- get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+ exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
+ party_account_currency, company_currency, ref_doc.posting_date
+ )
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
- outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
- - flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
+ outstanding_amount = (
+ flt(ref_doc.get("total_sanctioned_amount"))
+ + flt(ref_doc.get("total_taxes_and_charges"))
+ - flt(ref_doc.get("total_amount_reimbursed"))
+ - flt(ref_doc.get("total_advance_amount"))
+ )
elif reference_doctype == "Employee Advance":
- outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
+ outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
@@ -1380,18 +1645,22 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
else:
# Get the exchange rate based on the posting date of the ref doc.
- exchange_rate = get_exchange_rate(party_account_currency,
- company_currency, ref_doc.posting_date)
+ exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
- return frappe._dict({
- "due_date": ref_doc.get("due_date"),
- "total_amount": flt(total_amount),
- "outstanding_amount": flt(outstanding_amount),
- "exchange_rate": flt(exchange_rate),
- "bill_no": bill_no
- })
+ return frappe._dict(
+ {
+ "due_date": ref_doc.get("due_date"),
+ "total_amount": flt(total_amount),
+ "outstanding_amount": flt(outstanding_amount),
+ "exchange_rate": flt(exchange_rate),
+ "bill_no": bill_no,
+ }
+ )
-def get_amounts_based_on_reference_doctype(reference_doctype, ref_doc, party_account_currency, company_currency, reference_name):
+
+def get_amounts_based_on_reference_doctype(
+ reference_doctype, ref_doc, party_account_currency, company_currency, reference_name
+):
total_amount = outstanding_amount = exchange_rate = None
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
@@ -1404,35 +1673,46 @@ def get_amounts_based_on_reference_doctype(reference_doctype, ref_doc, party_acc
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
- exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+ exchange_rate = get_exchange_rate(
+ party_account_currency, company_currency, ref_doc.posting_date
+ )
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
return total_amount, outstanding_amount, exchange_rate
-def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_currency, company_currency):
+
+def get_amounts_based_on_ref_doc(
+ reference_doctype, ref_doc, party_account_currency, company_currency
+):
total_amount = outstanding_amount = exchange_rate = None
if ref_doc.doctype == "Expense Claim":
- total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
+ total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
- total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc)
+ total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(
+ party_account_currency, ref_doc
+ )
if not total_amount:
total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
- party_account_currency, company_currency, ref_doc)
+ party_account_currency, company_currency, ref_doc
+ )
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc
- exchange_rate = ref_doc.get("conversion_rate") or \
- get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+ exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
+ party_account_currency, company_currency, ref_doc.posting_date
+ )
outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
- reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency)
+ reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
+ )
return total_amount, outstanding_amount, exchange_rate, bill_no
+
def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
@@ -1441,7 +1721,10 @@ def get_total_amount_exchange_rate_for_employee_advance(party_account_currency,
return total_amount, exchange_rate
-def get_total_amount_exchange_rate_base_on_currency(party_account_currency, company_currency, ref_doc):
+
+def get_total_amount_exchange_rate_base_on_currency(
+ party_account_currency, company_currency, ref_doc
+):
exchange_rate = None
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
@@ -1451,16 +1734,23 @@ def get_total_amount_exchange_rate_base_on_currency(party_account_currency, comp
return total_amount, exchange_rate
-def get_bill_no_and_update_amounts(reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency):
+
+def get_bill_no_and_update_amounts(
+ reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
+):
outstanding_amount = bill_no = None
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
- outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
- - flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
+ outstanding_amount = (
+ flt(ref_doc.get("total_sanctioned_amount"))
+ + flt(ref_doc.get("total_taxes_and_charges"))
+ - flt(ref_doc.get("total_amount_reimbursed"))
+ - flt(ref_doc.get("total_advance_amount"))
+ )
elif reference_doctype == "Employee Advance":
- outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
+ outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
@@ -1482,15 +1772,20 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
party_account = set_party_account(dt, dn, doc, party_type)
party_account_currency = set_party_account_currency(dt, party_account, doc)
payment_type = set_payment_type(dt, doc)
- grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc)
+ grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(
+ party_amount, dt, party_account_currency, doc
+ )
# bank or cash
bank = get_bank_cash_account(doc, bank_account)
paid_amount, received_amount = set_paid_amount_and_received_amount(
- dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
+ dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
+ )
- paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc)
+ paid_amount, received_amount, discount_amount = apply_early_payment_discount(
+ paid_amount, received_amount, doc
+ )
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
@@ -1504,18 +1799,22 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.contact_email = doc.get("contact_email")
pe.ensure_supplier_is_not_blocked()
- pe.paid_from = party_account if payment_type=="Receive" else bank.account
- pe.paid_to = party_account if payment_type=="Pay" else bank.account
- pe.paid_from_account_currency = party_account_currency \
- if payment_type=="Receive" else bank.account_currency
- pe.paid_to_account_currency = party_account_currency if payment_type=="Pay" else bank.account_currency
+ pe.paid_from = party_account if payment_type == "Receive" else bank.account
+ pe.paid_to = party_account if payment_type == "Pay" else bank.account
+ pe.paid_from_account_currency = (
+ party_account_currency if payment_type == "Receive" else bank.account_currency
+ )
+ pe.paid_to_account_currency = (
+ party_account_currency if payment_type == "Pay" else bank.account_currency
+ )
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
- if dt in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice']:
- pe.project = (doc.get('project') or
- reduce(lambda prev,cur: prev or cur, [x.get('project') for x in doc.get('items')], None)) # get first non-empty project from items
+ if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
+ pe.project = doc.get("project") or reduce(
+ lambda prev, cur: prev or cur, [x.get("project") for x in doc.get("items")], None
+ ) # get first non-empty project from items
if pe.party_type in ["Customer", "Supplier"]:
bank_account = get_party_bank_account(pe.party_type, pe.party)
@@ -1524,44 +1823,57 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
# only Purchase Invoice can be blocked individually
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
- frappe.msgprint(_('{0} is on hold till {1}').format(doc.name, doc.release_date))
+ frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date))
else:
- if (doc.doctype in ('Sales Invoice', 'Purchase Invoice')
- and frappe.get_value('Payment Terms Template',
- {'name': doc.payment_terms_template}, 'allocate_payment_based_on_payment_terms')):
+ if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_value(
+ "Payment Terms Template",
+ {"name": doc.payment_terms_template},
+ "allocate_payment_based_on_payment_terms",
+ ):
- for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
- pe.append('references', reference)
+ for reference in get_reference_as_per_payment_terms(
+ doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+ ):
+ pe.append("references", reference)
else:
if dt == "Dunning":
- pe.append("references", {
- 'reference_doctype': 'Sales Invoice',
- 'reference_name': doc.get('sales_invoice'),
- "bill_no": doc.get("bill_no"),
- "due_date": doc.get("due_date"),
- 'total_amount': doc.get('outstanding_amount'),
- 'outstanding_amount': doc.get('outstanding_amount'),
- 'allocated_amount': doc.get('outstanding_amount')
- })
- pe.append("references", {
- 'reference_doctype': dt,
- 'reference_name': dn,
- "bill_no": doc.get("bill_no"),
- "due_date": doc.get("due_date"),
- 'total_amount': doc.get('dunning_amount'),
- 'outstanding_amount': doc.get('dunning_amount'),
- 'allocated_amount': doc.get('dunning_amount')
- })
+ pe.append(
+ "references",
+ {
+ "reference_doctype": "Sales Invoice",
+ "reference_name": doc.get("sales_invoice"),
+ "bill_no": doc.get("bill_no"),
+ "due_date": doc.get("due_date"),
+ "total_amount": doc.get("outstanding_amount"),
+ "outstanding_amount": doc.get("outstanding_amount"),
+ "allocated_amount": doc.get("outstanding_amount"),
+ },
+ )
+ pe.append(
+ "references",
+ {
+ "reference_doctype": dt,
+ "reference_name": dn,
+ "bill_no": doc.get("bill_no"),
+ "due_date": doc.get("due_date"),
+ "total_amount": doc.get("dunning_amount"),
+ "outstanding_amount": doc.get("dunning_amount"),
+ "allocated_amount": doc.get("dunning_amount"),
+ },
+ )
else:
- pe.append("references", {
- 'reference_doctype': dt,
- 'reference_name': dn,
- "bill_no": doc.get("bill_no"),
- "due_date": doc.get("due_date"),
- 'total_amount': grand_total,
- 'outstanding_amount': outstanding_amount,
- 'allocated_amount': outstanding_amount
- })
+ pe.append(
+ "references",
+ {
+ "reference_doctype": dt,
+ "reference_name": dn,
+ "bill_no": doc.get("bill_no"),
+ "due_date": doc.get("due_date"),
+ "total_amount": grand_total,
+ "outstanding_amount": outstanding_amount,
+ "allocated_amount": outstanding_amount,
+ },
+ )
pe.setup_party_account_field()
pe.set_missing_values()
@@ -1572,25 +1884,32 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
if discount_amount:
- pe.set_gain_or_loss(account_details={
- 'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"),
- 'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"),
- 'amount': discount_amount * (-1 if payment_type == "Pay" else 1)
- })
+ pe.set_gain_or_loss(
+ account_details={
+ "account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
+ "cost_center": pe.cost_center
+ or frappe.get_cached_value("Company", pe.company, "cost_center"),
+ "amount": discount_amount * (-1 if payment_type == "Pay" else 1),
+ }
+ )
pe.set_difference_amount()
return pe
+
def get_bank_cash_account(doc, bank_account):
- bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
- account=bank_account)
+ bank = get_default_bank_cash_account(
+ doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
+ )
if not bank:
- bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
- account=bank_account)
+ bank = get_default_bank_cash_account(
+ doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
+ )
return bank
+
def set_party_type(dt):
if dt in ("Sales Invoice", "Sales Order", "Dunning"):
party_type = "Customer"
@@ -1602,6 +1921,7 @@ def set_party_type(dt):
party_type = "Student"
return party_type
+
def set_party_account(dt, dn, doc, party_type):
if dt == "Sales Invoice":
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
@@ -1619,6 +1939,7 @@ def set_party_account(dt, dn, doc, party_type):
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
+
def set_party_account_currency(dt, party_account, doc):
if dt not in ("Sales Invoice", "Purchase Invoice"):
party_account_currency = get_account_currency(party_account)
@@ -1626,14 +1947,18 @@ def set_party_account_currency(dt, party_account, doc):
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
return party_account_currency
+
def set_payment_type(dt, doc):
- if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
- or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
- payment_type = "Receive"
+ if (
+ dt == "Sales Order"
+ or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)
+ ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
+ payment_type = "Receive"
else:
payment_type = "Pay"
return payment_type
+
def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc):
grand_total = outstanding_amount = 0
if party_amount:
@@ -1646,8 +1971,7 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
- outstanding_amount = doc.grand_total \
- - doc.total_amount_reimbursed
+ outstanding_amount = doc.grand_total - doc.total_amount_reimbursed
elif dt == "Employee Advance":
grand_total = flt(doc.advance_amount)
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
@@ -1671,7 +1995,10 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
outstanding_amount = grand_total - flt(doc.advance_paid)
return grand_total, outstanding_amount
-def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc):
+
+def set_paid_amount_and_received_amount(
+ dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
+):
paid_amount = received_amount = 0
if party_account_currency == bank.account_currency:
paid_amount = received_amount = abs(outstanding_amount)
@@ -1680,37 +2007,38 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
if bank_amount:
received_amount = bank_amount
else:
- received_amount = paid_amount * doc.get('conversion_rate', 1)
+ received_amount = paid_amount * doc.get("conversion_rate", 1)
if dt == "Employee Advance":
- received_amount = paid_amount * doc.get('exchange_rate', 1)
+ received_amount = paid_amount * doc.get("exchange_rate", 1)
else:
received_amount = abs(outstanding_amount)
if bank_amount:
paid_amount = bank_amount
else:
# if party account currency and bank currency is different then populate paid amount as well
- paid_amount = received_amount * doc.get('conversion_rate', 1)
+ paid_amount = received_amount * doc.get("conversion_rate", 1)
if dt == "Employee Advance":
- paid_amount = received_amount * doc.get('exchange_rate', 1)
+ paid_amount = received_amount * doc.get("exchange_rate", 1)
return paid_amount, received_amount
+
def apply_early_payment_discount(paid_amount, received_amount, doc):
total_discount = 0
- eligible_for_payments = ['Sales Order', 'Sales Invoice', 'Purchase Order', 'Purchase Invoice']
- has_payment_schedule = hasattr(doc, 'payment_schedule') and doc.payment_schedule
+ eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
+ has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule
if doc.doctype in eligible_for_payments and has_payment_schedule:
for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
- if term.discount_type == 'Percentage':
- discount_amount = flt(doc.get('grand_total')) * (term.discount / 100)
+ if term.discount_type == "Percentage":
+ discount_amount = flt(doc.get("grand_total")) * (term.discount / 100)
else:
discount_amount = term.discount
- discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
+ discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1)
- if doc.doctype == 'Sales Invoice':
+ if doc.doctype == "Sales Invoice":
paid_amount -= discount_amount
received_amount -= discount_amount_in_foreign_currency
else:
@@ -1720,38 +2048,46 @@ def apply_early_payment_discount(paid_amount, received_amount, doc):
total_discount += discount_amount
if total_discount:
- money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency'))
+ money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
return paid_amount, received_amount, total_discount
-def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
+
+def get_reference_as_per_payment_terms(
+ payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+):
references = []
for payment_term in payment_schedule:
- payment_term_outstanding = flt(payment_term.payment_amount - payment_term.paid_amount,
- payment_term.precision('payment_amount'))
+ payment_term_outstanding = flt(
+ payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount")
+ )
if payment_term_outstanding:
- references.append({
- 'reference_doctype': dt,
- 'reference_name': dn,
- 'bill_no': doc.get('bill_no'),
- 'due_date': doc.get('due_date'),
- 'total_amount': grand_total,
- 'outstanding_amount': outstanding_amount,
- 'payment_term': payment_term.payment_term,
- 'allocated_amount': payment_term_outstanding
- })
+ references.append(
+ {
+ "reference_doctype": dt,
+ "reference_name": dn,
+ "bill_no": doc.get("bill_no"),
+ "due_date": doc.get("due_date"),
+ "total_amount": grand_total,
+ "outstanding_amount": outstanding_amount,
+ "payment_term": payment_term.payment_term,
+ "allocated_amount": payment_term_outstanding,
+ }
+ )
return references
+
def get_paid_amount(dt, dn, party_type, party, account, due_date):
- if party_type=="Customer":
+ if party_type == "Customer":
dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
else:
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
- paid_amount = frappe.db.sql("""
+ paid_amount = frappe.db.sql(
+ """
select ifnull(sum({dr_or_cr}), 0) as paid_amount
from `tabGL Entry`
where against_voucher_type = %s
@@ -1761,41 +2097,58 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
and account = %s
and due_date = %s
and {dr_or_cr} > 0
- """.format(dr_or_cr=dr_or_cr), (dt, dn, party_type, party, account, due_date))
+ """.format(
+ dr_or_cr=dr_or_cr
+ ),
+ (dt, dn, party_type, party, account, due_date),
+ )
return paid_amount[0][0] if paid_amount else 0
+
@frappe.whitelist()
-def get_party_and_account_balance(company, date, paid_from=None, paid_to=None, ptype=None, pty=None, cost_center=None):
- return frappe._dict({
- "party_balance": get_balance_on(party_type=ptype, party=pty, cost_center=cost_center),
- "paid_from_account_balance": get_balance_on(paid_from, date, cost_center=cost_center),
- "paid_to_account_balance": get_balance_on(paid_to, date=date, cost_center=cost_center)
- })
+def get_party_and_account_balance(
+ company, date, paid_from=None, paid_to=None, ptype=None, pty=None, cost_center=None
+):
+ return frappe._dict(
+ {
+ "party_balance": get_balance_on(party_type=ptype, party=pty, cost_center=cost_center),
+ "paid_from_account_balance": get_balance_on(paid_from, date, cost_center=cost_center),
+ "paid_to_account_balance": get_balance_on(paid_to, date=date, cost_center=cost_center),
+ }
+ )
+
@frappe.whitelist()
def make_payment_order(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
+
def set_missing_values(source, target):
target.payment_order_type = "Payment Entry"
- target.append('references', dict(
- reference_doctype="Payment Entry",
- reference_name=source.name,
- bank_account=source.party_bank_account,
- amount=source.paid_amount,
- account=source.paid_to,
- supplier=source.party,
- mode_of_payment=source.mode_of_payment,
- ))
+ target.append(
+ "references",
+ dict(
+ reference_doctype="Payment Entry",
+ reference_name=source.name,
+ bank_account=source.party_bank_account,
+ amount=source.paid_amount,
+ account=source.paid_to,
+ supplier=source.party,
+ mode_of_payment=source.mode_of_payment,
+ ),
+ )
- doclist = get_mapped_doc("Payment Entry", source_name, {
- "Payment Entry": {
- "doctype": "Payment Order",
- "validation": {
- "docstatus": ["=", 1]
- },
- }
-
- }, target_doc, set_missing_values)
+ doclist = get_mapped_doc(
+ "Payment Entry",
+ source_name,
+ {
+ "Payment Entry": {
+ "doctype": "Payment Order",
+ "validation": {"docstatus": ["=", 1]},
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 349b8bb5b1..5b70b510d2 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -32,10 +32,9 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- ["Debtors - _TC", 0, 1000, so.name],
- ["_Test Cash - _TC", 1000.0, 0, None]
- ])
+ expected_gle = dict(
+ (d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
+ )
self.validate_gl_entries(pe.name, expected_gle)
@@ -48,9 +47,9 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(so_advance_paid, 0)
def test_payment_entry_for_blocked_supplier_invoice(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Invoices'
+ supplier.hold_type = "Invoices"
supplier.save()
self.assertRaises(frappe.ValidationError, make_purchase_invoice)
@@ -59,32 +58,40 @@ class TestPaymentEntry(unittest.TestCase):
supplier.save()
def test_payment_entry_for_blocked_supplier_payments(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
+ supplier.hold_type = "Payments"
supplier.save()
pi = make_purchase_invoice()
self.assertRaises(
- frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
- bank_account="_Test Bank - _TC")
+ frappe.ValidationError,
+ get_payment_entry,
+ dt="Purchase Invoice",
+ dn=pi.name,
+ bank_account="_Test Bank - _TC",
+ )
supplier.on_hold = 0
supplier.save()
def test_payment_entry_for_blocked_supplier_payments_today_date(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
+ supplier.hold_type = "Payments"
supplier.release_date = nowdate()
supplier.save()
pi = make_purchase_invoice()
self.assertRaises(
- frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
- bank_account="_Test Bank - _TC")
+ frappe.ValidationError,
+ get_payment_entry,
+ dt="Purchase Invoice",
+ dn=pi.name,
+ bank_account="_Test Bank - _TC",
+ )
supplier.on_hold = 0
supplier.save()
@@ -93,15 +100,15 @@ class TestPaymentEntry(unittest.TestCase):
# this test is meant to fail only if something fails in the try block
with self.assertRaises(Exception):
try:
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
- supplier.release_date = '2018-03-01'
+ supplier.hold_type = "Payments"
+ supplier.release_date = "2018-03-01"
supplier.save()
pi = make_purchase_invoice()
- get_payment_entry('Purchase Invoice', pi.name, bank_account="_Test Bank - _TC")
+ get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
supplier.on_hold = 0
supplier.save()
@@ -111,8 +118,12 @@ class TestPaymentEntry(unittest.TestCase):
raise Exception
def test_payment_entry_against_si_usd_to_usd(self):
- si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="USD", conversion_rate=50)
+ si = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
@@ -120,10 +131,13 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- ["_Test Receivable USD - _TC", 0, 5000, si.name],
- ["_Test Bank USD - _TC", 5000.0, 0, None]
- ])
+ expected_gle = dict(
+ (d[0], d)
+ for d in [
+ ["_Test Receivable USD - _TC", 0, 5000, si.name],
+ ["_Test Bank USD - _TC", 5000.0, 0, None],
+ ]
+ )
self.validate_gl_entries(pe.name, expected_gle)
@@ -136,8 +150,12 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 100)
def test_payment_entry_against_pi(self):
- pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
- currency="USD", conversion_rate=50)
+ pi = make_purchase_invoice(
+ supplier="_Test Supplier USD",
+ debit_to="_Test Payable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
@@ -145,20 +163,26 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- ["_Test Payable USD - _TC", 12500, 0, pi.name],
- ["_Test Bank USD - _TC", 0, 12500, None]
- ])
+ expected_gle = dict(
+ (d[0], d)
+ for d in [
+ ["_Test Payable USD - _TC", 12500, 0, pi.name],
+ ["_Test Bank USD - _TC", 0, 12500, None],
+ ]
+ )
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0)
-
def test_payment_against_sales_invoice_to_check_status(self):
- si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="USD", conversion_rate=50)
+ si = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
@@ -167,28 +191,35 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
- outstanding_amount, status = frappe.db.get_value("Sales Invoice", si.name, ["outstanding_amount", "status"])
+ outstanding_amount, status = frappe.db.get_value(
+ "Sales Invoice", si.name, ["outstanding_amount", "status"]
+ )
self.assertEqual(flt(outstanding_amount), 0)
- self.assertEqual(status, 'Paid')
+ self.assertEqual(status, "Paid")
pe.cancel()
- outstanding_amount, status = frappe.db.get_value("Sales Invoice", si.name, ["outstanding_amount", "status"])
+ outstanding_amount, status = frappe.db.get_value(
+ "Sales Invoice", si.name, ["outstanding_amount", "status"]
+ )
self.assertEqual(flt(outstanding_amount), 100)
- self.assertEqual(status, 'Unpaid')
+ self.assertEqual(status, "Unpaid")
def test_payment_entry_against_payment_terms(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template()
- si.payment_terms_template = 'Test Receivable Template'
+ si.payment_terms_template = "Test Receivable Template"
- si.append('taxes', {
- "charge_type": "On Net Total",
- "account_head": "_Test Account Service Tax - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Service Tax",
- "rate": 18
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 18,
+ },
+ )
si.save()
si.submit()
@@ -197,25 +228,28 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
si.load_from_db()
- self.assertEqual(pe.references[0].payment_term, 'Basic Amount Receivable')
- self.assertEqual(pe.references[1].payment_term, 'Tax Receivable')
+ self.assertEqual(pe.references[0].payment_term, "Basic Amount Receivable")
+ self.assertEqual(pe.references[1].payment_term, "Tax Receivable")
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
def test_payment_entry_against_payment_terms_with_discount(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template_with_discount()
- si.payment_terms_template = 'Test Discount Template'
+ si.payment_terms_template = "Test Discount Template"
- frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC')
+ frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
- si.append('taxes', {
- "charge_type": "On Net Total",
- "account_head": "_Test Account Service Tax - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Service Tax",
- "rate": 18
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 18,
+ },
+ )
si.save()
si.submit()
@@ -224,16 +258,19 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
si.load_from_db()
- self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount')
+ self.assertEqual(pe.references[0].payment_term, "30 Credit Days with 10% Discount")
self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
self.assertEqual(si.payment_schedule[0].paid_amount, 212.40)
self.assertEqual(si.payment_schedule[0].outstanding, 0)
self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
-
def test_payment_against_purchase_invoice_to_check_status(self):
- pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
- currency="USD", conversion_rate=50)
+ pi = make_purchase_invoice(
+ supplier="_Test Supplier USD",
+ debit_to="_Test Payable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
@@ -242,21 +279,27 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
- outstanding_amount, status = frappe.db.get_value("Purchase Invoice", pi.name, ["outstanding_amount", "status"])
+ outstanding_amount, status = frappe.db.get_value(
+ "Purchase Invoice", pi.name, ["outstanding_amount", "status"]
+ )
self.assertEqual(flt(outstanding_amount), 0)
- self.assertEqual(status, 'Paid')
+ self.assertEqual(status, "Paid")
pe.cancel()
- outstanding_amount, status = frappe.db.get_value("Purchase Invoice", pi.name, ["outstanding_amount", "status"])
+ outstanding_amount, status = frappe.db.get_value(
+ "Purchase Invoice", pi.name, ["outstanding_amount", "status"]
+ )
self.assertEqual(flt(outstanding_amount), 250)
- self.assertEqual(status, 'Unpaid')
+ self.assertEqual(status, "Unpaid")
def test_payment_entry_against_ec(self):
- payable = frappe.get_cached_value('Company', "_Test Company", 'default_payable_account')
+ payable = frappe.get_cached_value("Company", "_Test Company", "default_payable_account")
ec = make_expense_claim(payable, 300, 300, "_Test Company", "Travel Expenses - _TC")
- pe = get_payment_entry("Expense Claim", ec.name, bank_account="_Test Bank USD - _TC", bank_amount=300)
+ pe = get_payment_entry(
+ "Expense Claim", ec.name, bank_account="_Test Bank USD - _TC", bank_amount=300
+ )
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.source_exchange_rate = 1
@@ -264,68 +307,87 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- [payable, 300, 0, ec.name],
- ["_Test Bank USD - _TC", 0, 300, None]
- ])
+ expected_gle = dict(
+ (d[0], d) for d in [[payable, 300, 0, ec.name], ["_Test Bank USD - _TC", 0, 300, None]]
+ )
self.validate_gl_entries(pe.name, expected_gle)
- outstanding_amount = flt(frappe.db.get_value("Expense Claim", ec.name, "total_sanctioned_amount")) - \
- flt(frappe.db.get_value("Expense Claim", ec.name, "total_amount_reimbursed"))
+ outstanding_amount = flt(
+ frappe.db.get_value("Expense Claim", ec.name, "total_sanctioned_amount")
+ ) - flt(frappe.db.get_value("Expense Claim", ec.name, "total_amount_reimbursed"))
self.assertEqual(outstanding_amount, 0)
def test_payment_entry_against_si_usd_to_inr(self):
- si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="USD", conversion_rate=50)
- pe = get_payment_entry("Sales Invoice", si.name, party_amount=20,
- bank_account="_Test Bank - _TC", bank_amount=900)
+ si = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
+ pe = get_payment_entry(
+ "Sales Invoice", si.name, party_amount=20, bank_account="_Test Bank - _TC", bank_amount=900
+ )
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
self.assertEqual(pe.difference_amount, 100)
- pe.append("deductions", {
- "account": "_Test Exchange Gain/Loss - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "amount": 100
- })
+ pe.append(
+ "deductions",
+ {
+ "account": "_Test Exchange Gain/Loss - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "amount": 100,
+ },
+ )
pe.insert()
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- ["_Test Receivable USD - _TC", 0, 1000, si.name],
- ["_Test Bank - _TC", 900, 0, None],
- ["_Test Exchange Gain/Loss - _TC", 100.0, 0, None],
- ])
+ expected_gle = dict(
+ (d[0], d)
+ for d in [
+ ["_Test Receivable USD - _TC", 0, 1000, si.name],
+ ["_Test Bank - _TC", 900, 0, None],
+ ["_Test Exchange Gain/Loss - _TC", 100.0, 0, None],
+ ]
+ )
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 80)
- def test_payment_entry_against_si_usd_to_usd_with_deduction_in_base_currency (self):
- si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="USD", conversion_rate=50, do_not_save=1)
+ def test_payment_entry_against_si_usd_to_usd_with_deduction_in_base_currency(self):
+ si = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ do_not_save=1,
+ )
si.plc_conversion_rate = 50
si.save()
si.submit()
- pe = get_payment_entry("Sales Invoice", si.name, party_amount=20,
- bank_account="_Test Bank USD - _TC", bank_amount=900)
+ pe = get_payment_entry(
+ "Sales Invoice", si.name, party_amount=20, bank_account="_Test Bank USD - _TC", bank_amount=900
+ )
pe.source_exchange_rate = 45.263
pe.target_exchange_rate = 45.263
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
-
- pe.append("deductions", {
- "account": "_Test Exchange Gain/Loss - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "amount": 94.80
- })
+ pe.append(
+ "deductions",
+ {
+ "account": "_Test Exchange Gain/Loss - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "amount": 94.80,
+ },
+ )
pe.save()
@@ -359,8 +421,7 @@ class TestPaymentEntry(unittest.TestCase):
pe.set_amounts()
self.assertEqual(
- pe.source_exchange_rate, 65.1,
- "{0} is not equal to {1}".format(pe.source_exchange_rate, 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):
@@ -382,20 +443,26 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(pe.difference_amount, 500)
- pe.append("deductions", {
- "account": "_Test Exchange Gain/Loss - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "amount": 500
- })
+ pe.append(
+ "deductions",
+ {
+ "account": "_Test Exchange Gain/Loss - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "amount": 500,
+ },
+ )
pe.insert()
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- ["_Test Bank USD - _TC", 0, 5000, None],
- ["_Test Bank - _TC", 4500, 0, None],
- ["_Test Exchange Gain/Loss - _TC", 500.0, 0, None],
- ])
+ expected_gle = dict(
+ (d[0], d)
+ for d in [
+ ["_Test Bank USD - _TC", 0, 5000, None],
+ ["_Test Bank - _TC", 4500, 0, None],
+ ["_Test Exchange Gain/Loss - _TC", 500.0, 0, None],
+ ]
+ )
self.validate_gl_entries(pe.name, expected_gle)
@@ -435,10 +502,9 @@ class TestPaymentEntry(unittest.TestCase):
pe3.insert()
pe3.submit()
- expected_gle = dict((d[0], d) for d in [
- ["Debtors - _TC", 100, 0, si1.name],
- ["_Test Cash - _TC", 0, 100, None]
- ])
+ expected_gle = dict(
+ (d[0], d) for d in [["Debtors - _TC", 100, 0, si1.name], ["_Test Cash - _TC", 0, 100, None]]
+ )
self.validate_gl_entries(pe3.name, expected_gle)
@@ -462,12 +528,16 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(expected_gle[gle.account][3], gle.against_voucher)
def get_gle(self, voucher_no):
- return frappe.db.sql("""select account, debit, credit, against_voucher
+ return frappe.db.sql(
+ """select account, debit, credit, against_voucher
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""", voucher_no, as_dict=1)
+ order by account asc""",
+ voucher_no,
+ as_dict=1,
+ )
def test_payment_entry_write_off_difference(self):
- si = create_sales_invoice()
+ si = create_sales_invoice()
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
@@ -477,11 +547,10 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(pe.unallocated_amount, 10)
pe.received_amount = pe.paid_amount = 95
- pe.append("deductions", {
- "account": "_Test Write Off - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "amount": 5
- })
+ pe.append(
+ "deductions",
+ {"account": "_Test Write Off - _TC", "cost_center": "_Test Cost Center - _TC", "amount": 5},
+ )
pe.save()
self.assertEqual(pe.unallocated_amount, 0)
@@ -489,27 +558,37 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- ["Debtors - _TC", 0, 100, si.name],
- ["_Test Cash - _TC", 95, 0, None],
- ["_Test Write Off - _TC", 5, 0, None]
- ])
+ expected_gle = dict(
+ (d[0], d)
+ for d in [
+ ["Debtors - _TC", 0, 100, si.name],
+ ["_Test Cash - _TC", 95, 0, None],
+ ["_Test Write Off - _TC", 5, 0, None],
+ ]
+ )
self.validate_gl_entries(pe.name, expected_gle)
def test_payment_entry_exchange_gain_loss(self):
- si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="USD", conversion_rate=50)
+ si = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.source_exchange_rate = 55
- pe.append("deductions", {
- "account": "_Test Exchange Gain/Loss - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "amount": -500
- })
+ pe.append(
+ "deductions",
+ {
+ "account": "_Test Exchange Gain/Loss - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "amount": -500,
+ },
+ )
pe.save()
self.assertEqual(pe.unallocated_amount, 0)
@@ -517,11 +596,14 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
- expected_gle = dict((d[0], d) for d in [
- ["_Test Receivable USD - _TC", 0, 5000, si.name],
- ["_Test Bank USD - _TC", 5500, 0, None],
- ["_Test Exchange Gain/Loss - _TC", 0, 500, None],
- ])
+ expected_gle = dict(
+ (d[0], d)
+ for d in [
+ ["_Test Receivable USD - _TC", 0, 5000, si.name],
+ ["_Test Bank USD - _TC", 5500, 0, None],
+ ["_Test Exchange Gain/Loss - _TC", 0, 500, None],
+ ]
+ )
self.validate_gl_entries(pe.name, expected_gle)
@@ -530,10 +612,11 @@ class TestPaymentEntry(unittest.TestCase):
def test_payment_entry_against_sales_invoice_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
- 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")
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
self.assertEqual(pe.cost_center, si.cost_center)
@@ -546,18 +629,18 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
expected_values = {
- "_Test Bank - _TC": {
- "cost_center": cost_center
- },
- "Debtors - _TC": {
- "cost_center": cost_center
- }
+ "_Test Bank - _TC": {"cost_center": cost_center},
+ "Debtors - _TC": {"cost_center": cost_center},
}
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""", pe.name, as_dict=1)
+ order by account asc""",
+ pe.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -566,10 +649,13 @@ class TestPaymentEntry(unittest.TestCase):
def test_payment_entry_against_purchase_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
- pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
+ pi = make_purchase_invoice_against_cost_center(
+ cost_center=cost_center, credit_to="Creditors - _TC"
+ )
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
self.assertEqual(pe.cost_center, pi.cost_center)
@@ -582,18 +668,18 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
expected_values = {
- "_Test Bank - _TC": {
- "cost_center": cost_center
- },
- "Creditors - _TC": {
- "cost_center": cost_center
- }
+ "_Test Bank - _TC": {"cost_center": cost_center},
+ "Creditors - _TC": {"cost_center": cost_center},
}
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""", pe.name, as_dict=1)
+ order by account asc""",
+ pe.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -603,13 +689,16 @@ class TestPaymentEntry(unittest.TestCase):
def test_payment_entry_account_and_party_balance_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on
+
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
- 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)
- party_balance = get_balance_on(party_type="Customer", party=si.customer, cost_center=si.cost_center)
+ party_balance = get_balance_on(
+ party_type="Customer", party=si.customer, cost_center=si.cost_center
+ )
party_account_balance = get_balance_on(si.debit_to, cost_center=si.cost_center)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
@@ -634,94 +723,109 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(flt(expected_party_account_balance), party_account_balance)
def test_multi_currency_payment_entry_with_taxes(self):
- payment_entry = create_payment_entry(party='_Test Supplier USD', paid_to = '_Test Payable USD - _TC',
- save=True)
- payment_entry.append('taxes', {
- 'account_head': '_Test Account Service Tax - _TC',
- 'charge_type': 'Actual',
- 'tax_amount': 10,
- 'add_deduct_tax': 'Add',
- 'description': 'Test'
- })
+ payment_entry = create_payment_entry(
+ party="_Test Supplier USD", paid_to="_Test Payable USD - _TC", save=True
+ )
+ payment_entry.append(
+ "taxes",
+ {
+ "account_head": "_Test Account Service Tax - _TC",
+ "charge_type": "Actual",
+ "tax_amount": 10,
+ "add_deduct_tax": "Add",
+ "description": "Test",
+ },
+ )
payment_entry.save()
self.assertEqual(payment_entry.base_total_taxes_and_charges, 10)
- self.assertEqual(flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2))
+ self.assertEqual(
+ flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2)
+ )
+
def create_payment_entry(**args):
- payment_entry = frappe.new_doc('Payment Entry')
- payment_entry.company = args.get('company') or '_Test Company'
- payment_entry.payment_type = args.get('payment_type') or 'Pay'
- payment_entry.party_type = args.get('party_type') or 'Supplier'
- payment_entry.party = args.get('party') or '_Test Supplier'
- payment_entry.paid_from = args.get('paid_from') or '_Test Bank - _TC'
- payment_entry.paid_to = args.get('paid_to') or 'Creditors - _TC'
- payment_entry.paid_amount = args.get('paid_amount') or 1000
+ payment_entry = frappe.new_doc("Payment Entry")
+ payment_entry.company = args.get("company") or "_Test Company"
+ payment_entry.payment_type = args.get("payment_type") or "Pay"
+ payment_entry.party_type = args.get("party_type") or "Supplier"
+ payment_entry.party = args.get("party") or "_Test Supplier"
+ payment_entry.paid_from = args.get("paid_from") or "_Test Bank - _TC"
+ payment_entry.paid_to = args.get("paid_to") or "Creditors - _TC"
+ payment_entry.paid_amount = args.get("paid_amount") or 1000
payment_entry.setup_party_account_field()
payment_entry.set_missing_values()
payment_entry.set_exchange_rate()
payment_entry.received_amount = payment_entry.paid_amount / payment_entry.target_exchange_rate
- payment_entry.reference_no = 'Test001'
+ payment_entry.reference_no = "Test001"
payment_entry.reference_date = nowdate()
- if args.get('save'):
+ if args.get("save"):
payment_entry.save()
- if args.get('submit'):
+ if args.get("submit"):
payment_entry.submit()
return payment_entry
+
def create_payment_terms_template():
- create_payment_term('Basic Amount Receivable')
- create_payment_term('Tax Receivable')
+ create_payment_term("Basic Amount Receivable")
+ create_payment_term("Tax Receivable")
- if not frappe.db.exists('Payment Terms Template', 'Test Receivable Template'):
- payment_term_template = frappe.get_doc({
- 'doctype': 'Payment Terms Template',
- 'template_name': 'Test Receivable Template',
- 'allocate_payment_based_on_payment_terms': 1,
- 'terms': [{
- 'doctype': 'Payment Terms Template Detail',
- 'payment_term': 'Basic Amount Receivable',
- 'invoice_portion': 84.746,
- 'credit_days_based_on': 'Day(s) after invoice date',
- 'credit_days': 1
- },
+ if not frappe.db.exists("Payment Terms Template", "Test Receivable Template"):
+ payment_term_template = frappe.get_doc(
{
- 'doctype': 'Payment Terms Template Detail',
- 'payment_term': 'Tax Receivable',
- 'invoice_portion': 15.254,
- 'credit_days_based_on': 'Day(s) after invoice date',
- 'credit_days': 2
- }]
- }).insert()
+ "doctype": "Payment Terms Template",
+ "template_name": "Test Receivable Template",
+ "allocate_payment_based_on_payment_terms": 1,
+ "terms": [
+ {
+ "doctype": "Payment Terms Template Detail",
+ "payment_term": "Basic Amount Receivable",
+ "invoice_portion": 84.746,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 1,
+ },
+ {
+ "doctype": "Payment Terms Template Detail",
+ "payment_term": "Tax Receivable",
+ "invoice_portion": 15.254,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 2,
+ },
+ ],
+ }
+ ).insert()
+
def create_payment_terms_template_with_discount():
- create_payment_term('30 Credit Days with 10% Discount')
+ create_payment_term("30 Credit Days with 10% Discount")
+
+ if not frappe.db.exists("Payment Terms Template", "Test Discount Template"):
+ payment_term_template = frappe.get_doc(
+ {
+ "doctype": "Payment Terms Template",
+ "template_name": "Test Discount Template",
+ "allocate_payment_based_on_payment_terms": 1,
+ "terms": [
+ {
+ "doctype": "Payment Terms Template Detail",
+ "payment_term": "30 Credit Days with 10% Discount",
+ "invoice_portion": 100,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 2,
+ "discount": 10,
+ "discount_validity_based_on": "Day(s) after invoice date",
+ "discount_validity": 1,
+ }
+ ],
+ }
+ ).insert()
- if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'):
- payment_term_template = frappe.get_doc({
- 'doctype': 'Payment Terms Template',
- 'template_name': 'Test Discount Template',
- 'allocate_payment_based_on_payment_terms': 1,
- 'terms': [{
- 'doctype': 'Payment Terms Template Detail',
- 'payment_term': '30 Credit Days with 10% Discount',
- 'invoice_portion': 100,
- 'credit_days_based_on': 'Day(s) after invoice date',
- 'credit_days': 2,
- 'discount': 10,
- 'discount_validity_based_on': 'Day(s) after invoice date',
- 'discount_validity': 1
- }]
- }).insert()
def create_payment_term(name):
- if not frappe.db.exists('Payment Term', name):
- frappe.get_doc({
- 'doctype': 'Payment Term',
- 'payment_term_name': name
- }).insert()
+ if not frappe.db.exists("Payment Term", name):
+ frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()
diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py
index 25dc4e6a60..ab47b6151c 100644
--- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py
+++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py
@@ -18,10 +18,13 @@ class PaymentGatewayAccount(Document):
def update_default_payment_gateway(self):
if self.is_default:
- frappe.db.sql("""update `tabPayment Gateway Account` set is_default = 0
- where is_default = 1 """)
+ frappe.db.sql(
+ """update `tabPayment Gateway Account` set is_default = 0
+ where is_default = 1 """
+ )
def set_as_default_if_not_set(self):
- if not frappe.db.get_value("Payment Gateway Account",
- {"is_default": 1, "name": ("!=", self.name)}, "name"):
+ if not frappe.db.get_value(
+ "Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"
+ ):
self.is_default = 1
diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account_dashboard.py b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account_dashboard.py
index 3996892241..d0aaee8835 100644
--- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account_dashboard.py
+++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account_dashboard.py
@@ -1,15 +1,6 @@
def get_data():
return {
- 'fieldname': 'payment_gateway_account',
- 'non_standard_fieldnames': {
- 'Subscription Plan': 'payment_gateway'
- },
- 'transactions': [
- {
- 'items': ['Payment Request']
- },
- {
- 'items': ['Subscription Plan']
- }
- ]
+ "fieldname": "payment_gateway_account",
+ "non_standard_fieldnames": {"Subscription Plan": "payment_gateway"},
+ "transactions": [{"items": ["Payment Request"]}, {"items": ["Subscription Plan"]}],
}
diff --git a/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py b/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py
index 1895c12ad7..7a8cdf7335 100644
--- a/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py
+++ b/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Payment Gateway Account')
+
class TestPaymentGatewayAccount(unittest.TestCase):
pass
diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py
index 50a58b8a0a..3c45d20770 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/payment_order.py
@@ -18,9 +18,9 @@ class PaymentOrder(Document):
self.update_payment_status(cancel=True)
def update_payment_status(self, cancel=False):
- status = 'Payment Ordered'
+ status = "Payment Ordered"
if cancel:
- status = 'Initiated'
+ status = "Initiated"
if self.payment_order_type == "Payment Request":
ref_field = "status"
@@ -32,67 +32,67 @@ class PaymentOrder(Document):
for d in self.references:
frappe.db.set_value(self.payment_order_type, d.get(ref_doc_field), ref_field, status)
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference`
+ return frappe.db.sql(
+ """ select mode_of_payment from `tabPayment Order Reference`
where parent = %(parent)s and mode_of_payment like %(txt)s
- limit %(start)s, %(page_len)s""", {
- 'parent': filters.get("parent"),
- 'start': start,
- 'page_len': page_len,
- 'txt': "%%%s%%" % txt
- })
+ limit %(start)s, %(page_len)s""",
+ {"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
+ )
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql(""" select supplier from `tabPayment Order Reference`
+ return frappe.db.sql(
+ """ select supplier from `tabPayment Order Reference`
where parent = %(parent)s and supplier like %(txt)s and
(payment_reference is null or payment_reference='')
- limit %(start)s, %(page_len)s""", {
- 'parent': filters.get("parent"),
- 'start': start,
- 'page_len': page_len,
- 'txt': "%%%s%%" % txt
- })
+ limit %(start)s, %(page_len)s""",
+ {"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
+ )
+
@frappe.whitelist()
def make_payment_records(name, supplier, mode_of_payment=None):
- doc = frappe.get_doc('Payment Order', name)
+ doc = frappe.get_doc("Payment Order", name)
make_journal_entry(doc, supplier, mode_of_payment)
+
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.posting_date = nowdate()
- mode_of_payment_type = frappe._dict(frappe.get_all('Mode of Payment',
- fields = ["name", "type"], as_list=1))
+ mode_of_payment_type = frappe._dict(
+ frappe.get_all("Mode of Payment", fields=["name", "type"], as_list=1)
+ )
- je.voucher_type = 'Bank Entry'
- if mode_of_payment and mode_of_payment_type.get(mode_of_payment) == 'Cash':
+ je.voucher_type = "Bank Entry"
+ if mode_of_payment and mode_of_payment_type.get(mode_of_payment) == "Cash":
je.voucher_type = "Cash Entry"
paid_amt = 0
- party_account = get_party_account('Supplier', supplier, doc.company)
+ party_account = get_party_account("Supplier", supplier, doc.company)
for d in doc.references:
- if (d.supplier == supplier
- and (not mode_of_payment or mode_of_payment == d.mode_of_payment)):
- je.append('accounts', {
- 'account': party_account,
- 'debit_in_account_currency': d.amount,
- 'party_type': 'Supplier',
- 'party': supplier,
- 'reference_type': d.reference_doctype,
- 'reference_name': d.reference_name
- })
+ if d.supplier == supplier and (not mode_of_payment or mode_of_payment == d.mode_of_payment):
+ je.append(
+ "accounts",
+ {
+ "account": party_account,
+ "debit_in_account_currency": d.amount,
+ "party_type": "Supplier",
+ "party": supplier,
+ "reference_type": d.reference_doctype,
+ "reference_name": d.reference_name,
+ },
+ )
paid_amt += d.amount
- je.append('accounts', {
- 'account': doc.account,
- 'credit_in_account_currency': paid_amt
- })
+ je.append("accounts", {"account": doc.account, "credit_in_account_currency": paid_amt})
je.flags.ignore_mandatory = True
je.save()
diff --git a/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py b/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py
index 37bbaec33d..f82886e7c2 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py
+++ b/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
return {
- 'fieldname': 'payment_order',
- 'transactions': [
- {
- 'items': ['Payment Entry', 'Journal Entry']
- }
- ]
+ "fieldname": "payment_order",
+ "transactions": [{"items": ["Payment Entry", "Journal Entry"]}],
}
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py
index 3f4d89b4ea..0dcb1794b9 100644
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py
@@ -26,7 +26,9 @@ class TestPaymentOrder(unittest.TestCase):
def test_payment_order_creation_against_payment_entry(self):
purchase_invoice = make_purchase_invoice()
- payment_entry = get_payment_entry("Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC")
+ payment_entry = get_payment_entry(
+ "Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC"
+ )
payment_entry.reference_no = "_Test_Payment_Order"
payment_entry.reference_date = getdate()
payment_entry.party_bank_account = "Checking Account - Citi Bank"
@@ -40,13 +42,16 @@ class TestPaymentOrder(unittest.TestCase):
self.assertEqual(reference_doc.supplier, "_Test Supplier")
self.assertEqual(reference_doc.amount, 250)
+
def create_payment_order_against_payment_entry(ref_doc, order_type):
- payment_order = frappe.get_doc(dict(
- doctype="Payment Order",
- company="_Test Company",
- payment_order_type=order_type,
- company_bank_account="Checking Account - Citi Bank"
- ))
+ payment_order = frappe.get_doc(
+ dict(
+ doctype="Payment Order",
+ company="_Test Company",
+ payment_order_type=order_type,
+ company_bank_account="Checking Account - Citi Bank",
+ )
+ )
doc = make_payment_order(ref_doc.name, payment_order)
doc.save()
doc.submit()
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 548571d1d7..907b76915a 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -32,30 +32,43 @@ class PaymentReconciliation(Document):
non_reconciled_payments = payment_entries + journal_entries + dr_or_cr_notes
if self.payment_limit:
- non_reconciled_payments = non_reconciled_payments[:self.payment_limit]
+ non_reconciled_payments = non_reconciled_payments[: self.payment_limit]
- non_reconciled_payments = sorted(non_reconciled_payments, key=lambda k: k['posting_date'] or getdate(nowdate()))
+ non_reconciled_payments = sorted(
+ non_reconciled_payments, key=lambda k: k["posting_date"] or getdate(nowdate())
+ )
self.add_payment_entries(non_reconciled_payments)
def get_payment_entries(self):
- order_doctype = "Sales Order" if self.party_type=="Customer" else "Purchase Order"
+ order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order"
condition = self.get_conditions(get_payments=True)
- payment_entries = get_advance_payment_entries(self.party_type, self.party,
- self.receivable_payable_account, order_doctype, against_all_orders=True, limit=self.payment_limit,
- condition=condition)
+ payment_entries = get_advance_payment_entries(
+ self.party_type,
+ self.party,
+ self.receivable_payable_account,
+ order_doctype,
+ against_all_orders=True,
+ limit=self.payment_limit,
+ condition=condition,
+ )
return payment_entries
def get_jv_entries(self):
condition = self.get_conditions()
- dr_or_cr = ("credit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
- else "debit_in_account_currency")
+ dr_or_cr = (
+ "credit_in_account_currency"
+ if erpnext.get_party_account_type(self.party_type) == "Receivable"
+ else "debit_in_account_currency"
+ )
- bank_account_condition = "t2.against_account like %(bank_cash_account)s" \
- if self.bank_cash_account else "1=1"
+ bank_account_condition = (
+ "t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
+ )
- journal_entries = frappe.db.sql("""
+ journal_entries = frappe.db.sql(
+ """
select
"Journal Entry" as reference_type, t1.name as reference_name,
t1.posting_date, t1.remark as remarks, t2.name as reference_row,
@@ -76,31 +89,42 @@ class PaymentReconciliation(Document):
ELSE {bank_account_condition}
END)
order by t1.posting_date
- """.format(**{
- "dr_or_cr": dr_or_cr,
- "bank_account_condition": bank_account_condition,
- "condition": condition
- }), {
+ """.format(
+ **{
+ "dr_or_cr": dr_or_cr,
+ "bank_account_condition": bank_account_condition,
+ "condition": condition,
+ }
+ ),
+ {
"party_type": self.party_type,
"party": self.party,
"account": self.receivable_payable_account,
- "bank_cash_account": "%%%s%%" % self.bank_cash_account
- }, as_dict=1)
+ "bank_cash_account": "%%%s%%" % self.bank_cash_account,
+ },
+ as_dict=1,
+ )
return list(journal_entries)
def get_dr_or_cr_notes(self):
condition = self.get_conditions(get_return_invoices=True)
- dr_or_cr = ("credit_in_account_currency"
- if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")
+ dr_or_cr = (
+ "credit_in_account_currency"
+ if erpnext.get_party_account_type(self.party_type) == "Receivable"
+ else "debit_in_account_currency"
+ )
- reconciled_dr_or_cr = ("debit_in_account_currency"
- if dr_or_cr == "credit_in_account_currency" else "credit_in_account_currency")
+ reconciled_dr_or_cr = (
+ "debit_in_account_currency"
+ if dr_or_cr == "credit_in_account_currency"
+ else "credit_in_account_currency"
+ )
- voucher_type = ('Sales Invoice'
- if self.party_type == 'Customer' else "Purchase Invoice")
+ voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
- return frappe.db.sql(""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
+ return frappe.db.sql(
+ """ SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, doc.posting_date,
account_currency as currency
FROM `tab{doc}` doc, `tabGL Entry` gl
@@ -117,106 +141,115 @@ class PaymentReconciliation(Document):
amount > 0
ORDER BY doc.posting_date
""".format(
- doc=voucher_type,
- dr_or_cr=dr_or_cr,
- reconciled_dr_or_cr=reconciled_dr_or_cr,
- party_type_field=frappe.scrub(self.party_type),
- condition=condition or ""),
+ doc=voucher_type,
+ dr_or_cr=dr_or_cr,
+ reconciled_dr_or_cr=reconciled_dr_or_cr,
+ party_type_field=frappe.scrub(self.party_type),
+ condition=condition or "",
+ ),
{
- 'party': self.party,
- 'party_type': self.party_type,
- 'voucher_type': voucher_type,
- 'account': self.receivable_payable_account
- }, as_dict=1)
+ "party": self.party,
+ "party_type": self.party_type,
+ "voucher_type": voucher_type,
+ "account": self.receivable_payable_account,
+ },
+ as_dict=1,
+ )
def add_payment_entries(self, non_reconciled_payments):
- self.set('payments', [])
+ self.set("payments", [])
for payment in non_reconciled_payments:
- row = self.append('payments', {})
+ row = self.append("payments", {})
row.update(payment)
def get_invoice_entries(self):
- #Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
+ # Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
condition = self.get_conditions(get_invoices=True)
- non_reconciled_invoices = get_outstanding_invoices(self.party_type, self.party,
- self.receivable_payable_account, condition=condition)
+ non_reconciled_invoices = get_outstanding_invoices(
+ self.party_type, self.party, self.receivable_payable_account, condition=condition
+ )
if self.invoice_limit:
- non_reconciled_invoices = non_reconciled_invoices[:self.invoice_limit]
+ non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]
self.add_invoice_entries(non_reconciled_invoices)
def add_invoice_entries(self, non_reconciled_invoices):
- #Populate 'invoices' with JVs and Invoices to reconcile against
- self.set('invoices', [])
+ # Populate 'invoices' with JVs and Invoices to reconcile against
+ self.set("invoices", [])
for entry in non_reconciled_invoices:
- inv = self.append('invoices', {})
- inv.invoice_type = entry.get('voucher_type')
- inv.invoice_number = entry.get('voucher_no')
- inv.invoice_date = entry.get('posting_date')
- inv.amount = flt(entry.get('invoice_amount'))
- inv.currency = entry.get('currency')
- inv.outstanding_amount = flt(entry.get('outstanding_amount'))
+ inv = self.append("invoices", {})
+ inv.invoice_type = entry.get("voucher_type")
+ inv.invoice_number = entry.get("voucher_no")
+ inv.invoice_date = entry.get("posting_date")
+ inv.amount = flt(entry.get("invoice_amount"))
+ inv.currency = entry.get("currency")
+ inv.outstanding_amount = flt(entry.get("outstanding_amount"))
@frappe.whitelist()
def allocate_entries(self, args):
self.validate_entries()
entries = []
- for pay in args.get('payments'):
- pay.update({'unreconciled_amount': pay.get('amount')})
- for inv in args.get('invoices'):
- if pay.get('amount') >= inv.get('outstanding_amount'):
- res = self.get_allocated_entry(pay, inv, inv['outstanding_amount'])
- pay['amount'] = flt(pay.get('amount')) - flt(inv.get('outstanding_amount'))
- inv['outstanding_amount'] = 0
+ for pay in args.get("payments"):
+ pay.update({"unreconciled_amount": pay.get("amount")})
+ for inv in args.get("invoices"):
+ if pay.get("amount") >= inv.get("outstanding_amount"):
+ res = self.get_allocated_entry(pay, inv, inv["outstanding_amount"])
+ pay["amount"] = flt(pay.get("amount")) - flt(inv.get("outstanding_amount"))
+ inv["outstanding_amount"] = 0
else:
- res = self.get_allocated_entry(pay, inv, pay['amount'])
- inv['outstanding_amount'] = flt(inv.get('outstanding_amount')) - flt(pay.get('amount'))
- pay['amount'] = 0
- if pay.get('amount') == 0:
+ res = self.get_allocated_entry(pay, inv, pay["amount"])
+ inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount"))
+ pay["amount"] = 0
+ if pay.get("amount") == 0:
entries.append(res)
break
- elif inv.get('outstanding_amount') == 0:
+ elif inv.get("outstanding_amount") == 0:
entries.append(res)
continue
else:
break
- self.set('allocation', [])
+ self.set("allocation", [])
for entry in entries:
- if entry['allocated_amount'] != 0:
- row = self.append('allocation', {})
+ if entry["allocated_amount"] != 0:
+ row = self.append("allocation", {})
row.update(entry)
def get_allocated_entry(self, pay, inv, allocated_amount):
- return frappe._dict({
- 'reference_type': pay.get('reference_type'),
- 'reference_name': pay.get('reference_name'),
- 'reference_row': pay.get('reference_row'),
- 'invoice_type': inv.get('invoice_type'),
- 'invoice_number': inv.get('invoice_number'),
- 'unreconciled_amount': pay.get('unreconciled_amount'),
- 'amount': pay.get('amount'),
- 'allocated_amount': allocated_amount,
- 'difference_amount': pay.get('difference_amount')
- })
+ return frappe._dict(
+ {
+ "reference_type": pay.get("reference_type"),
+ "reference_name": pay.get("reference_name"),
+ "reference_row": pay.get("reference_row"),
+ "invoice_type": inv.get("invoice_type"),
+ "invoice_number": inv.get("invoice_number"),
+ "unreconciled_amount": pay.get("unreconciled_amount"),
+ "amount": pay.get("amount"),
+ "allocated_amount": allocated_amount,
+ "difference_amount": pay.get("difference_amount"),
+ }
+ )
@frappe.whitelist()
def reconcile(self):
self.validate_allocation()
- dr_or_cr = ("credit_in_account_currency"
- if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")
+ dr_or_cr = (
+ "credit_in_account_currency"
+ if erpnext.get_party_account_type(self.party_type) == "Receivable"
+ else "debit_in_account_currency"
+ )
entry_list = []
dr_or_cr_notes = []
- for row in self.get('allocation'):
+ for row in self.get("allocation"):
reconciled_entry = []
if row.invoice_number and row.allocated_amount:
- if row.reference_type in ['Sales Invoice', 'Purchase Invoice']:
+ if row.reference_type in ["Sales Invoice", "Purchase Invoice"]:
reconciled_entry = dr_or_cr_notes
else:
reconciled_entry = entry_list
@@ -233,23 +266,25 @@ class PaymentReconciliation(Document):
self.get_unreconciled_entries()
def get_payment_details(self, row, dr_or_cr):
- return frappe._dict({
- 'voucher_type': row.get('reference_type'),
- 'voucher_no' : row.get('reference_name'),
- 'voucher_detail_no' : row.get('reference_row'),
- 'against_voucher_type' : row.get('invoice_type'),
- 'against_voucher' : row.get('invoice_number'),
- 'account' : self.receivable_payable_account,
- 'party_type': self.party_type,
- 'party': self.party,
- 'is_advance' : row.get('is_advance'),
- 'dr_or_cr' : dr_or_cr,
- 'unreconciled_amount': flt(row.get('unreconciled_amount')),
- 'unadjusted_amount' : flt(row.get('amount')),
- 'allocated_amount' : flt(row.get('allocated_amount')),
- 'difference_amount': flt(row.get('difference_amount')),
- 'difference_account': row.get('difference_account')
- })
+ return frappe._dict(
+ {
+ "voucher_type": row.get("reference_type"),
+ "voucher_no": row.get("reference_name"),
+ "voucher_detail_no": row.get("reference_row"),
+ "against_voucher_type": row.get("invoice_type"),
+ "against_voucher": row.get("invoice_number"),
+ "account": self.receivable_payable_account,
+ "party_type": self.party_type,
+ "party": self.party,
+ "is_advance": row.get("is_advance"),
+ "dr_or_cr": dr_or_cr,
+ "unreconciled_amount": flt(row.get("unreconciled_amount")),
+ "unadjusted_amount": flt(row.get("amount")),
+ "allocated_amount": flt(row.get("allocated_amount")),
+ "difference_amount": flt(row.get("difference_amount")),
+ "difference_account": row.get("difference_account"),
+ }
+ )
def check_mandatory_to_fetch(self):
for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
@@ -267,7 +302,9 @@ class PaymentReconciliation(Document):
unreconciled_invoices = frappe._dict()
for inv in self.get("invoices"):
- unreconciled_invoices.setdefault(inv.invoice_type, {}).setdefault(inv.invoice_number, inv.outstanding_amount)
+ unreconciled_invoices.setdefault(inv.invoice_type, {}).setdefault(
+ inv.invoice_number, inv.outstanding_amount
+ )
invoices_to_reconcile = []
for row in self.get("allocation"):
@@ -275,13 +312,19 @@ class PaymentReconciliation(Document):
invoices_to_reconcile.append(row.invoice_number)
if flt(row.amount) - flt(row.allocated_amount) < 0:
- frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equal to remaining payment amount {2}")
- .format(row.idx, row.allocated_amount, row.amount))
+ frappe.throw(
+ _(
+ "Row {0}: Allocated amount {1} must be less than or equal to remaining payment amount {2}"
+ ).format(row.idx, row.allocated_amount, row.amount)
+ )
invoice_outstanding = unreconciled_invoices.get(row.invoice_type, {}).get(row.invoice_number)
if flt(row.allocated_amount) - invoice_outstanding > 0.009:
- frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equal to invoice outstanding amount {2}")
- .format(row.idx, row.allocated_amount, invoice_outstanding))
+ frappe.throw(
+ _(
+ "Row {0}: Allocated amount {1} must be less than or equal to invoice outstanding amount {2}"
+ ).format(row.idx, row.allocated_amount, invoice_outstanding)
+ )
if not invoices_to_reconcile:
frappe.throw(_("No records found in Allocation table"))
@@ -290,10 +333,21 @@ class PaymentReconciliation(Document):
condition = " and company = '{0}' ".format(self.company)
if get_invoices:
- condition += " and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date)) if self.from_invoice_date else ""
- condition += " and posting_date <= {0}".format(frappe.db.escape(self.to_invoice_date)) if self.to_invoice_date else ""
- dr_or_cr = ("debit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
- else "credit_in_account_currency")
+ condition += (
+ " and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date))
+ if self.from_invoice_date
+ else ""
+ )
+ condition += (
+ " and posting_date <= {0}".format(frappe.db.escape(self.to_invoice_date))
+ if self.to_invoice_date
+ else ""
+ )
+ dr_or_cr = (
+ "debit_in_account_currency"
+ if erpnext.get_party_account_type(self.party_type) == "Receivable"
+ else "credit_in_account_currency"
+ )
if self.minimum_invoice_amount:
condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_invoice_amount))
@@ -302,10 +356,21 @@ class PaymentReconciliation(Document):
elif get_return_invoices:
condition = " and doc.company = '{0}' ".format(self.company)
- condition += " and doc.posting_date >= {0}".format(frappe.db.escape(self.from_payment_date)) if self.from_payment_date else ""
- condition += " and doc.posting_date <= {0}".format(frappe.db.escape(self.to_payment_date)) if self.to_payment_date else ""
- dr_or_cr = ("gl.debit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
- else "gl.credit_in_account_currency")
+ condition += (
+ " and doc.posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
+ if self.from_payment_date
+ else ""
+ )
+ condition += (
+ " and doc.posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
+ if self.to_payment_date
+ else ""
+ )
+ dr_or_cr = (
+ "gl.debit_in_account_currency"
+ if erpnext.get_party_account_type(self.party_type) == "Receivable"
+ else "gl.credit_in_account_currency"
+ )
if self.minimum_invoice_amount:
condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_payment_amount))
@@ -313,55 +378,77 @@ class PaymentReconciliation(Document):
condition += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_payment_amount))
else:
- condition += " and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date)) if self.from_payment_date else ""
- condition += " and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date)) if self.to_payment_date else ""
+ condition += (
+ " and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
+ if self.from_payment_date
+ else ""
+ )
+ condition += (
+ " and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
+ if self.to_payment_date
+ else ""
+ )
if self.minimum_payment_amount:
- condition += " and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount)) if get_payments \
+ condition += (
+ " and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
+ if get_payments
else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
+ )
if self.maximum_payment_amount:
- condition += " and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount)) if get_payments \
+ condition += (
+ " and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
+ if get_payments
else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
+ )
return condition
+
def reconcile_dr_cr_note(dr_cr_notes, company):
for inv in dr_cr_notes:
- voucher_type = ('Credit Note'
- if inv.voucher_type == 'Sales Invoice' else 'Debit Note')
+ voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
- reconcile_dr_or_cr = ('debit_in_account_currency'
- if inv.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
+ reconcile_dr_or_cr = (
+ "debit_in_account_currency"
+ if inv.dr_or_cr == "credit_in_account_currency"
+ else "credit_in_account_currency"
+ )
company_currency = erpnext.get_company_currency(company)
- jv = frappe.get_doc({
- "doctype": "Journal Entry",
- "voucher_type": voucher_type,
- "posting_date": today(),
- "company": company,
- "multi_currency": 1 if inv.currency != company_currency else 0,
- "accounts": [
- {
- 'account': inv.account,
- 'party': inv.party,
- 'party_type': inv.party_type,
- inv.dr_or_cr: abs(inv.allocated_amount),
- 'reference_type': inv.against_voucher_type,
- 'reference_name': inv.against_voucher,
- 'cost_center': erpnext.get_default_cost_center(company)
- },
- {
- 'account': inv.account,
- 'party': inv.party,
- 'party_type': inv.party_type,
- reconcile_dr_or_cr: (abs(inv.allocated_amount)
- if abs(inv.unadjusted_amount) > abs(inv.allocated_amount) else abs(inv.unadjusted_amount)),
- 'reference_type': inv.voucher_type,
- 'reference_name': inv.voucher_no,
- 'cost_center': erpnext.get_default_cost_center(company)
- }
- ]
- })
+ jv = frappe.get_doc(
+ {
+ "doctype": "Journal Entry",
+ "voucher_type": voucher_type,
+ "posting_date": today(),
+ "company": company,
+ "multi_currency": 1 if inv.currency != company_currency else 0,
+ "accounts": [
+ {
+ "account": inv.account,
+ "party": inv.party,
+ "party_type": inv.party_type,
+ inv.dr_or_cr: abs(inv.allocated_amount),
+ "reference_type": inv.against_voucher_type,
+ "reference_name": inv.against_voucher,
+ "cost_center": erpnext.get_default_cost_center(company),
+ },
+ {
+ "account": inv.account,
+ "party": inv.party,
+ "party_type": inv.party_type,
+ reconcile_dr_or_cr: (
+ abs(inv.allocated_amount)
+ if abs(inv.unadjusted_amount) > abs(inv.allocated_amount)
+ else abs(inv.unadjusted_amount)
+ ),
+ "reference_type": inv.voucher_type,
+ "reference_name": inv.voucher_no,
+ "cost_center": erpnext.get_default_cost_center(company),
+ },
+ ],
+ }
+ )
jv.flags.ignore_mandatory = True
jv.submit()
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index d72d8f7018..5264987acf 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -24,7 +24,7 @@ from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscr
class PaymentRequest(Document):
def validate(self):
if self.get("__islocal"):
- self.status = 'Draft'
+ self.status = "Draft"
self.validate_reference_document()
self.validate_payment_request_amount()
self.validate_currency()
@@ -35,51 +35,67 @@ class PaymentRequest(Document):
frappe.throw(_("To create a Payment Request reference document is required"))
def validate_payment_request_amount(self):
- existing_payment_request_amount = \
- get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
+ existing_payment_request_amount = get_existing_payment_request_amount(
+ self.reference_doctype, self.reference_name
+ )
if existing_payment_request_amount:
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 getattr(ref_doc, "order_type") != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
- if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
- frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
- .format(self.reference_doctype))
+ if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
+ frappe.throw(
+ _("Total Payment Request amount cannot be greater than {0} amount").format(
+ self.reference_doctype
+ )
+ )
def validate_currency(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
- if self.payment_account and ref_doc.currency != frappe.db.get_value("Account", self.payment_account, "account_currency"):
+ if self.payment_account and ref_doc.currency != frappe.db.get_value(
+ "Account", self.payment_account, "account_currency"
+ ):
frappe.throw(_("Transaction currency must be same as Payment Gateway currency"))
def validate_subscription_details(self):
if self.is_a_subscription:
amount = 0
for subscription_plan in self.subscription_plans:
- payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway")
+ payment_gateway = frappe.db.get_value(
+ "Subscription Plan", subscription_plan.plan, "payment_gateway"
+ )
if payment_gateway != self.payment_gateway_account:
- frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request').format(subscription_plan.name))
+ frappe.throw(
+ _(
+ "The payment gateway account in plan {0} is different from the payment gateway account in this payment request"
+ ).format(subscription_plan.name)
+ )
rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty)
amount += rate
if amount != self.grand_total:
- frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.").format(self.grand_total, amount))
+ frappe.msgprint(
+ _(
+ "The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document."
+ ).format(self.grand_total, amount)
+ )
def on_submit(self):
- if self.payment_request_type == 'Outward':
- self.db_set('status', 'Initiated')
+ if self.payment_request_type == "Outward":
+ self.db_set("status", "Initiated")
return
- elif self.payment_request_type == 'Inward':
- self.db_set('status', 'Requested')
+ elif self.payment_request_type == "Inward":
+ self.db_set("status", "Requested")
send_mail = self.payment_gateway_validation() if self.payment_gateway else None
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") \
- or self.flags.mute_email:
+ if (
+ hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart"
+ ) or self.flags.mute_email:
send_mail = False
if send_mail and self.payment_channel != "Phone":
@@ -101,23 +117,27 @@ class PaymentRequest(Document):
request_amount=request_amount,
sender=self.email_to,
currency=self.currency,
- payment_gateway=self.payment_gateway
+ payment_gateway=self.payment_gateway,
)
controller.validate_transaction_currency(self.currency)
controller.request_for_payment(**payment_record)
def get_request_amount(self):
- data_of_completed_requests = frappe.get_all("Integration Request", filters={
- 'reference_doctype': self.doctype,
- 'reference_docname': self.name,
- 'status': 'Completed'
- }, pluck="data")
+ data_of_completed_requests = frappe.get_all(
+ "Integration Request",
+ filters={
+ "reference_doctype": self.doctype,
+ "reference_docname": self.name,
+ "status": "Completed",
+ },
+ pluck="data",
+ )
if not data_of_completed_requests:
return self.grand_total
- request_amounts = sum(json.loads(d).get('request_amount') for d in data_of_completed_requests)
+ request_amounts = sum(json.loads(d).get("request_amount") for d in data_of_completed_requests)
return request_amounts
def on_cancel(self):
@@ -126,8 +146,9 @@ class PaymentRequest(Document):
def make_invoice(self):
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 getattr(ref_doc, "order_type") == "Shopping Cart":
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
+
si = make_sales_invoice(self.reference_name, ignore_permissions=True)
si.allocate_advances_automatically = True
si = si.insert(ignore_permissions=True)
@@ -136,7 +157,7 @@ class PaymentRequest(Document):
def payment_gateway_validation(self):
try:
controller = get_payment_gateway_controller(self.payment_gateway)
- if hasattr(controller, 'on_payment_request_submission'):
+ if hasattr(controller, "on_payment_request_submission"):
return controller.on_payment_request_submission(self)
else:
return True
@@ -148,36 +169,45 @@ class PaymentRequest(Document):
self.payment_url = self.get_payment_url()
if self.payment_url:
- self.db_set('payment_url', self.payment_url)
+ self.db_set("payment_url", self.payment_url)
- if self.payment_url or not self.payment_gateway_account \
- or (self.payment_gateway_account and self.payment_channel == "Phone"):
- self.db_set('status', 'Initiated')
+ if (
+ self.payment_url
+ or not self.payment_gateway_account
+ or (self.payment_gateway_account and self.payment_channel == "Phone")
+ ):
+ self.db_set("status", "Initiated")
def get_payment_url(self):
if self.reference_doctype != "Fees":
- data = frappe.db.get_value(self.reference_doctype, self.reference_name, ["company", "customer_name"], as_dict=1)
+ data = frappe.db.get_value(
+ self.reference_doctype, self.reference_name, ["company", "customer_name"], as_dict=1
+ )
else:
- data = frappe.db.get_value(self.reference_doctype, self.reference_name, ["student_name"], as_dict=1)
+ data = frappe.db.get_value(
+ self.reference_doctype, self.reference_name, ["student_name"], as_dict=1
+ )
data.update({"company": frappe.defaults.get_defaults().company})
controller = get_payment_gateway_controller(self.payment_gateway)
controller.validate_transaction_currency(self.currency)
- if hasattr(controller, 'validate_minimum_transaction_amount'):
+ if hasattr(controller, "validate_minimum_transaction_amount"):
controller.validate_minimum_transaction_amount(self.currency, self.grand_total)
- return controller.get_payment_url(**{
- "amount": flt(self.grand_total, self.precision("grand_total")),
- "title": data.company.encode("utf-8"),
- "description": self.subject.encode("utf-8"),
- "reference_doctype": "Payment Request",
- "reference_docname": self.name,
- "payer_email": self.email_to or frappe.session.user,
- "payer_name": frappe.safe_encode(data.customer_name),
- "order_id": self.name,
- "currency": self.currency
- })
+ return controller.get_payment_url(
+ **{
+ "amount": flt(self.grand_total, self.precision("grand_total")),
+ "title": data.company.encode("utf-8"),
+ "description": self.subject.encode("utf-8"),
+ "reference_doctype": "Payment Request",
+ "reference_docname": self.name,
+ "payer_email": self.email_to or frappe.session.user,
+ "payer_name": frappe.safe_encode(data.customer_name),
+ "order_id": self.name,
+ "currency": self.currency,
+ }
+ )
def set_as_paid(self):
if self.payment_channel == "Phone":
@@ -202,32 +232,47 @@ class PaymentRequest(Document):
else:
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)
+ party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(
+ party_account
+ )
bank_amount = self.grand_total
- if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
+ if (
+ party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
+ ):
party_amount = ref_doc.base_grand_total
else:
party_amount = self.grand_total
- payment_entry = get_payment_entry(self.reference_doctype, self.reference_name, party_amount=party_amount,
- bank_account=self.payment_account, bank_amount=bank_amount)
+ payment_entry = get_payment_entry(
+ self.reference_doctype,
+ self.reference_name,
+ party_amount=party_amount,
+ bank_account=self.payment_account,
+ bank_amount=bank_amount,
+ )
- payment_entry.update({
- "reference_no": self.name,
- "reference_date": nowdate(),
- "remarks": "Payment Entry against {0} {1} via Payment Request {2}".format(self.reference_doctype,
- self.reference_name, self.name)
- })
+ payment_entry.update(
+ {
+ "reference_no": self.name,
+ "reference_date": nowdate(),
+ "remarks": "Payment Entry against {0} {1} via Payment Request {2}".format(
+ self.reference_doctype, self.reference_name, self.name
+ ),
+ }
+ )
if payment_entry.difference_amount:
company_details = get_company_defaults(ref_doc.company)
- payment_entry.append("deductions", {
- "account": company_details.exchange_gain_loss_account,
- "cost_center": company_details.cost_center,
- "amount": payment_entry.difference_amount
- })
+ payment_entry.append(
+ "deductions",
+ {
+ "account": company_details.exchange_gain_loss_account,
+ "cost_center": company_details.cost_center,
+ "amount": payment_entry.difference_amount,
+ },
+ )
if submit:
payment_entry.insert(ignore_permissions=True)
@@ -243,16 +288,23 @@ class PaymentRequest(Document):
"subject": self.subject,
"message": self.get_message(),
"now": True,
- "attachments": [frappe.attach_print(self.reference_doctype, self.reference_name,
- file_name=self.reference_name, print_format=self.print_format)]}
- enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args)
+ "attachments": [
+ frappe.attach_print(
+ self.reference_doctype,
+ self.reference_name,
+ file_name=self.reference_name,
+ print_format=self.print_format,
+ )
+ ],
+ }
+ enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
def get_message(self):
"""return message with payment gateway link"""
context = {
"doc": frappe.get_doc(self.reference_doctype, self.reference_name),
- "payment_url": self.payment_url
+ "payment_url": self.payment_url,
}
if self.message:
@@ -266,22 +318,26 @@ class PaymentRequest(Document):
def check_if_payment_entry_exists(self):
if self.status == "Paid":
- if frappe.get_all("Payment Entry Reference",
+ if frappe.get_all(
+ "Payment Entry Reference",
filters={"reference_name": self.reference_name, "docstatus": ["<", 2]},
fields=["parent"],
- limit=1):
- frappe.throw(_("Payment Entry already exists"), title=_('Error'))
+ limit=1,
+ ):
+ frappe.throw(_("Payment Entry already exists"), title=_("Error"))
def make_communication_entry(self):
"""Make communication entry"""
- comm = frappe.get_doc({
- "doctype":"Communication",
- "subject": self.subject,
- "content": self.get_message(),
- "sent_or_received": "Sent",
- "reference_doctype": self.reference_doctype,
- "reference_name": self.reference_name
- })
+ comm = frappe.get_doc(
+ {
+ "doctype": "Communication",
+ "subject": self.subject,
+ "content": self.get_message(),
+ "sent_or_received": "Sent",
+ "reference_doctype": self.reference_doctype,
+ "reference_name": self.reference_name,
+ }
+ )
comm.insert(ignore_permissions=True)
def get_payment_success_url(self):
@@ -298,16 +354,17 @@ class PaymentRequest(Document):
self.set_as_paid()
# if shopping cart enabled and in session
- if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
- and frappe.local.session.user != "Guest") and self.payment_channel != "Phone":
+ if (
+ shopping_cart_settings.enabled
+ and hasattr(frappe.local, "session")
+ and frappe.local.session.user != "Guest"
+ ) and self.payment_channel != "Phone":
success_url = shopping_cart_settings.payment_success_url
if success_url:
- redirect_to = ({
- "Orders": "/orders",
- "Invoices": "/invoices",
- "My Account": "/me"
- }).get(success_url, "/me")
+ redirect_to = ({"Orders": "/orders", "Invoices": "/invoices", "My Account": "/me"}).get(
+ success_url, "/me"
+ )
else:
redirect_to = get_url("/orders/{0}".format(self.reference_name))
@@ -317,6 +374,7 @@ class PaymentRequest(Document):
if payment_provider == "stripe":
return create_stripe_subscription(gateway_controller, data)
+
@frappe.whitelist(allow_guest=True)
def make_payment_request(**args):
"""Make payment request"""
@@ -329,49 +387,62 @@ def make_payment_request(**args):
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
if args.loyalty_points and args.dt == "Sales Order":
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
+
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
- frappe.db.set_value("Sales Order", args.dn, "loyalty_points", int(args.loyalty_points), update_modified=False)
- frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
+ frappe.db.set_value(
+ "Sales Order", args.dn, "loyalty_points", int(args.loyalty_points), update_modified=False
+ )
+ frappe.db.set_value(
+ "Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False
+ )
grand_total = grand_total - loyalty_amount
- bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
- if args.get('party_type') else '')
+ bank_account = (
+ get_party_bank_account(args.get("party_type"), args.get("party"))
+ if args.get("party_type")
+ else ""
+ )
existing_payment_request = None
if args.order_type == "Shopping Cart":
- existing_payment_request = frappe.db.get_value("Payment Request",
- {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ("!=", 2)})
+ existing_payment_request = frappe.db.get_value(
+ "Payment Request",
+ {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ("!=", 2)},
+ )
if existing_payment_request:
- frappe.db.set_value("Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False)
+ frappe.db.set_value(
+ "Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False
+ )
pr = frappe.get_doc("Payment Request", existing_payment_request)
else:
if args.order_type != "Shopping Cart":
- existing_payment_request_amount = \
- get_existing_payment_request_amount(args.dt, args.dn)
+ existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
if existing_payment_request_amount:
grand_total -= existing_payment_request_amount
pr = frappe.new_doc("Payment Request")
- pr.update({
- "payment_gateway_account": gateway_account.get("name"),
- "payment_gateway": gateway_account.get("payment_gateway"),
- "payment_account": gateway_account.get("payment_account"),
- "payment_channel": gateway_account.get("payment_channel"),
- "payment_request_type": args.get("payment_request_type"),
- "currency": ref_doc.currency,
- "grand_total": grand_total,
- "mode_of_payment": args.mode_of_payment,
- "email_to": args.recipient_id or ref_doc.owner,
- "subject": _("Payment Request for {0}").format(args.dn),
- "message": gateway_account.get("message") or get_dummy_message(ref_doc),
- "reference_doctype": args.dt,
- "reference_name": args.dn,
- "party_type": args.get("party_type") or "Customer",
- "party": args.get("party") or ref_doc.get("customer"),
- "bank_account": bank_account
- })
+ pr.update(
+ {
+ "payment_gateway_account": gateway_account.get("name"),
+ "payment_gateway": gateway_account.get("payment_gateway"),
+ "payment_account": gateway_account.get("payment_account"),
+ "payment_channel": gateway_account.get("payment_channel"),
+ "payment_request_type": args.get("payment_request_type"),
+ "currency": ref_doc.currency,
+ "grand_total": grand_total,
+ "mode_of_payment": args.mode_of_payment,
+ "email_to": args.recipient_id or ref_doc.owner,
+ "subject": _("Payment Request for {0}").format(args.dn),
+ "message": gateway_account.get("message") or get_dummy_message(ref_doc),
+ "reference_doctype": args.dt,
+ "reference_name": args.dn,
+ "party_type": args.get("party_type") or "Customer",
+ "party": args.get("party") or ref_doc.get("customer"),
+ "bank_account": bank_account,
+ }
+ )
if args.order_type == "Shopping Cart" or args.mute_email:
pr.flags.mute_email = True
@@ -390,6 +461,7 @@ def make_payment_request(**args):
return pr.as_dict()
+
def get_amount(ref_doc, payment_account=None):
"""get amount based on doctype"""
dt = ref_doc.doctype
@@ -411,18 +483,20 @@ def get_amount(ref_doc, payment_account=None):
elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
- if grand_total > 0 :
+ if grand_total > 0:
return grand_total
else:
frappe.throw(_("Payment Entry is already created"))
+
def get_existing_payment_request_amount(ref_dt, ref_dn):
"""
Get the existing payment request which are unpaid or partially paid for payment channel other than Phone
and get the summation of existing paid payment request for Phone payment channel.
"""
- existing_payment_request_amount = frappe.db.sql("""
+ existing_payment_request_amount = frappe.db.sql(
+ """
select sum(grand_total)
from `tabPayment Request`
where
@@ -432,10 +506,13 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
and (status != 'Paid'
or (payment_channel = 'Phone'
and status = 'Paid'))
- """, (ref_dt, ref_dn))
+ """,
+ (ref_dt, ref_dn),
+ )
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
-def get_gateway_details(args): # nosemgrep
+
+def get_gateway_details(args): # nosemgrep
"""return gateway and payment account of default payment gateway"""
if args.get("payment_gateway_account"):
return get_payment_gateway_account(args.get("payment_gateway_account"))
@@ -448,58 +525,74 @@ def get_gateway_details(args): # nosemgrep
return gateway_account
+
def get_payment_gateway_account(args):
- return frappe.db.get_value("Payment Gateway Account", args,
+ return frappe.db.get_value(
+ "Payment Gateway Account",
+ args,
["name", "payment_gateway", "payment_account", "message"],
- as_dict=1)
+ as_dict=1,
+ )
+
@frappe.whitelist()
def get_print_format_list(ref_doctype):
print_format_list = ["Standard"]
- print_format_list.extend([p.name for p in frappe.get_all("Print Format",
- filters={"doc_type": ref_doctype})])
+ print_format_list.extend(
+ [p.name for p in frappe.get_all("Print Format", filters={"doc_type": ref_doctype})]
+ )
+
+ return {"print_format": print_format_list}
- return {
- "print_format": print_format_list
- }
@frappe.whitelist(allow_guest=True)
def resend_payment_email(docname):
return frappe.get_doc("Payment Request", docname).send_email()
+
@frappe.whitelist()
def make_payment_entry(docname):
doc = frappe.get_doc("Payment Request", docname)
return doc.create_payment_entry(submit=False).as_dict()
+
def update_payment_req_status(doc, method):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details
for ref in doc.references:
- payment_request_name = frappe.db.get_value("Payment Request",
- {"reference_doctype": ref.reference_doctype, "reference_name": ref.reference_name,
- "docstatus": 1})
+ payment_request_name = frappe.db.get_value(
+ "Payment Request",
+ {
+ "reference_doctype": ref.reference_doctype,
+ "reference_name": ref.reference_name,
+ "docstatus": 1,
+ },
+ )
if payment_request_name:
- ref_details = get_reference_details(ref.reference_doctype, ref.reference_name, doc.party_account_currency)
- pay_req_doc = frappe.get_doc('Payment Request', payment_request_name)
+ ref_details = get_reference_details(
+ ref.reference_doctype, ref.reference_name, doc.party_account_currency
+ )
+ pay_req_doc = frappe.get_doc("Payment Request", payment_request_name)
status = pay_req_doc.status
if status != "Paid" and not ref_details.outstanding_amount:
- status = 'Paid'
+ status = "Paid"
elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount:
- status = 'Partially Paid'
+ status = "Partially Paid"
elif ref_details.outstanding_amount == ref_details.total_amount:
- if pay_req_doc.payment_request_type == 'Outward':
- status = 'Initiated'
- elif pay_req_doc.payment_request_type == 'Inward':
- status = 'Requested'
+ if pay_req_doc.payment_request_type == "Outward":
+ status = "Initiated"
+ elif pay_req_doc.payment_request_type == "Inward":
+ status = "Requested"
+
+ pay_req_doc.db_set("status", status)
- pay_req_doc.db_set('status', status)
def get_dummy_message(doc):
- return frappe.render_template("""{% if doc.contact_person -%}
+ return frappe.render_template(
+ """{% if doc.contact_person -%}
"
@@ -53,10 +65,12 @@ class POSInvoiceMergeLog(Document):
frappe.throw(msg)
def on_submit(self):
- pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
+ pos_invoice_docs = [
+ 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]
- sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
+ 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_invoice, credit_note = "", ""
if returns:
@@ -65,12 +79,14 @@ class POSInvoiceMergeLog(Document):
if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales)
- self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
+ self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
def on_cancel(self):
- pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
+ pos_invoice_docs = [
+ frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices
+ ]
self.update_pos_invoices(pos_invoice_docs)
self.cancel_linked_invoices()
@@ -118,9 +134,8 @@ class POSInvoiceMergeLog(Document):
loyalty_amount_sum, loyalty_points_sum, idx = 0, 0, 1
-
for doc in data:
- map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
+ map_doc(doc, invoice, table_map={"doctype": invoice.doctype})
if doc.redeem_loyalty_points:
invoice.loyalty_redemption_account = doc.loyalty_redemption_account
@@ -128,11 +143,17 @@ class POSInvoiceMergeLog(Document):
loyalty_points_sum += doc.loyalty_points
loyalty_amount_sum += doc.loyalty_amount
- for item in doc.get('items'):
+ for item in doc.get("items"):
found = False
for i in items:
- if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and
- i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse):
+ if (
+ i.item_code == item.item_code
+ and not i.serial_no
+ and not i.batch_no
+ and i.uom == item.uom
+ and i.net_rate == item.net_rate
+ and i.warehouse == item.warehouse
+ ):
found = True
i.qty = i.qty + item.qty
i.amount = i.amount + item.net_amount
@@ -148,7 +169,7 @@ class POSInvoiceMergeLog(Document):
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
items.append(si_item)
- for tax in doc.get('taxes'):
+ for tax in doc.get("taxes"):
found = False
for t in taxes:
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
@@ -157,7 +178,7 @@ class POSInvoiceMergeLog(Document):
update_item_wise_tax_detail(t, tax)
found = True
if not found:
- tax.charge_type = 'Actual'
+ tax.charge_type = "Actual"
tax.idx = idx
idx += 1
tax.included_in_print_rate = 0
@@ -166,7 +187,7 @@ class POSInvoiceMergeLog(Document):
tax.item_wise_tax_detail = tax.item_wise_tax_detail
taxes.append(tax)
- for payment in doc.get('payments'):
+ for payment in doc.get("payments"):
found = False
for pay in payments:
if pay.account == payment.account and pay.mode_of_payment == payment.mode_of_payment:
@@ -181,52 +202,59 @@ class POSInvoiceMergeLog(Document):
base_rounding_adjustment += doc.base_rounding_adjustment
base_rounded_total += doc.base_rounded_total
-
if loyalty_points_sum:
invoice.redeem_loyalty_points = 1
invoice.loyalty_points = loyalty_points_sum
invoice.loyalty_amount = loyalty_amount_sum
- invoice.set('items', items)
- invoice.set('payments', payments)
- invoice.set('taxes', taxes)
- invoice.set('rounding_adjustment',rounding_adjustment)
- invoice.set('base_rounding_adjustment',base_rounding_adjustment)
- invoice.set('rounded_total',rounded_total)
- invoice.set('base_rounded_total',base_rounded_total)
+ invoice.set("items", items)
+ invoice.set("payments", payments)
+ invoice.set("taxes", taxes)
+ invoice.set("rounding_adjustment", rounding_adjustment)
+ invoice.set("base_rounding_adjustment", base_rounding_adjustment)
+ invoice.set("rounded_total", rounded_total)
+ invoice.set("base_rounded_total", base_rounded_total)
invoice.additional_discount_percentage = 0
invoice.discount_amount = 0.0
invoice.taxes_and_charges = None
invoice.ignore_pricing_rule = 1
invoice.customer = self.customer
- if self.merge_invoices_based_on == 'Customer Group':
+ if self.merge_invoices_based_on == "Customer Group":
invoice.flags.ignore_pos_profile = True
- invoice.pos_profile = ''
+ invoice.pos_profile = ""
return invoice
def get_new_sales_invoice(self):
- sales_invoice = frappe.new_doc('Sales Invoice')
+ sales_invoice = frappe.new_doc("Sales Invoice")
sales_invoice.customer = self.customer
sales_invoice.is_pos = 1
return sales_invoice
- def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
+ def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_note=""):
for doc in invoice_docs:
doc.load_from_db()
- doc.update({ 'consolidated_invoice': None if self.docstatus==2 else (credit_note if doc.is_return else sales_invoice) })
+ doc.update(
+ {
+ "consolidated_invoice": None
+ if self.docstatus == 2
+ else (credit_note if doc.is_return else sales_invoice)
+ }
+ )
doc.set_status(update=True)
doc.save()
def cancel_linked_invoices(self):
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
- if not si_name: continue
- si = frappe.get_doc('Sales Invoice', si_name)
+ if not si_name:
+ continue
+ si = frappe.get_doc("Sales Invoice", si_name)
si.flags.ignore_validate = True
si.cancel()
+
def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
consolidated_tax_detail = json.loads(consolidate_tax_row.item_wise_tax_detail)
tax_row_detail = json.loads(tax_row.item_wise_tax_detail)
@@ -237,70 +265,85 @@ def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
for item_code, tax_data in tax_row_detail.items():
if consolidated_tax_detail.get(item_code):
consolidated_tax_data = consolidated_tax_detail.get(item_code)
- consolidated_tax_detail.update({
- item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]]
- })
+ consolidated_tax_detail.update(
+ {item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]]}
+ )
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(
+ consolidated_tax_detail, separators=(",", ":")
+ )
- consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(',', ':'))
def get_all_unconsolidated_invoices():
filters = {
- 'consolidated_invoice': [ 'in', [ '', None ]],
- 'status': ['not in', ['Consolidated']],
- 'docstatus': 1
+ "consolidated_invoice": ["in", ["", None]],
+ "status": ["not in", ["Consolidated"]],
+ "docstatus": 1,
}
- pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
- fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer', 'is_return', 'return_against'])
+ pos_invoices = frappe.db.get_all(
+ "POS Invoice",
+ filters=filters,
+ fields=[
+ "name as pos_invoice",
+ "posting_date",
+ "grand_total",
+ "customer",
+ "is_return",
+ "return_against",
+ ],
+ )
return pos_invoices
+
def get_invoice_customer_map(pos_invoices):
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Customer 2' : [{}] }
pos_invoice_customer_map = {}
for invoice in pos_invoices:
- customer = invoice.get('customer')
+ customer = invoice.get("customer")
pos_invoice_customer_map.setdefault(customer, [])
pos_invoice_customer_map[customer].append(invoice)
return pos_invoice_customer_map
+
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
- invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions'))
+ invoices = pos_invoices or (closing_entry and closing_entry.get("pos_transactions"))
if frappe.flags.in_test and not invoices:
invoices = get_all_unconsolidated_invoices()
invoice_by_customer = get_invoice_customer_map(invoices)
if len(invoices) >= 10 and closing_entry:
- closing_entry.set_status(update=True, status='Queued')
- enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
+ closing_entry.set_status(update=True, status="Queued")
+ enqueue_job(
+ create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry
+ )
else:
create_merge_logs(invoice_by_customer, closing_entry)
+
def unconsolidate_pos_invoices(closing_entry):
merge_logs = frappe.get_all(
- 'POS Invoice Merge Log',
- filters={ 'pos_closing_entry': closing_entry.name },
- pluck='name'
+ "POS Invoice Merge Log", filters={"pos_closing_entry": closing_entry.name}, pluck="name"
)
if len(merge_logs) >= 10:
- closing_entry.set_status(update=True, status='Queued')
+ closing_entry.set_status(update=True, status="Queued")
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
else:
cancel_merge_logs(merge_logs, closing_entry)
+
def split_invoices(invoices):
- '''
+ """
Splits invoices into multiple groups
Use-case:
If a serial no is sold and later it is returned
then split the invoices such that the selling entry is merged first and then the return entry
- '''
+ """
# Input
# invoices = [
# {'pos_invoice': 'Invoice with SR#1 & SR#2', 'is_return': 0},
@@ -315,17 +358,26 @@ def split_invoices(invoices):
_invoices = []
special_invoices = []
- pos_return_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in invoices if d.is_return and d.return_against]
+ pos_return_docs = [
+ frappe.get_cached_doc("POS Invoice", d.pos_invoice)
+ for d in invoices
+ if d.is_return and d.return_against
+ ]
for pos_invoice in pos_return_docs:
for item in pos_invoice.items:
if not item.serial_no:
continue
- return_against_is_added = any(d for d in _invoices if d.pos_invoice == pos_invoice.return_against)
+ return_against_is_added = any(
+ d for d in _invoices if d.pos_invoice == pos_invoice.return_against
+ )
if return_against_is_added:
break
- return_against_is_consolidated = frappe.db.get_value('POS Invoice', pos_invoice.return_against, 'status', cache=True) == 'Consolidated'
+ return_against_is_consolidated = (
+ frappe.db.get_value("POS Invoice", pos_invoice.return_against, "status", cache=True)
+ == "Consolidated"
+ )
if return_against_is_consolidated:
break
@@ -338,22 +390,25 @@ def split_invoices(invoices):
return _invoices
+
def create_merge_logs(invoice_by_customer, closing_entry=None):
try:
for customer, invoices in invoice_by_customer.items():
for _invoices in split_invoices(invoices):
- merge_log = frappe.new_doc('POS Invoice Merge Log')
- merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
+ merge_log = frappe.new_doc("POS Invoice Merge Log")
+ merge_log.posting_date = (
+ getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
+ )
merge_log.customer = customer
- merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
+ merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
- merge_log.set('pos_invoices', _invoices)
+ merge_log.set("pos_invoices", _invoices)
merge_log.save(ignore_permissions=True)
merge_log.submit()
if closing_entry:
- closing_entry.set_status(update=True, status='Submitted')
- closing_entry.db_set('error_message', '')
+ closing_entry.set_status(update=True, status="Submitted")
+ closing_entry.db_set("error_message", "")
closing_entry.update_opening_entry()
except Exception as e:
@@ -362,24 +417,25 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
error_message = safe_load_json(message_log)
if closing_entry:
- closing_entry.set_status(update=True, status='Failed')
- closing_entry.db_set('error_message', error_message)
+ closing_entry.set_status(update=True, status="Failed")
+ closing_entry.db_set("error_message", error_message)
raise
finally:
frappe.db.commit()
- frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
+ frappe.publish_realtime("closing_process_complete", {"user": frappe.session.user})
+
def cancel_merge_logs(merge_logs, closing_entry=None):
try:
for log in merge_logs:
- merge_log = frappe.get_doc('POS Invoice Merge Log', log)
+ merge_log = frappe.get_doc("POS Invoice Merge Log", log)
merge_log.flags.ignore_permissions = True
merge_log.cancel()
if closing_entry:
- closing_entry.set_status(update=True, status='Cancelled')
- closing_entry.db_set('error_message', '')
+ closing_entry.set_status(update=True, status="Cancelled")
+ closing_entry.db_set("error_message", "")
closing_entry.update_opening_entry(for_cancel=True)
except Exception as e:
@@ -388,18 +444,19 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
error_message = safe_load_json(message_log)
if closing_entry:
- closing_entry.set_status(update=True, status='Submitted')
- closing_entry.db_set('error_message', error_message)
+ closing_entry.set_status(update=True, status="Submitted")
+ closing_entry.db_set("error_message", error_message)
raise
finally:
frappe.db.commit()
- frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
+ frappe.publish_realtime("closing_process_complete", {"user": frappe.session.user})
+
def enqueue_job(job, **kwargs):
check_scheduler_status()
- closing_entry = kwargs.get('closing_entry') or {}
+ closing_entry = kwargs.get("closing_entry") or {}
job_name = closing_entry.get("name")
if not job_already_enqueued(job_name):
@@ -414,24 +471,27 @@ def enqueue_job(job, **kwargs):
)
if job == create_merge_logs:
- msg = _('POS Invoices will be consolidated in a background process')
+ msg = _("POS Invoices will be consolidated in a background process")
else:
- msg = _('POS Invoices will be unconsolidated in a background process')
+ msg = _("POS Invoices will be unconsolidated in a background process")
frappe.msgprint(msg, alert=1)
+
def check_scheduler_status():
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
+
def job_already_enqueued(job_name):
enqueued_jobs = [d.get("job_name") for d in get_info()]
if job_name in enqueued_jobs:
return True
+
def safe_load_json(message):
try:
- json_message = json.loads(message).get('message')
+ json_message = json.loads(message).get("message")
except Exception:
json_message = message
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index fe57ce2f0f..9e696f18b6 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -24,21 +24,19 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
- pos_inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
- })
+ pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
- pos_inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
- })
+ pos_inv2.append(
+ "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}
+ )
pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
- pos_inv3.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
- })
+ pos_inv3.append(
+ "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}
+ )
pos_inv3.submit()
consolidate_pos_invoices()
@@ -63,31 +61,29 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
- pos_inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
- })
+ pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
- pos_inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
- })
+ pos_inv2.append(
+ "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}
+ )
pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
- pos_inv3.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
- })
+ pos_inv3.append(
+ "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}
+ )
pos_inv3.submit()
pos_inv_cn = make_sales_return(pos_inv.name)
pos_inv_cn.set("payments", [])
- pos_inv_cn.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -100
- })
- pos_inv_cn.append('payments', {
- 'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': -200
- })
+ pos_inv_cn.append(
+ "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -100}
+ )
+ pos_inv_cn.append(
+ "payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": -200}
+ )
pos_inv_cn.paid_amount = -300
pos_inv_cn.submit()
@@ -103,9 +99,9 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
consolidated_credit_note = frappe.get_doc("Sales Invoice", pos_inv_cn.consolidated_invoice)
self.assertEqual(consolidated_credit_note.is_return, 1)
- self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, 'Cash')
+ self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, "Cash")
self.assertEqual(consolidated_credit_note.payments[0].amount, -100)
- self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, 'Bank Draft')
+ self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, "Bank Draft")
self.assertEqual(consolidated_credit_note.payments[1].amount, -200)
finally:
@@ -119,41 +115,47 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
try:
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
- inv.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 9
- })
+ inv.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 9,
+ },
+ )
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
- inv2.get('items')[0].item_code = '_Test Item 2'
- inv2.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 5
- })
+ inv2.get("items")[0].item_code = "_Test Item 2"
+ inv2.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 5,
+ },
+ )
inv2.insert()
inv2.submit()
consolidate_pos_invoices()
inv.load_from_db()
- consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
- item_wise_tax_detail = json.loads(consolidated_invoice.get('taxes')[0].item_wise_tax_detail)
+ consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
+ item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
- tax_rate, amount = item_wise_tax_detail.get('_Test Item')
+ tax_rate, amount = item_wise_tax_detail.get("_Test Item")
self.assertEqual(tax_rate, 9)
self.assertEqual(amount, 9)
- tax_rate2, amount2 = item_wise_tax_detail.get('_Test Item 2')
+ tax_rate2, amount2 = item_wise_tax_detail.get("_Test Item 2")
self.assertEqual(tax_rate2, 5)
self.assertEqual(amount2, 5)
finally:
@@ -161,11 +163,10 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
-
def test_consolidation_round_off_error_1(self):
- '''
+ """
Test round off error in consolidated invoice creation if POS Invoice has inclusive tax
- '''
+ """
frappe.db.sql("delete from `tabPOS Invoice`")
@@ -180,43 +181,45 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
init_user_and_profile()
inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
- inv.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 7.5,
- "included_in_print_rate": 1
- })
- inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 30000
- })
+ inv.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 7.5,
+ "included_in_print_rate": 1,
+ },
+ )
+ inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
- inv2.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 7.5,
- "included_in_print_rate": 1
- })
- inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 30000
- })
+ inv2.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 7.5,
+ "included_in_print_rate": 1,
+ },
+ )
+ inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv2.insert()
inv2.submit()
consolidate_pos_invoices()
inv.load_from_db()
- consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+ consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.outstanding_amount, 0)
- self.assertEqual(consolidated_invoice.status, 'Paid')
+ self.assertEqual(consolidated_invoice.status, "Paid")
finally:
frappe.set_user("Administrator")
@@ -224,9 +227,9 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidation_round_off_error_2(self):
- '''
+ """
Test the same case as above but with an Unpaid POS Invoice
- '''
+ """
frappe.db.sql("delete from `tabPOS Invoice`")
try:
@@ -240,57 +243,59 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
init_user_and_profile()
inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
- inv.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 7.5,
- "included_in_print_rate": 1
- })
- inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60000
- })
+ inv.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 7.5,
+ "included_in_print_rate": 1,
+ },
+ )
+ inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
- inv2.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 7.5,
- "included_in_print_rate": 1
- })
- inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60000
- })
+ inv2.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 7.5,
+ "included_in_print_rate": 1,
+ },
+ )
+ inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv2.insert()
inv2.submit()
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
- inv3.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000
- })
+ inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
inv3.insert()
inv3.submit()
consolidate_pos_invoices()
inv.load_from_db()
- consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+ consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.outstanding_amount, 800)
- self.assertNotEqual(consolidated_invoice.status, 'Paid')
+ self.assertNotEqual(consolidated_invoice.status, "Paid")
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
- @change_settings("System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3})
+ @change_settings(
+ "System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}
+ )
def test_consolidation_round_off_error_3(self):
frappe.db.sql("delete from `tabPOS Invoice`")
@@ -308,28 +313,32 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:
- inv.append("items", {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC",
- "qty": -1,
- "rate": rate,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
+ inv.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": -1,
+ "rate": rate,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
+ inv.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
- })
- inv.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 15,
- "included_in_print_rate": 1
- })
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 15,
+ "included_in_print_rate": 1,
+ },
+ )
inv.payments = []
- inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -157
- })
+ inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -157})
inv.paid_amount = -157
inv.save()
inv.submit()
@@ -337,8 +346,8 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
consolidate_pos_invoices()
inv.load_from_db()
- consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
- self.assertEqual(consolidated_invoice.status, 'Return')
+ consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
+ self.assertEqual(consolidated_invoice.status, "Return")
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
finally:
@@ -347,9 +356,9 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidation_rounding_adjustment(self):
- '''
+ """
Test if the rounding adjustment is calculated correctly
- '''
+ """
frappe.db.sql("delete from `tabPOS Invoice`")
try:
@@ -363,23 +372,19 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
init_user_and_profile()
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
- inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 70
- })
+ inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70})
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
- inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60
- })
+ inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60})
inv2.insert()
inv2.submit()
consolidate_pos_invoices()
inv.load_from_db()
- consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+ consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
finally:
@@ -388,7 +393,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
def test_serial_no_case_1(self):
- '''
+ """
Create a POS Invoice with serial no
Create a Return Invoice with serial no
Create a POS Invoice with serial no again
@@ -396,7 +401,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
The first POS Invoice should be consolidated with a separate 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
@@ -410,15 +415,13 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
init_user_and_profile()
pos_inv = create_pos_invoice(
- item_code="_Test Serialized Item With Series",
- serial_no=serial_no,
- qty=1,
- rate=100,
- do_not_submit=1
+ item_code="_Test Serialized Item With Series",
+ serial_no=serial_no,
+ qty=1,
+ rate=100,
+ do_not_submit=1,
)
- pos_inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 100
- })
+ pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv.submit()
pos_inv_cn = make_sales_return(pos_inv.name)
@@ -426,15 +429,13 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
pos_inv_cn.submit()
pos_inv2 = create_pos_invoice(
- item_code="_Test Serialized Item With Series",
- serial_no=serial_no,
- qty=1,
- rate=100,
- do_not_submit=1
+ item_code="_Test Serialized Item With Series",
+ serial_no=serial_no,
+ qty=1,
+ rate=100,
+ do_not_submit=1,
)
- pos_inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 100
- })
+ pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv2.submit()
consolidate_pos_invoices()
@@ -447,4 +448,4 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
- frappe.db.sql("delete from `tabPOS Invoice`")
\ No newline at end of file
+ frappe.db.sql("delete from `tabPOS Invoice`")
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
index 0b2e045e5a..3cd14264bb 100644
--- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
@@ -17,7 +17,9 @@ class POSOpeningEntry(StatusUpdater):
def validate_pos_profile_and_cashier(self):
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
- frappe.throw(_("POS Profile {} does not belongs to company {}").format(self.pos_profile, self.company))
+ frappe.throw(
+ _("POS Profile {} does not belongs to company {}").format(self.pos_profile, self.company)
+ )
if not cint(frappe.db.get_value("User", self.user, "enabled")):
frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user))
@@ -26,8 +28,11 @@ class POSOpeningEntry(StatusUpdater):
invalid_modes = []
for d in self.balance_details:
if d.mode_of_payment:
- account = frappe.db.get_value("Mode of Payment Account",
- {"parent": d.mode_of_payment, "company": self.company}, "default_account")
+ account = frappe.db.get_value(
+ "Mode of Payment Account",
+ {"parent": d.mode_of_payment, "company": self.company},
+ "default_account",
+ )
if not account:
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
diff --git a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py
index 105d53d00e..64c658ab15 100644
--- a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py
+++ b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py
@@ -9,6 +9,7 @@ import frappe
class TestPOSOpeningEntry(unittest.TestCase):
pass
+
def create_opening_entry(pos_profile, user):
entry = frappe.new_doc("POS Opening Entry")
entry.pos_profile = pos_profile.name
@@ -16,11 +17,9 @@ def create_opening_entry(pos_profile, user):
entry.company = pos_profile.company
entry.period_start_date = frappe.utils.get_datetime()
- balance_details = [];
+ balance_details = []
for d in pos_profile.payments:
- balance_details.append(frappe._dict({
- 'mode_of_payment': d.mode_of_payment
- }))
+ balance_details.append(frappe._dict({"mode_of_payment": d.mode_of_payment}))
entry.set("balance_details", balance_details)
entry.submit()
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index 1d49c3df14..65fd4af350 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -17,29 +17,42 @@ class POSProfile(Document):
def validate_default_profile(self):
for row in self.applicable_for_users:
- res = frappe.db.sql("""select pf.name
+ res = frappe.db.sql(
+ """select pf.name
from
`tabPOS Profile User` pfu, `tabPOS Profile` pf
where
pf.name = pfu.parent and pfu.user = %s and pf.name != %s and pf.company = %s
- and pfu.default=1 and pf.disabled = 0""", (row.user, self.name, self.company))
+ and pfu.default=1 and pf.disabled = 0""",
+ (row.user, self.name, self.company),
+ )
if row.default and res:
- msgprint(_("Already set default in pos profile {0} for user {1}, kindly disabled default")
- .format(res[0][0], row.user), raise_exception=1)
+ msgprint(
+ _("Already set default in pos profile {0} for user {1}, kindly disabled default").format(
+ res[0][0], row.user
+ ),
+ raise_exception=1,
+ )
elif not row.default and not res:
- msgprint(_("User {0} doesn't have any default POS Profile. Check Default at Row {1} for this User.")
- .format(row.user, row.idx))
+ msgprint(
+ _(
+ "User {0} doesn't have any default POS Profile. Check Default at Row {1} for this User."
+ ).format(row.user, row.idx)
+ )
def validate_all_link_fields(self):
- accounts = {"Account": [self.income_account,
- self.expense_account], "Cost Center": [self.cost_center],
- "Warehouse": [self.warehouse]}
+ accounts = {
+ "Account": [self.income_account, self.expense_account],
+ "Cost Center": [self.cost_center],
+ "Warehouse": [self.warehouse],
+ }
for link_dt, dn_list in accounts.items():
for link_dn in dn_list:
- if link_dn and not frappe.db.exists({"doctype": link_dt,
- "company": self.company, "name": link_dn}):
+ if link_dn and not frappe.db.exists(
+ {"doctype": link_dt, "company": self.company, "name": link_dn}
+ ):
frappe.throw(_("{0} does not belong to Company {1}").format(link_dn, self.company))
def validate_duplicate_groups(self):
@@ -47,10 +60,15 @@ class POSProfile(Document):
customer_groups = [d.customer_group for d in self.customer_groups]
if len(item_groups) != len(set(item_groups)):
- frappe.throw(_("Duplicate item group found in the item group table"), title = "Duplicate Item Group")
+ frappe.throw(
+ _("Duplicate item group found in the item group table"), title="Duplicate Item Group"
+ )
if len(customer_groups) != len(set(customer_groups)):
- frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
+ frappe.throw(
+ _("Duplicate customer group found in the cutomer group table"),
+ title="Duplicate Customer Group",
+ )
def validate_payment_methods(self):
if not self.payments:
@@ -68,7 +86,7 @@ class POSProfile(Document):
account = frappe.db.get_value(
"Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company},
- "default_account"
+ "default_account",
)
if not account:
@@ -91,12 +109,16 @@ class POSProfile(Document):
frappe.defaults.clear_default("is_pos")
if not include_current_pos:
- condition = " where pfu.name != '%s' and pfu.default = 1 " % self.name.replace("'", "\'")
+ condition = " where pfu.name != '%s' and pfu.default = 1 " % self.name.replace("'", "'")
else:
condition = " where pfu.default = 1 "
- pos_view_users = frappe.db.sql_list("""select pfu.user
- from `tabPOS Profile User` as pfu {0}""".format(condition))
+ pos_view_users = frappe.db.sql_list(
+ """select pfu.user
+ from `tabPOS Profile User` as pfu {0}""".format(
+ condition
+ )
+ )
for user in pos_view_users:
if user:
@@ -104,48 +126,62 @@ class POSProfile(Document):
else:
frappe.defaults.set_global_default("is_pos", 1)
+
def get_item_groups(pos_profile):
item_groups = []
- pos_profile = frappe.get_cached_doc('POS Profile', pos_profile)
+ pos_profile = frappe.get_cached_doc("POS Profile", pos_profile)
- if pos_profile.get('item_groups'):
+ if pos_profile.get("item_groups"):
# Get items based on the item groups defined in the POS profile
- for data in pos_profile.get('item_groups'):
- item_groups.extend(["%s" % frappe.db.escape(d.name) for d in get_child_nodes('Item Group', data.item_group)])
+ for data in pos_profile.get("item_groups"):
+ item_groups.extend(
+ ["%s" % frappe.db.escape(d.name) for d in get_child_nodes("Item Group", data.item_group)]
+ )
return list(set(item_groups))
+
def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
- return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
- lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
+ return frappe.db.sql(
+ """ Select name, lft, rgt from `tab{tab}` where
+ lft >= {lft} and rgt <= {rgt} order by lft""".format(
+ tab=group_type, lft=lft, rgt=rgt
+ ),
+ as_dict=1,
+ )
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
- user = frappe.session['user']
- company = filters.get('company') or frappe.defaults.get_user_default('company')
+ user = frappe.session["user"]
+ company = filters.get("company") or frappe.defaults.get_user_default("company")
args = {
- 'user': user,
- 'start': start,
- 'company': company,
- 'page_len': page_len,
- 'txt': '%%%s%%' % txt
+ "user": user,
+ "start": start,
+ "company": company,
+ "page_len": page_len,
+ "txt": "%%%s%%" % txt,
}
- pos_profile = frappe.db.sql("""select pf.name
+ pos_profile = frappe.db.sql(
+ """select pf.name
from
`tabPOS Profile` pf, `tabPOS Profile User` pfu
where
pfu.parent = pf.name and pfu.user = %(user)s and pf.company = %(company)s
and (pf.name like %(txt)s)
- and pf.disabled = 0 limit %(start)s, %(page_len)s""", args)
+ and pf.disabled = 0 limit %(start)s, %(page_len)s""",
+ args,
+ )
if not pos_profile:
- del args['user']
+ del args["user"]
- pos_profile = frappe.db.sql("""select pf.name
+ pos_profile = frappe.db.sql(
+ """select pf.name
from
`tabPOS Profile` pf left join `tabPOS Profile User` pfu
on
@@ -154,26 +190,37 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
ifnull(pfu.user, '') = ''
and pf.company = %(company)s
and pf.name like %(txt)s
- and pf.disabled = 0""", args)
+ and pf.disabled = 0""",
+ args,
+ )
return pos_profile
+
@frappe.whitelist()
def set_default_profile(pos_profile, company):
modified = now()
user = frappe.session.user
if pos_profile and company:
- frappe.db.sql(""" update `tabPOS Profile User` pfu, `tabPOS Profile` pf
+ frappe.db.sql(
+ """ update `tabPOS Profile User` pfu, `tabPOS Profile` pf
set
pfu.default = 0, pf.modified = %s, pf.modified_by = %s
where
pfu.user = %s and pf.name = pfu.parent and pf.company = %s
- and pfu.default = 1""", (modified, user, user, company), auto_commit=1)
+ and pfu.default = 1""",
+ (modified, user, user, company),
+ auto_commit=1,
+ )
- frappe.db.sql(""" update `tabPOS Profile User` pfu, `tabPOS Profile` pf
+ frappe.db.sql(
+ """ update `tabPOS Profile User` pfu, `tabPOS Profile` pf
set
pfu.default = 1, pf.modified = %s, pf.modified_by = %s
where
pfu.user = %s and pf.name = pfu.parent and pf.company = %s and pf.name = %s
- """, (modified, user, user, company, pos_profile), auto_commit=1)
+ """,
+ (modified, user, user, company, pos_profile),
+ auto_commit=1,
+ )
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index c8cf0d2a0f..788aa62701 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -8,7 +8,8 @@ import frappe
from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes
from erpnext.stock.get_item_details import get_pos_profile
-test_dependencies = ['Item']
+test_dependencies = ["Item"]
+
class TestPOSProfile(unittest.TestCase):
def test_pos_profile(self):
@@ -17,46 +18,64 @@ class TestPOSProfile(unittest.TestCase):
pos_profile = get_pos_profile("_Test Company") or {}
if pos_profile:
doc = frappe.get_doc("POS Profile", pos_profile.get("name"))
- doc.append('item_groups', {'item_group': '_Test Item Group'})
- doc.append('customer_groups', {'customer_group': '_Test Customer Group'})
+ doc.append("item_groups", {"item_group": "_Test Item Group"})
+ doc.append("customer_groups", {"customer_group": "_Test Customer Group"})
doc.save()
items = get_items_list(doc, doc.company)
customers = get_customers_list(doc)
- products_count = frappe.db.sql(""" select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1)
- customers_count = frappe.db.sql(""" select count(name) from tabCustomer where customer_group = '_Test Customer Group'""")
+ products_count = frappe.db.sql(
+ """ select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1
+ )
+ customers_count = frappe.db.sql(
+ """ select count(name) from tabCustomer where customer_group = '_Test Customer Group'"""
+ )
self.assertEqual(len(items), products_count[0][0])
self.assertEqual(len(customers), customers_count[0][0])
frappe.db.sql("delete from `tabPOS Profile`")
+
def get_customers_list(pos_profile=None):
if pos_profile is None:
pos_profile = {}
cond = "1=1"
customer_groups = []
- if pos_profile.get('customer_groups'):
+ if pos_profile.get("customer_groups"):
# Get customers based on the customer groups defined in the POS profile
- for d in pos_profile.get('customer_groups'):
- customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))])
- cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups)))
+ for d in pos_profile.get("customer_groups"):
+ customer_groups.extend(
+ [d.get("name") for d in get_child_nodes("Customer Group", d.get("customer_group"))]
+ )
+ cond = "customer_group in (%s)" % (", ".join(["%s"] * len(customer_groups)))
- return frappe.db.sql(""" select name, customer_name, customer_group,
+ return (
+ frappe.db.sql(
+ """ select name, customer_name, customer_group,
territory, customer_pos_id from tabCustomer where disabled = 0
- and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {}
+ and {cond}""".format(
+ cond=cond
+ ),
+ tuple(customer_groups),
+ as_dict=1,
+ )
+ or {}
+ )
+
def get_items_list(pos_profile, company):
cond = ""
args_list = []
- if pos_profile.get('item_groups'):
+ if pos_profile.get("item_groups"):
# Get items based on the item groups defined in the POS profile
- for d in pos_profile.get('item_groups'):
- args_list.extend([d.name for d in get_child_nodes('Item Group', d.item_group)])
+ for d in pos_profile.get("item_groups"):
+ args_list.extend([d.name for d in get_child_nodes("Item Group", d.item_group)])
if args_list:
- 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(
+ """
select
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,
@@ -69,7 +88,13 @@ def get_items_list(pos_profile, company):
where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.is_fixed_asset = 0
{cond}
- """.format(cond=cond), tuple([company] + args_list), as_dict=1)
+ """.format(
+ cond=cond
+ ),
+ tuple([company] + args_list),
+ as_dict=1,
+ )
+
def make_pos_profile(**args):
frappe.db.sql("delete from `tabPOS Payment Method`")
@@ -77,38 +102,34 @@ def make_pos_profile(**args):
args = frappe._dict(args)
- pos_profile = frappe.get_doc({
- "company": args.company or "_Test Company",
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "currency": args.currency or "INR",
- "doctype": "POS Profile",
- "expense_account": args.expense_account or "_Test Account Cost for Goods Sold - _TC",
- "income_account": args.income_account or "Sales - _TC",
- "name": args.name or "_Test POS Profile",
- "naming_series": "_T-POS Profile-",
- "selling_price_list": args.selling_price_list or "_Test Price List",
- "territory": args.territory or "_Test Territory",
- "customer_group": frappe.db.get_value('Customer Group', {'is_group': 0}, 'name'),
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "write_off_account": args.write_off_account or "_Test Write Off - _TC",
- "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
- })
+ pos_profile = frappe.get_doc(
+ {
+ "company": args.company or "_Test Company",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "currency": args.currency or "INR",
+ "doctype": "POS Profile",
+ "expense_account": args.expense_account or "_Test Account Cost for Goods Sold - _TC",
+ "income_account": args.income_account or "Sales - _TC",
+ "name": args.name or "_Test POS Profile",
+ "naming_series": "_T-POS Profile-",
+ "selling_price_list": args.selling_price_list or "_Test Price List",
+ "territory": args.territory or "_Test Territory",
+ "customer_group": frappe.db.get_value("Customer Group", {"is_group": 0}, "name"),
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "write_off_account": args.write_off_account or "_Test Write Off - _TC",
+ "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC",
+ }
+ )
mode_of_payment = frappe.get_doc("Mode of Payment", "Cash")
company = args.company or "_Test Company"
default_account = args.income_account or "Sales - _TC"
if not frappe.db.get_value("Mode of Payment Account", {"company": company, "parent": "Cash"}):
- mode_of_payment.append("accounts", {
- "company": company,
- "default_account": default_account
- })
+ mode_of_payment.append("accounts", {"company": company, "default_account": default_account})
mode_of_payment.save()
- pos_profile.append("payments", {
- 'mode_of_payment': 'Cash',
- 'default': 1
- })
+ pos_profile.append("payments", {"mode_of_payment": "Cash", "default": 1})
if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
pos_profile.insert()
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 933fda8a0a..08cec6a858 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -12,11 +12,11 @@ from frappe import _, throw
from frappe.model.document import Document
from frappe.utils import cint, flt, getdate
-apply_on_dict = {"Item Code": "items",
- "Item Group": "item_groups", "Brand": "brands"}
+apply_on_dict = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}
other_fields = ["other_item_code", "other_item_group", "other_brand"]
+
class PricingRule(Document):
def validate(self):
self.validate_mandatory()
@@ -31,7 +31,8 @@ class PricingRule(Document):
self.validate_dates()
self.validate_condition()
- if not self.margin_type: self.margin_rate_or_amount = 0.0
+ if not self.margin_type:
+ self.margin_rate_or_amount = 0.0
def validate_duplicate_apply_on(self):
field = apply_on_dict.get(self.apply_on)
@@ -49,36 +50,51 @@ class PricingRule(Document):
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
if self.apply_rule_on_other:
- o_field = 'other_' + frappe.scrub(self.apply_rule_on_other)
+ o_field = "other_" + frappe.scrub(self.apply_rule_on_other)
if not self.get(o_field) and o_field in other_fields:
- frappe.throw(_("For the 'Apply Rule On Other' condition the field {0} is mandatory")
- .format(frappe.bold(self.apply_rule_on_other)))
+ frappe.throw(
+ _("For the 'Apply Rule On Other' condition the field {0} is mandatory").format(
+ frappe.bold(self.apply_rule_on_other)
+ )
+ )
-
- if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
+ if self.price_or_product_discount == "Price" and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
if self.apply_discount_on_rate:
if not self.priority:
- throw(_("As the field {0} is enabled, the field {1} is mandatory.")
- .format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
+ throw(
+ _("As the field {0} is enabled, the field {1} is mandatory.").format(
+ frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")
+ )
+ )
if self.priority and cint(self.priority) == 1:
- 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")))
+ 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")
+ )
+ )
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected"))
- if not self.selling and self.applicable_for in ["Customer", "Customer Group",
- "Territory", "Sales Partner", "Campaign"]:
- throw(_("Selling must be checked, if Applicable For is selected as {0}")
- .format(self.applicable_for))
+ if not self.selling and self.applicable_for in [
+ "Customer",
+ "Customer Group",
+ "Territory",
+ "Sales Partner",
+ "Campaign",
+ ]:
+ throw(
+ _("Selling must be checked, if Applicable For is selected as {0}").format(self.applicable_for)
+ )
if not self.buying and self.applicable_for in ["Supplier", "Supplier Group"]:
- throw(_("Buying must be checked, if Applicable For is selected as {0}")
- .format(self.applicable_for))
+ throw(
+ _("Buying must be checked, if Applicable For is selected as {0}").format(self.applicable_for)
+ )
def validate_min_max_qty(self):
if self.min_qty and self.max_qty and flt(self.min_qty) > flt(self.max_qty):
@@ -95,11 +111,12 @@ class PricingRule(Document):
# reset all values except for the logic field
options = (self.meta.get_options(logic_field) or "").split("\n")
for f in options:
- if not f: continue
+ if not f:
+ continue
scrubbed_f = frappe.scrub(f)
- if logic_field == 'apply_on':
+ if logic_field == "apply_on":
apply_on_f = apply_on_dict.get(f, f)
else:
apply_on_f = scrubbed_f
@@ -112,8 +129,11 @@ class PricingRule(Document):
apply_rule_on_other = frappe.scrub(self.apply_rule_on_other or "")
- cleanup_other_fields = (other_fields if not apply_rule_on_other
- else [o_field for o_field in other_fields if o_field != 'other_' + apply_rule_on_other])
+ cleanup_other_fields = (
+ other_fields
+ if not apply_rule_on_other
+ else [o_field for o_field in other_fields if o_field != "other_" + apply_rule_on_other]
+ )
for other_field in cleanup_other_fields:
self.set(other_field, None)
@@ -123,7 +143,7 @@ class PricingRule(Document):
if flt(self.get(frappe.scrub(field))) < 0:
throw(_("{0} can not be negative").format(field))
- if self.price_or_product_discount == 'Product' and not self.free_item:
+ if self.price_or_product_discount == "Product" and not self.free_item:
if self.mixed_conditions:
frappe.throw(_("Free item code is not selected"))
else:
@@ -150,31 +170,37 @@ class PricingRule(Document):
frappe.throw(_("Valid from date must be less than valid upto date"))
def validate_condition(self):
- if self.condition and ("=" in self.condition) and re.match(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+', self.condition):
+ if (
+ self.condition
+ and ("=" in self.condition)
+ and re.match(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+', self.condition)
+ ):
frappe.throw(_("Invalid condition expression"))
-#--------------------------------------------------------------------------------
+
+# --------------------------------------------------------------------------------
+
@frappe.whitelist()
def apply_pricing_rule(args, doc=None):
"""
- args = {
- "items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
- "customer": "something",
- "customer_group": "something",
- "territory": "something",
- "supplier": "something",
- "supplier_group": "something",
- "currency": "something",
- "conversion_rate": "something",
- "price_list": "something",
- "plc_conversion_rate": "something",
- "company": "something",
- "transaction_date": "something",
- "campaign": "something",
- "sales_partner": "something",
- "ignore_pricing_rule": "something"
- }
+ args = {
+ "items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
+ "customer": "something",
+ "customer_group": "something",
+ "territory": "something",
+ "supplier": "something",
+ "supplier_group": "something",
+ "currency": "something",
+ "conversion_rate": "something",
+ "price_list": "something",
+ "plc_conversion_rate": "something",
+ "company": "something",
+ "transaction_date": "something",
+ "campaign": "something",
+ "sales_partner": "something",
+ "ignore_pricing_rule": "something"
+ }
"""
if isinstance(args, str):
@@ -188,16 +214,23 @@ def apply_pricing_rule(args, doc=None):
# list of dictionaries
out = []
- if args.get("doctype") == "Material Request": return out
+ if args.get("doctype") == "Material Request":
+ return out
item_list = args.get("items")
args.pop("items")
- set_serial_nos_based_on_fifo = frappe.db.get_single_value("Stock Settings",
- "automatically_set_serial_nos_based_on_fifo")
+ set_serial_nos_based_on_fifo = frappe.db.get_single_value(
+ "Stock Settings", "automatically_set_serial_nos_based_on_fifo"
+ )
- item_code_list = tuple(item.get('item_code') for item in item_list)
- query_items = frappe.get_all('Item', fields=['item_code','has_serial_no'], filters=[['item_code','in',item_code_list]],as_list=1)
+ item_code_list = tuple(item.get("item_code") for item in item_list)
+ query_items = frappe.get_all(
+ "Item",
+ fields=["item_code", "has_serial_no"],
+ filters=[["item_code", "in", item_code_list]],
+ as_list=1,
+ )
serialized_items = dict()
for item_code, val in query_items:
serialized_items.setdefault(item_code, val)
@@ -205,26 +238,31 @@ def apply_pricing_rule(args, doc=None):
for item in item_list:
args_copy = copy.deepcopy(args)
args_copy.update(item)
- data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc)
+ data = get_pricing_rule_for_item(args_copy, item.get("price_list_rate"), doc=doc)
out.append(data)
- if serialized_items.get(item.get('item_code')) and not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
+ if (
+ serialized_items.get(item.get("item_code"))
+ and not item.get("serial_no")
+ and set_serial_nos_based_on_fifo
+ and not args.get("is_return")
+ ):
out[0].update(get_serial_no_for_item(args_copy))
return out
+
def get_serial_no_for_item(args):
from erpnext.stock.get_item_details import get_serial_no
- item_details = frappe._dict({
- "doctype": args.doctype,
- "name": args.name,
- "serial_no": args.serial_no
- })
+ item_details = frappe._dict(
+ {"doctype": args.doctype, "name": args.name, "serial_no": args.serial_no}
+ )
if args.get("parenttype") in ("Sales Invoice", "Delivery Note") and flt(args.stock_qty) > 0:
item_details.serial_no = get_serial_no(args)
return item_details
+
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
@@ -239,18 +277,20 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if doc:
doc = frappe.get_doc(doc)
- if (args.get('is_free_item') or
- args.get("parenttype") == "Material Request"): return {}
+ if args.get("is_free_item") or args.get("parenttype") == "Material Request":
+ return {}
- item_details = frappe._dict({
- "doctype": args.doctype,
- "has_margin": False,
- "name": args.name,
- "free_item_data": [],
- "parent": args.parent,
- "parenttype": args.parenttype,
- "child_docname": args.get('child_docname'),
- })
+ item_details = frappe._dict(
+ {
+ "doctype": args.doctype,
+ "has_margin": False,
+ "name": args.name,
+ "free_item_data": [],
+ "parent": args.parent,
+ "parenttype": args.parenttype,
+ "child_docname": args.get("child_docname"),
+ }
+ )
if args.ignore_pricing_rule or not args.item_code:
if frappe.db.exists(args.doctype, args.name) and args.get("pricing_rules"):
@@ -264,20 +304,25 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
update_args_for_pricing_rule(args)
- pricing_rules = (get_applied_pricing_rules(args.get('pricing_rules'))
- if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
+ pricing_rules = (
+ get_applied_pricing_rules(args.get("pricing_rules"))
+ if for_validate and args.get("pricing_rules")
+ else get_pricing_rules(args, doc)
+ )
if pricing_rules:
rules = []
for pricing_rule in pricing_rules:
- if not pricing_rule: continue
+ if not pricing_rule:
+ continue
if isinstance(pricing_rule, str):
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
- if pricing_rule.get('suggestion'): continue
+ if pricing_rule.get("suggestion"):
+ continue
item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
item_details.price_or_product_discount = pricing_rule.get("price_or_product_discount")
@@ -285,14 +330,19 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
rules.append(get_pricing_rule_details(args, pricing_rule))
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
- item_details.update({
- 'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
- 'price_or_product_discount': pricing_rule.price_or_product_discount,
- 'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
- if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
- })
+ item_details.update(
+ {
+ "apply_rule_on_other_items": json.dumps(pricing_rule.apply_rule_on_other_items),
+ "price_or_product_discount": pricing_rule.price_or_product_discount,
+ "apply_rule_on": (
+ frappe.scrub(pricing_rule.apply_rule_on_other)
+ if pricing_rule.apply_rule_on_other
+ else frappe.scrub(pricing_rule.get("apply_on"))
+ ),
+ }
+ )
- if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
+ if pricing_rule.coupon_code_based == 1 and args.coupon_code == None:
return item_details
if not pricing_rule.validate_applied_rule:
@@ -309,7 +359,8 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
- if not doc: return item_details
+ if not doc:
+ return item_details
elif args.get("pricing_rules"):
item_details = remove_pricing_rule_for_item(
@@ -321,19 +372,22 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
return item_details
+
def update_args_for_pricing_rule(args):
if not (args.item_group and args.brand):
try:
- args.item_group, args.brand = frappe.get_cached_value("Item", args.item_code, ["item_group", "brand"])
+ args.item_group, args.brand = frappe.get_cached_value(
+ "Item", args.item_code, ["item_group", "brand"]
+ )
except frappe.DoesNotExistError:
return
if not args.item_group:
frappe.throw(_("Item Group not mentioned in item master for item {0}").format(args.item_code))
- if args.transaction_type=="selling":
+ if args.transaction_type == "selling":
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()
else:
customer = frappe.get_cached_value("Customer", args.customer, ["customer_group", "territory"])
@@ -347,20 +401,25 @@ def update_args_for_pricing_rule(args):
args.supplier_group = frappe.get_cached_value("Supplier", args.supplier, "supplier_group")
args.customer = args.customer_group = args.territory = None
+
def get_pricing_rule_details(args, pricing_rule):
- return frappe._dict({
- 'pricing_rule': pricing_rule.name,
- 'rate_or_discount': pricing_rule.rate_or_discount,
- 'margin_type': pricing_rule.margin_type,
- 'item_code': args.get("item_code"),
- 'child_docname': args.get('child_docname')
- })
+ return frappe._dict(
+ {
+ "pricing_rule": pricing_rule.name,
+ "rate_or_discount": pricing_rule.rate_or_discount,
+ "margin_type": pricing_rule.margin_type,
+ "item_code": args.get("item_code"),
+ "child_docname": args.get("child_docname"),
+ }
+ )
+
def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount
- if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
- or (pricing_rule.margin_type == 'Percentage')):
+ if (
+ pricing_rule.margin_type in ["Amount", "Percentage"] and pricing_rule.currency == args.currency
+ ) or (pricing_rule.margin_type == "Percentage"):
item_details.margin_type = pricing_rule.margin_type
item_details.has_margin = True
@@ -369,7 +428,7 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
else:
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
- if pricing_rule.rate_or_discount == 'Rate':
+ if pricing_rule.rate_or_discount == "Rate":
pricing_rule_rate = 0.0
if pricing_rule.currency == args.currency:
pricing_rule_rate = pricing_rule.rate
@@ -377,63 +436,71 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule_rate:
# Override already set price list rate (from item price)
# if pricing_rule_rate > 0
- item_details.update({
- "price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
- })
- item_details.update({
- "discount_percentage": 0.0
- })
+ item_details.update(
+ {
+ "price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
+ }
+ )
+ item_details.update({"discount_percentage": 0.0})
- for apply_on in ['Discount Amount', 'Discount Percentage']:
- if pricing_rule.rate_or_discount != apply_on: continue
+ for apply_on in ["Discount Amount", "Discount Percentage"]:
+ if pricing_rule.rate_or_discount != apply_on:
+ continue
field = frappe.scrub(apply_on)
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
# Apply discount on discounted rate
- item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
+ item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)
else:
if field not in item_details:
item_details.setdefault(field, 0)
- item_details[field] += (pricing_rule.get(field, 0)
- if pricing_rule else args.get(field, 0))
+ item_details[field] += pricing_rule.get(field, 0) if pricing_rule else args.get(field, 0)
+
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
get_pricing_rule_items,
)
- for d in get_applied_pricing_rules(pricing_rules):
- if not d or not frappe.db.exists("Pricing Rule", d): continue
- pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
- if pricing_rule.price_or_product_discount == 'Price':
- if pricing_rule.rate_or_discount == 'Discount Percentage':
+ for d in get_applied_pricing_rules(pricing_rules):
+ if not d or not frappe.db.exists("Pricing Rule", d):
+ continue
+ pricing_rule = frappe.get_cached_doc("Pricing Rule", d)
+
+ if pricing_rule.price_or_product_discount == "Price":
+ if pricing_rule.rate_or_discount == "Discount Percentage":
item_details.discount_percentage = 0.0
item_details.discount_amount = 0.0
item_details.rate = rate or 0.0
- if pricing_rule.rate_or_discount == 'Discount Amount':
+ if pricing_rule.rate_or_discount == "Discount Amount":
item_details.discount_amount = 0.0
- if pricing_rule.margin_type in ['Percentage', 'Amount']:
+ if pricing_rule.margin_type in ["Percentage", "Amount"]:
item_details.margin_rate_or_amount = 0.0
item_details.margin_type = None
- elif pricing_rule.get('free_item'):
- item_details.remove_free_item = (item_code if pricing_rule.get('same_item')
- else pricing_rule.get('free_item'))
+ elif pricing_rule.get("free_item"):
+ item_details.remove_free_item = (
+ item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item")
+ )
if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"):
items = get_pricing_rule_items(pricing_rule)
- item_details.apply_on = (frappe.scrub(pricing_rule.apply_rule_on_other)
- if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
- item_details.applied_on_items = ','.join(items)
+ item_details.apply_on = (
+ frappe.scrub(pricing_rule.apply_rule_on_other)
+ if pricing_rule.apply_rule_on_other
+ else frappe.scrub(pricing_rule.get("apply_on"))
+ )
+ item_details.applied_on_items = ",".join(items)
- item_details.pricing_rules = ''
+ item_details.pricing_rules = ""
item_details.pricing_rule_removed = True
return item_details
+
@frappe.whitelist()
def remove_pricing_rules(item_list):
if isinstance(item_list, str):
@@ -451,19 +518,26 @@ def remove_pricing_rules(item_list):
return out
+
def set_transaction_type(args):
if args.transaction_type:
return
if args.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
args.transaction_type = "selling"
- elif args.doctype in ("Material Request", "Supplier Quotation", "Purchase Order",
- "Purchase Receipt", "Purchase Invoice"):
- args.transaction_type = "buying"
+ elif args.doctype in (
+ "Material Request",
+ "Supplier Quotation",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ ):
+ args.transaction_type = "buying"
elif args.customer:
args.transaction_type = "selling"
else:
args.transaction_type = "buying"
+
@frappe.whitelist()
def make_pricing_rule(doctype, docname):
doc = frappe.new_doc("Pricing Rule")
@@ -474,15 +548,18 @@ def make_pricing_rule(doctype, docname):
return doc
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
- items = [filters.get('value')]
- if filters.get('apply_on') != 'Item Code':
- field = frappe.scrub(filters.get('apply_on'))
- items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get('value')})]
+ items = [filters.get("value")]
+ if filters.get("apply_on") != "Item Code":
+ field = frappe.scrub(filters.get("apply_on"))
+ items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get("value")})]
- return frappe.get_all('UOM Conversion Detail', filters={
- 'parent': ('in', items),
- 'uom': ("like", "{0}%".format(txt))
- }, fields = ["distinct uom"], as_list=1)
+ return frappe.get_all(
+ "UOM Conversion Detail",
+ filters={"parent": ("in", items), "uom": ("like", "{0}%".format(txt))},
+ fields=["distinct uom"],
+ as_list=1,
+ )
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index 8338a5b0ad..4cf19b4454 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
import unittest
import frappe
@@ -30,31 +29,31 @@ class TestPricingRule(unittest.TestCase):
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
- "items": [{
- "item_code": "_Test Item"
- }],
+ "items": [{"item_code": "_Test Item"}],
"currency": "USD",
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
"discount_percentage": 10,
- "company": "_Test Company"
+ "company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
- args = frappe._dict({
- "item_code": "_Test Item",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Order",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "name": None
- })
+ args = frappe._dict(
+ {
+ "item_code": "_Test Item",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Order",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "name": None,
+ }
+ )
details = get_item_details(args)
self.assertEqual(details.get("discount_percentage"), 10)
@@ -73,9 +72,7 @@ class TestPricingRule(unittest.TestCase):
prule = frappe.get_doc(test_record.copy())
prule.apply_on = "Item Group"
prule.items = []
- prule.append('item_groups', {
- 'item_group': "All Item Groups"
- })
+ prule.append("item_groups", {"item_group": "All Item Groups"})
prule.title = "_Test Pricing Rule for Item Group"
prule.discount_percentage = 15
prule.insert()
@@ -98,6 +95,7 @@ class TestPricingRule(unittest.TestCase):
frappe.db.sql("update `tabPricing Rule` set priority=NULL where campaign='_Test Campaign'")
from erpnext.accounts.doctype.pricing_rule.utils import MultiplePricingRuleConflict
+
self.assertRaises(MultiplePricingRuleConflict, get_item_details, args)
args.item_code = "_Test Item 2"
@@ -113,41 +111,47 @@ class TestPricingRule(unittest.TestCase):
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
- "items": [{
- "item_code": "_Test FG Item 2",
- }],
+ "items": [
+ {
+ "item_code": "_Test FG Item 2",
+ }
+ ],
"selling": 1,
"currency": "USD",
"rate_or_discount": "Discount Percentage",
"rate": 0,
"margin_type": "Percentage",
"margin_rate_or_amount": 10,
- "company": "_Test Company"
+ "company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
- item_price = frappe.get_doc({
- "doctype": "Item Price",
- "price_list": "_Test Price List 2",
- "item_code": "_Test FG Item 2",
- "price_list_rate": 100
- })
+ item_price = frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": "_Test Price List 2",
+ "item_code": "_Test FG Item 2",
+ "price_list_rate": 100,
+ }
+ )
item_price.insert(ignore_permissions=True)
- args = frappe._dict({
- "item_code": "_Test FG Item 2",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Order",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "name": None
- })
+ args = frappe._dict(
+ {
+ "item_code": "_Test FG Item 2",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Order",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "name": None,
+ }
+ )
details = get_item_details(args)
self.assertEqual(details.get("margin_type"), "Percentage")
self.assertEqual(details.get("margin_rate_or_amount"), 10)
@@ -176,25 +180,27 @@ class TestPricingRule(unittest.TestCase):
"discount_percentage": 10,
"applicable_for": "Customer Group",
"customer_group": "All Customer Groups",
- "company": "_Test Company"
+ "company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
- args = frappe._dict({
- "item_code": "Mixed Cond Item 1",
- "item_group": "Products",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Order",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "customer_group": "_Test Customer Group",
- "name": None
- })
+ args = frappe._dict(
+ {
+ "item_code": "Mixed Cond Item 1",
+ "item_group": "Products",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Order",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "customer_group": "_Test Customer Group",
+ "name": None,
+ }
+ )
details = get_item_details(args)
self.assertEqual(details.get("discount_percentage"), 10)
@@ -204,72 +210,79 @@ class TestPricingRule(unittest.TestCase):
from erpnext.stock.get_item_details import get_item_details
if not frappe.db.exists("Item", "Test Variant PRT"):
- frappe.get_doc({
- "doctype": "Item",
- "item_code": "Test Variant PRT",
- "item_name": "Test Variant PRT",
- "description": "Test Variant PRT",
- "item_group": "_Test Item Group",
- "is_stock_item": 1,
- "variant_of": "_Test Variant Item",
- "default_warehouse": "_Test Warehouse - _TC",
- "stock_uom": "_Test UOM",
- "attributes": [
+ frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": "Test Variant PRT",
+ "item_name": "Test Variant PRT",
+ "description": "Test Variant PRT",
+ "item_group": "_Test Item Group",
+ "is_stock_item": 1,
+ "variant_of": "_Test Variant Item",
+ "default_warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "_Test UOM",
+ "attributes": [{"attribute": "Test Size", "attribute_value": "Medium"}],
+ }
+ ).insert()
+
+ frappe.get_doc(
+ {
+ "doctype": "Pricing Rule",
+ "title": "_Test Pricing Rule 1",
+ "apply_on": "Item Code",
+ "currency": "USD",
+ "items": [
{
- "attribute": "Test Size",
- "attribute_value": "Medium"
+ "item_code": "_Test Variant Item",
}
],
- }).insert()
+ "selling": 1,
+ "rate_or_discount": "Discount Percentage",
+ "rate": 0,
+ "discount_percentage": 7.5,
+ "company": "_Test Company",
+ }
+ ).insert()
- frappe.get_doc({
- "doctype": "Pricing Rule",
- "title": "_Test Pricing Rule 1",
- "apply_on": "Item Code",
- "currency": "USD",
- "items": [{
- "item_code": "_Test Variant Item",
- }],
- "selling": 1,
- "rate_or_discount": "Discount Percentage",
- "rate": 0,
- "discount_percentage": 7.5,
- "company": "_Test Company"
- }).insert()
-
- args = frappe._dict({
- "item_code": "Test Variant PRT",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Order",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "name": None
- })
+ args = frappe._dict(
+ {
+ "item_code": "Test Variant PRT",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Order",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "name": None,
+ }
+ )
details = get_item_details(args)
self.assertEqual(details.get("discount_percentage"), 7.5)
# add a new pricing rule for that item code, it should take priority
- frappe.get_doc({
- "doctype": "Pricing Rule",
- "title": "_Test Pricing Rule 2",
- "apply_on": "Item Code",
- "items": [{
- "item_code": "Test Variant PRT",
- }],
- "currency": "USD",
- "selling": 1,
- "rate_or_discount": "Discount Percentage",
- "rate": 0,
- "discount_percentage": 17.5,
- "priority": 1,
- "company": "_Test Company"
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Pricing Rule",
+ "title": "_Test Pricing Rule 2",
+ "apply_on": "Item Code",
+ "items": [
+ {
+ "item_code": "Test Variant PRT",
+ }
+ ],
+ "currency": "USD",
+ "selling": 1,
+ "rate_or_discount": "Discount Percentage",
+ "rate": 0,
+ "discount_percentage": 17.5,
+ "priority": 1,
+ "company": "_Test Company",
+ }
+ ).insert()
details = get_item_details(args)
self.assertEqual(details.get("discount_percentage"), 17.5)
@@ -280,33 +293,31 @@ class TestPricingRule(unittest.TestCase):
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
- "items": [{
- "item_code": "_Test Item",
- }],
+ "items": [
+ {
+ "item_code": "_Test Item",
+ }
+ ],
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
"min_qty": 5,
"max_qty": 7,
"discount_percentage": 17.5,
- "company": "_Test Company"
+ "company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
- if not frappe.db.get_value('UOM Conversion Detail',
- {'parent': '_Test Item', 'uom': 'box'}):
- item = frappe.get_doc('Item', '_Test Item')
- item.append('uoms', {
- 'uom': 'Box',
- 'conversion_factor': 5
- })
+ if not frappe.db.get_value("UOM Conversion Detail", {"parent": "_Test Item", "uom": "box"}):
+ item = frappe.get_doc("Item", "_Test Item")
+ item.append("uoms", {"uom": "Box", "conversion_factor": 5})
item.save(ignore_permissions=True)
# With pricing rule
so = make_sales_order(item_code="_Test Item", qty=1, uom="Box", do_not_submit=True)
so.items[0].price_list_rate = 100
so.submit()
- so = frappe.get_doc('Sales Order', so.name)
+ so = frappe.get_doc("Sales Order", so.name)
self.assertEqual(so.items[0].discount_percentage, 17.5)
self.assertEqual(so.items[0].rate, 82.5)
@@ -314,13 +325,15 @@ class TestPricingRule(unittest.TestCase):
so = make_sales_order(item_code="_Test Item", qty=2, uom="Box", do_not_submit=True)
so.items[0].price_list_rate = 100
so.submit()
- so = frappe.get_doc('Sales Order', so.name)
+ so = frappe.get_doc("Sales Order", so.name)
self.assertEqual(so.items[0].discount_percentage, 0)
self.assertEqual(so.items[0].rate, 100)
def test_pricing_rule_with_margin_and_discount(self):
- frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
- make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10, discount_percentage=10)
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
+ make_pricing_rule(
+ selling=1, margin_type="Percentage", margin_rate_or_amount=10, discount_percentage=10
+ )
si = create_sales_invoice(do_not_save=True)
si.items[0].price_list_rate = 1000
si.payment_schedule = []
@@ -334,9 +347,14 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(item.rate, 990)
def test_pricing_rule_with_margin_and_discount_amount(self):
- frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
- make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10,
- rate_or_discount="Discount Amount", discount_amount=110)
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
+ make_pricing_rule(
+ selling=1,
+ margin_type="Percentage",
+ margin_rate_or_amount=10,
+ rate_or_discount="Discount Amount",
+ discount_amount=110,
+ )
si = create_sales_invoice(do_not_save=True)
si.items[0].price_list_rate = 1000
si.payment_schedule = []
@@ -349,15 +367,17 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(item.rate, 990)
def test_pricing_rule_for_product_discount_on_same_item(self):
- frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
- "items": [{
- "item_code": "_Test Item",
- }],
+ "items": [
+ {
+ "item_code": "_Test Item",
+ }
+ ],
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
@@ -367,7 +387,7 @@ class TestPricingRule(unittest.TestCase):
"price_or_product_discount": "Product",
"same_item": 1,
"free_qty": 1,
- "company": "_Test Company"
+ "company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
@@ -377,17 +397,18 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
-
def test_pricing_rule_for_product_discount_on_different_item(self):
- frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
- "items": [{
- "item_code": "_Test Item",
- }],
+ "items": [
+ {
+ "item_code": "_Test Item",
+ }
+ ],
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
@@ -398,7 +419,7 @@ class TestPricingRule(unittest.TestCase):
"same_item": 0,
"free_item": "_Test Item 2",
"free_qty": 1,
- "company": "_Test Company"
+ "company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
@@ -409,15 +430,17 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(so.items[1].item_code, "_Test Item 2")
def test_cumulative_pricing_rule(self):
- frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Cumulative Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Cumulative Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
- "items": [{
- "item_code": "_Test Item",
- }],
+ "items": [
+ {
+ "item_code": "_Test Item",
+ }
+ ],
"is_cumulative": 1,
"selling": 1,
"applicable_for": "Customer",
@@ -430,24 +453,26 @@ class TestPricingRule(unittest.TestCase):
"price_or_product_discount": "Price",
"company": "_Test Company",
"valid_from": frappe.utils.nowdate(),
- "valid_upto": frappe.utils.nowdate()
+ "valid_upto": frappe.utils.nowdate(),
}
frappe.get_doc(test_record.copy()).insert()
- args = frappe._dict({
- "item_code": "_Test Item",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Invoice",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "name": None,
- "transaction_date": frappe.utils.nowdate()
- })
+ args = frappe._dict(
+ {
+ "item_code": "_Test Item",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Invoice",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "name": None,
+ "transaction_date": frappe.utils.nowdate(),
+ }
+ )
details = get_item_details(args)
self.assertTrue(details)
@@ -455,8 +480,12 @@ class TestPricingRule(unittest.TestCase):
def test_pricing_rule_for_condition(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
- make_pricing_rule(selling=1, margin_type="Percentage", \
- condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
+ make_pricing_rule(
+ selling=1,
+ margin_type="Percentage",
+ condition="customer=='_Test Customer 1' and is_return==0",
+ discount_percentage=10,
+ )
# Incorrect Customer and Correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
@@ -480,10 +509,20 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(item.rate, 900)
def test_multiple_pricing_rules(self):
- make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
- title="_Test Pricing Rule 1")
- make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2,
- apply_multiple_pricing_rules=1)
+ make_pricing_rule(
+ discount_percentage=20,
+ selling=1,
+ priority=1,
+ apply_multiple_pricing_rules=1,
+ title="_Test Pricing Rule 1",
+ )
+ make_pricing_rule(
+ discount_percentage=10,
+ selling=1,
+ title="_Test Pricing Rule 2",
+ priority=2,
+ apply_multiple_pricing_rules=1,
+ )
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 30)
si.delete()
@@ -494,10 +533,21 @@ class TestPricingRule(unittest.TestCase):
def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
- make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
- title="_Test Pricing Rule 1")
- make_pricing_rule(discount_percentage=10, selling=1, priority=2,
- apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1)
+ make_pricing_rule(
+ discount_percentage=20,
+ selling=1,
+ priority=1,
+ apply_multiple_pricing_rules=1,
+ title="_Test Pricing Rule 1",
+ )
+ make_pricing_rule(
+ discount_percentage=10,
+ selling=1,
+ priority=2,
+ apply_discount_on_rate=1,
+ title="_Test Pricing Rule 2",
+ apply_multiple_pricing_rules=1,
+ )
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 28)
@@ -514,16 +564,18 @@ class TestPricingRule(unittest.TestCase):
"doctype": "Pricing Rule",
"title": "_Test Water Flask Rule",
"apply_on": "Item Code",
- "items": [{
- "item_code": "Water Flask",
- }],
+ "items": [
+ {
+ "item_code": "Water Flask",
+ }
+ ],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 0,
"margin_type": "Percentage",
"margin_rate_or_amount": 2,
- "company": "_Test Company"
+ "company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
@@ -550,9 +602,11 @@ class TestPricingRule(unittest.TestCase):
"doctype": "Pricing Rule",
"title": "_Test Sanitizer Rule",
"apply_on": "Item Code",
- "items": [{
- "item_code": "Test Sanitizer Item",
- }],
+ "items": [
+ {
+ "item_code": "Test Sanitizer Item",
+ }
+ ],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
@@ -560,63 +614,73 @@ class TestPricingRule(unittest.TestCase):
"priority": 2,
"margin_type": "Percentage",
"margin_rate_or_amount": 0.0,
- "company": "_Test Company"
+ "company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
- rule.rate_or_discount = 'Rate'
+ rule.rate_or_discount = "Rate"
rule.rate = 100.0
rule.insert()
rule1 = frappe.get_doc(pricing_rule_record)
- rule1.currency = 'USD'
- rule1.rate_or_discount = 'Rate'
+ rule1.currency = "USD"
+ rule1.rate_or_discount = "Rate"
rule1.rate = 2.0
rule1.priority = 1
rule1.insert()
- args = frappe._dict({
- "item_code": "Test Sanitizer Item",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "USD",
- "doctype": "Sales Invoice",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "name": None,
- "transaction_date": frappe.utils.nowdate()
- })
+ args = frappe._dict(
+ {
+ "item_code": "Test Sanitizer Item",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "USD",
+ "doctype": "Sales Invoice",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "name": None,
+ "transaction_date": frappe.utils.nowdate(),
+ }
+ )
details = get_item_details(args)
self.assertEqual(details.price_list_rate, 2.0)
-
- args = frappe._dict({
- "item_code": "Test Sanitizer Item",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "INR",
- "doctype": "Sales Invoice",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "name": None,
- "transaction_date": frappe.utils.nowdate()
- })
+ args = frappe._dict(
+ {
+ "item_code": "Test Sanitizer Item",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "INR",
+ "doctype": "Sales Invoice",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "name": None,
+ "transaction_date": frappe.utils.nowdate(),
+ }
+ )
details = get_item_details(args)
self.assertEqual(details.price_list_rate, 100.0)
def test_pricing_rule_for_transaction(self):
make_item("Water Flask 1")
- frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
- make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
- apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
+ make_pricing_rule(
+ selling=1,
+ min_qty=5,
+ price_or_product_discount="Product",
+ apply_on="Transaction",
+ free_item="Water Flask 1",
+ free_qty=1,
+ free_item_rate=10,
+ )
si = create_sales_invoice(qty=5, do_not_submit=True)
self.assertEqual(len(si.items), 2)
@@ -637,14 +701,16 @@ class TestPricingRule(unittest.TestCase):
"title": "_Test Water Flask Rule",
"apply_on": "Item Code",
"price_or_product_discount": "Price",
- "items": [{
- "item_code": "Water Flask",
- }],
+ "items": [
+ {
+ "item_code": "Water Flask",
+ }
+ ],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 20,
- "company": "_Test Company"
+ "company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
@@ -669,10 +735,22 @@ class TestPricingRule(unittest.TestCase):
item.delete()
def test_multiple_pricing_rules_with_min_qty(self):
- make_pricing_rule(discount_percentage=20, selling=1, priority=1, min_qty=4,
- apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 1")
- make_pricing_rule(discount_percentage=10, selling=1, priority=2, min_qty=4,
- apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 2")
+ make_pricing_rule(
+ discount_percentage=20,
+ selling=1,
+ priority=1,
+ min_qty=4,
+ apply_multiple_pricing_rules=1,
+ title="_Test Pricing Rule with Min Qty - 1",
+ )
+ make_pricing_rule(
+ discount_percentage=10,
+ selling=1,
+ priority=2,
+ min_qty=4,
+ apply_multiple_pricing_rules=1,
+ title="_Test Pricing Rule with Min Qty - 2",
+ )
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD")
item = si.items[0]
@@ -691,73 +769,86 @@ class TestPricingRule(unittest.TestCase):
test_dependencies = ["Campaign"]
+
def make_pricing_rule(**args):
args = frappe._dict(args)
- doc = frappe.get_doc({
- "doctype": "Pricing Rule",
- "title": args.title or "_Test Pricing Rule",
- "company": args.company or "_Test Company",
- "apply_on": args.apply_on or "Item Code",
- "applicable_for": args.applicable_for,
- "selling": args.selling or 0,
- "currency": "USD",
- "apply_discount_on_rate": args.apply_discount_on_rate or 0,
- "buying": args.buying or 0,
- "min_qty": args.min_qty or 0.0,
- "max_qty": args.max_qty or 0.0,
- "rate_or_discount": args.rate_or_discount or "Discount Percentage",
- "discount_percentage": args.discount_percentage or 0.0,
- "rate": args.rate or 0.0,
- "margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
- "condition": args.condition or '',
- "priority": args.priority or 1,
- "discount_amount": args.discount_amount or 0.0,
- "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Pricing Rule",
+ "title": args.title or "_Test Pricing Rule",
+ "company": args.company or "_Test Company",
+ "apply_on": args.apply_on or "Item Code",
+ "applicable_for": args.applicable_for,
+ "selling": args.selling or 0,
+ "currency": "USD",
+ "apply_discount_on_rate": args.apply_discount_on_rate or 0,
+ "buying": args.buying or 0,
+ "min_qty": args.min_qty or 0.0,
+ "max_qty": args.max_qty or 0.0,
+ "rate_or_discount": args.rate_or_discount or "Discount Percentage",
+ "discount_percentage": args.discount_percentage or 0.0,
+ "rate": args.rate or 0.0,
+ "margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
+ "condition": args.condition or "",
+ "priority": args.priority or 1,
+ "discount_amount": args.discount_amount or 0.0,
+ "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
+ }
+ )
- for field in ["free_item", "free_qty", "free_item_rate", "priority",
- "margin_type", "price_or_product_discount"]:
+ for field in [
+ "free_item",
+ "free_qty",
+ "free_item_rate",
+ "priority",
+ "margin_type",
+ "price_or_product_discount",
+ ]:
if args.get(field):
doc.set(field, args.get(field))
- apply_on = doc.apply_on.replace(' ', '_').lower()
- child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
+ apply_on = doc.apply_on.replace(" ", "_").lower()
+ child_table = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}
if doc.apply_on != "Transaction":
- doc.append(child_table.get(doc.apply_on), {
- apply_on: args.get(apply_on) or "_Test Item"
- })
+ doc.append(child_table.get(doc.apply_on), {apply_on: args.get(apply_on) or "_Test Item"})
doc.insert(ignore_permissions=True)
if args.get(apply_on) and apply_on != "item_code":
doc.db_set(apply_on, args.get(apply_on))
- applicable_for = doc.applicable_for.replace(' ', '_').lower()
+ applicable_for = doc.applicable_for.replace(" ", "_").lower()
if args.get(applicable_for):
doc.db_set(applicable_for, args.get(applicable_for))
return doc
+
def setup_pricing_rule_data():
- if not frappe.db.exists('Campaign', '_Test Campaign'):
- frappe.get_doc({
- 'doctype': 'Campaign',
- 'campaign_name': '_Test Campaign',
- 'name': '_Test Campaign'
- }).insert()
+ if not frappe.db.exists("Campaign", "_Test Campaign"):
+ frappe.get_doc(
+ {"doctype": "Campaign", "campaign_name": "_Test Campaign", "name": "_Test Campaign"}
+ ).insert()
+
def delete_existing_pricing_rules():
- for doctype in ["Pricing Rule", "Pricing Rule Item Code",
- "Pricing Rule Item Group", "Pricing Rule Brand"]:
+ for doctype in [
+ "Pricing Rule",
+ "Pricing Rule Item Code",
+ "Pricing Rule Item Group",
+ "Pricing Rule Brand",
+ ]:
frappe.db.sql("delete from `tab{0}`".format(doctype))
def make_item_price(item, price_list_name, item_price):
- frappe.get_doc({
- 'doctype': 'Item Price',
- 'price_list': price_list_name,
- 'item_code': item,
- 'price_list_rate': item_price
- }).insert(ignore_permissions=True, ignore_mandatory=True)
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": price_list_name,
+ "item_code": item,
+ "price_list_rate": item_price,
+ }
+ ).insert(ignore_permissions=True, ignore_mandatory=True)
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 7792590c9c..70926cfbd7 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -16,22 +16,21 @@ from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
-class MultiplePricingRuleConflict(frappe.ValidationError): pass
+class MultiplePricingRuleConflict(frappe.ValidationError):
+ pass
+
+
+apply_on_table = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}
-apply_on_table = {
- 'Item Code': 'items',
- 'Item Group': 'item_groups',
- 'Brand': 'brands'
-}
def get_pricing_rules(args, doc=None):
pricing_rules = []
- values = {}
+ values = {}
- if not frappe.db.exists('Pricing Rule', {'disable': 0, args.transaction_type: 1}):
+ if not frappe.db.exists("Pricing Rule", {"disable": 0, args.transaction_type: 1}):
return
- for apply_on in ['Item Code', 'Item Group', 'Brand']:
+ for apply_on in ["Item Code", "Item Group", "Brand"]:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
break
@@ -40,7 +39,8 @@ def get_pricing_rules(args, doc=None):
pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc)
- if not pricing_rules: return []
+ if not pricing_rules:
+ return []
if apply_multiple_pricing_rules(pricing_rules):
pricing_rules = sorted_by_priority(pricing_rules, args, doc)
@@ -56,6 +56,7 @@ def get_pricing_rules(args, doc=None):
return rules
+
def sorted_by_priority(pricing_rules, args, doc=None):
# If more than one pricing rules, then sort by priority
pricing_rules_list = []
@@ -64,10 +65,10 @@ def sorted_by_priority(pricing_rules, args, doc=None):
for pricing_rule in pricing_rules:
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
if pricing_rule:
- if not pricing_rule.get('priority'):
- pricing_rule['priority'] = 1
+ if not pricing_rule.get("priority"):
+ pricing_rule["priority"] = 1
- if pricing_rule.get('apply_multiple_pricing_rules'):
+ if pricing_rule.get("apply_multiple_pricing_rules"):
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
for key in sorted(pricing_rule_dict):
@@ -75,6 +76,7 @@ def sorted_by_priority(pricing_rules, args, doc=None):
return pricing_rules_list
+
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = []
if doc:
@@ -92,40 +94,48 @@ def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
return filtered_pricing_rules
+
def _get_pricing_rules(apply_on, args, values):
apply_on_field = frappe.scrub(apply_on)
- if not args.get(apply_on_field): return []
+ if not args.get(apply_on_field):
+ return []
- child_doc = '`tabPricing Rule {0}`'.format(apply_on)
+ child_doc = "`tabPricing Rule {0}`".format(apply_on)
conditions = item_variant_condition = item_conditions = ""
values[apply_on_field] = args.get(apply_on_field)
- if apply_on_field in ['item_code', 'brand']:
- item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format(child_doc=child_doc,
- apply_on_field = apply_on_field)
+ if apply_on_field in ["item_code", "brand"]:
+ item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format(
+ child_doc=child_doc, apply_on_field=apply_on_field
+ )
- if apply_on_field == 'item_code':
+ if apply_on_field == "item_code":
if "variant_of" not in args:
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
if args.variant_of:
- item_variant_condition = ' or {child_doc}.item_code=%(variant_of)s '.format(child_doc=child_doc)
- values['variant_of'] = args.variant_of
- elif apply_on_field == 'item_group':
+ item_variant_condition = " or {child_doc}.item_code=%(variant_of)s ".format(
+ child_doc=child_doc
+ )
+ values["variant_of"] = args.variant_of
+ elif apply_on_field == "item_group":
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
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:
warehouse_conditions = " and {0}".format(warehouse_conditions)
- if not args.price_list: args.price_list = None
+ if not args.price_list:
+ args.price_list = None
conditions += " and ifnull(`tabPricing Rule`.for_price_list, '') in (%(price_list)s, '')"
values["price_list"] = args.get("price_list")
- pricing_rules = frappe.db.sql("""select `tabPricing Rule`.*,
+ pricing_rules = (
+ frappe.db.sql(
+ """select `tabPricing Rule`.*,
{child_doc}.{apply_on_field}, {child_doc}.uom
from `tabPricing Rule`, {child_doc}
where ({item_conditions} or (`tabPricing Rule`.apply_rule_on_other is not null
@@ -135,25 +145,35 @@ def _get_pricing_rules(apply_on, args, values):
`tabPricing Rule`.{transaction_type} = 1 {warehouse_cond} {conditions}
order by `tabPricing Rule`.priority desc,
`tabPricing Rule`.name desc""".format(
- child_doc = child_doc,
- apply_on_field = apply_on_field,
- item_conditions = item_conditions,
- item_variant_condition = item_variant_condition,
- transaction_type = args.transaction_type,
- warehouse_cond = warehouse_conditions,
- apply_on_other_field = "other_{0}".format(apply_on_field),
- conditions = conditions), values, as_dict=1) or []
+ child_doc=child_doc,
+ apply_on_field=apply_on_field,
+ item_conditions=item_conditions,
+ item_variant_condition=item_variant_condition,
+ transaction_type=args.transaction_type,
+ warehouse_cond=warehouse_conditions,
+ apply_on_other_field="other_{0}".format(apply_on_field),
+ conditions=conditions,
+ ),
+ values,
+ as_dict=1,
+ )
+ or []
+ )
return pricing_rules
-def apply_multiple_pricing_rules(pricing_rules):
- apply_multiple_rule = [d.apply_multiple_pricing_rules
- for d in pricing_rules if d.apply_multiple_pricing_rules]
- if not apply_multiple_rule: return False
+def apply_multiple_pricing_rules(pricing_rules):
+ apply_multiple_rule = [
+ d.apply_multiple_pricing_rules for d in pricing_rules if d.apply_multiple_pricing_rules
+ ]
+
+ if not apply_multiple_rule:
+ return False
return True
+
def _get_tree_conditions(args, parenttype, table, allow_blank=True):
field = frappe.scrub(parenttype)
condition = ""
@@ -169,28 +189,37 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
except TypeError:
frappe.throw(_("Invalid {0}").format(args.get(field)))
- parent_groups = frappe.db.sql_list("""select name from `tab%s`
- where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+ parent_groups = frappe.db.sql_list(
+ """select name from `tab%s`
+ where lft<=%s and rgt>=%s"""
+ % (parenttype, "%s", "%s"),
+ (lft, rgt),
+ )
if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
- root_name = frappe.db.get_list(parenttype,
- {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
+ root_name = frappe.db.get_list(
+ parenttype,
+ {"is_group": 1, parent_field: ("is", "not set")},
+ "name",
+ as_list=1,
+ ignore_permissions=True,
+ )
if root_name and root_name[0][0]:
parent_groups.append(root_name[0][0])
if parent_groups:
- if allow_blank: parent_groups.append('')
+ if allow_blank:
+ parent_groups.append("")
condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
- table=table,
- field=field,
- parent_groups=", ".join(frappe.db.escape(d) for d in parent_groups)
+ table=table, field=field, parent_groups=", ".join(frappe.db.escape(d) for d in parent_groups)
)
frappe.flags.tree_conditions[key] = condition
return condition
+
def get_other_conditions(conditions, values, args):
for field in ["company", "customer", "supplier", "campaign", "sales_partner"]:
if args.get(field):
@@ -200,17 +229,18 @@ def get_other_conditions(conditions, values, args):
conditions += " and ifnull(`tabPricing Rule`.{0}, '') = ''".format(field)
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`")
if group_condition:
conditions += " and " + group_condition
if args.get("transaction_date"):
conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01')
and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
- values['transaction_date'] = args.get('transaction_date')
+ values["transaction_date"] = args.get("transaction_date")
return conditions
+
def filter_pricing_rules(args, pricing_rules, doc=None):
if not isinstance(pricing_rules, list):
pricing_rules = [pricing_rules]
@@ -219,15 +249,16 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
# filter for qty
if pricing_rules:
- stock_qty = flt(args.get('stock_qty'))
- amount = flt(args.get('price_list_rate')) * flt(args.get('qty'))
+ stock_qty = flt(args.get("stock_qty"))
+ amount = flt(args.get("price_list_rate")) * flt(args.get("qty"))
if pricing_rules[0].apply_rule_on_other:
field = frappe.scrub(pricing_rules[0].apply_rule_on_other)
- if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return
+ if field and pricing_rules[0].get("other_" + field) != args.get(field):
+ return
- pr_doc = frappe.get_cached_doc('Pricing Rule', pricing_rules[0].name)
+ pr_doc = frappe.get_cached_doc("Pricing Rule", pricing_rules[0].name)
if pricing_rules[0].mixed_conditions and doc:
stock_qty, amount, items = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
@@ -235,7 +266,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
pricing_rule_args.apply_rule_on_other_items = items
elif pricing_rules[0].is_cumulative:
- items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
+ items = [args.get(frappe.scrub(pr_doc.get("apply_on")))]
data = get_qty_amount_data_for_cumulative(pr_doc, args, items)
if data:
@@ -249,13 +280,15 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
if not pricing_rules:
for d in original_pricing_rule:
- if not d.threshold_percentage: continue
+ if not d.threshold_percentage:
+ continue
- msg = validate_quantity_and_amount_for_suggestion(d, stock_qty,
- amount, args.get('item_code'), args.get('transaction_type'))
+ msg = validate_quantity_and_amount_for_suggestion(
+ d, stock_qty, amount, args.get("item_code"), args.get("transaction_type")
+ )
if msg:
- return {'suggestion': msg, 'item_code': args.get('item_code')}
+ return {"suggestion": msg, "item_code": args.get("item_code")}
# add variant_of property in pricing rule
for p in pricing_rules:
@@ -265,7 +298,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
p.variant_of = None
if len(pricing_rules) > 1:
- filtered_rules = list(filter(lambda x: x.currency==args.get('currency'), pricing_rules))
+ filtered_rules = list(filter(lambda x: x.currency == args.get("currency"), pricing_rules))
if filtered_rules:
pricing_rules = filtered_rules
@@ -273,7 +306,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
if pricing_rules:
max_priority = max(cint(p.priority) for p in pricing_rules)
if max_priority:
- pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
+ pricing_rules = list(filter(lambda x: cint(x.priority) == max_priority, pricing_rules))
if pricing_rules and not isinstance(pricing_rules, list):
pricing_rules = list(pricing_rules)
@@ -281,42 +314,61 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
if len(pricing_rules) > 1:
rate_or_discount = list(set(d.rate_or_discount for d in pricing_rules))
if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
- pricing_rules = list(filter(lambda x: x.for_price_list==args.price_list, pricing_rules)) \
- or pricing_rules
+ pricing_rules = (
+ list(filter(lambda x: x.for_price_list == args.price_list, pricing_rules)) or pricing_rules
+ )
if len(pricing_rules) > 1 and not args.for_shopping_cart:
- frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
- .format("\n".join(d.name for d in pricing_rules)), MultiplePricingRuleConflict)
+ frappe.throw(
+ _(
+ "Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}"
+ ).format("\n".join(d.name for d in pricing_rules)),
+ MultiplePricingRuleConflict,
+ )
elif pricing_rules:
return pricing_rules[0]
-def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, transaction_type):
- fieldname, msg = '', ''
- type_of_transaction = 'purchase' if transaction_type == 'buying' else 'sale'
- for field, value in {'min_qty': qty, 'min_amt': amount}.items():
- if (args.get(field) and value < args.get(field)
- and (args.get(field) - cint(args.get(field) * args.threshold_percentage * 0.01)) <= value):
+def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, transaction_type):
+ fieldname, msg = "", ""
+ type_of_transaction = "purchase" if transaction_type == "buying" else "sale"
+
+ for field, value in {"min_qty": qty, "min_amt": amount}.items():
+ if (
+ args.get(field)
+ and value < args.get(field)
+ and (args.get(field) - cint(args.get(field) * args.threshold_percentage * 0.01)) <= value
+ ):
fieldname = field
- for field, value in {'max_qty': qty, 'max_amt': amount}.items():
- if (args.get(field) and value > args.get(field)
- and (args.get(field) + cint(args.get(field) * args.threshold_percentage * 0.01)) >= value):
+ for field, value in {"max_qty": qty, "max_amt": amount}.items():
+ if (
+ args.get(field)
+ and value > args.get(field)
+ and (args.get(field) + cint(args.get(field) * args.threshold_percentage * 0.01)) >= value
+ ):
fieldname = field
if fieldname:
- msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
- .format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
+ msg = _(
+ "If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item."
+ ).format(
+ type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)
+ )
- if fieldname in ['min_amt', 'max_amt']:
- msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
- .format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
- bold(item_code), bold(args.rule_description)))
+ if fieldname in ["min_amt", "max_amt"]:
+ msg = _("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.").format(
+ type_of_transaction,
+ fmt_money(args.get(fieldname), currency=args.get("currency")),
+ bold(item_code),
+ bold(args.rule_description),
+ )
frappe.msgprint(msg)
return msg
+
def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
rules = []
@@ -327,16 +379,19 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
if rule.get("uom"):
conversion_factor = get_conversion_factor(rule.item_code, rule.uom).get("conversion_factor", 1)
- if (flt(qty) >= (flt(rule.min_qty) * conversion_factor)
- and (flt(qty)<= (rule.max_qty * conversion_factor) if rule.max_qty else True)):
+ if flt(qty) >= (flt(rule.min_qty) * conversion_factor) and (
+ flt(qty) <= (rule.max_qty * conversion_factor) if rule.max_qty else True
+ ):
status = True
# if user has created item price against the transaction UOM
if args and rule.get("uom") == args.get("uom"):
conversion_factor = 1.0
- if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
- and (flt(rate)<= (rule.max_amt * conversion_factor) if rule.max_amt else True)):
+ if status and (
+ flt(rate) >= (flt(rule.min_amt) * conversion_factor)
+ and (flt(rate) <= (rule.max_amt * conversion_factor) if rule.max_amt else True)
+ ):
status = True
else:
status = False
@@ -346,6 +401,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
return rules
+
def if_all_rules_same(pricing_rules, fields):
all_rules_same = True
val = [pricing_rules[0].get(k) for k in fields]
@@ -356,30 +412,34 @@ def if_all_rules_same(pricing_rules, fields):
return all_rules_same
+
def apply_internal_priority(pricing_rules, field_set, args):
filtered_rules = []
for field in field_set:
if args.get(field):
# filter function always returns a filter object even if empty
# list conversion is necessary to check for an empty result
- filtered_rules = list(filter(lambda x: x.get(field)==args.get(field), pricing_rules))
- if filtered_rules: break
+ filtered_rules = list(filter(lambda x: x.get(field) == args.get(field), pricing_rules))
+ if filtered_rules:
+ break
return filtered_rules or pricing_rules
+
def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
sum_qty, sum_amt = [0, 0]
items = get_pricing_rule_items(pr_doc) or []
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
+ apply_on = frappe.scrub(pr_doc.get("apply_on"))
if items and doc.get("items"):
- for row in doc.get('items'):
- if (row.get(apply_on) or args.get(apply_on)) not in items: continue
+ for row in doc.get("items"):
+ if (row.get(apply_on) or args.get(apply_on)) not in items:
+ continue
if pr_doc.mixed_conditions:
- amt = args.get('qty') * args.get("price_list_rate")
+ amt = args.get("qty") * args.get("price_list_rate")
if args.get("item_code") != row.get("item_code"):
- amt = flt(row.get('qty')) * flt(row.get("price_list_rate") or args.get("rate"))
+ amt = flt(row.get("qty")) * flt(row.get("price_list_rate") or args.get("rate"))
sum_qty += flt(row.get("stock_qty")) or flt(args.get("stock_qty")) or flt(args.get("qty"))
sum_amt += amt
@@ -393,28 +453,33 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
return sum_qty, sum_amt, items
+
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
items = get_pricing_rule_items(pr_doc)
for row in doc.items:
if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items:
- pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
- row.get("amount"), pricing_rules, row)
+ pricing_rules = filter_pricing_rules_for_qty_amount(
+ row.get("stock_qty"), row.get("amount"), pricing_rules, row
+ )
if pricing_rules and pricing_rules[0]:
pricing_rules[0].apply_rule_on_other_items = items
return pricing_rules
+
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
if items is None:
items = []
sum_qty, sum_amt = [0, 0]
- doctype = doc.get('parenttype') or doc.doctype
+ doctype = doc.get("parenttype") or doc.doctype
- date_field = 'transaction_date' if frappe.get_meta(doctype).has_field('transaction_date') else 'posting_date'
+ date_field = (
+ "transaction_date" if frappe.get_meta(doctype).has_field("transaction_date") else "posting_date"
+ )
- child_doctype = '{0} Item'.format(doctype)
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
+ child_doctype = "{0} Item".format(doctype)
+ apply_on = frappe.scrub(pr_doc.get("apply_on"))
values = [pr_doc.valid_from, pr_doc.valid_upto]
condition = ""
@@ -423,72 +488,86 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
warehouses = get_child_warehouses(pr_doc.warehouse)
condition += """ and `tab{child_doc}`.warehouse in ({warehouses})
- """.format(child_doc=child_doctype, warehouses = ','.join(['%s'] * len(warehouses)))
+ """.format(
+ child_doc=child_doctype, warehouses=",".join(["%s"] * len(warehouses))
+ )
values.extend(warehouses)
if items:
- condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(child_doc = child_doctype,
- apply_on = apply_on, items = ','.join(['%s'] * len(items)))
+ condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(
+ child_doc=child_doctype, apply_on=apply_on, items=",".join(["%s"] * len(items))
+ )
values.extend(items)
- data_set = frappe.db.sql(""" SELECT `tab{child_doc}`.stock_qty,
+ data_set = frappe.db.sql(
+ """ SELECT `tab{child_doc}`.stock_qty,
`tab{child_doc}`.amount
FROM `tab{child_doc}`, `tab{parent_doc}`
WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name and `tab{parent_doc}`.{date_field}
between %s and %s and `tab{parent_doc}`.docstatus = 1
{condition} group by `tab{child_doc}`.name
- """.format(parent_doc = doctype,
- child_doc = child_doctype,
- condition = condition,
- date_field = date_field
- ), tuple(values), as_dict=1)
+ """.format(
+ parent_doc=doctype, child_doc=child_doctype, condition=condition, date_field=date_field
+ ),
+ tuple(values),
+ as_dict=1,
+ )
for data in data_set:
- sum_qty += data.get('stock_qty')
- sum_amt += data.get('amount')
+ sum_qty += data.get("stock_qty")
+ sum_amt += data.get("amount")
return [sum_qty, sum_amt]
+
def apply_pricing_rule_on_transaction(doc):
conditions = "apply_on = 'Transaction'"
values = {}
conditions = get_other_conditions(conditions, values, doc)
- pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
+ pricing_rules = frappe.db.sql(
+ """ Select `tabPricing Rule`.* from `tabPricing Rule`
where {conditions} and `tabPricing Rule`.disable = 0
- """.format(conditions = conditions), values, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ values,
+ as_dict=1,
+ )
if pricing_rules:
- pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
- doc.total, pricing_rules)
+ pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, doc.total, pricing_rules)
if not pricing_rules:
remove_free_item(doc)
for d in pricing_rules:
- if d.price_or_product_discount == 'Price':
+ if d.price_or_product_discount == "Price":
if d.apply_discount_on:
- doc.set('apply_discount_on', d.apply_discount_on)
+ doc.set("apply_discount_on", d.apply_discount_on)
- for field in ['additional_discount_percentage', 'discount_amount']:
- pr_field = ('discount_percentage'
- if field == 'additional_discount_percentage' else field)
+ for field in ["additional_discount_percentage", "discount_amount"]:
+ pr_field = "discount_percentage" if field == "additional_discount_percentage" else field
- if not d.get(pr_field): continue
+ if not d.get(pr_field):
+ continue
- if 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))
+ if (
+ 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))
else:
if not d.coupon_code_based:
doc.set(field, d.get(pr_field))
- elif doc.get('coupon_code'):
+ elif doc.get("coupon_code"):
# coupon code based pricing rule
- coupon_code_pricing_rule = frappe.db.get_value('Coupon Code', doc.get('coupon_code'), 'pricing_rule')
+ coupon_code_pricing_rule = frappe.db.get_value(
+ "Coupon Code", doc.get("coupon_code"), "pricing_rule"
+ )
if coupon_code_pricing_rule == d.name:
# if selected coupon code is linked with pricing rule
doc.set(field, d.get(pr_field))
@@ -500,83 +579,93 @@ def apply_pricing_rule_on_transaction(doc):
doc.set(field, 0)
doc.calculate_taxes_and_totals()
- elif d.price_or_product_discount == 'Product':
- item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []})
+ elif d.price_or_product_discount == "Product":
+ item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []})
get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values()
doc.calculate_taxes_and_totals()
+
def remove_free_item(doc):
for d in doc.items:
if d.is_free_item:
doc.remove(d)
+
def get_applied_pricing_rules(pricing_rules):
if pricing_rules:
- if pricing_rules.startswith('['):
+ if pricing_rules.startswith("["):
return json.loads(pricing_rules)
else:
- return pricing_rules.split(',')
+ return pricing_rules.split(",")
return []
+
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = pricing_rule.free_item
- if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
+ if pricing_rule.same_item and pricing_rule.get("apply_on") != "Transaction":
free_item = item_details.item_code or args.item_code
if not free_item:
- frappe.throw(_("Free item not set in the pricing rule {0}")
- .format(get_link_to_form("Pricing Rule", pricing_rule.name)))
+ frappe.throw(
+ _("Free item not set in the pricing rule {0}").format(
+ get_link_to_form("Pricing Rule", pricing_rule.name)
+ )
+ )
qty = pricing_rule.free_qty or 1
if pricing_rule.is_recursive:
- transaction_qty = args.get('qty') if args else doc.total_qty
+ transaction_qty = args.get("qty") if args else doc.total_qty
if transaction_qty:
qty = flt(transaction_qty) * qty
free_item_data_args = {
- 'item_code': free_item,
- 'qty': qty,
- 'pricing_rules': pricing_rule.name,
- 'rate': pricing_rule.free_item_rate or 0,
- 'price_list_rate': pricing_rule.free_item_rate or 0,
- 'is_free_item': 1
+ "item_code": free_item,
+ "qty": qty,
+ "pricing_rules": pricing_rule.name,
+ "rate": pricing_rule.free_item_rate or 0,
+ "price_list_rate": pricing_rule.free_item_rate or 0,
+ "is_free_item": 1,
}
- item_data = frappe.get_cached_value('Item', free_item, ['item_name',
- 'description', 'stock_uom'], as_dict=1)
+ item_data = frappe.get_cached_value(
+ "Item", free_item, ["item_name", "description", "stock_uom"], as_dict=1
+ )
free_item_data_args.update(item_data)
- free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
- free_item_data_args['conversion_factor'] = get_conversion_factor(free_item,
- free_item_data_args['uom']).get("conversion_factor", 1)
+ free_item_data_args["uom"] = pricing_rule.free_item_uom or item_data.stock_uom
+ free_item_data_args["conversion_factor"] = get_conversion_factor(
+ free_item, free_item_data_args["uom"]
+ ).get("conversion_factor", 1)
- if item_details.get("parenttype") == 'Purchase Order':
- free_item_data_args['schedule_date'] = doc.schedule_date if doc else today()
+ if item_details.get("parenttype") == "Purchase Order":
+ free_item_data_args["schedule_date"] = doc.schedule_date if doc else today()
- if item_details.get("parenttype") == 'Sales Order':
- free_item_data_args['delivery_date'] = doc.delivery_date if doc else today()
+ if item_details.get("parenttype") == "Sales Order":
+ free_item_data_args["delivery_date"] = doc.delivery_date if doc else today()
item_details.free_item_data.append(free_item_data_args)
+
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
if pricing_rule_args:
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
for args in pricing_rule_args:
- if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
- doc.append('items', args)
+ if not items or (args.get("item_code"), args.get("pricing_rules")) not in items:
+ doc.append("items", args)
+
def get_pricing_rule_items(pr_doc):
apply_on_data = []
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
+ apply_on = frappe.scrub(pr_doc.get("apply_on"))
- pricing_rule_apply_on = apply_on_table.get(pr_doc.get('apply_on'))
+ pricing_rule_apply_on = apply_on_table.get(pr_doc.get("apply_on"))
for d in pr_doc.get(pricing_rule_apply_on):
- if apply_on == 'item_group':
+ if apply_on == "item_group":
apply_on_data.extend(get_child_item_groups(d.get(apply_on)))
else:
apply_on_data.append(d.get(apply_on))
@@ -587,6 +676,7 @@ def get_pricing_rule_items(pr_doc):
return list(set(apply_on_data))
+
def validate_coupon_code(coupon_name):
coupon = frappe.get_doc("Coupon Code", coupon_name)
@@ -599,16 +689,21 @@ def validate_coupon_code(coupon_name):
elif coupon.used >= coupon.maximum_use:
frappe.throw(_("Sorry, this coupon code is no longer valid"))
-def update_coupon_code_count(coupon_name,transaction_type):
- coupon=frappe.get_doc("Coupon Code",coupon_name)
+
+def update_coupon_code_count(coupon_name, transaction_type):
+ coupon = frappe.get_doc("Coupon Code", coupon_name)
if coupon:
- if transaction_type=='used':
- if coupon.used
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.'
+ self.body = "Hello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}."
validate_template(self.subject)
validate_template(self.body)
if not self.customers:
- frappe.throw(_('Customers not selected.'))
+ frappe.throw(_("Customers not selected."))
if self.enable_auto_email:
self.to_date = self.start_date
@@ -40,113 +40,148 @@ class ProcessStatementOfAccounts(Document):
def get_report_pdf(doc, consolidated=True):
statement_dict = {}
- ageing = ''
+ ageing = ""
base_template_path = "frappe/www/printview.html"
- template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
+ template_path = (
+ "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
+ )
for entry in doc.customers:
if doc.include_ageing:
- ageing_filters = frappe._dict({
- 'company': doc.company,
- 'report_date': doc.to_date,
- 'ageing_based_on': doc.ageing_based_on,
- 'range1': 30,
- 'range2': 60,
- 'range3': 90,
- 'range4': 120,
- 'customer': entry.customer
- })
+ ageing_filters = frappe._dict(
+ {
+ "company": doc.company,
+ "report_date": doc.to_date,
+ "ageing_based_on": doc.ageing_based_on,
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120,
+ "customer": entry.customer,
+ }
+ )
col1, ageing = get_ageing(ageing_filters)
if ageing:
- ageing[0]['ageing_based_on'] = doc.ageing_based_on
+ ageing[0]["ageing_based_on"] = doc.ageing_based_on
- tax_id = frappe.get_doc('Customer', entry.customer).tax_id
- presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \
- or doc.currency or get_company_currency(doc.company)
+ tax_id = frappe.get_doc("Customer", entry.customer).tax_id
+ presentation_currency = (
+ get_party_account_currency("Customer", entry.customer, doc.company)
+ or doc.currency
+ or get_company_currency(doc.company)
+ )
if doc.letter_head:
from frappe.www.printview import get_letter_head
+
letter_head = get_letter_head(doc, 0)
- filters= frappe._dict({
- 'from_date': doc.from_date,
- 'to_date': doc.to_date,
- 'company': doc.company,
- 'finance_book': doc.finance_book if doc.finance_book else None,
- 'account': [doc.account] if doc.account else None,
- 'party_type': 'Customer',
- 'party': [entry.customer],
- 'presentation_currency': presentation_currency,
- 'group_by': doc.group_by,
- 'currency': doc.currency,
- 'cost_center': [cc.cost_center_name for cc in doc.cost_center],
- 'project': [p.project_name for p in doc.project],
- 'show_opening_entries': 0,
- 'include_default_book_entries': 0,
- 'tax_id': tax_id if tax_id else None
- })
+ filters = frappe._dict(
+ {
+ "from_date": doc.from_date,
+ "to_date": doc.to_date,
+ "company": doc.company,
+ "finance_book": doc.finance_book if doc.finance_book else None,
+ "account": [doc.account] if doc.account else None,
+ "party_type": "Customer",
+ "party": [entry.customer],
+ "presentation_currency": presentation_currency,
+ "group_by": doc.group_by,
+ "currency": doc.currency,
+ "cost_center": [cc.cost_center_name for cc in doc.cost_center],
+ "project": [p.project_name for p in doc.project],
+ "show_opening_entries": 0,
+ "include_default_book_entries": 0,
+ "tax_id": tax_id if tax_id else None,
+ }
+ )
col, res = get_soa(filters)
for x in [0, -2, -1]:
- res[x]['account'] = res[x]['account'].replace("'","")
+ res[x]["account"] = res[x]["account"].replace("'", "")
if len(res) == 3:
continue
- html = frappe.render_template(template_path, \
- {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None,
+ html = frappe.render_template(
+ template_path,
+ {
+ "filters": filters,
+ "data": res,
+ "ageing": ageing[0] if (doc.include_ageing and ageing) else None,
"letter_head": letter_head if doc.letter_head else None,
- "terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
- if doc.terms_and_conditions else None})
+ "terms_and_conditions": frappe.db.get_value(
+ "Terms and Conditions", doc.terms_and_conditions, "terms"
+ )
+ if doc.terms_and_conditions
+ else None,
+ },
+ )
- html = frappe.render_template(base_template_path, {"body": html, \
- "css": get_print_style(), "title": "Statement For " + entry.customer})
+ html = frappe.render_template(
+ base_template_path,
+ {"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
+ )
statement_dict[entry.customer] = html
if not bool(statement_dict):
return False
elif consolidated:
- result = ''.join(list(statement_dict.values()))
- return get_pdf(result, {'orientation': doc.orientation})
+ result = "".join(list(statement_dict.values()))
+ return get_pdf(result, {"orientation": doc.orientation})
else:
for customer, statement_html in statement_dict.items():
- statement_dict[customer]=get_pdf(statement_html, {'orientation': doc.orientation})
+ statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
return statement_dict
+
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
fields_dict = {
- 'Customer Group': 'customer_group',
- 'Territory': 'territory',
+ "Customer Group": "customer_group",
+ "Territory": "territory",
}
collection = frappe.get_doc(customer_collection, collection_name)
- selected = [customer.name for customer in frappe.get_list(customer_collection, filters=[
- ['lft', '>=', collection.lft],
- ['rgt', '<=', collection.rgt]
- ],
- fields=['name'],
- order_by='lft asc, rgt desc'
- )]
- return frappe.get_list('Customer', fields=['name', 'email_id'], \
- filters=[[fields_dict[customer_collection], 'IN', selected]])
+ selected = [
+ customer.name
+ for customer in frappe.get_list(
+ customer_collection,
+ filters=[["lft", ">=", collection.lft], ["rgt", "<=", collection.rgt]],
+ fields=["name"],
+ order_by="lft asc, rgt desc",
+ )
+ ]
+ return frappe.get_list(
+ "Customer",
+ fields=["name", "email_id"],
+ filters=[[fields_dict[customer_collection], "IN", selected]],
+ )
+
def get_customers_based_on_sales_person(sales_person):
- lft, rgt = frappe.db.get_value("Sales Person",
- sales_person, ["lft", "rgt"])
- records = frappe.db.sql("""
+ lft, rgt = frappe.db.get_value("Sales Person", sales_person, ["lft", "rgt"])
+ records = frappe.db.sql(
+ """
select distinct parent, parenttype
from `tabSales Team` steam
where parenttype = 'Customer'
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
- """, (lft, rgt), as_dict=1)
+ """,
+ (lft, rgt),
+ as_dict=1,
+ )
sales_person_records = frappe._dict()
for d in records:
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
- if sales_person_records.get('Customer'):
- return frappe.get_list('Customer', fields=['name', 'email_id'], \
- filters=[['name', 'in', list(sales_person_records['Customer'])]])
+ if sales_person_records.get("Customer"):
+ return frappe.get_list(
+ "Customer",
+ fields=["name", "email_id"],
+ filters=[["name", "in", list(sales_person_records["Customer"])]],
+ )
else:
return []
+
def get_recipients_and_cc(customer, doc):
recipients = []
for clist in doc.customers:
@@ -155,65 +190,72 @@ def get_recipients_and_cc(customer, doc):
if doc.primary_mandatory and clist.primary_email:
recipients.append(clist.primary_email)
cc = []
- if doc.cc_to != '':
+ if doc.cc_to != "":
try:
- cc=[frappe.get_value('User', doc.cc_to, 'email')]
+ cc = [frappe.get_value("User", doc.cc_to, "email")]
except Exception:
pass
return recipients, cc
+
def get_context(customer, doc):
template_doc = copy.deepcopy(doc)
del template_doc.customers
template_doc.from_date = format_date(template_doc.from_date)
template_doc.to_date = format_date(template_doc.to_date)
return {
- 'doc': template_doc,
- 'customer': frappe.get_doc('Customer', customer),
- 'frappe': frappe.utils
+ "doc": template_doc,
+ "customer": frappe.get_doc("Customer", customer),
+ "frappe": frappe.utils,
}
+
@frappe.whitelist()
def fetch_customers(customer_collection, collection_name, primary_mandatory):
customer_list = []
customers = []
- if customer_collection == 'Sales Person':
+ if customer_collection == "Sales Person":
customers = get_customers_based_on_sales_person(collection_name)
if not bool(customers):
- frappe.throw(_('No Customers found with selected options.'))
+ frappe.throw(_("No Customers found with selected options."))
else:
- if customer_collection == 'Sales Partner':
- customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
- filters=[['default_sales_partner', '=', collection_name]])
+ if customer_collection == "Sales Partner":
+ customers = frappe.get_list(
+ "Customer",
+ fields=["name", "email_id"],
+ filters=[["default_sales_partner", "=", collection_name]],
+ )
else:
- customers = get_customers_based_on_territory_or_customer_group(customer_collection, collection_name)
+ customers = get_customers_based_on_territory_or_customer_group(
+ customer_collection, collection_name
+ )
for customer in customers:
- primary_email = customer.get('email_id') or ''
+ primary_email = customer.get("email_id") or ""
billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False)
if int(primary_mandatory):
- if (primary_email == ''):
+ if primary_email == "":
continue
- elif (billing_email == '') and (primary_email == ''):
+ elif (billing_email == "") and (primary_email == ""):
continue
- customer_list.append({
- 'name': customer.name,
- 'primary_email': primary_email,
- 'billing_email': billing_email
- })
+ customer_list.append(
+ {"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
+ )
return customer_list
+
@frappe.whitelist()
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
- """ Returns first email from Contact Email table as a Billing email
- when Is Billing Contact checked
- and Primary email- email with Is Primary checked """
+ """Returns first email from Contact Email table as a Billing email
+ when Is Billing Contact checked
+ and Primary email- email with Is Primary checked"""
- billing_email = frappe.db.sql("""
+ billing_email = frappe.db.sql(
+ """
SELECT
email.email_id
FROM
@@ -231,42 +273,43 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
and link.link_name=%s
and contact.is_billing_contact=1
ORDER BY
- contact.creation desc""", customer_name)
+ contact.creation desc""",
+ customer_name,
+ )
if len(billing_email) == 0 or (billing_email[0][0] is None):
if billing_and_primary:
frappe.throw(_("No billing email found for customer: {0}").format(customer_name))
else:
- return ''
+ return ""
if billing_and_primary:
- primary_email = frappe.get_value('Customer', customer_name, 'email_id')
+ primary_email = frappe.get_value("Customer", customer_name, "email_id")
if primary_email is None and int(primary_mandatory):
frappe.throw(_("No primary email found for customer: {0}").format(customer_name))
- return [primary_email or '', billing_email[0][0]]
+ return [primary_email or "", billing_email[0][0]]
else:
- return billing_email[0][0] or ''
+ return billing_email[0][0] or ""
+
@frappe.whitelist()
def download_statements(document_name):
- doc = frappe.get_doc('Process Statement Of Accounts', document_name)
+ doc = frappe.get_doc("Process Statement Of Accounts", document_name)
report = get_report_pdf(doc)
if report:
- frappe.local.response.filename = doc.name + '.pdf'
+ frappe.local.response.filename = doc.name + ".pdf"
frappe.local.response.filecontent = report
frappe.local.response.type = "download"
+
@frappe.whitelist()
def send_emails(document_name, from_scheduler=False):
- doc = frappe.get_doc('Process Statement Of Accounts', document_name)
+ doc = frappe.get_doc("Process Statement Of Accounts", document_name)
report = get_report_pdf(doc, consolidated=False)
if report:
for customer, report_pdf in report.items():
- attachments = [{
- 'fname': customer + '.pdf',
- 'fcontent': report_pdf
- }]
+ attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}]
recipients, cc = get_recipients_and_cc(customer, doc)
context = get_context(customer, doc)
@@ -274,7 +317,7 @@ def send_emails(document_name, from_scheduler=False):
message = frappe.render_template(doc.body, context)
frappe.enqueue(
- queue='short',
+ queue="short",
method=frappe.sendmail,
recipients=recipients,
sender=frappe.session.user,
@@ -282,28 +325,34 @@ def send_emails(document_name, from_scheduler=False):
subject=subject,
message=message,
now=True,
- reference_doctype='Process Statement Of Accounts',
+ reference_doctype="Process Statement Of Accounts",
reference_name=document_name,
- attachments=attachments
+ attachments=attachments,
)
if doc.enable_auto_email and from_scheduler:
new_to_date = getdate(today())
- if doc.frequency == 'Weekly':
+ if doc.frequency == "Weekly":
new_to_date = add_days(new_to_date, 7)
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)
- doc.add_comment('Comment', 'Emails sent on: ' + frappe.utils.format_datetime(frappe.utils.now()))
- doc.db_set('to_date', new_to_date, commit=True)
- doc.db_set('from_date', new_from_date, commit=True)
+ doc.add_comment(
+ "Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())
+ )
+ doc.db_set("to_date", new_to_date, commit=True)
+ doc.db_set("from_date", new_from_date, commit=True)
return True
else:
return False
+
@frappe.whitelist()
def send_auto_email():
- selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1})
+ selected = frappe.get_list(
+ "Process Statement Of Accounts",
+ filters={"to_date": format_date(today()), "enable_auto_email": 1},
+ )
for entry in selected:
send_emails(entry.name, from_scheduler=True)
return True
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index 5fbe93ee68..fac9be7bdb 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -6,29 +6,73 @@ import frappe
from frappe import _
from frappe.model.document import Document
-pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group',
- 'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from',
- 'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier',
- 'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
+pricing_rule_fields = [
+ "apply_on",
+ "mixed_conditions",
+ "is_cumulative",
+ "other_item_code",
+ "other_item_group",
+ "apply_rule_on_other",
+ "other_brand",
+ "selling",
+ "buying",
+ "applicable_for",
+ "valid_from",
+ "valid_upto",
+ "customer",
+ "customer_group",
+ "territory",
+ "sales_partner",
+ "campaign",
+ "supplier",
+ "supplier_group",
+ "company",
+ "currency",
+ "apply_multiple_pricing_rules",
+]
-other_fields = ['min_qty', 'max_qty', 'min_amt',
- 'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description']
+other_fields = [
+ "min_qty",
+ "max_qty",
+ "min_amt",
+ "max_amt",
+ "priority",
+ "warehouse",
+ "threshold_percentage",
+ "rule_description",
+]
-price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate',
- 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules']
+price_discount_fields = [
+ "rate_or_discount",
+ "apply_discount_on",
+ "apply_discount_on_rate",
+ "rate",
+ "discount_amount",
+ "discount_percentage",
+ "validate_applied_rule",
+ "apply_multiple_pricing_rules",
+]
+
+product_discount_fields = [
+ "free_item",
+ "free_qty",
+ "free_item_uom",
+ "free_item_rate",
+ "same_item",
+ "is_recursive",
+ "apply_multiple_pricing_rules",
+]
-product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
- 'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules']
class TransactionExists(frappe.ValidationError):
pass
+
class PromotionalScheme(Document):
def validate(self):
if not self.selling and not self.buying:
frappe.throw(_("Either 'Selling' or 'Buying' must be selected"), title=_("Mandatory"))
- if not (self.price_discount_slabs
- or self.product_discount_slabs):
+ if not (self.price_discount_slabs or self.product_discount_slabs):
frappe.throw(_("Price or product discount slabs are required"))
self.validate_applicable_for()
@@ -39,7 +83,7 @@ class PromotionalScheme(Document):
applicable_for = frappe.scrub(self.applicable_for)
if not self.get(applicable_for):
- msg = (f'The field {frappe.bold(self.applicable_for)} is required')
+ msg = f"The field {frappe.bold(self.applicable_for)} is required"
frappe.throw(_(msg))
def validate_pricing_rules(self):
@@ -53,28 +97,28 @@ class PromotionalScheme(Document):
if self._doc_before_save.applicable_for == self.applicable_for:
return
- 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:
- if frappe.db.exists('Pricing Rule Detail',
- {'pricing_rule': docname.name, 'docstatus': ('<', 2)}):
+ if frappe.db.exists(
+ "Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}
+ ):
raise_for_transaction_exists(self.name)
if docnames and not transaction_exists:
for docname in docnames:
- frappe.delete_doc('Pricing Rule', docname.name)
+ frappe.delete_doc("Pricing Rule", docname.name)
def on_update(self):
- pricing_rules = frappe.get_all(
- 'Pricing Rule',
- fields = ["promotional_scheme_id", "name", "creation"],
- filters = {
- 'promotional_scheme': self.name,
- 'applicable_for': self.applicable_for
- },
- order_by = 'creation asc',
- ) or {}
+ pricing_rules = (
+ frappe.get_all(
+ "Pricing Rule",
+ fields=["promotional_scheme_id", "name", "creation"],
+ filters={"promotional_scheme": self.name, "applicable_for": self.applicable_for},
+ order_by="creation asc",
+ )
+ or {}
+ )
self.update_pricing_rules(pricing_rules)
def update_pricing_rules(self, pricing_rules):
@@ -83,7 +127,7 @@ class PromotionalScheme(Document):
names = []
for rule in pricing_rules:
names.append(rule.name)
- rules[rule.get('promotional_scheme_id')] = names
+ rules[rule.get("promotional_scheme_id")] = names
docs = get_pricing_rules(self, rules)
@@ -100,34 +144,38 @@ class PromotionalScheme(Document):
frappe.msgprint(_("New {0} pricing rules are created").format(count))
def on_trash(self):
- for rule in frappe.get_all('Pricing Rule',
- {'promotional_scheme': self.name}):
- frappe.delete_doc('Pricing Rule', rule.name)
+ for rule in frappe.get_all("Pricing Rule", {"promotional_scheme": self.name}):
+ frappe.delete_doc("Pricing Rule", rule.name)
+
def raise_for_transaction_exists(name):
- msg = (f"""You can't change the {frappe.bold(_('Applicable For'))}
- because transactions are present against the Promotional Scheme {frappe.bold(name)}. """)
- msg += 'Kindly disable this Promotional Scheme and create new for new Applicable For.'
+ msg = f"""You can't change the {frappe.bold(_('Applicable For'))}
+ because transactions are present against the Promotional Scheme {frappe.bold(name)}. """
+ msg += "Kindly disable this Promotional Scheme and create new for new Applicable For."
frappe.throw(_(msg), TransactionExists)
+
def get_pricing_rules(doc, rules=None):
if rules is None:
rules = {}
new_doc = []
- for child_doc, fields in {'price_discount_slabs': price_discount_fields,
- 'product_discount_slabs': product_discount_fields}.items():
+ for child_doc, fields in {
+ "price_discount_slabs": price_discount_fields,
+ "product_discount_slabs": product_discount_fields,
+ }.items():
if doc.get(child_doc):
new_doc.extend(_get_pricing_rules(doc, child_doc, fields, rules))
return new_doc
+
def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
if rules is None:
rules = {}
new_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)):
if d.name in rules:
@@ -138,15 +186,23 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
else:
for applicable_for_value in args.get(applicable_for):
docname = get_pricing_rule_docname(d, applicable_for, applicable_for_value)
- pr = prepare_pricing_rule(args, doc, child_doc, discount_fields,
- d, docname, applicable_for, applicable_for_value)
+ pr = prepare_pricing_rule(
+ args, doc, child_doc, discount_fields, d, docname, applicable_for, applicable_for_value
+ )
new_doc.append(pr)
elif args.get(applicable_for):
applicable_for_values = args.get(applicable_for) or []
for applicable_for_value in applicable_for_values:
- pr = prepare_pricing_rule(args, doc, child_doc, discount_fields,
- d, applicable_for=applicable_for, value= applicable_for_value)
+ pr = prepare_pricing_rule(
+ args,
+ doc,
+ child_doc,
+ discount_fields,
+ d,
+ applicable_for=applicable_for,
+ value=applicable_for_value,
+ )
new_doc.append(pr)
else:
@@ -155,20 +211,24 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
return new_doc
-def get_pricing_rule_docname(row: dict, applicable_for: str = None, applicable_for_value: str = None) -> str:
- fields = ['promotional_scheme_id', 'name']
- filters = {
- 'promotional_scheme_id': row.name
- }
+
+def get_pricing_rule_docname(
+ row: dict, applicable_for: str = None, applicable_for_value: str = None
+) -> str:
+ fields = ["promotional_scheme_id", "name"]
+ filters = {"promotional_scheme_id": row.name}
if applicable_for:
fields.append(applicable_for)
filters[applicable_for] = applicable_for_value
- docname = frappe.get_all('Pricing Rule', fields = fields, filters = filters)
- return docname[0].name if docname else ''
+ docname = frappe.get_all("Pricing Rule", fields=fields, filters=filters)
+ return docname[0].name if docname else ""
-def prepare_pricing_rule(args, doc, child_doc, discount_fields, d, docname=None, applicable_for=None, value=None):
+
+def prepare_pricing_rule(
+ args, doc, child_doc, discount_fields, d, docname=None, applicable_for=None, value=None
+):
if docname:
pr = frappe.get_doc("Pricing Rule", docname)
else:
@@ -182,33 +242,31 @@ def prepare_pricing_rule(args, doc, child_doc, discount_fields, d, docname=None,
return set_args(temp_args, pr, doc, child_doc, discount_fields, d)
+
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
pr.update(args)
- for field in (other_fields + discount_fields):
+ for field in other_fields + discount_fields:
pr.set(field, child_doc_fields.get(field))
pr.promotional_scheme_id = child_doc_fields.name
pr.promotional_scheme = doc.name
pr.disable = child_doc_fields.disable if child_doc_fields.disable else doc.disable
- pr.price_or_product_discount = ('Price'
- if child_doc == 'price_discount_slabs' else 'Product')
+ pr.price_or_product_discount = "Price" if child_doc == "price_discount_slabs" else "Product"
- for field in ['items', 'item_groups', 'brands']:
+ for field in ["items", "item_groups", "brands"]:
if doc.get(field):
pr.set(field, [])
- apply_on = frappe.scrub(doc.get('apply_on'))
+ apply_on = frappe.scrub(doc.get("apply_on"))
for d in doc.get(field):
- pr.append(field, {
- apply_on: d.get(apply_on),
- 'uom': d.uom
- })
+ pr.append(field, {apply_on: d.get(apply_on), "uom": d.uom})
return pr
+
def get_args_for_pricing_rule(doc):
- args = { 'promotional_scheme': doc.name }
- applicable_for = frappe.scrub(doc.get('applicable_for'))
+ args = {"promotional_scheme": doc.name}
+ applicable_for = frappe.scrub(doc.get("applicable_for"))
for d in pricing_rule_fields:
if d == applicable_for:
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py
index 6d07924268..70e87404a3 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py
@@ -3,11 +3,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'promotional_scheme',
- 'transactions': [
- {
- 'label': _('Reference'),
- 'items': ['Pricing Rule']
- }
- ]
+ "fieldname": "promotional_scheme",
+ "transactions": [{"label": _("Reference"), "items": ["Pricing Rule"]}],
}
diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
index 49192a45f8..b3b9d7b208 100644
--- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
@@ -11,96 +11,111 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
class TestPromotionalScheme(unittest.TestCase):
def setUp(self):
- if frappe.db.exists('Promotional Scheme', '_Test Scheme'):
- frappe.delete_doc('Promotional Scheme', '_Test Scheme')
+ if frappe.db.exists("Promotional Scheme", "_Test Scheme"):
+ frappe.delete_doc("Promotional Scheme", "_Test Scheme")
def test_promotional_scheme(self):
- ps = make_promotional_scheme(applicable_for='Customer', customer='_Test Customer')
- price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"],
- filters = {'promotional_scheme': ps.name})
- self.assertTrue(len(price_rules),1)
- price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
- self.assertTrue(price_doc_details.customer, '_Test Customer')
+ ps = make_promotional_scheme(applicable_for="Customer", customer="_Test Customer")
+ price_rules = frappe.get_all(
+ "Pricing Rule",
+ fields=["promotional_scheme_id", "name", "creation"],
+ filters={"promotional_scheme": ps.name},
+ )
+ self.assertTrue(len(price_rules), 1)
+ price_doc_details = frappe.db.get_value(
+ "Pricing Rule", price_rules[0].name, ["customer", "min_qty", "discount_percentage"], as_dict=1
+ )
+ self.assertTrue(price_doc_details.customer, "_Test Customer")
self.assertTrue(price_doc_details.min_qty, 4)
self.assertTrue(price_doc_details.discount_percentage, 20)
ps.price_discount_slabs[0].min_qty = 6
- ps.append('customer', {
- 'customer': "_Test Customer 2"})
+ ps.append("customer", {"customer": "_Test Customer 2"})
ps.save()
- price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
- filters = {'promotional_scheme': ps.name})
+ price_rules = frappe.get_all(
+ "Pricing Rule",
+ fields=["promotional_scheme_id", "name"],
+ filters={"promotional_scheme": ps.name},
+ )
self.assertTrue(len(price_rules), 2)
- price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[1].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
- self.assertTrue(price_doc_details.customer, '_Test Customer 2')
+ price_doc_details = frappe.db.get_value(
+ "Pricing Rule", price_rules[1].name, ["customer", "min_qty", "discount_percentage"], as_dict=1
+ )
+ self.assertTrue(price_doc_details.customer, "_Test Customer 2")
self.assertTrue(price_doc_details.min_qty, 6)
self.assertTrue(price_doc_details.discount_percentage, 20)
- price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
- self.assertTrue(price_doc_details.customer, '_Test Customer')
+ price_doc_details = frappe.db.get_value(
+ "Pricing Rule", price_rules[0].name, ["customer", "min_qty", "discount_percentage"], as_dict=1
+ )
+ self.assertTrue(price_doc_details.customer, "_Test Customer")
self.assertTrue(price_doc_details.min_qty, 6)
- frappe.delete_doc('Promotional Scheme', ps.name)
- price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
- filters = {'promotional_scheme': ps.name})
+ frappe.delete_doc("Promotional Scheme", ps.name)
+ price_rules = frappe.get_all(
+ "Pricing Rule",
+ fields=["promotional_scheme_id", "name"],
+ filters={"promotional_scheme": ps.name},
+ )
self.assertEqual(price_rules, [])
def test_promotional_scheme_without_applicable_for(self):
ps = make_promotional_scheme()
- price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
+ price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
self.assertTrue(len(price_rules), 1)
- frappe.delete_doc('Promotional Scheme', ps.name)
+ frappe.delete_doc("Promotional Scheme", ps.name)
- price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
+ price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
self.assertEqual(price_rules, [])
def test_change_applicable_for_in_promotional_scheme(self):
ps = make_promotional_scheme()
- price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
+ price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
self.assertTrue(len(price_rules), 1)
- so = make_sales_order(qty=5, currency='USD', do_not_save=True)
+ so = make_sales_order(qty=5, currency="USD", do_not_save=True)
so.set_missing_values()
so.save()
self.assertEqual(price_rules[0].name, so.pricing_rules[0].pricing_rule)
- ps.applicable_for = 'Customer'
- ps.append('customer', {
- 'customer': '_Test Customer'
- })
+ ps.applicable_for = "Customer"
+ ps.append("customer", {"customer": "_Test Customer"})
self.assertRaises(TransactionExists, ps.save)
- frappe.delete_doc('Sales Order', so.name)
- frappe.delete_doc('Promotional Scheme', ps.name)
- price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
+ frappe.delete_doc("Sales Order", so.name)
+ frappe.delete_doc("Promotional Scheme", ps.name)
+ price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
self.assertEqual(price_rules, [])
+
def make_promotional_scheme(**args):
args = frappe._dict(args)
- ps = frappe.new_doc('Promotional Scheme')
- ps.name = '_Test Scheme'
- ps.append('items',{
- 'item_code': '_Test Item'
- })
+ ps = frappe.new_doc("Promotional Scheme")
+ ps.name = "_Test Scheme"
+ ps.append("items", {"item_code": "_Test Item"})
ps.selling = 1
- ps.append('price_discount_slabs',{
- 'min_qty': 4,
- 'validate_applied_rule': 0,
- 'discount_percentage': 20,
- 'rule_description': 'Test'
- })
+ ps.append(
+ "price_discount_slabs",
+ {
+ "min_qty": 4,
+ "validate_applied_rule": 0,
+ "discount_percentage": 20,
+ "rule_description": "Test",
+ },
+ )
- ps.company = '_Test Company'
+ ps.company = "_Test Company"
if args.applicable_for:
ps.applicable_for = args.applicable_for
- ps.append(frappe.scrub(args.applicable_for), {
- frappe.scrub(args.applicable_for): args.get(frappe.scrub(args.applicable_for))
- })
+ ps.append(
+ frappe.scrub(args.applicable_for),
+ {frappe.scrub(args.applicable_for): args.get(frappe.scrub(args.applicable_for))},
+ )
ps.save()
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 7654aa44dc..57bc0a7721 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -41,27 +41,30 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
)
-class WarehouseMissingError(frappe.ValidationError): pass
+class WarehouseMissingError(frappe.ValidationError):
+ pass
+
+
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
class PurchaseInvoice(BuyingController):
def __init__(self, *args, **kwargs):
super(PurchaseInvoice, self).__init__(*args, **kwargs)
- self.status_updater = [{
- 'source_dt': 'Purchase Invoice Item',
- 'target_dt': 'Purchase Order Item',
- 'join_field': 'po_detail',
- 'target_field': 'billed_amt',
- 'target_parent_dt': 'Purchase Order',
- 'target_parent_field': 'per_billed',
- 'target_ref_field': 'amount',
- 'source_field': 'amount',
- 'percent_join_field': 'purchase_order',
- 'overflow_type': 'billing'
- }]
+ self.status_updater = [
+ {
+ "source_dt": "Purchase Invoice Item",
+ "target_dt": "Purchase Order Item",
+ "join_field": "po_detail",
+ "target_field": "billed_amt",
+ "target_parent_dt": "Purchase Order",
+ "target_parent_field": "per_billed",
+ "target_ref_field": "amount",
+ "source_field": "amount",
+ "percent_join_field": "purchase_order",
+ "overflow_type": "billing",
+ }
+ ]
def onload(self):
super(PurchaseInvoice, self).onload()
@@ -70,15 +73,14 @@ class PurchaseInvoice(BuyingController):
def before_save(self):
if not self.on_hold:
- self.release_date = ''
-
+ self.release_date = ""
def invoice_is_blocked(self):
return self.on_hold and (not self.release_date or self.release_date > getdate(nowdate()))
def validate(self):
if not self.is_opening:
- self.is_opening = 'No'
+ self.is_opening = "No"
self.validate_posting_time()
@@ -90,14 +92,14 @@ class PurchaseInvoice(BuyingController):
self.validate_supplier_invoice()
# validate cash purchase
- if (self.is_paid == 1):
+ if self.is_paid == 1:
self.validate_cash()
# validate service stop date to lie in between start and end date
validate_service_stop_date(self)
- if self._action=="submit" and self.update_stock:
- self.make_batches('warehouse')
+ if self._action == "submit" and self.update_stock:
+ self.make_batches("warehouse")
self.validate_release_date()
self.check_conversion_rate()
@@ -115,39 +117,46 @@ class PurchaseInvoice(BuyingController):
self.create_remarks()
self.set_status()
self.validate_purchase_receipt_if_update_stock()
- validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
+ validate_inter_company_party(
+ self.doctype, self.supplier, self.company, self.inter_company_invoice_reference
+ )
self.reset_default_field_value("set_warehouse", "items", "warehouse")
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
def validate_release_date(self):
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
- frappe.throw(_('Release date must be in the future'))
+ frappe.throw(_("Release date must be in the future"))
def validate_cash(self):
if not self.cash_bank_account and flt(self.paid_amount):
frappe.throw(_("Cash or Bank Account is mandatory for making payment entry"))
- if (flt(self.paid_amount) + flt(self.write_off_amount)
- - flt(self.get("rounded_total") or self.grand_total)
- > 1/(10**(self.precision("base_grand_total") + 1))):
+ if flt(self.paid_amount) + flt(self.write_off_amount) - flt(
+ self.get("rounded_total") or self.grand_total
+ ) > 1 / (10 ** (self.precision("base_grand_total") + 1)):
frappe.throw(_("""Paid amount + Write Off Amount can not be greater than Grand Total"""))
def create_remarks(self):
if not self.remarks:
if self.bill_no and self.bill_date:
- self.remarks = _("Against Supplier Invoice {0} dated {1}").format(self.bill_no,
- formatdate(self.bill_date))
+ self.remarks = _("Against Supplier Invoice {0} dated {1}").format(
+ self.bill_no, formatdate(self.bill_date)
+ )
else:
self.remarks = _("No Remarks")
def set_missing_values(self, for_validate=False):
if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
- self.party_account_currency = frappe.db.get_value("Account", self.credit_to, "account_currency", cache=True)
+ self.party_account_currency = frappe.db.get_value(
+ "Account", self.credit_to, "account_currency", cache=True
+ )
if not self.due_date:
- self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company, self.bill_date)
+ self.due_date = get_due_date(
+ self.posting_date, "Supplier", self.supplier, self.company, self.bill_date
+ )
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
if tds_category and not for_validate:
@@ -159,8 +168,12 @@ class PurchaseInvoice(BuyingController):
def check_conversion_rate(self):
default_currency = erpnext.get_company_currency(self.company)
if not default_currency:
- throw(_('Please enter default currency in Company Master'))
- if (self.currency == default_currency and flt(self.conversion_rate) != 1.00) or not self.conversion_rate or (self.currency != default_currency and flt(self.conversion_rate) == 1.00):
+ throw(_("Please enter default currency in Company Master"))
+ if (
+ (self.currency == default_currency and flt(self.conversion_rate) != 1.00)
+ or not self.conversion_rate
+ or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
+ ):
throw(_("Conversion rate cannot be 0 or 1"))
def validate_credit_to_acc(self):
@@ -169,19 +182,24 @@ class PurchaseInvoice(BuyingController):
if not self.credit_to:
self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
- account = frappe.db.get_value("Account", self.credit_to,
- ["account_type", "report_type", "account_currency"], as_dict=True)
+ account = frappe.db.get_value(
+ "Account", self.credit_to, ["account_type", "report_type", "account_currency"], as_dict=True
+ )
if account.report_type != "Balance Sheet":
frappe.throw(
- _("Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.")
- .format(frappe.bold("Credit To")), title=_("Invalid Account")
+ _(
+ "Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account."
+ ).format(frappe.bold("Credit To")),
+ title=_("Invalid Account"),
)
if self.supplier and account.account_type != "Payable":
frappe.throw(
- _("Please ensure {} account {} is a Payable account. Change the account type to Payable or select a different account.")
- .format(frappe.bold("Credit To"), frappe.bold(self.credit_to)), title=_("Invalid Account")
+ _(
+ "Please ensure {} account {} is a Payable account. Change the account type to Payable or select a different account."
+ ).format(frappe.bold("Credit To"), frappe.bold(self.credit_to)),
+ title=_("Invalid Account"),
)
self.party_account_currency = account.account_currency
@@ -189,51 +207,61 @@ class PurchaseInvoice(BuyingController):
def check_on_hold_or_closed_status(self):
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:
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):
- super(PurchaseInvoice, self).validate_with_previous_doc({
- "Purchase Order": {
- "ref_dn_field": "purchase_order",
- "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
- },
- "Purchase Order Item": {
- "ref_dn_field": "po_detail",
- "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]],
- "is_child_table": True,
- "allow_duplicate_prev_row_id": True
- },
- "Purchase Receipt": {
- "ref_dn_field": "purchase_receipt",
- "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
- },
- "Purchase Receipt Item": {
- "ref_dn_field": "pr_detail",
- "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]],
- "is_child_table": True
+ super(PurchaseInvoice, self).validate_with_previous_doc(
+ {
+ "Purchase Order": {
+ "ref_dn_field": "purchase_order",
+ "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
+ },
+ "Purchase Order Item": {
+ "ref_dn_field": "po_detail",
+ "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]],
+ "is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
+ },
+ "Purchase Receipt": {
+ "ref_dn_field": "purchase_receipt",
+ "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
+ },
+ "Purchase Receipt Item": {
+ "ref_dn_field": "pr_detail",
+ "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]],
+ "is_child_table": True,
+ },
}
- })
+ )
- if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')) and not self.is_return:
- self.validate_rate_with_reference_doc([
- ["Purchase Order", "purchase_order", "po_detail"],
- ["Purchase Receipt", "purchase_receipt", "pr_detail"]
- ])
+ if (
+ cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
+ ):
+ self.validate_rate_with_reference_doc(
+ [
+ ["Purchase Order", "purchase_order", "po_detail"],
+ ["Purchase Receipt", "purchase_receipt", "pr_detail"],
+ ]
+ )
def validate_warehouse(self, for_validate=True):
if self.update_stock and for_validate:
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.warehouse:
- frappe.throw(_("Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}").
- format(d.idx, d.item_code, self.company), exc=WarehouseMissingError)
+ frappe.throw(
+ _(
+ "Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}"
+ ).format(d.idx, d.item_code, self.company),
+ exc=WarehouseMissingError,
+ )
super(PurchaseInvoice, self).validate_warehouse()
def validate_item_code(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.item_code:
frappe.msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
@@ -261,51 +289,82 @@ class PurchaseInvoice(BuyingController):
if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
- if auto_accounting_for_stock and item.item_code in stock_items \
- and self.is_opening == 'No' and not item.is_fixed_asset \
- and (not item.po_detail or
- not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")):
+ if (
+ auto_accounting_for_stock
+ and item.item_code in stock_items
+ and self.is_opening == "No"
+ and not item.is_fixed_asset
+ and (
+ not item.po_detail
+ 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 for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
- msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
- item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
+ if (
+ for_validate
+ and item.expense_account
+ and item.expense_account != warehouse_account[item.warehouse]["account"]
+ ):
+ msg = _(
+ "Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account"
+ ).format(
+ item.idx,
+ frappe.bold(warehouse_account[item.warehouse]["account"]),
+ frappe.bold(item.expense_account),
+ frappe.bold(item.warehouse),
+ )
frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = warehouse_account[item.warehouse]["account"]
else:
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
if item.purchase_receipt:
- negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry`
+ negative_expense_booked_in_pr = frappe.db.sql(
+ """select name from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no=%s and account = %s""",
- (item.purchase_receipt, stock_not_billed_account))
+ (item.purchase_receipt, stock_not_billed_account),
+ )
if negative_expense_booked_in_pr:
- if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
- msg = _("Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}").format(
- item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))
+ if (
+ for_validate and item.expense_account and item.expense_account != stock_not_billed_account
+ ):
+ msg = _(
+ "Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}"
+ ).format(
+ item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt)
+ )
frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account
else:
# 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
- if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
- msg = _("Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.").format(
- item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))
+ if (
+ for_validate and item.expense_account and item.expense_account != stock_not_billed_account
+ ):
+ msg = _(
+ "Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}."
+ ).format(
+ item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code)
+ )
msg += "
"
- msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
+ msg += _(
+ "This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice"
+ )
frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
- asset_category_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
- company = self.company)
+ asset_category_account = get_asset_category_account(
+ "fixed_asset_account", item=item.item_code, company=self.company
+ )
if not asset_category_account:
- form_link = get_link_to_form('Asset Category', asset_category)
+ form_link = get_link_to_form("Asset Category", asset_category)
throw(
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
- title=_("Missing Account")
+ title=_("Missing Account"),
)
item.expense_account = asset_category_account
elif item.is_fixed_asset and item.pr_detail:
@@ -314,8 +373,8 @@ class PurchaseInvoice(BuyingController):
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
def validate_expense_account(self):
- for item in self.get('items'):
- validate_account_head(item.idx, item.expense_account, self.company, 'Expense')
+ for item in self.get("items"):
+ validate_account_head(item.idx, item.expense_account, self.company, "Expense")
def set_against_expense_account(self):
against_accounts = []
@@ -326,32 +385,44 @@ class PurchaseInvoice(BuyingController):
self.against_expense_account = ",".join(against_accounts)
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('Supplier', self.supplier, 'allow_purchase_invoice_creation_without_purchase_order'):
+ if frappe.get_value(
+ "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order"
+ ):
return
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.purchase_order:
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
msg += "
"
msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format(
- frappe.bold(_('Purchase Order Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
+ frappe.bold(_("Purchase Order Required")),
+ frappe.bold("No"),
+ get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
+ )
throw(msg, title=_("Mandatory Purchase Order"))
def pr_required(self):
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('Supplier', self.supplier, 'allow_purchase_invoice_creation_without_purchase_receipt'):
+ if frappe.get_value(
+ "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_receipt"
+ ):
return
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.purchase_receipt and d.item_code in stock_items:
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
msg += "
"
- msg += _("To submit the invoice without purchase receipt please set {0} as {1} in {2}").format(
- frappe.bold(_('Purchase Receipt Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
+ msg += _(
+ "To submit the invoice without purchase receipt please set {0} as {1} in {2}"
+ ).format(
+ frappe.bold(_("Purchase Receipt Required")),
+ frappe.bold("No"),
+ get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
+ )
throw(msg, title=_("Mandatory Purchase Receipt"))
def validate_write_off_account(self):
@@ -359,56 +430,65 @@ class PurchaseInvoice(BuyingController):
throw(_("Please enter Write Off Account"))
def check_prev_docstatus(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.purchase_order:
- submitted = frappe.db.sql("select name from `tabPurchase Order` where docstatus = 1 and name = %s", d.purchase_order)
+ submitted = frappe.db.sql(
+ "select name from `tabPurchase Order` where docstatus = 1 and name = %s", d.purchase_order
+ )
if not submitted:
frappe.throw(_("Purchase Order {0} is not submitted").format(d.purchase_order))
if d.purchase_receipt:
- submitted = frappe.db.sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt)
+ submitted = frappe.db.sql(
+ "select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt
+ )
if not submitted:
frappe.throw(_("Purchase Receipt {0} is not submitted").format(d.purchase_receipt))
def update_status_updater_args(self):
if cint(self.update_stock):
- self.status_updater.append({
- 'source_dt': 'Purchase Invoice Item',
- 'target_dt': 'Purchase Order Item',
- 'join_field': 'po_detail',
- 'target_field': 'received_qty',
- 'target_parent_dt': 'Purchase Order',
- 'target_parent_field': 'per_received',
- 'target_ref_field': 'qty',
- 'source_field': 'received_qty',
- 'second_source_dt': 'Purchase Receipt Item',
- 'second_source_field': 'received_qty',
- 'second_join_field': 'purchase_order_item',
- 'percent_join_field':'purchase_order',
- 'overflow_type': 'receipt',
- 'extra_cond': """ and exists(select name from `tabPurchase Invoice`
- where name=`tabPurchase Invoice Item`.parent and update_stock = 1)"""
- })
+ self.status_updater.append(
+ {
+ "source_dt": "Purchase Invoice Item",
+ "target_dt": "Purchase Order Item",
+ "join_field": "po_detail",
+ "target_field": "received_qty",
+ "target_parent_dt": "Purchase Order",
+ "target_parent_field": "per_received",
+ "target_ref_field": "qty",
+ "source_field": "received_qty",
+ "second_source_dt": "Purchase Receipt Item",
+ "second_source_field": "received_qty",
+ "second_join_field": "purchase_order_item",
+ "percent_join_field": "purchase_order",
+ "overflow_type": "receipt",
+ "extra_cond": """ and exists(select name from `tabPurchase Invoice`
+ where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""",
+ }
+ )
if cint(self.is_return):
- self.status_updater.append({
- 'source_dt': 'Purchase Invoice Item',
- 'target_dt': 'Purchase Order Item',
- 'join_field': 'po_detail',
- 'target_field': 'returned_qty',
- 'source_field': '-1 * qty',
- 'second_source_dt': 'Purchase Receipt Item',
- 'second_source_field': '-1 * qty',
- 'second_join_field': 'purchase_order_item',
- 'overflow_type': 'receipt',
- 'extra_cond': """ and exists (select name from `tabPurchase Invoice`
- where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)"""
- })
+ self.status_updater.append(
+ {
+ "source_dt": "Purchase Invoice Item",
+ "target_dt": "Purchase Order Item",
+ "join_field": "po_detail",
+ "target_field": "returned_qty",
+ "source_field": "-1 * qty",
+ "second_source_dt": "Purchase Receipt Item",
+ "second_source_field": "-1 * qty",
+ "second_join_field": "purchase_order_item",
+ "overflow_type": "receipt",
+ "extra_cond": """ and exists (select name from `tabPurchase Invoice`
+ where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)""",
+ }
+ )
def validate_purchase_receipt_if_update_stock(self):
if self.update_stock:
for item in self.get("items"):
if item.purchase_receipt:
- frappe.throw(_("Stock cannot be updated against Purchase Receipt {0}")
- .format(item.purchase_receipt))
+ frappe.throw(
+ _("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)
+ )
def on_submit(self):
super(PurchaseInvoice, self).on_submit()
@@ -417,8 +497,9 @@ class PurchaseInvoice(BuyingController):
self.update_status_updater_args()
self.update_prevdoc_status()
- frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
- self.company, self.base_grand_total)
+ frappe.get_doc("Authorization Control").validate_approving_authority(
+ self.doctype, self.company, self.base_grand_total
+ )
if not self.is_return:
self.update_against_document_in_jv()
@@ -433,6 +514,7 @@ class PurchaseInvoice(BuyingController):
self.update_stock_ledger()
self.set_consumed_qty_in_po()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
+
update_serial_nos_after_submit(self, "items")
# this sequence because outstanding may get -negative
@@ -455,13 +537,23 @@ class PurchaseInvoice(BuyingController):
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
if self.docstatus == 1:
- make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
+ make_gl_entries(
+ gl_entries,
+ update_outstanding=update_outstanding,
+ merge_entries=False,
+ from_repost=from_repost,
+ )
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
- update_outstanding_amt(self.credit_to, "Supplier", self.supplier,
- self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
+ update_outstanding_amt(
+ self.credit_to,
+ "Supplier",
+ self.supplier,
+ self.doctype,
+ self.return_against if cint(self.is_return) and self.return_against else self.name,
+ )
elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@@ -510,28 +602,41 @@ class PurchaseInvoice(BuyingController):
def make_supplier_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
- grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
- base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total)
- else self.base_grand_total, self.precision("base_grand_total"))
+ grand_total = (
+ self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
+ )
+ base_grand_total = flt(
+ self.base_rounded_total
+ if (self.base_rounding_adjustment and self.base_rounded_total)
+ else self.base_grand_total,
+ self.precision("base_grand_total"),
+ )
if grand_total and not self.is_internal_transfer():
- # Did not use base_grand_total to book rounding loss gle
- gl_entries.append(
- self.get_gl_dict({
+ # Did not use base_grand_total to book rounding loss gle
+ gl_entries.append(
+ self.get_gl_dict(
+ {
"account": self.credit_to,
"party_type": "Supplier",
"party": self.supplier,
"due_date": self.due_date,
"against": self.against_expense_account,
"credit": base_grand_total,
- "credit_in_account_currency": base_grand_total \
- if self.party_account_currency==self.company_currency else grand_total,
- "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
+ "credit_in_account_currency": base_grand_total
+ if self.party_account_currency == self.company_currency
+ else grand_total,
+ "against_voucher": self.return_against
+ if cint(self.is_return) and self.return_against
+ else self.name,
"against_voucher_type": self.doctype,
"project": self.project,
- "cost_center": self.cost_center
- }, self.party_account_currency, item=self)
+ "cost_center": self.cost_center,
+ },
+ self.party_account_currency,
+ item=self,
)
+ )
def make_item_gl_entries(self, gl_entries):
# item gl entries
@@ -543,22 +648,33 @@ class PurchaseInvoice(BuyingController):
voucher_wise_stock_value = {}
if self.update_stock:
- stock_ledger_entries = frappe.get_all("Stock Ledger Entry",
- fields = ["voucher_detail_no", "stock_value_difference", "warehouse"],
- filters={"voucher_no": self.name, "voucher_type": self.doctype, "is_cancelled": 0}
+ stock_ledger_entries = frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["voucher_detail_no", "stock_value_difference", "warehouse"],
+ filters={"voucher_no": self.name, "voucher_type": self.doctype, "is_cancelled": 0},
)
for d in stock_ledger_entries:
- voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
+ voucher_wise_stock_value.setdefault(
+ (d.voucher_detail_no, d.warehouse), d.stock_value_difference
+ )
- valuation_tax_accounts = [d.account_head for d in self.get("taxes")
- if d.category in ('Valuation', 'Total and Valuation')
- and flt(d.base_tax_amount_after_discount_amount)]
+ valuation_tax_accounts = [
+ d.account_head
+ for d in self.get("taxes")
+ if d.category in ("Valuation", "Total and Valuation")
+ and flt(d.base_tax_amount_after_discount_amount)
+ ]
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
- enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
- provisional_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, \
- 'enable_provisional_accounting_for_non_stock_items'))
+ enable_discount_accounting = cint(
+ frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+ )
+ provisional_accounting_for_non_stock_items = cint(
+ frappe.db.get_value(
+ "Company", self.company, "enable_provisional_accounting_for_non_stock_items"
+ )
+ )
purchase_receipt_doc_map = {}
@@ -570,86 +686,122 @@ class PurchaseInvoice(BuyingController):
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
# warehouse account
- warehouse_debit_amount = self.make_stock_adjustment_entry(gl_entries,
- item, voucher_wise_stock_value, account_currency)
+ warehouse_debit_amount = self.make_stock_adjustment_entry(
+ gl_entries, item, voucher_wise_stock_value, account_currency
+ )
if item.from_warehouse:
- gl_entries.append(self.get_gl_dict({
- "account": warehouse_account[item.warehouse]['account'],
- "against": warehouse_account[item.from_warehouse]["account"],
- "cost_center": item.cost_center,
- "project": item.project or self.project,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": warehouse_debit_amount,
- }, warehouse_account[item.warehouse]["account_currency"], item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": warehouse_account[item.warehouse]["account"],
+ "against": warehouse_account[item.from_warehouse]["account"],
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": warehouse_debit_amount,
+ },
+ warehouse_account[item.warehouse]["account_currency"],
+ item=item,
+ )
+ )
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
- gl_entries.append(self.get_gl_dict({
- "account": warehouse_account[item.from_warehouse]['account'],
- "against": warehouse_account[item.warehouse]["account"],
- "cost_center": item.cost_center,
- "project": item.project or self.project,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
- }, warehouse_account[item.from_warehouse]["account_currency"], item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": warehouse_account[item.from_warehouse]["account"],
+ "against": warehouse_account[item.warehouse]["account"],
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
+ },
+ warehouse_account[item.from_warehouse]["account_currency"],
+ item=item,
+ )
+ )
# Do not book expense for transfer within same company transfer
if not self.is_internal_transfer():
gl_entries.append(
- self.get_gl_dict({
- "account": item.expense_account,
- "against": self.supplier,
- "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "cost_center": item.cost_center,
- "project": item.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": item.expense_account,
+ "against": self.supplier,
+ "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "cost_center": item.cost_center,
+ "project": item.project,
+ },
+ account_currency,
+ item=item,
+ )
)
else:
if not self.is_internal_transfer():
gl_entries.append(
- self.get_gl_dict({
- "account": item.expense_account,
- "against": self.supplier,
- "debit": warehouse_debit_amount,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": item.expense_account,
+ "against": self.supplier,
+ "debit": warehouse_debit_amount,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ account_currency,
+ item=item,
+ )
)
# Amount added through landed-cost-voucher
if landed_cost_entries:
for account, amount in landed_cost_entries[(item.item_code, item.name)].items():
- gl_entries.append(self.get_gl_dict({
- "account": account,
- "against": item.expense_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(amount["base_amount"]),
- "credit_in_account_currency": flt(amount["amount"]),
- "project": item.project or self.project
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": account,
+ "against": item.expense_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(amount["base_amount"]),
+ "credit_in_account_currency": flt(amount["amount"]),
+ "project": item.project or self.project,
+ },
+ item=item,
+ )
+ )
# sub-contracting warehouse
if flt(item.rm_supp_cost):
supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"]
if not supplier_warehouse_account:
- frappe.throw(_("Please set account in Warehouse {0}")
- .format(self.supplier_warehouse))
- gl_entries.append(self.get_gl_dict({
- "account": supplier_warehouse_account,
- "against": item.expense_account,
- "cost_center": item.cost_center,
- "project": item.project or self.project,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(item.rm_supp_cost)
- }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item))
+ frappe.throw(_("Please set account in Warehouse {0}").format(self.supplier_warehouse))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": supplier_warehouse_account,
+ "against": item.expense_account,
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(item.rm_supp_cost),
+ },
+ warehouse_account[self.supplier_warehouse]["account_currency"],
+ item=item,
+ )
+ )
- elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)):
- expense_account = (item.expense_account
- if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
+ elif not item.is_fixed_asset or (
+ item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
+ ):
+ expense_account = (
+ item.expense_account
+ if (not item.enable_deferred_expense or self.is_return)
+ else item.deferred_expense_account
+ )
if not item.is_fixed_asset:
dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
@@ -666,103 +818,154 @@ class PurchaseInvoice(BuyingController):
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
- expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0,
- 'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail,
- 'account':provisional_account}, ['name'])
+ expense_booked_in_pr = frappe.db.get_value(
+ "GL Entry",
+ {
+ "is_cancelled": 0,
+ "voucher_type": "Purchase Receipt",
+ "voucher_no": item.purchase_receipt,
+ "voucher_detail_no": item.pr_detail,
+ "account": provisional_account,
+ },
+ ["name"],
+ )
if expense_booked_in_pr:
# Intentionally passing purchase invoice item to handle partial billing
- purchase_receipt_doc.add_provisional_gl_entry(item, gl_entries, self.posting_date, reverse=1)
+ purchase_receipt_doc.add_provisional_gl_entry(
+ item, gl_entries, self.posting_date, reverse=1
+ )
if not self.is_internal_transfer():
- gl_entries.append(self.get_gl_dict({
- "account": expense_account,
- "against": self.supplier,
- "debit": amount,
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": expense_account,
+ "against": self.supplier,
+ "debit": amount,
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ account_currency,
+ item=item,
+ )
+ )
# check if the exchange rate has changed
- if item.get('purchase_receipt'):
- if 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]:
+ if item.get("purchase_receipt"):
+ if (
+ 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]
+ ):
- discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * \
- (exchange_rate_map[item.purchase_receipt] - self.conversion_rate)
+ discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * (
+ exchange_rate_map[item.purchase_receipt] - self.conversion_rate
+ )
gl_entries.append(
- self.get_gl_dict({
- "account": expense_account,
- "against": self.supplier,
- "debit": discrepancy_caused_by_exchange_rate_difference,
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": expense_account,
+ "against": self.supplier,
+ "debit": discrepancy_caused_by_exchange_rate_difference,
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ account_currency,
+ item=item,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": self.get_company_default("exchange_gain_loss_account"),
- "against": self.supplier,
- "credit": discrepancy_caused_by_exchange_rate_difference,
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": self.get_company_default("exchange_gain_loss_account"),
+ "against": self.supplier,
+ "credit": discrepancy_caused_by_exchange_rate_difference,
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ account_currency,
+ item=item,
+ )
)
# If asset is bought through this document and not linked to PR
if self.update_stock and item.landed_cost_voucher_amount:
- expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
+ expenses_included_in_asset_valuation = self.get_company_default(
+ "expenses_included_in_asset_valuation"
+ )
# Amount added through landed-cost-voucher
- gl_entries.append(self.get_gl_dict({
- "account": expenses_included_in_asset_valuation,
- "against": expense_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(item.landed_cost_voucher_amount),
- "project": item.project or self.project
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": expenses_included_in_asset_valuation,
+ "against": expense_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(item.landed_cost_voucher_amount),
+ "project": item.project or self.project,
+ },
+ item=item,
+ )
+ )
- gl_entries.append(self.get_gl_dict({
- "account": expense_account,
- "against": expenses_included_in_asset_valuation,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": flt(item.landed_cost_voucher_amount),
- "project": item.project or self.project
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": expense_account,
+ "against": expenses_included_in_asset_valuation,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": flt(item.landed_cost_voucher_amount),
+ "project": item.project or self.project,
+ },
+ item=item,
+ )
+ )
# update gross amount of asset bought through this document
- assets = frappe.db.get_all('Asset',
- filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
+ assets = frappe.db.get_all(
+ "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
)
for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
- frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
+ frappe.db.set_value(
+ "Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
+ )
- if self.auto_accounting_for_stock and self.is_opening == "No" and \
- item.item_code in stock_items and item.item_tax_amount:
- # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
- if item.purchase_receipt and valuation_tax_accounts:
- negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry`
+ if (
+ self.auto_accounting_for_stock
+ and self.is_opening == "No"
+ and item.item_code in stock_items
+ and item.item_tax_amount
+ ):
+ # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
+ if item.purchase_receipt and valuation_tax_accounts:
+ negative_expense_booked_in_pr = frappe.db.sql(
+ """select name from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""",
- (item.purchase_receipt, valuation_tax_accounts))
+ (item.purchase_receipt, valuation_tax_accounts),
+ )
- if not negative_expense_booked_in_pr:
- gl_entries.append(
- self.get_gl_dict({
+ if not negative_expense_booked_in_pr:
+ gl_entries.append(
+ self.get_gl_dict(
+ {
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center,
- "project": item.project or self.project
- }, item=item)
+ "project": item.project or self.project,
+ },
+ item=item,
)
+ )
- self.negative_expense_to_be_booked += flt(item.item_tax_amount, \
- item.precision("item_tax_amount"))
+ self.negative_expense_to_be_booked += flt(
+ item.item_tax_amount, item.precision("item_tax_amount")
+ )
def get_asset_gl_entry(self, gl_entries):
arbnb_account = self.get_company_default("asset_received_but_not_billed")
@@ -770,125 +973,179 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"):
if item.is_fixed_asset:
- asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
+ asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
- item_exp_acc_type = frappe.db.get_value('Account', item.expense_account, 'account_type')
- if (not item.expense_account or item_exp_acc_type not in ['Asset Received But Not Billed', 'Fixed Asset']):
+ item_exp_acc_type = frappe.db.get_value("Account", item.expense_account, "account_type")
+ if not item.expense_account or item_exp_acc_type not in [
+ "Asset Received But Not Billed",
+ "Fixed Asset",
+ ]:
item.expense_account = arbnb_account
if not self.update_stock:
arbnb_currency = get_account_currency(item.expense_account)
- gl_entries.append(self.get_gl_dict({
- "account": item.expense_account,
- "against": self.supplier,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "debit": base_asset_amount,
- "debit_in_account_currency": (base_asset_amount
- if arbnb_currency == self.company_currency else asset_amount),
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": item.expense_account,
+ "against": self.supplier,
+ "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
+ "debit": base_asset_amount,
+ "debit_in_account_currency": (
+ base_asset_amount if arbnb_currency == self.company_currency else asset_amount
+ ),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ item=item,
+ )
+ )
if item.item_tax_amount:
asset_eiiav_currency = get_account_currency(eiiav_account)
- gl_entries.append(self.get_gl_dict({
- "account": eiiav_account,
- "against": self.supplier,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "cost_center": item.cost_center,
- "project": item.project or self.project,
- "credit": item.item_tax_amount,
- "credit_in_account_currency": (item.item_tax_amount
- if asset_eiiav_currency == self.company_currency else
- item.item_tax_amount / self.conversion_rate)
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": eiiav_account,
+ "against": self.supplier,
+ "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ "credit": item.item_tax_amount,
+ "credit_in_account_currency": (
+ item.item_tax_amount
+ if asset_eiiav_currency == self.company_currency
+ else item.item_tax_amount / self.conversion_rate
+ ),
+ },
+ item=item,
+ )
+ )
else:
- cwip_account = get_asset_account("capital_work_in_progress_account",
- asset_category=item.asset_category,company=self.company)
+ cwip_account = get_asset_account(
+ "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
+ )
cwip_account_currency = get_account_currency(cwip_account)
- gl_entries.append(self.get_gl_dict({
- "account": cwip_account,
- "against": self.supplier,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "debit": base_asset_amount,
- "debit_in_account_currency": (base_asset_amount
- if cwip_account_currency == self.company_currency else asset_amount),
- "cost_center": self.cost_center,
- "project": item.project or self.project
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": cwip_account,
+ "against": self.supplier,
+ "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
+ "debit": base_asset_amount,
+ "debit_in_account_currency": (
+ base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
+ ),
+ "cost_center": self.cost_center,
+ "project": item.project or self.project,
+ },
+ item=item,
+ )
+ )
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
asset_eiiav_currency = get_account_currency(eiiav_account)
- gl_entries.append(self.get_gl_dict({
- "account": eiiav_account,
- "against": self.supplier,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "cost_center": item.cost_center,
- "credit": item.item_tax_amount,
- "project": item.project or self.project,
- "credit_in_account_currency": (item.item_tax_amount
- if asset_eiiav_currency == self.company_currency else
- item.item_tax_amount / self.conversion_rate)
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": eiiav_account,
+ "against": self.supplier,
+ "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
+ "cost_center": item.cost_center,
+ "credit": item.item_tax_amount,
+ "project": item.project or self.project,
+ "credit_in_account_currency": (
+ item.item_tax_amount
+ if asset_eiiav_currency == self.company_currency
+ else item.item_tax_amount / self.conversion_rate
+ ),
+ },
+ item=item,
+ )
+ )
# When update stock is checked
# Assets are bought through this document then it will be linked to this document
if self.update_stock:
if flt(item.landed_cost_voucher_amount):
- gl_entries.append(self.get_gl_dict({
- "account": eiiav_account,
- "against": cwip_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(item.landed_cost_voucher_amount),
- "project": item.project or self.project
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": eiiav_account,
+ "against": cwip_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(item.landed_cost_voucher_amount),
+ "project": item.project or self.project,
+ },
+ item=item,
+ )
+ )
- gl_entries.append(self.get_gl_dict({
- "account": cwip_account,
- "against": eiiav_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": flt(item.landed_cost_voucher_amount),
- "project": item.project or self.project
- }, item=item))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": cwip_account,
+ "against": eiiav_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": flt(item.landed_cost_voucher_amount),
+ "project": item.project or self.project,
+ },
+ item=item,
+ )
+ )
# update gross amount of assets bought through this document
- assets = frappe.db.get_all('Asset',
- filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
+ assets = frappe.db.get_all(
+ "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
)
for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
- frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
+ frappe.db.set_value(
+ "Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
+ )
return gl_entries
- def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
+ def make_stock_adjustment_entry(
+ self, gl_entries, item, voucher_wise_stock_value, account_currency
+ ):
net_amt_precision = item.precision("base_net_amount")
val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
- warehouse_debit_amount = flt(flt(item.valuation_rate, val_rate_db_precision)
- * flt(item.qty) * flt(item.conversion_factor), net_amt_precision)
+ warehouse_debit_amount = flt(
+ flt(item.valuation_rate, val_rate_db_precision) * flt(item.qty) * flt(item.conversion_factor),
+ net_amt_precision,
+ )
# Stock ledger value is not matching with the warehouse amount
- if (self.update_stock and voucher_wise_stock_value.get(item.name) and
- warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)):
+ if (
+ self.update_stock
+ and voucher_wise_stock_value.get(item.name)
+ and warehouse_debit_amount
+ != 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")
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount
gl_entries.append(
- self.get_gl_dict({
- "account": cost_of_goods_sold_account,
- "against": item.expense_account,
- "debit": stock_adjustment_amt,
- "remarks": self.get("remarks") or _("Stock Adjustment"),
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": cost_of_goods_sold_account,
+ "against": item.expense_account,
+ "debit": stock_adjustment_amt,
+ "remarks": self.get("remarks") or _("Stock Adjustment"),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ account_currency,
+ item=item,
+ )
)
warehouse_debit_amount = stock_amount
@@ -898,7 +1155,9 @@ class PurchaseInvoice(BuyingController):
def make_tax_gl_entries(self, gl_entries):
# tax table gl entries
valuation_tax = {}
- enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+ enable_discount_accounting = cint(
+ frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+ )
for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
@@ -908,24 +1167,35 @@ class PurchaseInvoice(BuyingController):
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
gl_entries.append(
- self.get_gl_dict({
- "account": tax.account_head,
- "against": self.supplier,
- dr_or_cr: base_amount,
- dr_or_cr + "_in_account_currency": base_amount
- if account_currency==self.company_currency
+ self.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "against": self.supplier,
+ dr_or_cr: base_amount,
+ dr_or_cr + "_in_account_currency": base_amount
+ if account_currency == self.company_currency
else amount,
- "cost_center": tax.cost_center
- }, account_currency, item=tax)
+ "cost_center": tax.cost_center,
+ },
+ account_currency,
+ item=tax,
+ )
)
# accumulate valuation tax
- if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(base_amount) \
- and not self.is_internal_transfer():
+ if (
+ self.is_opening == "No"
+ and tax.category in ("Valuation", "Valuation and Total")
+ and flt(base_amount)
+ and not self.is_internal_transfer()
+ ):
if self.auto_accounting_for_stock and not tax.cost_center:
- frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
+ frappe.throw(
+ _("Cost Center is required in row {0} in Taxes table for type {1}").format(
+ tax.idx, _(tax.category)
+ )
+ )
valuation_tax.setdefault(tax.name, 0)
- valuation_tax[tax.name] += \
- (tax.add_deduct_tax == "Add" and 1 or -1) * flt(base_amount)
+ valuation_tax[tax.name] += (tax.add_deduct_tax == "Add" and 1 or -1) * flt(base_amount)
if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
# credit valuation tax amount in "Expenses Included In Valuation"
@@ -939,17 +1209,22 @@ class PurchaseInvoice(BuyingController):
if i == len(valuation_tax):
applicable_amount = amount_including_divisional_loss
else:
- applicable_amount = self.negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
+ applicable_amount = self.negative_expense_to_be_booked * (
+ valuation_tax[tax.name] / total_valuation_amount
+ )
amount_including_divisional_loss -= applicable_amount
gl_entries.append(
- self.get_gl_dict({
- "account": tax.account_head,
- "cost_center": tax.cost_center,
- "against": self.supplier,
- "credit": applicable_amount,
- "remarks": self.remarks or _("Accounting Entry for Stock"),
- }, item=tax)
+ self.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "against": self.supplier,
+ "credit": applicable_amount,
+ "remarks": self.remarks or _("Accounting Entry for Stock"),
+ },
+ item=tax,
+ )
)
i += 1
@@ -958,18 +1233,24 @@ class PurchaseInvoice(BuyingController):
for tax in self.get("taxes"):
if valuation_tax.get(tax.name):
gl_entries.append(
- self.get_gl_dict({
- "account": tax.account_head,
- "cost_center": tax.cost_center,
- "against": self.supplier,
- "credit": valuation_tax[tax.name],
- "remarks": self.remarks or _("Accounting Entry for Stock")
- }, item=tax))
+ self.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "against": self.supplier,
+ "credit": valuation_tax[tax.name],
+ "remarks": self.remarks or _("Accounting Entry for Stock"),
+ },
+ item=tax,
+ )
+ )
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):
- self._enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+ self._enable_discount_accounting = cint(
+ frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+ )
return self._enable_discount_accounting
@@ -977,13 +1258,18 @@ class PurchaseInvoice(BuyingController):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
- self.get_gl_dict({
- "account": self.unrealized_profit_loss_account,
- "against": self.supplier,
- "credit": flt(self.total_taxes_and_charges),
- "credit_in_account_currency": flt(self.base_total_taxes_and_charges),
- "cost_center": self.cost_center
- }, account_currency, item=self))
+ self.get_gl_dict(
+ {
+ "account": self.unrealized_profit_loss_account,
+ "against": self.supplier,
+ "credit": flt(self.total_taxes_and_charges),
+ "credit_in_account_currency": flt(self.base_total_taxes_and_charges),
+ "cost_center": self.cost_center,
+ },
+ account_currency,
+ item=self,
+ )
+ )
def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries
@@ -991,30 +1277,42 @@ class PurchaseInvoice(BuyingController):
bank_account_currency = get_account_currency(self.cash_bank_account)
# CASH, make payment entries
gl_entries.append(
- self.get_gl_dict({
- "account": self.credit_to,
- "party_type": "Supplier",
- "party": self.supplier,
- "against": self.cash_bank_account,
- "debit": self.base_paid_amount,
- "debit_in_account_currency": self.base_paid_amount \
- if self.party_account_currency==self.company_currency else self.paid_amount,
- "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype,
- "cost_center": self.cost_center,
- "project": self.project
- }, self.party_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.credit_to,
+ "party_type": "Supplier",
+ "party": self.supplier,
+ "against": self.cash_bank_account,
+ "debit": self.base_paid_amount,
+ "debit_in_account_currency": self.base_paid_amount
+ if self.party_account_currency == self.company_currency
+ else self.paid_amount,
+ "against_voucher": self.return_against
+ if cint(self.is_return) and self.return_against
+ else self.name,
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center,
+ "project": self.project,
+ },
+ self.party_account_currency,
+ item=self,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": self.cash_bank_account,
- "against": self.supplier,
- "credit": self.base_paid_amount,
- "credit_in_account_currency": self.base_paid_amount \
- if bank_account_currency==self.company_currency else self.paid_amount,
- "cost_center": self.cost_center
- }, bank_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.cash_bank_account,
+ "against": self.supplier,
+ "credit": self.base_paid_amount,
+ "credit_in_account_currency": self.base_paid_amount
+ if bank_account_currency == self.company_currency
+ else self.paid_amount,
+ "cost_center": self.cost_center,
+ },
+ bank_account_currency,
+ item=self,
+ )
)
def make_write_off_gl_entry(self, gl_entries):
@@ -1024,48 +1322,64 @@ class PurchaseInvoice(BuyingController):
write_off_account_currency = get_account_currency(self.write_off_account)
gl_entries.append(
- self.get_gl_dict({
- "account": self.credit_to,
- "party_type": "Supplier",
- "party": self.supplier,
- "against": self.write_off_account,
- "debit": self.base_write_off_amount,
- "debit_in_account_currency": self.base_write_off_amount \
- if self.party_account_currency==self.company_currency else self.write_off_amount,
- "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype,
- "cost_center": self.cost_center,
- "project": self.project
- }, self.party_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.credit_to,
+ "party_type": "Supplier",
+ "party": self.supplier,
+ "against": self.write_off_account,
+ "debit": self.base_write_off_amount,
+ "debit_in_account_currency": self.base_write_off_amount
+ if self.party_account_currency == self.company_currency
+ else self.write_off_amount,
+ "against_voucher": self.return_against
+ if cint(self.is_return) and self.return_against
+ else self.name,
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center,
+ "project": self.project,
+ },
+ self.party_account_currency,
+ item=self,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": self.write_off_account,
- "against": self.supplier,
- "credit": flt(self.base_write_off_amount),
- "credit_in_account_currency": self.base_write_off_amount \
- if write_off_account_currency==self.company_currency else self.write_off_amount,
- "cost_center": self.cost_center or self.write_off_cost_center
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.write_off_account,
+ "against": self.supplier,
+ "credit": flt(self.base_write_off_amount),
+ "credit_in_account_currency": self.base_write_off_amount
+ if write_off_account_currency == self.company_currency
+ else self.write_off_amount,
+ "cost_center": self.cost_center or self.write_off_cost_center,
+ },
+ item=self,
+ )
)
def make_gle_for_rounding_adjustment(self, gl_entries):
# if rounding adjustment in small and conversion rate is also small then
# 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
- # then base_rounding_adjustment becomes zero and error is thrown in GL Entry
- if not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment:
- round_off_account, round_off_cost_center = \
- get_round_off_account_and_cost_center(self.company)
+ # then base_rounding_adjustment becomes zero and error is thrown in GL Entry
+ if (
+ not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
+ ):
+ round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
gl_entries.append(
- self.get_gl_dict({
- "account": round_off_account,
- "against": self.supplier,
- "debit_in_account_currency": self.rounding_adjustment,
- "debit": self.base_rounding_adjustment,
- "cost_center": self.cost_center or round_off_cost_center,
- }, item=self))
+ self.get_gl_dict(
+ {
+ "account": round_off_account,
+ "against": self.supplier,
+ "debit_in_account_currency": self.rounding_adjustment,
+ "debit": self.base_rounding_adjustment,
+ "cost_center": self.cost_center or round_off_cost_center,
+ },
+ item=self,
+ )
+ )
def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self)
@@ -1096,10 +1410,10 @@ class PurchaseInvoice(BuyingController):
self.repost_future_sle_and_gle()
self.update_project()
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.update_advance_tax_references(cancel=1)
def update_project(self):
@@ -1120,19 +1434,22 @@ class PurchaseInvoice(BuyingController):
if cint(frappe.db.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")):
fiscal_year = get_fiscal_year(self.posting_date, company=self.company, as_dict=True)
- pi = frappe.db.sql('''select name from `tabPurchase Invoice`
+ pi = frappe.db.sql(
+ """select name from `tabPurchase Invoice`
where
bill_no = %(bill_no)s
and supplier = %(supplier)s
and name != %(name)s
and docstatus < 2
- and posting_date between %(year_start_date)s and %(year_end_date)s''', {
- "bill_no": self.bill_no,
- "supplier": self.supplier,
- "name": self.name,
- "year_start_date": fiscal_year.year_start_date,
- "year_end_date": fiscal_year.year_end_date
- })
+ and posting_date between %(year_start_date)s and %(year_end_date)s""",
+ {
+ "bill_no": self.bill_no,
+ "supplier": self.supplier,
+ "name": self.name,
+ "year_start_date": fiscal_year.year_start_date,
+ "year_end_date": fiscal_year.year_end_date,
+ },
+ )
if pi:
pi = pi[0][0]
@@ -1142,16 +1459,26 @@ class PurchaseInvoice(BuyingController):
updated_pr = []
for d in self.get("items"):
if d.pr_detail:
- billed_amt = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
- where pr_detail=%s and docstatus=1""", d.pr_detail)
+ billed_amt = frappe.db.sql(
+ """select sum(amount) from `tabPurchase Invoice Item`
+ where pr_detail=%s and docstatus=1""",
+ d.pr_detail,
+ )
billed_amt = billed_amt and billed_amt[0][0] or 0
- frappe.db.set_value("Purchase Receipt Item", d.pr_detail, "billed_amt", billed_amt, update_modified=update_modified)
+ frappe.db.set_value(
+ "Purchase Receipt Item",
+ d.pr_detail,
+ "billed_amt",
+ billed_amt,
+ update_modified=update_modified,
+ )
updated_pr.append(d.purchase_receipt)
elif d.po_detail:
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
for pr in set(updated_pr):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
+
pr_doc = frappe.get_doc("Purchase Receipt", pr)
update_billing_percentage(pr_doc, update_modified=update_modified)
@@ -1159,25 +1486,29 @@ class PurchaseInvoice(BuyingController):
self.due_date = None
def block_invoice(self, hold_comment=None, release_date=None):
- self.db_set('on_hold', 1)
- self.db_set('hold_comment', cstr(hold_comment))
- self.db_set('release_date', release_date)
+ self.db_set("on_hold", 1)
+ self.db_set("hold_comment", cstr(hold_comment))
+ self.db_set("release_date", release_date)
def unblock_invoice(self):
- self.db_set('on_hold', 0)
- self.db_set('release_date', None)
+ self.db_set("on_hold", 0)
+ self.db_set("release_date", None)
def set_tax_withholding(self):
if not self.apply_tds:
return
- if self.apply_tds and not self.get('tax_withholding_category'):
- self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category')
+ if self.apply_tds and not self.get("tax_withholding_category"):
+ self.tax_withholding_category = frappe.db.get_value(
+ "Supplier", self.supplier, "tax_withholding_category"
+ )
if not self.tax_withholding_category:
return
- tax_withholding_details, advance_taxes = get_party_tax_withholding_details(self, self.tax_withholding_category)
+ tax_withholding_details, advance_taxes = get_party_tax_withholding_details(
+ self, self.tax_withholding_category
+ )
# Adjust TDS paid on advances
self.allocate_advance_tds(tax_withholding_details, advance_taxes)
@@ -1195,8 +1526,11 @@ class PurchaseInvoice(BuyingController):
if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("taxes", tax_withholding_details)
- to_remove = [d for d in self.taxes
- if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
+ to_remove = [
+ d
+ for d in self.taxes
+ if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")
+ ]
for d in to_remove:
self.remove(d)
@@ -1205,27 +1539,33 @@ class PurchaseInvoice(BuyingController):
self.calculate_taxes_and_totals()
def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
- self.set('advance_tax', [])
+ self.set("advance_tax", [])
for tax in advance_taxes:
allocated_amount = 0
pending_amount = flt(tax.tax_amount - tax.allocated_amount)
- if flt(tax_withholding_details.get('tax_amount')) >= pending_amount:
- tax_withholding_details['tax_amount'] -= pending_amount
+ if flt(tax_withholding_details.get("tax_amount")) >= pending_amount:
+ tax_withholding_details["tax_amount"] -= pending_amount
allocated_amount = pending_amount
- elif flt(tax_withholding_details.get('tax_amount')) and flt(tax_withholding_details.get('tax_amount')) < pending_amount:
- allocated_amount = tax_withholding_details['tax_amount']
- tax_withholding_details['tax_amount'] = 0
+ elif (
+ flt(tax_withholding_details.get("tax_amount"))
+ and flt(tax_withholding_details.get("tax_amount")) < pending_amount
+ ):
+ allocated_amount = tax_withholding_details["tax_amount"]
+ tax_withholding_details["tax_amount"] = 0
- self.append('advance_tax', {
- 'reference_type': 'Payment Entry',
- 'reference_name': tax.parent,
- 'reference_detail': tax.name,
- 'account_head': tax.account_head,
- 'allocated_amount': allocated_amount
- })
+ self.append(
+ "advance_tax",
+ {
+ "reference_type": "Payment Entry",
+ "reference_name": tax.parent,
+ "reference_detail": tax.name,
+ "account_head": tax.account_head,
+ "allocated_amount": allocated_amount,
+ },
+ )
def update_advance_tax_references(self, cancel=0):
- for tax in self.get('advance_tax'):
+ for tax in self.get("advance_tax"):
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
if cancel:
@@ -1239,8 +1579,8 @@ class PurchaseInvoice(BuyingController):
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
- if self.get('amended_from'):
- self.status = 'Draft'
+ if self.get("amended_from"):
+ self.status = "Draft"
return
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
@@ -1251,19 +1591,25 @@ class PurchaseInvoice(BuyingController):
status = "Cancelled"
elif self.docstatus == 1:
if self.is_internal_transfer():
- self.status = 'Internal Transfer'
+ self.status = "Internal Transfer"
elif is_overdue(self, total):
self.status = "Overdue"
elif 0 < outstanding_amount < total:
self.status = "Partly Paid"
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid"
- #Check if outstanding amount is 0 due to debit note issued against invoice
- elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
+ # Check if outstanding amount is 0 due to debit note issued against invoice
+ elif (
+ outstanding_amount <= 0
+ and self.is_return == 0
+ and frappe.db.get_value(
+ "Purchase Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1}
+ )
+ ):
self.status = "Debit Note Issued"
elif self.is_return == 1:
self.status = "Return"
- elif outstanding_amount<=0:
+ elif outstanding_amount <= 0:
self.status = "Paid"
else:
self.status = "Submitted"
@@ -1271,106 +1617,126 @@ class PurchaseInvoice(BuyingController):
self.status = "Draft"
if update:
- self.db_set('status', self.status, update_modified = update_modified)
+ self.db_set("status", self.status, update_modified=update_modified)
+
# to get details of purchase invoice/receipt from which this doc was created for exchange rate difference handling
def get_purchase_document_details(doc):
- if doc.doctype == 'Purchase Invoice':
- doc_reference = 'purchase_receipt'
- items_reference = 'pr_detail'
- parent_doctype = 'Purchase Receipt'
- child_doctype = 'Purchase Receipt Item'
+ if doc.doctype == "Purchase Invoice":
+ doc_reference = "purchase_receipt"
+ items_reference = "pr_detail"
+ parent_doctype = "Purchase Receipt"
+ child_doctype = "Purchase Receipt Item"
else:
- doc_reference = 'purchase_invoice'
- items_reference = 'purchase_invoice_item'
- parent_doctype = 'Purchase Invoice'
- child_doctype = 'Purchase Invoice Item'
+ doc_reference = "purchase_invoice"
+ items_reference = "purchase_invoice_item"
+ parent_doctype = "Purchase Invoice"
+ child_doctype = "Purchase Invoice Item"
purchase_receipts_or_invoices = []
items = []
- for item in doc.get('items'):
+ for item in doc.get("items"):
if item.get(doc_reference):
purchase_receipts_or_invoices.append(item.get(doc_reference))
if item.get(items_reference):
items.append(item.get(items_reference))
- exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in',
- purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1))
+ exchange_rate_map = frappe._dict(
+ frappe.get_all(
+ parent_doctype,
+ filters={"name": ("in", purchase_receipts_or_invoices)},
+ fields=["name", "conversion_rate"],
+ as_list=1,
+ )
+ )
- net_rate_map = frappe._dict(frappe.get_all(child_doctype, filters={'name': ('in',
- items)}, fields=['name', 'net_rate'], as_list=1))
+ net_rate_map = frappe._dict(
+ frappe.get_all(
+ child_doctype, filters={"name": ("in", items)}, fields=["name", "net_rate"], as_list=1
+ )
+ )
return exchange_rate_map, net_rate_map
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
+
list_context = get_list_context(context)
- list_context.update({
- 'show_sidebar': True,
- 'show_search': True,
- 'no_breadcrumbs': True,
- 'title': _('Purchase Invoices'),
- })
+ list_context.update(
+ {
+ "show_sidebar": True,
+ "show_search": True,
+ "no_breadcrumbs": True,
+ "title": _("Purchase Invoices"),
+ }
+ )
return list_context
+
@erpnext.allow_regional
def make_regional_gl_entries(gl_entries, doc):
return gl_entries
+
@frappe.whitelist()
def make_debit_note(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
return make_return_doc("Purchase Invoice", source_name, target_doc)
+
@frappe.whitelist()
def make_stock_entry(source_name, target_doc=None):
- doc = get_mapped_doc("Purchase Invoice", source_name, {
- "Purchase Invoice": {
- "doctype": "Stock Entry",
- "validation": {
- "docstatus": ["=", 1]
- }
- },
- "Purchase Invoice Item": {
- "doctype": "Stock Entry Detail",
- "field_map": {
- "stock_qty": "transfer_qty",
- "batch_no": "batch_no"
+ doc = get_mapped_doc(
+ "Purchase Invoice",
+ source_name,
+ {
+ "Purchase Invoice": {"doctype": "Stock Entry", "validation": {"docstatus": ["=", 1]}},
+ "Purchase Invoice Item": {
+ "doctype": "Stock Entry Detail",
+ "field_map": {"stock_qty": "transfer_qty", "batch_no": "batch_no"},
},
- }
- }, target_doc)
+ },
+ target_doc,
+ )
return doc
+
@frappe.whitelist()
def change_release_date(name, release_date=None):
- if frappe.db.exists('Purchase Invoice', name):
- pi = frappe.get_doc('Purchase Invoice', name)
- pi.db_set('release_date', release_date)
+ if frappe.db.exists("Purchase Invoice", name):
+ pi = frappe.get_doc("Purchase Invoice", name)
+ pi.db_set("release_date", release_date)
@frappe.whitelist()
def unblock_invoice(name):
- if frappe.db.exists('Purchase Invoice', name):
- pi = frappe.get_doc('Purchase Invoice', name)
+ if frappe.db.exists("Purchase Invoice", name):
+ pi = frappe.get_doc("Purchase Invoice", name)
pi.unblock_invoice()
@frappe.whitelist()
def block_invoice(name, release_date, hold_comment=None):
- if frappe.db.exists('Purchase Invoice', name):
- pi = frappe.get_doc('Purchase Invoice', name)
+ if frappe.db.exists("Purchase Invoice", name):
+ pi = frappe.get_doc("Purchase Invoice", name)
pi.block_invoice(hold_comment, release_date)
+
@frappe.whitelist()
def make_inter_company_sales_invoice(source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
+
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
+
def on_doctype_update():
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
+
@frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None):
def update_item(obj, target, source_parent):
@@ -1378,33 +1744,37 @@ def make_purchase_receipt(source_name, target_doc=None):
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
- target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
- flt(obj.rate) * flt(source_parent.conversion_rate)
+ target.base_amount = (
+ (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) * flt(source_parent.conversion_rate)
+ )
- doc = get_mapped_doc("Purchase Invoice", source_name, {
- "Purchase Invoice": {
- "doctype": "Purchase Receipt",
- "validation": {
- "docstatus": ["=", 1],
- }
- },
- "Purchase Invoice Item": {
- "doctype": "Purchase Receipt Item",
- "field_map": {
- "name": "purchase_invoice_item",
- "parent": "purchase_invoice",
- "bom": "bom",
- "purchase_order": "purchase_order",
- "po_detail": "purchase_order_item",
- "material_request": "material_request",
- "material_request_item": "material_request_item"
+ doc = get_mapped_doc(
+ "Purchase Invoice",
+ source_name,
+ {
+ "Purchase Invoice": {
+ "doctype": "Purchase Receipt",
+ "validation": {
+ "docstatus": ["=", 1],
+ },
},
- "postprocess": update_item,
- "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
+ "Purchase Invoice Item": {
+ "doctype": "Purchase Receipt Item",
+ "field_map": {
+ "name": "purchase_invoice_item",
+ "parent": "purchase_invoice",
+ "bom": "bom",
+ "purchase_order": "purchase_order",
+ "po_detail": "purchase_order_item",
+ "material_request": "material_request",
+ "material_request_item": "material_request_item",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
+ },
+ "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
},
- "Purchase Taxes and Charges": {
- "doctype": "Purchase Taxes and Charges"
- }
- }, target_doc)
+ target_doc,
+ )
return doc
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py
index 76c9fcdd7c..10dd0ef6e2 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py
@@ -3,35 +3,26 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'purchase_invoice',
- 'non_standard_fieldnames': {
- 'Journal Entry': 'reference_name',
- 'Payment Entry': 'reference_name',
- 'Payment Request': 'reference_name',
- 'Landed Cost Voucher': 'receipt_document',
- 'Purchase Invoice': 'return_against',
- 'Auto Repeat': 'reference_document'
+ "fieldname": "purchase_invoice",
+ "non_standard_fieldnames": {
+ "Journal Entry": "reference_name",
+ "Payment Entry": "reference_name",
+ "Payment Request": "reference_name",
+ "Landed Cost Voucher": "receipt_document",
+ "Purchase Invoice": "return_against",
+ "Auto Repeat": "reference_document",
},
- 'internal_links': {
- 'Purchase Order': ['items', 'purchase_order'],
- 'Purchase Receipt': ['items', 'purchase_receipt'],
+ "internal_links": {
+ "Purchase Order": ["items", "purchase_order"],
+ "Purchase Receipt": ["items", "purchase_receipt"],
},
- 'transactions': [
+ "transactions": [
+ {"label": _("Payment"), "items": ["Payment Entry", "Payment Request", "Journal Entry"]},
{
- 'label': _('Payment'),
- 'items': ['Payment Entry', 'Payment Request', 'Journal Entry']
+ "label": _("Reference"),
+ "items": ["Purchase Order", "Purchase Receipt", "Asset", "Landed Cost Voucher"],
},
- {
- 'label': _('Reference'),
- 'items': ['Purchase Order', 'Purchase Receipt', 'Asset', 'Landed Cost Voucher']
- },
- {
- 'label': _('Returns'),
- 'items': ['Purchase Invoice']
- },
- {
- 'label': _('Subscription'),
- 'items': ['Auto Repeat']
- },
- ]
+ {"label": _("Returns"), "items": ["Purchase Invoice"]},
+ {"label": _("Subscription"), "items": ["Auto Repeat"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index d51a008d94..843f66d546 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
import unittest
import frappe
@@ -31,6 +30,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_tra
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
test_ignore = ["Serial No"]
+
class TestPurchaseInvoice(unittest.TestCase):
@classmethod
def setUpClass(self):
@@ -43,16 +43,18 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_purchase_invoice_received_qty(self):
"""
- 1. Test if received qty is validated against accepted + rejected
- 2. Test if received qty is auto set on save
+ 1. Test if received qty is validated against accepted + rejected
+ 2. Test if received qty is auto set on save
"""
pi = make_purchase_invoice(
qty=1,
rejected_qty=1,
received_qty=3,
item_code="_Test Item Home Desktop 200",
- rejected_warehouse = "_Test Rejected Warehouse - _TC",
- update_stock=True, do_not_save=True)
+ rejected_warehouse="_Test Rejected Warehouse - _TC",
+ update_stock=True,
+ do_not_save=True,
+ )
self.assertRaises(QtyMismatchError, pi.save)
pi.items[0].received_qty = 0
@@ -79,18 +81,26 @@ class TestPurchaseInvoice(unittest.TestCase):
"_Test Account CST - _TC": [29.88, 0],
"_Test Account VAT - _TC": [156.25, 0],
"_Test Account Discount - _TC": [0, 168.03],
- "Round Off - _TC": [0, 0.3]
+ "Round Off - _TC": [0, 0.3],
}
- gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
- where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name, as_dict=1)
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
+ where voucher_type = 'Purchase Invoice' and voucher_no = %s""",
+ pi.name,
+ as_dict=1,
+ )
for d in gl_entries:
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
def test_gl_entries_with_perpetual_inventory(self):
- pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
- warehouse= "Stores - TCP1", cost_center = "Main - TCP1",
- expense_account ="_Test Account Cost for Goods Sold - TCP1",
- get_taxes_and_charges=True, qty=10)
+ pi = make_purchase_invoice(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ get_taxes_and_charges=True,
+ qty=10,
+ )
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
@@ -104,6 +114,7 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_payment_entry_unlink_against_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+
unlink_payment_on_cancel_of_invoice(0)
pi_doc = make_purchase_invoice()
@@ -119,7 +130,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pe.save(ignore_permissions=True)
pe.submit()
- pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
+ pi_doc = frappe.get_doc("Purchase Invoice", pi_doc.name)
pi_doc.load_from_db()
self.assertTrue(pi_doc.status, "Paid")
@@ -127,7 +138,7 @@ class TestPurchaseInvoice(unittest.TestCase):
unlink_payment_on_cancel_of_invoice()
def test_purchase_invoice_for_blocked_supplier(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
supplier.save()
@@ -137,9 +148,9 @@ class TestPurchaseInvoice(unittest.TestCase):
supplier.save()
def test_purchase_invoice_for_blocked_supplier_invoice(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Invoices'
+ supplier.hold_type = "Invoices"
supplier.save()
self.assertRaises(frappe.ValidationError, make_purchase_invoice)
@@ -148,31 +159,40 @@ class TestPurchaseInvoice(unittest.TestCase):
supplier.save()
def test_purchase_invoice_for_blocked_supplier_payment(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
+ supplier.hold_type = "Payments"
supplier.save()
pi = make_purchase_invoice()
self.assertRaises(
- frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC")
+ frappe.ValidationError,
+ get_payment_entry,
+ dt="Purchase Invoice",
+ dn=pi.name,
+ bank_account="_Test Bank - _TC",
+ )
supplier.on_hold = 0
supplier.save()
def test_purchase_invoice_for_blocked_supplier_payment_today_date(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
+ supplier.hold_type = "Payments"
supplier.release_date = nowdate()
supplier.save()
pi = make_purchase_invoice()
self.assertRaises(
- frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
- bank_account="_Test Bank - _TC")
+ frappe.ValidationError,
+ get_payment_entry,
+ dt="Purchase Invoice",
+ dn=pi.name,
+ bank_account="_Test Bank - _TC",
+ )
supplier.on_hold = 0
supplier.save()
@@ -181,15 +201,15 @@ class TestPurchaseInvoice(unittest.TestCase):
# this test is meant to fail only if something fails in the try block
with self.assertRaises(Exception):
try:
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
- supplier.release_date = '2018-03-01'
+ supplier.hold_type = "Payments"
+ supplier.release_date = "2018-03-01"
supplier.save()
pi = make_purchase_invoice()
- get_payment_entry('Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC")
+ get_payment_entry("Purchase Invoice", dn=pi.name, bank_account="_Test Bank - _TC")
supplier.on_hold = 0
supplier.save()
@@ -203,7 +223,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.release_date = nowdate()
self.assertRaises(frappe.ValidationError, pi.save)
- pi.release_date = ''
+ pi.release_date = ""
pi.save()
def test_purchase_invoice_temporary_blocked(self):
@@ -212,7 +232,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.save()
pi.submit()
- pe = get_payment_entry('Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC")
+ pe = get_payment_entry("Purchase Invoice", dn=pi.name, bank_account="_Test Bank - _TC")
self.assertRaises(frappe.ValidationError, pe.save)
@@ -228,9 +248,24 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_gl_entries_with_perpetual_inventory_against_pr(self):
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ supplier_warehouse="Work In Progress - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ get_taxes_and_charges=True,
+ )
- pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
+ pi = make_purchase_invoice(
+ company="_Test Company with perpetual inventory",
+ supplier_warehouse="Work In Progress - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ get_taxes_and_charges=True,
+ qty=10,
+ do_not_save="True",
+ )
for d in pi.items:
d.purchase_receipt = pr.name
@@ -243,18 +278,25 @@ class TestPurchaseInvoice(unittest.TestCase):
self.check_gle_for_pi(pi.name)
def check_gle_for_pi(self, pi):
- gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit
+ gl_entries = frappe.db.sql(
+ """select account, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- group by account""", pi, as_dict=1)
+ group by account""",
+ pi,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- ["Creditors - TCP1", 0, 720],
- ["Stock Received But Not Billed - TCP1", 500.0, 0],
- ["_Test Account Shipping Charges - TCP1", 100.0, 0.0],
- ["_Test Account VAT - TCP1", 120.0, 0]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ ["Creditors - TCP1", 0, 720],
+ ["Stock Received But Not Billed - TCP1", 500.0, 0],
+ ["_Test Account Shipping Charges - TCP1", 100.0, 0.0],
+ ["_Test Account VAT - TCP1", 120.0, 0],
+ ]
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
@@ -266,8 +308,12 @@ class TestPurchaseInvoice(unittest.TestCase):
make_purchase_invoice as create_purchase_invoice,
)
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse='Stores - TCP1',
- currency = "USD", conversion_rate = 70)
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ currency="USD",
+ conversion_rate=70,
+ )
pi = create_purchase_invoice(pr.name)
pi.conversion_rate = 80
@@ -276,25 +322,34 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.submit()
# Get exchnage gain and loss account
- exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
+ exchange_gain_loss_account = frappe.db.get_value(
+ "Company", pi.company, "exchange_gain_loss_account"
+ )
# fetching the latest GL Entry with exchange gain and loss account account
- amount = frappe.db.get_value('GL Entry', {'account': exchange_gain_loss_account, 'voucher_no': pi.name}, 'debit')
- discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount)
+ amount = frappe.db.get_value(
+ "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit"
+ )
+ discrepancy_caused_by_exchange_rate_diff = abs(
+ pi.items[0].base_net_amount - pr.items[0].base_net_amount
+ )
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
def test_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
- discount_account = create_account(account_name="Discount Account",
- parent_account="Indirect Expenses - _TC", company="_Test Company")
+ discount_account = create_account(
+ account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC",
+ company="_Test Company",
+ )
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 225.0, nowdate()],
- ["Discount Account - _TC", 0.0, 25.0, nowdate()]
+ ["Discount Account - _TC", 0.0, 25.0, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
@@ -302,28 +357,34 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
- additional_discount_account = create_account(account_name="Discount Account",
- parent_account="Indirect Expenses - _TC", company="_Test Company")
+ additional_discount_account = create_account(
+ account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC",
+ company="_Test Company",
+ )
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
pi.apply_discount_on = "Grand Total"
pi.additional_discount_account = additional_discount_account
pi.additional_discount_percentage = 10
pi.disable_rounded_total = 1
- pi.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": "_Test Account VAT - _TC",
- "cost_center": "Main - _TC",
- "description": "Test",
- "rate": 10
- })
+ pi.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account VAT - _TC",
+ "cost_center": "Main - _TC",
+ "description": "Test",
+ "rate": 10,
+ },
+ )
pi.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 247.5, nowdate()],
- ["Discount Account - _TC", 0.0, 27.5, nowdate()]
+ ["Discount Account - _TC", 0.0, 27.5, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
@@ -331,7 +392,7 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(test_records[1])
pi.insert()
- pi.naming_series = 'TEST-'
+ pi.naming_series = "TEST-"
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
@@ -340,25 +401,33 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.load_from_db()
self.assertTrue(pi.status, "Draft")
- pi.naming_series = 'TEST-'
+ pi.naming_series = "TEST-"
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
def test_gl_entries_for_non_stock_items_with_perpetual_inventory(self):
- pi = make_purchase_invoice(item_code = "_Test Non Stock Item",
- company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
- cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
+ pi = make_purchase_invoice(
+ item_code="_Test Non Stock Item",
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ )
self.assertTrue(pi.status, "Unpaid")
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi.name, as_dict=1)
+ order by account asc""",
+ pi.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
expected_values = [
["_Test Account Cost for Goods Sold - TCP1", 250.0, 0],
- ["Creditors - TCP1", 0, 250]
+ ["Creditors - TCP1", 0, 250],
]
for i, gle in enumerate(gl_entries):
@@ -373,7 +442,7 @@ class TestPurchaseInvoice(unittest.TestCase):
expected_values = [
["_Test Item Home Desktop 100", 90, 59],
- ["_Test Item Home Desktop 200", 135, 177]
+ ["_Test Item Home Desktop 200", 135, 177],
]
for i, item in enumerate(pi.get("items")):
self.assertEqual(item.item_code, expected_values[i][0])
@@ -405,10 +474,7 @@ class TestPurchaseInvoice(unittest.TestCase):
wrapper.insert()
wrapper.load_from_db()
- expected_values = [
- ["_Test FG Item", 90, 59],
- ["_Test Item Home Desktop 200", 135, 177]
- ]
+ expected_values = [["_Test FG Item", 90, 59], ["_Test Item Home Desktop 200", 135, 177]]
for i, item in enumerate(wrapper.get("items")):
self.assertEqual(item.item_code, expected_values[i][0])
self.assertEqual(item.item_tax_amount, expected_values[i][1])
@@ -445,14 +511,17 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = frappe.copy_doc(test_records[0])
pi.disable_rounded_total = 1
pi.allocate_advances_automatically = 0
- pi.append("advances", {
- "reference_type": "Journal Entry",
- "reference_name": jv.name,
- "reference_row": jv.get("accounts")[0].name,
- "advance_amount": 400,
- "allocated_amount": 300,
- "remarks": jv.remark
- })
+ pi.append(
+ "advances",
+ {
+ "reference_type": "Journal Entry",
+ "reference_name": jv.name,
+ "reference_row": jv.get("accounts")[0].name,
+ "advance_amount": 400,
+ "allocated_amount": 300,
+ "remarks": jv.remark,
+ },
+ )
pi.insert()
self.assertEqual(pi.outstanding_amount, 1212.30)
@@ -465,14 +534,24 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.submit()
pi.load_from_db()
- self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
+ self.assertTrue(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
where reference_type='Purchase Invoice'
- and reference_name=%s and debit_in_account_currency=300""", pi.name))
+ and reference_name=%s and debit_in_account_currency=300""",
+ pi.name,
+ )
+ )
pi.cancel()
- self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
- where reference_type='Purchase Invoice' and reference_name=%s""", pi.name))
+ self.assertFalse(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
+ where reference_type='Purchase Invoice' and reference_name=%s""",
+ pi.name,
+ )
+ )
def test_invoice_with_advance_and_multi_payment_terms(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
@@ -486,20 +565,26 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = frappe.copy_doc(test_records[0])
pi.disable_rounded_total = 1
pi.allocate_advances_automatically = 0
- pi.append("advances", {
- "reference_type": "Journal Entry",
- "reference_name": jv.name,
- "reference_row": jv.get("accounts")[0].name,
- "advance_amount": 400,
- "allocated_amount": 300,
- "remarks": jv.remark
- })
+ pi.append(
+ "advances",
+ {
+ "reference_type": "Journal Entry",
+ "reference_name": jv.name,
+ "reference_row": jv.get("accounts")[0].name,
+ "advance_amount": 400,
+ "allocated_amount": 300,
+ "remarks": jv.remark,
+ },
+ )
pi.insert()
- pi.update({
- "payment_schedule": get_payment_terms("_Test Payment Term Template",
- pi.posting_date, pi.grand_total, pi.base_grand_total)
- })
+ pi.update(
+ {
+ "payment_schedule": get_payment_terms(
+ "_Test Payment Term Template", pi.posting_date, pi.grand_total, pi.base_grand_total
+ )
+ }
+ )
pi.save()
pi.submit()
@@ -513,7 +598,9 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertTrue(
frappe.db.sql(
"select name from `tabJournal Entry Account` where reference_type='Purchase Invoice' and "
- "reference_name=%s and debit_in_account_currency=300", pi.name)
+ "reference_name=%s and debit_in_account_currency=300",
+ pi.name,
+ )
)
self.assertEqual(pi.outstanding_amount, 1212.30)
@@ -523,49 +610,76 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertFalse(
frappe.db.sql(
"select name from `tabJournal Entry Account` where reference_type='Purchase Invoice' and "
- "reference_name=%s", pi.name)
+ "reference_name=%s",
+ pi.name,
+ )
)
def test_total_purchase_cost_for_project(self):
if not frappe.db.exists("Project", {"project_name": "_Test Project for Purchase"}):
- project = make_project({'project_name':'_Test Project for Purchase'})
+ project = make_project({"project_name": "_Test Project for Purchase"})
else:
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
- existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
+ existing_purchase_cost = frappe.db.sql(
+ """select sum(base_net_amount)
from `tabPurchase Invoice Item`
where project = '{0}'
- and docstatus=1""".format(project.name))
+ and docstatus=1""".format(
+ project.name
+ )
+ )
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
- self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
- existing_purchase_cost + 15000)
+ self.assertEqual(
+ frappe.db.get_value("Project", project.name, "total_purchase_cost"),
+ existing_purchase_cost + 15000,
+ )
pi1 = make_purchase_invoice(qty=10, project=project.name)
- self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
- existing_purchase_cost + 15500)
+ self.assertEqual(
+ frappe.db.get_value("Project", project.name, "total_purchase_cost"),
+ existing_purchase_cost + 15500,
+ )
pi1.cancel()
- self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
- existing_purchase_cost + 15000)
+ self.assertEqual(
+ frappe.db.get_value("Project", project.name, "total_purchase_cost"),
+ existing_purchase_cost + 15000,
+ )
pi.cancel()
- self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost)
+ self.assertEqual(
+ frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost
+ )
def test_return_purchase_invoice_with_perpetual_inventory(self):
- pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
- cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
-
- return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
- company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
- cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
+ pi = make_purchase_invoice(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ )
+ return_pi = make_purchase_invoice(
+ is_return=1,
+ return_against=pi.name,
+ qty=-2,
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ )
# check gl entries for return
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
- order by account desc""", ("Purchase Invoice", return_pi.name), as_dict=1)
+ order by account desc""",
+ ("Purchase Invoice", return_pi.name),
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -579,13 +693,21 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account][1], gle.credit)
def test_multi_currency_gle(self):
- pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
- currency="USD", conversion_rate=50)
+ pi = make_purchase_invoice(
+ supplier="_Test Supplier USD",
+ credit_to="_Test Payable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi.name, as_dict=1)
+ order by account asc""",
+ pi.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -595,53 +717,74 @@ class TestPurchaseInvoice(unittest.TestCase):
"debit": 0,
"debit_in_account_currency": 0,
"credit": 12500,
- "credit_in_account_currency": 250
+ "credit_in_account_currency": 250,
},
"_Test Account Cost for Goods Sold - _TC": {
"account_currency": "INR",
"debit": 12500,
"debit_in_account_currency": 12500,
"credit": 0,
- "credit_in_account_currency": 0
- }
+ "credit_in_account_currency": 0,
+ },
}
- for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
+ for field in (
+ "account_currency",
+ "debit",
+ "debit_in_account_currency",
+ "credit",
+ "credit_in_account_currency",
+ ):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
-
# Check for valid currency
- pi1 = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
- do_not_save=True)
+ pi1 = make_purchase_invoice(
+ supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC", do_not_save=True
+ )
self.assertRaises(InvalidCurrency, pi1.save)
# cancel
pi.cancel()
- gle = frappe.db.sql("""select name from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s""", pi.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry`
+ where voucher_type='Sales Invoice' and voucher_no=%s""",
+ pi.name,
+ )
self.assertFalse(gle)
def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self):
- pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
+ pi = make_purchase_invoice(
+ update_stock=1,
+ posting_date=frappe.utils.nowdate(),
+ posting_time=frappe.utils.nowtime(),
+ cash_bank_account="Cash - TCP1",
+ company="_Test Company with perpetual inventory",
+ supplier_warehouse="Work In Progress - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ )
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi.name, as_dict=1)
+ order by account asc""",
+ pi.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
stock_in_hand_account = get_inventory_account(pi.company, pi.get("items")[0].warehouse)
- expected_gl_entries = dict((d[0], d) for d in [
- [pi.credit_to, 0.0, 250.0],
- [stock_in_hand_account, 250.0, 0.0]
- ])
+ expected_gl_entries = dict(
+ (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):
self.assertEqual(expected_gl_entries[gle.account][0], gle.account)
@@ -650,22 +793,39 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self):
- pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", is_paid=1, company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
+ pi = make_purchase_invoice(
+ update_stock=1,
+ posting_date=frappe.utils.nowdate(),
+ posting_time=frappe.utils.nowtime(),
+ cash_bank_account="Cash - TCP1",
+ is_paid=1,
+ company="_Test Company with perpetual inventory",
+ supplier_warehouse="Work In Progress - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ )
- gl_entries = frappe.db.sql("""select account, account_currency, sum(debit) as debit,
+ gl_entries = frappe.db.sql(
+ """select account, account_currency, sum(debit) as debit,
sum(credit) as credit, debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- group by account, voucher_no order by account asc;""", pi.name, as_dict=1)
+ group by account, voucher_no order by account asc;""",
+ pi.name,
+ as_dict=1,
+ )
stock_in_hand_account = get_inventory_account(pi.company, pi.get("items")[0].warehouse)
self.assertTrue(gl_entries)
- expected_gl_entries = dict((d[0], d) for d in [
- [pi.credit_to, 250.0, 250.0],
- [stock_in_hand_account, 250.0, 0.0],
- ["Cash - TCP1", 0.0, 250.0]
- ])
+ expected_gl_entries = dict(
+ (d[0], d)
+ for d in [
+ [pi.credit_to, 250.0, 250.0],
+ [stock_in_hand_account, 250.0, 0.0],
+ ["Cash - TCP1", 0.0, 250.0],
+ ]
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gl_entries[gle.account][0], gle.account)
@@ -673,31 +833,36 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[gle.account][2], gle.credit)
def test_auto_batch(self):
- item_code = frappe.db.get_value('Item',
- {'has_batch_no': 1, 'create_new_batch':1}, 'name')
+ item_code = frappe.db.get_value("Item", {"has_batch_no": 1, "create_new_batch": 1}, "name")
if not item_code:
- doc = frappe.get_doc({
- 'doctype': 'Item',
- 'is_stock_item': 1,
- 'item_code': 'test batch item',
- 'item_group': 'Products',
- 'has_batch_no': 1,
- 'create_new_batch': 1
- }).insert(ignore_permissions=True)
+ doc = frappe.get_doc(
+ {
+ "doctype": "Item",
+ "is_stock_item": 1,
+ "item_code": "test batch item",
+ "item_group": "Products",
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ }
+ ).insert(ignore_permissions=True)
item_code = doc.name
- pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime(), item_code=item_code)
+ pi = make_purchase_invoice(
+ update_stock=1,
+ posting_date=frappe.utils.nowdate(),
+ posting_time=frappe.utils.nowtime(),
+ item_code=item_code,
+ )
- self.assertTrue(frappe.db.get_value('Batch',
- {'item': item_code, 'reference_name': pi.name}))
+ self.assertTrue(frappe.db.get_value("Batch", {"item": item_code, "reference_name": pi.name}))
def test_update_stock_and_purchase_return(self):
actual_qty_0 = get_qty_after_transaction()
- pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime())
+ pi = make_purchase_invoice(
+ update_stock=1, posting_date=frappe.utils.nowdate(), posting_time=frappe.utils.nowtime()
+ )
actual_qty_1 = get_qty_after_transaction()
self.assertEqual(actual_qty_0 + 5, actual_qty_1)
@@ -724,13 +889,20 @@ class TestPurchaseInvoice(unittest.TestCase):
from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
- update_backflush_based_on('BOM')
- make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
- make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
- qty=100, basic_rate=100)
+ update_backflush_based_on("BOM")
+ make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
+ )
+ make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse 1 - _TC",
+ qty=100,
+ basic_rate=100,
+ )
- pi = make_purchase_invoice(item_code="_Test FG Item", qty=10, rate=500,
- update_stock=1, is_subcontracted="Yes")
+ pi = make_purchase_invoice(
+ item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted="Yes"
+ )
self.assertEqual(len(pi.get("supplied_items")), 2)
@@ -738,15 +910,26 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
def test_rejected_serial_no(self):
- pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
- rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC",
- allow_zero_valuation_rate=1)
+ pi = make_purchase_invoice(
+ item_code="_Test Serialized Item With Series",
+ received_qty=2,
+ qty=1,
+ rejected_qty=1,
+ rate=500,
+ update_stock=1,
+ rejected_warehouse="_Test Rejected Warehouse - _TC",
+ allow_zero_valuation_rate=1,
+ )
- self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"),
- pi.get("items")[0].warehouse)
+ self.assertEqual(
+ frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"),
+ pi.get("items")[0].warehouse,
+ )
- self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].rejected_serial_no,
- "warehouse"), pi.get("items")[0].rejected_warehouse)
+ self.assertEqual(
+ frappe.db.get_value("Serial No", pi.get("items")[0].rejected_serial_no, "warehouse"),
+ pi.get("items")[0].rejected_warehouse,
+ )
def test_outstanding_amount_after_advance_jv_cancelation(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
@@ -754,85 +937,95 @@ class TestPurchaseInvoice(unittest.TestCase):
)
jv = frappe.copy_doc(jv_test_records[1])
- jv.accounts[0].is_advance = 'Yes'
+ jv.accounts[0].is_advance = "Yes"
jv.insert()
jv.submit()
pi = frappe.copy_doc(test_records[0])
- pi.append("advances", {
- "reference_type": "Journal Entry",
- "reference_name": jv.name,
- "reference_row": jv.get("accounts")[0].name,
- "advance_amount": 400,
- "allocated_amount": 300,
- "remarks": jv.remark
- })
+ pi.append(
+ "advances",
+ {
+ "reference_type": "Journal Entry",
+ "reference_name": jv.name,
+ "reference_row": jv.get("accounts")[0].name,
+ "advance_amount": 400,
+ "allocated_amount": 300,
+ "remarks": jv.remark,
+ },
+ )
pi.insert()
pi.submit()
pi.load_from_db()
- #check outstanding after advance allocation
+ # check outstanding after advance allocation
self.assertEqual(flt(pi.outstanding_amount), flt(pi.rounded_total - pi.total_advance))
- #added to avoid Document has been modified exception
+ # added to avoid Document has been modified exception
jv = frappe.get_doc("Journal Entry", jv.name)
jv.cancel()
pi.load_from_db()
- #check outstanding after advance cancellation
+ # check outstanding after advance cancellation
self.assertEqual(flt(pi.outstanding_amount), flt(pi.rounded_total + pi.total_advance))
def test_outstanding_amount_after_advance_payment_entry_cancelation(self):
- pe = frappe.get_doc({
- "doctype": "Payment Entry",
- "payment_type": "Pay",
- "party_type": "Supplier",
- "party": "_Test Supplier",
- "company": "_Test Company",
- "paid_from_account_currency": "INR",
- "paid_to_account_currency": "INR",
- "source_exchange_rate": 1,
- "target_exchange_rate": 1,
- "reference_no": "1",
- "reference_date": nowdate(),
- "received_amount": 300,
- "paid_amount": 300,
- "paid_from": "_Test Cash - _TC",
- "paid_to": "_Test Payable - _TC"
- })
+ pe = frappe.get_doc(
+ {
+ "doctype": "Payment Entry",
+ "payment_type": "Pay",
+ "party_type": "Supplier",
+ "party": "_Test Supplier",
+ "company": "_Test Company",
+ "paid_from_account_currency": "INR",
+ "paid_to_account_currency": "INR",
+ "source_exchange_rate": 1,
+ "target_exchange_rate": 1,
+ "reference_no": "1",
+ "reference_date": nowdate(),
+ "received_amount": 300,
+ "paid_amount": 300,
+ "paid_from": "_Test Cash - _TC",
+ "paid_to": "_Test Payable - _TC",
+ }
+ )
pe.insert()
pe.submit()
pi = frappe.copy_doc(test_records[0])
pi.is_pos = 0
- pi.append("advances", {
- "doctype": "Purchase Invoice Advance",
- "reference_type": "Payment Entry",
- "reference_name": pe.name,
- "advance_amount": 300,
- "allocated_amount": 300,
- "remarks": pe.remarks
- })
+ pi.append(
+ "advances",
+ {
+ "doctype": "Purchase Invoice Advance",
+ "reference_type": "Payment Entry",
+ "reference_name": pe.name,
+ "advance_amount": 300,
+ "allocated_amount": 300,
+ "remarks": pe.remarks,
+ },
+ )
pi.insert()
pi.submit()
pi.load_from_db()
- #check outstanding after advance allocation
+ # check outstanding after advance allocation
self.assertEqual(flt(pi.outstanding_amount), flt(pi.rounded_total - pi.total_advance))
- #added to avoid Document has been modified exception
+ # added to avoid Document has been modified exception
pe = frappe.get_doc("Payment Entry", pe.name)
pe.cancel()
pi.load_from_db()
- #check outstanding after advance cancellation
+ # check outstanding after advance cancellation
self.assertEqual(flt(pi.outstanding_amount), flt(pi.rounded_total + pi.total_advance))
def test_purchase_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
- shipping_rule = create_shipping_rule(shipping_rule_type = "Buying", shipping_rule_name = "Shipping Rule - Purchase Invoice Test")
+ shipping_rule = create_shipping_rule(
+ shipping_rule_type="Buying", shipping_rule_name="Shipping Rule - Purchase Invoice Test"
+ )
pi = frappe.copy_doc(test_records[0])
@@ -848,16 +1041,20 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_make_pi_without_terms(self):
pi = make_purchase_invoice(do_not_save=1)
- self.assertFalse(pi.get('payment_schedule'))
+ self.assertFalse(pi.get("payment_schedule"))
pi.insert()
- self.assertTrue(pi.get('payment_schedule'))
+ self.assertTrue(pi.get("payment_schedule"))
def test_duplicate_due_date_in_terms(self):
pi = make_purchase_invoice(do_not_save=1)
- 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))
+ 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)
@@ -865,12 +1062,13 @@ class TestPurchaseInvoice(unittest.TestCase):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount
- pi = make_purchase_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
+ pi = make_purchase_invoice(item_code="_Test Item", qty=(5 * -1), rate=500, is_return=1)
pi.load_from_db()
self.assertTrue(pi.status, "Return")
- outstanding_amount = get_outstanding_amount(pi.doctype,
- pi.name, "Creditors - _TC", pi.supplier, "Supplier")
+ outstanding_amount = get_outstanding_amount(
+ pi.doctype, pi.name, "Creditors - _TC", pi.supplier, "Supplier"
+ )
self.assertEqual(pi.outstanding_amount, outstanding_amount)
@@ -885,30 +1083,33 @@ class TestPurchaseInvoice(unittest.TestCase):
pe.insert()
pe.submit()
- pi_doc = frappe.get_doc('Purchase Invoice', pi.name)
+ pi_doc = frappe.get_doc("Purchase Invoice", pi.name)
self.assertEqual(pi_doc.outstanding_amount, 0)
def test_purchase_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
- pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
+ pi = make_purchase_invoice_against_cost_center(
+ cost_center=cost_center, credit_to="Creditors - _TC"
+ )
self.assertEqual(pi.cost_center, cost_center)
expected_values = {
- "Creditors - _TC": {
- "cost_center": cost_center
- },
- "_Test Account Cost for Goods Sold - _TC": {
- "cost_center": cost_center
- }
+ "Creditors - _TC": {"cost_center": cost_center},
+ "_Test Account Cost for Goods Sold - _TC": {"cost_center": cost_center},
}
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi.name, as_dict=1)
+ order by account asc""",
+ pi.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -917,21 +1118,21 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_purchase_invoice_without_cost_center(self):
cost_center = "_Test Cost Center - _TC"
- pi = make_purchase_invoice(credit_to="Creditors - _TC")
+ pi = make_purchase_invoice(credit_to="Creditors - _TC")
expected_values = {
- "Creditors - _TC": {
- "cost_center": None
- },
- "_Test Account Cost for Goods Sold - _TC": {
- "cost_center": cost_center
- }
+ "Creditors - _TC": {"cost_center": None},
+ "_Test Account Cost for Goods Sold - _TC": {"cost_center": cost_center},
}
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi.name, as_dict=1)
+ order by account asc""",
+ pi.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -939,36 +1140,40 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_purchase_invoice_with_project_link(self):
- project = make_project({
- 'project_name': 'Purchase Invoice Project',
- 'project_template_name': 'Test Project Template',
- 'start_date': '2020-01-01'
- })
- item_project = make_project({
- 'project_name': 'Purchase Invoice Item Project',
- 'project_template_name': 'Test Project Template',
- 'start_date': '2019-06-01'
- })
+ project = make_project(
+ {
+ "project_name": "Purchase Invoice Project",
+ "project_template_name": "Test Project Template",
+ "start_date": "2020-01-01",
+ }
+ )
+ item_project = make_project(
+ {
+ "project_name": "Purchase Invoice Item Project",
+ "project_template_name": "Test Project Template",
+ "start_date": "2019-06-01",
+ }
+ )
- pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
+ pi = make_purchase_invoice(credit_to="Creditors - _TC", do_not_save=1)
pi.items[0].project = item_project.name
pi.project = project.name
pi.submit()
expected_values = {
- "Creditors - _TC": {
- "project": project.name
- },
- "_Test Account Cost for Goods Sold - _TC": {
- "project": item_project.name
- }
+ "Creditors - _TC": {"project": project.name},
+ "_Test Account Cost for Goods Sold - _TC": {"project": item_project.name},
}
- gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, project, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi.name, as_dict=1)
+ order by account asc""",
+ pi.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -976,10 +1181,11 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account]["project"], gle.project)
def test_deferred_expense_via_journal_entry(self):
- deferred_account = create_account(account_name="Deferred Expense",
- parent_account="Current Assets - _TC", company="_Test Company")
+ deferred_account = create_account(
+ account_name="Deferred Expense", parent_account="Current Assets - _TC", company="_Test Company"
+ )
- acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 1
acc_settings.submit_journal_entries = 1
acc_settings.save()
@@ -991,7 +1197,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
pi.set_posting_time = 1
- pi.posting_date = '2019-01-10'
+ pi.posting_date = "2019-01-10"
pi.items[0].enable_deferred_expense = 1
pi.items[0].service_start_date = "2019-01-10"
pi.items[0].service_end_date = "2019-03-15"
@@ -999,14 +1205,16 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.save()
pi.submit()
- pda1 = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date=nowdate(),
- start_date="2019-01-01",
- end_date="2019-03-31",
- type="Expense",
- company="_Test Company"
- ))
+ pda1 = frappe.get_doc(
+ dict(
+ doctype="Process Deferred Accounting",
+ posting_date=nowdate(),
+ start_date="2019-01-01",
+ end_date="2019-03-31",
+ type="Expense",
+ company="_Test Company",
+ )
+ )
pda1.insert()
pda1.submit()
@@ -1017,13 +1225,17 @@ class TestPurchaseInvoice(unittest.TestCase):
["_Test Account Cost for Goods Sold - _TC", 0.0, 43.08, "2019-02-28"],
[deferred_account, 43.08, 0.0, "2019-02-28"],
["_Test Account Cost for Goods Sold - _TC", 0.0, 23.07, "2019-03-15"],
- [deferred_account, 23.07, 0.0, "2019-03-15"]
+ [deferred_account, 23.07, 0.0, "2019-03-15"],
]
- gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+ gl_entries = gl_entries = frappe.db.sql(
+ """select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
- order by posting_date asc, account asc""", (pi.items[0].name, pi.posting_date), as_dict=1)
+ order by posting_date asc, account asc""",
+ (pi.items[0].name, pi.posting_date),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
@@ -1031,108 +1243,139 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][2], gle.debit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
- acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
def test_gain_loss_with_advance_entry(self):
unlink_enabled = frappe.db.get_value(
- "Accounts Settings", "Accounts Settings",
- "unlink_payment_on_cancel_of_invoice")
+ "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
+ )
frappe.db.set_value(
- "Accounts Settings", "Accounts Settings",
- "unlink_payment_on_cancel_of_invoice", 1)
+ "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1
+ )
original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account")
- frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC")
+ frappe.db.set_value(
+ "Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC"
+ )
- pay = frappe.get_doc({
- 'doctype': 'Payment Entry',
- 'company': '_Test Company',
- 'payment_type': 'Pay',
- 'party_type': 'Supplier',
- 'party': '_Test Supplier USD',
- 'paid_to': '_Test Payable USD - _TC',
- 'paid_from': 'Cash - _TC',
- 'paid_amount': 70000,
- 'target_exchange_rate': 70,
- 'received_amount': 1000,
- })
+ pay = frappe.get_doc(
+ {
+ "doctype": "Payment Entry",
+ "company": "_Test Company",
+ "payment_type": "Pay",
+ "party_type": "Supplier",
+ "party": "_Test Supplier USD",
+ "paid_to": "_Test Payable USD - _TC",
+ "paid_from": "Cash - _TC",
+ "paid_amount": 70000,
+ "target_exchange_rate": 70,
+ "received_amount": 1000,
+ }
+ )
pay.insert()
pay.submit()
- pi = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
- conversion_rate=75, rate=500, do_not_save=1, qty=1)
+ pi = make_purchase_invoice(
+ supplier="_Test Supplier USD",
+ currency="USD",
+ conversion_rate=75,
+ rate=500,
+ do_not_save=1,
+ qty=1,
+ )
pi.cost_center = "_Test Cost Center - _TC"
pi.advances = []
- pi.append("advances", {
- "reference_type": "Payment Entry",
- "reference_name": pay.name,
- "advance_amount": 1000,
- "remarks": pay.remarks,
- "allocated_amount": 500,
- "ref_exchange_rate": 70
- })
+ pi.append(
+ "advances",
+ {
+ "reference_type": "Payment Entry",
+ "reference_name": pay.name,
+ "advance_amount": 1000,
+ "remarks": pay.remarks,
+ "allocated_amount": 500,
+ "ref_exchange_rate": 70,
+ },
+ )
pi.save()
pi.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 37500.0],
["_Test Payable USD - _TC", -35000.0],
- ["Exchange Gain/Loss - _TC", -2500.0]
+ ["Exchange Gain/Loss - _TC", -2500.0],
]
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select account, sum(debit - credit) as balance from `tabGL Entry`
where voucher_no=%s
group by account
- order by account asc""", (pi.name), as_dict=1)
+ order by account asc""",
+ (pi.name),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.balance)
- pi_2 = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
- conversion_rate=73, rate=500, do_not_save=1, qty=1)
+ pi_2 = make_purchase_invoice(
+ supplier="_Test Supplier USD",
+ currency="USD",
+ conversion_rate=73,
+ rate=500,
+ do_not_save=1,
+ qty=1,
+ )
pi_2.cost_center = "_Test Cost Center - _TC"
pi_2.advances = []
- pi_2.append("advances", {
- "reference_type": "Payment Entry",
- "reference_name": pay.name,
- "advance_amount": 500,
- "remarks": pay.remarks,
- "allocated_amount": 500,
- "ref_exchange_rate": 70
- })
+ pi_2.append(
+ "advances",
+ {
+ "reference_type": "Payment Entry",
+ "reference_name": pay.name,
+ "advance_amount": 500,
+ "remarks": pay.remarks,
+ "allocated_amount": 500,
+ "ref_exchange_rate": 70,
+ },
+ )
pi_2.save()
pi_2.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 36500.0],
["_Test Payable USD - _TC", -35000.0],
- ["Exchange Gain/Loss - _TC", -1500.0]
+ ["Exchange Gain/Loss - _TC", -1500.0],
]
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select account, sum(debit - credit) as balance from `tabGL Entry`
where voucher_no=%s
- group by account order by account asc""", (pi_2.name), as_dict=1)
+ group by account order by account asc""",
+ (pi_2.name),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.balance)
- expected_gle = [
- ["_Test Payable USD - _TC", 70000.0],
- ["Cash - _TC", -70000.0]
- ]
+ expected_gle = [["_Test Payable USD - _TC", 70000.0], ["Cash - _TC", -70000.0]]
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select account, sum(debit - credit) as balance from `tabGL Entry`
where voucher_no=%s and is_cancelled=0
- group by account order by account asc""", (pay.name), as_dict=1)
+ group by account order by account asc""",
+ (pay.name),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
@@ -1147,44 +1390,57 @@ class TestPurchaseInvoice(unittest.TestCase):
pay.reload()
pay.cancel()
- frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
+ frappe.db.set_value(
+ "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
+ )
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
def test_purchase_invoice_advance_taxes(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
# create a new supplier to test
- supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
- tax_withholding_category = 'TDS - 194 - Dividends - Individual')
+ supplier = create_supplier(
+ supplier_name="_Test TDS Advance Supplier",
+ tax_withholding_category="TDS - 194 - Dividends - Individual",
+ )
# Update tax withholding category with current fiscal year and rate details
- update_tax_witholding_category('_Test Company', 'TDS Payable - _TC')
+ update_tax_witholding_category("_Test Company", "TDS Payable - _TC")
# Create Purchase Order with TDS applied
- po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
- posting_date='2021-09-15')
+ po = create_purchase_order(
+ do_not_save=1,
+ supplier=supplier.name,
+ rate=3000,
+ item="_Test Non Stock Item",
+ posting_date="2021-09-15",
+ )
po.save()
po.submit()
# Create Payment Entry Against the order
- payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
- payment_entry.paid_from = 'Cash - _TC'
+ payment_entry = get_payment_entry(dt="Purchase Order", dn=po.name)
+ payment_entry.paid_from = "Cash - _TC"
payment_entry.apply_tax_withholding_amount = 1
- payment_entry.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
+ payment_entry.tax_withholding_category = "TDS - 194 - Dividends - Individual"
payment_entry.save()
payment_entry.submit()
# Check GLE for Payment Entry
expected_gle = [
- ['Cash - _TC', 0, 27000],
- ['Creditors - _TC', 30000, 0],
- ['TDS Payable - _TC', 0, 3000],
+ ["Cash - _TC", 0, 27000],
+ ["Creditors - _TC", 30000, 0],
+ ["TDS Payable - _TC", 0, 3000],
]
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry`
where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""", (payment_entry.name), as_dict=1)
+ order by account asc""",
+ (payment_entry.name),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
@@ -1194,23 +1450,24 @@ class TestPurchaseInvoice(unittest.TestCase):
# Create Purchase Invoice against Purchase Order
purchase_invoice = get_mapped_purchase_invoice(po.name)
purchase_invoice.allocate_advances_automatically = 1
- purchase_invoice.items[0].item_code = '_Test Non Stock Item'
- purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC'
+ purchase_invoice.items[0].item_code = "_Test Non Stock Item"
+ purchase_invoice.items[0].expense_account = "_Test Account Cost for Goods Sold - _TC"
purchase_invoice.save()
purchase_invoice.submit()
# Check GLE for Purchase Invoice
# Zero net effect on final TDS Payable on invoice
- expected_gle = [
- ['_Test Account Cost for Goods Sold - _TC', 30000],
- ['Creditors - _TC', -30000]
- ]
+ expected_gle = [["_Test Account Cost for Goods Sold - _TC", 30000], ["Creditors - _TC", -30000]]
- gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
+ gl_entries = frappe.db.sql(
+ """select account, sum(debit - credit) as amount
from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no=%s
group by account
- order by account asc""", (purchase_invoice.name), as_dict=1)
+ order by account asc""",
+ (purchase_invoice.name),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
@@ -1226,27 +1483,32 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_provisional_accounting_entry(self):
item = create_item("_Test Non Stock Item", is_stock_item=0)
- provisional_account = create_account(account_name="Provision Account",
- parent_account="Current Liabilities - _TC", company="_Test Company")
+ provisional_account = create_account(
+ account_name="Provision Account",
+ parent_account="Current Liabilities - _TC",
+ company="_Test Company",
+ )
- company = frappe.get_doc('Company', '_Test Company')
+ company = frappe.get_doc("Company", "_Test Company")
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()
- pr = make_purchase_receipt(item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2))
+ pr = make_purchase_receipt(
+ item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2)
+ )
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
- pi.items[0].expense_account = 'Cost of Goods Sold - _TC'
+ pi.items[0].expense_account = "Cost of Goods Sold - _TC"
pi.save()
pi.submit()
# Check GLE for Purchase Invoice
expected_gle = [
- ['Cost of Goods Sold - _TC', 250, 0, add_days(pr.posting_date, -1)],
- ['Creditors - _TC', 0, 250, add_days(pr.posting_date, -1)]
+ ["Cost of Goods Sold - _TC", 250, 0, add_days(pr.posting_date, -1)],
+ ["Creditors - _TC", 0, 250, add_days(pr.posting_date, -1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
@@ -1255,7 +1517,7 @@ class TestPurchaseInvoice(unittest.TestCase):
["Provision Account - _TC", 250, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 250, pr.posting_date],
["Provision Account - _TC", 0, 250, pi.posting_date],
- ["_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(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
@@ -1263,11 +1525,16 @@ class TestPurchaseInvoice(unittest.TestCase):
company.enable_provisional_accounting_for_non_stock_items = 0
company.save()
+
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
- gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s
- order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
+ order by posting_date asc, account asc""",
+ (voucher_no, posting_date),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
@@ -1275,45 +1542,55 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
doc.assertEqual(expected_gle[i][2], gle.credit)
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
+
def update_tax_witholding_category(company, account):
from erpnext.accounts.utils import get_fiscal_year
fiscal_year = get_fiscal_year(date=nowdate())
- if not frappe.db.get_value('Tax Withholding Rate',
- {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]),
- 'to_date': ('<=', fiscal_year[2])}):
- tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
- tds_category.set('rates', [])
+ if not frappe.db.get_value(
+ "Tax Withholding Rate",
+ {
+ "parent": "TDS - 194 - Dividends - Individual",
+ "from_date": (">=", fiscal_year[1]),
+ "to_date": ("<=", fiscal_year[2]),
+ },
+ ):
+ tds_category = frappe.get_doc("Tax Withholding Category", "TDS - 194 - Dividends - Individual")
+ tds_category.set("rates", [])
- tds_category.append('rates', {
- 'from_date': fiscal_year[1],
- 'to_date': fiscal_year[2],
- 'tax_withholding_rate': 10,
- 'single_threshold': 2500,
- 'cumulative_threshold': 0
- })
+ tds_category.append(
+ "rates",
+ {
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
+ "tax_withholding_rate": 10,
+ "single_threshold": 2500,
+ "cumulative_threshold": 0,
+ },
+ )
tds_category.save()
- if not frappe.db.get_value('Tax Withholding Account',
- {'parent': 'TDS - 194 - Dividends - Individual', 'account': account}):
- tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
- tds_category.append('accounts', {
- 'company': company,
- 'account': account
- })
+ if not frappe.db.get_value(
+ "Tax Withholding Account", {"parent": "TDS - 194 - Dividends - Individual", "account": account}
+ ):
+ tds_category = frappe.get_doc("Tax Withholding Category", "TDS - 194 - Dividends - Individual")
+ tds_category.append("accounts", {"company": company, "account": account})
tds_category.save()
+
def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable
accounts_settings.save()
+
def enable_discount_accounting(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.enable_discount_accounting = enable
accounts_settings.save()
+
def make_purchase_invoice(**args):
pi = frappe.new_doc("Purchase Invoice")
args = frappe._dict(args)
@@ -1326,7 +1603,7 @@ def make_purchase_invoice(**args):
pi.is_paid = 1
if args.cash_bank_account:
- pi.cash_bank_account=args.cash_bank_account
+ pi.cash_bank_account = args.cash_bank_account
pi.company = args.company or "_Test Company"
pi.supplier = args.supplier or "_Test Supplier"
@@ -1338,27 +1615,30 @@ def make_purchase_invoice(**args):
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
pi.cost_center = args.parent_cost_center
- pi.append("items", {
- "item_code": args.item or args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 5,
- "received_qty": args.received_qty or 0,
- "rejected_qty": args.rejected_qty or 0,
- "rate": args.rate or 50,
- "price_list_rate": args.price_list_rate or 50,
- "expense_account": args.expense_account or '_Test Account Cost for Goods Sold - _TC',
- "discount_account": args.discount_account or None,
- "discount_amount": args.discount_amount or 0,
- "conversion_factor": 1.0,
- "serial_no": args.serial_no,
- "stock_uom": args.uom or "_Test UOM",
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "project": args.project,
- "rejected_warehouse": args.rejected_warehouse or "",
- "rejected_serial_no": args.rejected_serial_no or "",
- "asset_location": args.location or "",
- "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0
- })
+ pi.append(
+ "items",
+ {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 5,
+ "received_qty": args.received_qty or 0,
+ "rejected_qty": args.rejected_qty or 0,
+ "rate": args.rate or 50,
+ "price_list_rate": args.price_list_rate or 50,
+ "expense_account": args.expense_account or "_Test Account Cost for Goods Sold - _TC",
+ "discount_account": args.discount_account or None,
+ "discount_amount": args.discount_amount or 0,
+ "conversion_factor": 1.0,
+ "serial_no": args.serial_no,
+ "stock_uom": args.uom or "_Test UOM",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "project": args.project,
+ "rejected_warehouse": args.rejected_warehouse or "",
+ "rejected_serial_no": args.rejected_serial_no or "",
+ "asset_location": args.location or "",
+ "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0,
+ },
+ )
if args.get_taxes_and_charges:
taxes = get_taxes()
@@ -1371,6 +1651,7 @@ def make_purchase_invoice(**args):
pi.submit()
return pi
+
def make_purchase_invoice_against_cost_center(**args):
pi = frappe.new_doc("Purchase Invoice")
args = frappe._dict(args)
@@ -1383,7 +1664,7 @@ def make_purchase_invoice_against_cost_center(**args):
pi.is_paid = 1
if args.cash_bank_account:
- pi.cash_bank_account=args.cash_bank_account
+ pi.cash_bank_account = args.cash_bank_account
pi.company = args.company or "_Test Company"
pi.cost_center = args.cost_center or "_Test Cost Center - _TC"
@@ -1397,25 +1678,29 @@ def make_purchase_invoice_against_cost_center(**args):
if args.supplier_warehouse:
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
- pi.append("items", {
- "item_code": args.item or args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 5,
- "received_qty": args.received_qty or 0,
- "rejected_qty": args.rejected_qty or 0,
- "rate": args.rate or 50,
- "conversion_factor": 1.0,
- "serial_no": args.serial_no,
- "stock_uom": "_Test UOM",
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "project": args.project,
- "rejected_warehouse": args.rejected_warehouse or "",
- "rejected_serial_no": args.rejected_serial_no or ""
- })
+ pi.append(
+ "items",
+ {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 5,
+ "received_qty": args.received_qty or 0,
+ "rejected_qty": args.rejected_qty or 0,
+ "rate": args.rate or 50,
+ "conversion_factor": 1.0,
+ "serial_no": args.serial_no,
+ "stock_uom": "_Test UOM",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "project": args.project,
+ "rejected_warehouse": args.rejected_warehouse or "",
+ "rejected_serial_no": args.rejected_serial_no or "",
+ },
+ )
if not args.do_not_save:
pi.insert()
if not args.do_not_submit:
pi.submit()
return pi
-test_records = frappe.get_test_records('Purchase Invoice')
+
+test_records = frappe.get_test_records("Purchase Invoice")
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py
index f5eb404d0a..70d29bfda2 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py
@@ -16,5 +16,5 @@ class PurchaseTaxesandChargesTemplate(Document):
def autoname(self):
if self.company and self.title:
- abbr = frappe.get_cached_value('Company', self.company, 'abbr')
- self.name = '{0} - {1}'.format(self.title, abbr)
+ abbr = frappe.get_cached_value("Company", self.company, "abbr")
+ self.name = "{0} - {1}".format(self.title, abbr)
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py
index 3176556ec5..1f0ea211f2 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py
@@ -3,18 +3,15 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'taxes_and_charges',
- 'non_standard_fieldnames': {
- 'Tax Rule': 'purchase_tax_template',
+ "fieldname": "taxes_and_charges",
+ "non_standard_fieldnames": {
+ "Tax Rule": "purchase_tax_template",
},
- 'transactions': [
+ "transactions": [
{
- 'label': _('Transactions'),
- 'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
+ "label": _("Transactions"),
+ "items": ["Purchase Invoice", "Purchase Order", "Purchase Receipt"],
},
- {
- 'label': _('References'),
- 'items': ['Supplier Quotation', 'Tax Rule']
- }
- ]
+ {"label": _("References"), "items": ["Supplier Quotation", "Tax Rule"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py
index b5b4a67d75..1d02f05504 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Purchase Taxes and Charges Template')
+
class TestPurchaseTaxesandChargesTemplate(unittest.TestCase):
pass
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 94334892f0..7d98c22033 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -49,27 +49,28 @@ from erpnext.stock.doctype.serial_no.serial_no import (
update_serial_nos_after_submit,
)
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
+
class SalesInvoice(SellingController):
def __init__(self, *args, **kwargs):
super(SalesInvoice, self).__init__(*args, **kwargs)
- self.status_updater = [{
- 'source_dt': 'Sales Invoice Item',
- 'target_field': 'billed_amt',
- 'target_ref_field': 'amount',
- 'target_dt': 'Sales Order Item',
- 'join_field': 'so_detail',
- 'target_parent_dt': 'Sales Order',
- 'target_parent_field': 'per_billed',
- 'source_field': 'amount',
- 'percent_join_field': 'sales_order',
- 'status_field': 'billing_status',
- 'keyword': 'Billed',
- 'overflow_type': 'billing'
- }]
+ self.status_updater = [
+ {
+ "source_dt": "Sales Invoice Item",
+ "target_field": "billed_amt",
+ "target_ref_field": "amount",
+ "target_dt": "Sales Order Item",
+ "join_field": "so_detail",
+ "target_parent_dt": "Sales Order",
+ "target_parent_field": "per_billed",
+ "source_field": "amount",
+ "percent_join_field": "sales_order",
+ "status_field": "billing_status",
+ "keyword": "Billed",
+ "overflow_type": "billing",
+ }
+ ]
def set_indicator(self):
"""Set indicator for portal"""
@@ -114,7 +115,9 @@ class SalesInvoice(SellingController):
self.validate_item_cost_centers()
self.validate_income_account()
- validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference)
+ validate_inter_company_party(
+ self.doctype, self.customer, self.company, self.inter_company_invoice_reference
+ )
if cint(self.is_pos):
self.validate_pos()
@@ -130,15 +133,21 @@ class SalesInvoice(SellingController):
validate_service_stop_date(self)
if not self.is_opening:
- self.is_opening = 'No'
+ self.is_opening = "No"
- if self._action != 'submit' and self.update_stock and not self.is_return:
- set_batch_nos(self, 'warehouse', True)
+ if self._action != "submit" and self.update_stock and not self.is_return:
+ set_batch_nos(self, "warehouse", True)
if self.redeem_loyalty_points:
- lp = frappe.get_doc('Loyalty Program', self.loyalty_program)
- self.loyalty_redemption_account = lp.expense_account if not self.loyalty_redemption_account else self.loyalty_redemption_account
- self.loyalty_redemption_cost_center = lp.cost_center if not self.loyalty_redemption_cost_center else self.loyalty_redemption_cost_center
+ lp = frappe.get_doc("Loyalty Program", self.loyalty_program)
+ self.loyalty_redemption_account = (
+ lp.expense_account if not self.loyalty_redemption_account else self.loyalty_redemption_account
+ )
+ self.loyalty_redemption_cost_center = (
+ lp.cost_center
+ if not self.loyalty_redemption_cost_center
+ else self.loyalty_redemption_cost_center
+ )
self.set_against_income_account()
self.validate_c_form()
@@ -155,11 +164,16 @@ class SalesInvoice(SellingController):
if self.is_pos and not self.is_return:
self.verify_payment_amount_is_positive()
- #validate amount in mode of payments for returned invoices for pos must be negative
+ # validate amount in mode of payments for returned invoices for pos must be negative
if self.is_pos and self.is_return:
self.verify_payment_amount_is_negative()
- if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points and not self.is_consolidated:
+ if (
+ self.redeem_loyalty_points
+ and self.loyalty_program
+ and self.loyalty_points
+ and not self.is_consolidated
+ ):
validate_loyalty_points(self, self.loyalty_points)
self.reset_default_field_value("set_warehouse", "items", "warehouse")
@@ -172,18 +186,28 @@ class SalesInvoice(SellingController):
if self.update_stock:
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
- elif asset.status in ("Scrapped", "Cancelled") or (asset.status == "Sold" and not self.is_return):
- frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
+ elif asset.status in ("Scrapped", "Cancelled") or (
+ asset.status == "Sold" and not self.is_return
+ ):
+ frappe.throw(
+ _("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(
+ d.idx, d.asset, asset.status
+ )
+ )
def validate_item_cost_centers(self):
for item in self.items:
cost_center_company = frappe.get_cached_value("Cost Center", item.cost_center, "company")
if cost_center_company != self.company:
- frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)))
+ frappe.throw(
+ _("Row #{0}: Cost Center {1} does not belong to company {2}").format(
+ frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)
+ )
+ )
def validate_income_account(self):
- for item in self.get('items'):
- validate_account_head(item.idx, item.income_account, self.company, 'Income')
+ for item in self.get("items"):
+ validate_account_head(item.idx, item.income_account, self.company, "Income")
def set_tax_withholding(self):
tax_withholding_details = get_party_tax_withholding_details(self)
@@ -202,8 +226,11 @@ class SalesInvoice(SellingController):
if not accounts or tax_withholding_account not in accounts:
self.append("taxes", tax_withholding_details)
- to_remove = [d for d in self.taxes
- if not d.tax_amount and d.charge_type == "Actual" and d.account_head == tax_withholding_account]
+ to_remove = [
+ d
+ for d in self.taxes
+ if not d.tax_amount and d.charge_type == "Actual" and d.account_head == tax_withholding_account
+ ]
for d in to_remove:
self.remove(d)
@@ -218,8 +245,9 @@ class SalesInvoice(SellingController):
self.validate_pos_paid_amount()
if not self.auto_repeat:
- frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
- self.company, self.base_grand_total, self)
+ frappe.get_doc("Authorization Control").validate_approving_authority(
+ self.doctype, self.company, self.base_grand_total, self
+ )
self.check_prev_docstatus()
@@ -239,7 +267,6 @@ class SalesInvoice(SellingController):
if self.is_return and self.update_stock:
update_serial_nos_after_submit(self, "items")
-
# this sequence because outstanding may get -ve
self.make_gl_entries()
@@ -258,7 +285,9 @@ class SalesInvoice(SellingController):
self.update_time_sheet(self.name)
- if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
+ if (
+ frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction"
+ ):
update_company_current_month_sales(self.company)
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
@@ -266,7 +295,9 @@ class SalesInvoice(SellingController):
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
if not self.is_return and not self.is_consolidated and self.loyalty_program:
self.make_loyalty_point_entry()
- elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program:
+ elif (
+ self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program
+ ):
against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
against_si_doc.delete_loyalty_point_entry()
against_si_doc.make_loyalty_point_entry()
@@ -295,16 +326,16 @@ class SalesInvoice(SellingController):
def check_if_consolidated_invoice(self):
# since POS Invoice extends Sales Invoice, we explicitly check if doctype is Sales Invoice
if self.doctype == "Sales Invoice" and self.is_consolidated:
- invoice_or_credit_note = "consolidated_credit_note" if self.is_return else "consolidated_invoice"
+ invoice_or_credit_note = (
+ "consolidated_credit_note" if self.is_return else "consolidated_invoice"
+ )
pos_closing_entry = frappe.get_all(
- "POS Invoice Merge Log",
- filters={ invoice_or_credit_note: self.name },
- pluck="pos_closing_entry"
+ "POS Invoice Merge Log", filters={invoice_or_credit_note: self.name}, pluck="pos_closing_entry"
)
if pos_closing_entry and pos_closing_entry[0]:
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
frappe.bold("Consolidated Sales Invoice"),
- get_link_to_form("POS Closing Entry", pos_closing_entry[0])
+ get_link_to_form("POS Closing Entry", pos_closing_entry[0]),
)
frappe.throw(msg, title=_("Not Allowed"))
@@ -346,14 +377,18 @@ class SalesInvoice(SellingController):
if self.update_stock == 1:
self.repost_future_sle_and_gle()
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
- if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
+ if (
+ frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction"
+ ):
update_company_current_month_sales(self.company)
self.update_project()
if not self.is_return and not self.is_consolidated and self.loyalty_program:
self.delete_loyalty_point_entry()
- elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program:
+ elif (
+ self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program
+ ):
against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
against_si_doc.delete_loyalty_point_entry()
against_si_doc.make_loyalty_point_entry()
@@ -361,50 +396,56 @@ class SalesInvoice(SellingController):
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.unlink_sales_invoice_from_timesheets()
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
def update_status_updater_args(self):
if cint(self.update_stock):
- self.status_updater.append({
- 'source_dt':'Sales Invoice Item',
- 'target_dt':'Sales Order Item',
- 'target_parent_dt':'Sales Order',
- 'target_parent_field':'per_delivered',
- 'target_field':'delivered_qty',
- 'target_ref_field':'qty',
- 'source_field':'qty',
- 'join_field':'so_detail',
- 'percent_join_field':'sales_order',
- 'status_field':'delivery_status',
- 'keyword':'Delivered',
- 'second_source_dt': 'Delivery Note Item',
- 'second_source_field': 'qty',
- 'second_join_field': 'so_detail',
- 'overflow_type': 'delivery',
- 'extra_cond': """ and exists(select name from `tabSales Invoice`
- where name=`tabSales Invoice Item`.parent and update_stock = 1)"""
- })
+ self.status_updater.append(
+ {
+ "source_dt": "Sales Invoice Item",
+ "target_dt": "Sales Order Item",
+ "target_parent_dt": "Sales Order",
+ "target_parent_field": "per_delivered",
+ "target_field": "delivered_qty",
+ "target_ref_field": "qty",
+ "source_field": "qty",
+ "join_field": "so_detail",
+ "percent_join_field": "sales_order",
+ "status_field": "delivery_status",
+ "keyword": "Delivered",
+ "second_source_dt": "Delivery Note Item",
+ "second_source_field": "qty",
+ "second_join_field": "so_detail",
+ "overflow_type": "delivery",
+ "extra_cond": """ and exists(select name from `tabSales Invoice`
+ where name=`tabSales Invoice Item`.parent and update_stock = 1)""",
+ }
+ )
if cint(self.is_return):
- self.status_updater.append({
- 'source_dt': 'Sales Invoice Item',
- 'target_dt': 'Sales Order Item',
- 'join_field': 'so_detail',
- 'target_field': 'returned_qty',
- 'target_parent_dt': 'Sales Order',
- 'source_field': '-1 * qty',
- 'second_source_dt': 'Delivery Note Item',
- 'second_source_field': '-1 * qty',
- 'second_join_field': 'so_detail',
- 'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)"""
- })
+ self.status_updater.append(
+ {
+ "source_dt": "Sales Invoice Item",
+ "target_dt": "Sales Order Item",
+ "join_field": "so_detail",
+ "target_field": "returned_qty",
+ "target_parent_dt": "Sales Order",
+ "source_field": "-1 * qty",
+ "second_source_dt": "Delivery Note Item",
+ "second_source_field": "-1 * qty",
+ "second_join_field": "so_detail",
+ "extra_cond": """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)""",
+ }
+ )
def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit
validate_against_credit_limit = False
- bypass_credit_limit_check_at_sales_order = frappe.db.get_value("Customer Credit Limit",
- filters={'parent': self.customer, 'parenttype': 'Customer', 'company': self.company},
- fieldname=["bypass_credit_limit_check"])
+ bypass_credit_limit_check_at_sales_order = frappe.db.get_value(
+ "Customer Credit Limit",
+ filters={"parent": self.customer, "parenttype": "Customer", "company": self.company},
+ fieldname=["bypass_credit_limit_check"],
+ )
if bypass_credit_limit_check_at_sales_order:
validate_against_credit_limit = True
@@ -418,7 +459,7 @@ class SalesInvoice(SellingController):
def unlink_sales_invoice_from_timesheets(self):
for row in self.timesheets:
- timesheet = frappe.get_doc('Timesheet', row.time_sheet)
+ timesheet = frappe.get_doc("Timesheet", row.time_sheet)
for time_log in timesheet.time_logs:
if time_log.sales_invoice == self.name:
time_log.sales_invoice = None
@@ -434,15 +475,17 @@ class SalesInvoice(SellingController):
if not self.debit_to:
self.debit_to = get_party_account("Customer", self.customer, self.company)
- self.party_account_currency = frappe.db.get_value("Account", self.debit_to, "account_currency", cache=True)
+ self.party_account_currency = frappe.db.get_value(
+ "Account", self.debit_to, "account_currency", cache=True
+ )
if not self.due_date and self.customer:
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
super(SalesInvoice, self).set_missing_values(for_validate)
print_format = pos.get("print_format") if pos else None
- if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
- print_format = 'POS Invoice'
+ if not print_format and not cint(frappe.db.get_value("Print Format", "POS Invoice", "disabled")):
+ print_format = "POS Invoice"
if pos:
return {
@@ -450,7 +493,7 @@ class SalesInvoice(SellingController):
"allow_edit_rate": pos.get("allow_user_to_edit_rate"),
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
"campaign": pos.get("campaign"),
- "allow_print_before_pay": pos.get("allow_print_before_pay")
+ "allow_print_before_pay": pos.get("allow_print_before_pay"),
}
def update_time_sheet(self, sales_invoice):
@@ -466,9 +509,11 @@ class SalesInvoice(SellingController):
def update_time_sheet_detail(self, timesheet, args, sales_invoice):
for data in timesheet.time_logs:
- if (self.project and args.timesheet_detail == data.name) or \
- (not self.project and not data.sales_invoice) or \
- (not sales_invoice and data.sales_invoice == self.name):
+ if (
+ (self.project and args.timesheet_detail == data.name)
+ or (not self.project and not data.sales_invoice)
+ or (not sales_invoice and data.sales_invoice == self.name)
+ ):
data.sales_invoice = sales_invoice
def on_update(self):
@@ -478,7 +523,7 @@ class SalesInvoice(SellingController):
paid_amount = 0.0
base_paid_amount = 0.0
for data in self.payments:
- data.base_amount = flt(data.amount*self.conversion_rate, self.precision("base_paid_amount"))
+ data.base_amount = flt(data.amount * self.conversion_rate, self.precision("base_paid_amount"))
paid_amount += data.amount
base_paid_amount += data.base_amount
@@ -489,7 +534,7 @@ class SalesInvoice(SellingController):
for data in self.timesheets:
if data.time_sheet:
status = frappe.db.get_value("Timesheet", data.time_sheet, "status")
- if status not in ['Submitted', 'Payslip']:
+ if status not in ["Submitted", "Payslip"]:
frappe.throw(_("Timesheet {0} is already completed or cancelled").format(data.time_sheet))
def set_pos_fields(self, for_validate=False):
@@ -498,20 +543,23 @@ class SalesInvoice(SellingController):
return
if not self.account_for_change_amount:
- self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
+ self.account_for_change_amount = frappe.get_cached_value(
+ "Company", self.company, "default_cash_account"
+ )
from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details
+
if not self.pos_profile and not self.flags.ignore_pos_profile:
pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
return
- self.pos_profile = pos_profile.get('name')
+ self.pos_profile = pos_profile.get("name")
pos = {}
if self.pos_profile:
- pos = frappe.get_doc('POS Profile', self.pos_profile)
+ pos = frappe.get_doc("POS Profile", self.pos_profile)
- if not self.get('payments') and not for_validate:
+ if not self.get("payments") and not for_validate:
update_multi_mode_option(self, pos)
if pos:
@@ -524,35 +572,52 @@ class SalesInvoice(SellingController):
if not for_validate:
self.ignore_pricing_rule = pos.ignore_pricing_rule
- if pos.get('account_for_change_amount'):
- self.account_for_change_amount = pos.get('account_for_change_amount')
+ if pos.get("account_for_change_amount"):
+ self.account_for_change_amount = pos.get("account_for_change_amount")
- for fieldname in ('currency', 'letter_head', 'tc_name',
- 'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
- 'write_off_cost_center', 'apply_discount_on', 'cost_center'):
- if (not for_validate) or (for_validate and not self.get(fieldname)):
- self.set(fieldname, pos.get(fieldname))
+ for fieldname in (
+ "currency",
+ "letter_head",
+ "tc_name",
+ "company",
+ "select_print_heading",
+ "write_off_account",
+ "taxes_and_charges",
+ "write_off_cost_center",
+ "apply_discount_on",
+ "cost_center",
+ ):
+ if (not for_validate) or (for_validate and not self.get(fieldname)):
+ self.set(fieldname, pos.get(fieldname))
if pos.get("company_address"):
self.company_address = pos.get("company_address")
if self.customer:
- customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
- customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list')
- selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
+ customer_price_list, customer_group = frappe.get_value(
+ "Customer", self.customer, ["default_price_list", "customer_group"]
+ )
+ customer_group_price_list = frappe.get_value(
+ "Customer Group", customer_group, "default_price_list"
+ )
+ selling_price_list = (
+ customer_price_list or customer_group_price_list or pos.get("selling_price_list")
+ )
else:
- selling_price_list = pos.get('selling_price_list')
+ selling_price_list = pos.get("selling_price_list")
if selling_price_list:
- self.set('selling_price_list', selling_price_list)
+ self.set("selling_price_list", selling_price_list)
if not for_validate:
self.update_stock = cint(pos.get("update_stock"))
# set pos values in items
for item in self.get("items"):
- if item.get('item_code'):
- profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos, update_data=True)
+ if item.get("item_code"):
+ profile_details = get_pos_profile_item_details(
+ pos, frappe._dict(item.as_dict()), pos, update_data=True
+ )
for fname, val in profile_details.items():
if (not for_validate) or (for_validate and not item.get(fname)):
item.set(fname, val)
@@ -576,22 +641,29 @@ class SalesInvoice(SellingController):
if not self.debit_to:
self.raise_missing_debit_credit_account_error("Customer", self.customer)
- account = frappe.get_cached_value("Account", self.debit_to,
- ["account_type", "report_type", "account_currency"], as_dict=True)
+ account = frappe.get_cached_value(
+ "Account", self.debit_to, ["account_type", "report_type", "account_currency"], as_dict=True
+ )
if not account:
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
if account.report_type != "Balance Sheet":
- msg = _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " "
- msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
+ msg = (
+ _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " "
+ )
+ msg += _(
+ "You can change the parent account to a Balance Sheet account or select a different account."
+ )
frappe.throw(msg, title=_("Invalid Account"))
if self.customer and account.account_type != "Receivable":
- msg = _("Please ensure {} account {} is a Receivable account.").format(
- frappe.bold("Debit To"),
- frappe.bold(self.debit_to)
- ) + " "
+ msg = (
+ _("Please ensure {} account {} is a Receivable account.").format(
+ frappe.bold("Debit To"), frappe.bold(self.debit_to)
+ )
+ + " "
+ )
msg += _("Change the account type to Receivable or select a different account.")
frappe.throw(msg, title=_("Invalid Account"))
@@ -600,52 +672,60 @@ class SalesInvoice(SellingController):
def clear_unallocated_mode_of_payments(self):
self.set("payments", self.get("payments", {"amount": ["not in", [0, None, ""]]}))
- frappe.db.sql("""delete from `tabSales Invoice Payment` where parent = %s
- and amount = 0""", self.name)
+ frappe.db.sql(
+ """delete from `tabSales Invoice Payment` where parent = %s
+ and amount = 0""",
+ self.name,
+ )
def validate_with_previous_doc(self):
- super(SalesInvoice, self).validate_with_previous_doc({
- "Sales Order": {
- "ref_dn_field": "sales_order",
- "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]]
- },
- "Sales Order Item": {
- "ref_dn_field": "so_detail",
- "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
- "is_child_table": True,
- "allow_duplicate_prev_row_id": True
- },
- "Delivery Note": {
- "ref_dn_field": "delivery_note",
- "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]]
- },
- "Delivery Note Item": {
- "ref_dn_field": "dn_detail",
- "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
- "is_child_table": True,
- "allow_duplicate_prev_row_id": True
- },
- })
+ super(SalesInvoice, self).validate_with_previous_doc(
+ {
+ "Sales Order": {
+ "ref_dn_field": "sales_order",
+ "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
+ },
+ "Sales Order Item": {
+ "ref_dn_field": "so_detail",
+ "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
+ "is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
+ },
+ "Delivery Note": {
+ "ref_dn_field": "delivery_note",
+ "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
+ },
+ "Delivery Note Item": {
+ "ref_dn_field": "dn_detail",
+ "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
+ "is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
+ },
+ }
+ )
- if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')) and not self.is_return:
- self.validate_rate_with_reference_doc([
- ["Sales Order", "sales_order", "so_detail"],
- ["Delivery Note", "delivery_note", "dn_detail"]
- ])
+ if (
+ cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate"))
+ and not self.is_return
+ ):
+ self.validate_rate_with_reference_doc(
+ [["Sales Order", "sales_order", "so_detail"], ["Delivery Note", "delivery_note", "dn_detail"]]
+ )
def set_against_income_account(self):
"""Set against account for debit to account"""
against_acc = []
- for d in self.get('items'):
+ for d in self.get("items"):
if d.income_account and d.income_account not in against_acc:
against_acc.append(d.income_account)
- self.against_income_account = ','.join(against_acc)
+ self.against_income_account = ",".join(against_acc)
def add_remarks(self):
if not self.remarks:
if self.po_no and self.po_date:
- self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
- formatdate(self.po_date))
+ self.remarks = _("Against Customer Order {0} dated {1}").format(
+ self.po_no, formatdate(self.po_date)
+ )
else:
self.remarks = _("No Remarks")
@@ -661,36 +741,41 @@ class SalesInvoice(SellingController):
if self.is_return:
return
- prev_doc_field_map = {'Sales Order': ['so_required', 'is_pos'],'Delivery Note': ['dn_required', 'update_stock']}
+ prev_doc_field_map = {
+ "Sales Order": ["so_required", "is_pos"],
+ "Delivery Note": ["dn_required", "update_stock"],
+ }
for key, value in prev_doc_field_map.items():
- if frappe.db.get_single_value('Selling Settings', value[0]) == 'Yes':
+ if frappe.db.get_single_value("Selling Settings", value[0]) == "Yes":
- if frappe.get_value('Customer', self.customer, value[0]):
+ if frappe.get_value("Customer", self.customer, value[0]):
continue
- for d in self.get('items'):
- if (d.item_code and not d.get(key.lower().replace(' ', '_')) and not self.get(value[1])):
+ for d in self.get("items"):
+ if d.item_code and not d.get(key.lower().replace(" ", "_")) and not self.get(value[1]):
msgprint(_("{0} is mandatory for Item {1}").format(key, d.item_code), raise_exception=1)
-
def validate_proj_cust(self):
"""check for does customer belong to same project as entered.."""
if self.project and self.customer:
- res = frappe.db.sql("""select name from `tabProject`
+ res = frappe.db.sql(
+ """select name from `tabProject`
where name = %s and (customer = %s or customer is null or customer = '')""",
- (self.project, self.customer))
+ (self.project, self.customer),
+ )
if not res:
- throw(_("Customer {0} does not belong to project {1}").format(self.customer,self.project))
+ throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project))
def validate_pos(self):
if self.is_return:
invoice_total = self.rounded_total or self.grand_total
- if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \
- 1.0/(10.0**(self.precision("grand_total") + 1.0)):
- frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
+ if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > 1.0 / (
+ 10.0 ** (self.precision("grand_total") + 1.0)
+ ):
+ frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
def validate_item_code(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.item_code and self.is_opening == "No":
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
@@ -698,17 +783,24 @@ class SalesInvoice(SellingController):
super(SalesInvoice, self).validate_warehouse()
for d in self.get_item_list():
- if not d.warehouse and d.item_code and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
+ if (
+ not d.warehouse
+ and d.item_code
+ and frappe.get_cached_value("Item", d.item_code, "is_stock_item")
+ ):
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
def validate_delivery_note(self):
for d in self.get("items"):
if d.delivery_note:
- msgprint(_("Stock cannot be updated against Delivery Note {0}").format(d.delivery_note), raise_exception=1)
+ msgprint(
+ _("Stock cannot be updated against Delivery Note {0}").format(d.delivery_note),
+ raise_exception=1,
+ )
def validate_write_off_account(self):
if flt(self.write_off_amount) and not self.write_off_account:
- self.write_off_account = frappe.get_cached_value('Company', self.company, 'write_off_account')
+ self.write_off_account = frappe.get_cached_value("Company", self.company, "write_off_account")
if flt(self.write_off_amount) and not self.write_off_account:
msgprint(_("Please enter Write Off Account"), raise_exception=1)
@@ -718,18 +810,23 @@ class SalesInvoice(SellingController):
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
def validate_c_form(self):
- """ Blank C-form no if C-form applicable marked as 'No'"""
- if self.amended_from and self.c_form_applicable == 'No' and self.c_form_no:
- frappe.db.sql("""delete from `tabC-Form Invoice Detail` where invoice_no = %s
- and parent = %s""", (self.amended_from, self.c_form_no))
+ """Blank C-form no if C-form applicable marked as 'No'"""
+ if self.amended_from and self.c_form_applicable == "No" and self.c_form_no:
+ frappe.db.sql(
+ """delete from `tabC-Form Invoice Detail` where invoice_no = %s
+ and parent = %s""",
+ (self.amended_from, self.c_form_no),
+ )
- frappe.db.set(self, 'c_form_no', '')
+ frappe.db.set(self, "c_form_no", "")
def validate_c_form_on_cancel(self):
- """ Display message if C-Form no exists on cancellation of Sales Invoice"""
- if self.c_form_applicable == 'Yes' and self.c_form_no:
- msgprint(_("Please remove this Invoice {0} from C-Form {1}")
- .format(self.name, self.c_form_no), raise_exception = 1)
+ """Display message if C-Form no exists on cancellation of Sales Invoice"""
+ if self.c_form_applicable == "Yes" and self.c_form_no:
+ msgprint(
+ _("Please remove this Invoice {0} from C-Form {1}").format(self.name, self.c_form_no),
+ raise_exception=1,
+ )
def validate_dropship_item(self):
for item in self.items:
@@ -738,27 +835,36 @@ class SalesInvoice(SellingController):
frappe.throw(_("Could not update stock, invoice contains drop shipping item."))
def update_current_stock(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.item_code and d.warehouse:
- bin = frappe.db.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
- d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
+ bin = frappe.db.sql(
+ "select actual_qty from `tabBin` where item_code = %s and warehouse = %s",
+ (d.item_code, d.warehouse),
+ as_dict=1,
+ )
+ d.actual_qty = bin and flt(bin[0]["actual_qty"]) or 0
- for d in self.get('packed_items'):
- bin = frappe.db.sql("select actual_qty, projected_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
- d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
- d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0
+ for d in self.get("packed_items"):
+ bin = frappe.db.sql(
+ "select actual_qty, projected_qty from `tabBin` where item_code = %s and warehouse = %s",
+ (d.item_code, d.warehouse),
+ as_dict=1,
+ )
+ d.actual_qty = bin and flt(bin[0]["actual_qty"]) or 0
+ d.projected_qty = bin and flt(bin[0]["projected_qty"]) or 0
def update_packing_list(self):
if cint(self.update_stock) == 1:
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+
make_packing_list(self)
else:
- self.set('packed_items', [])
+ self.set("packed_items", [])
def set_billing_hours_and_amount(self):
if not self.project:
for timesheet in self.timesheets:
- ts_doc = frappe.get_doc('Timesheet', timesheet.time_sheet)
+ ts_doc = frappe.get_doc("Timesheet", timesheet.time_sheet)
if not timesheet.billing_hours and ts_doc.total_billable_hours:
timesheet.billing_hours = ts_doc.total_billable_hours
@@ -773,17 +879,20 @@ class SalesInvoice(SellingController):
@frappe.whitelist()
def add_timesheet_data(self):
- self.set('timesheets', [])
+ self.set("timesheets", [])
if self.project:
for data in get_projectwise_timesheet_data(self.project):
- self.append('timesheets', {
- 'time_sheet': data.time_sheet,
- 'billing_hours': data.billing_hours,
- 'billing_amount': data.billing_amount,
- 'timesheet_detail': data.name,
- 'activity_type': data.activity_type,
- 'description': data.description
- })
+ self.append(
+ "timesheets",
+ {
+ "time_sheet": data.time_sheet,
+ "billing_hours": data.billing_hours,
+ "billing_amount": data.billing_amount,
+ "timesheet_detail": data.name,
+ "activity_type": data.activity_type,
+ "description": data.description,
+ },
+ )
self.calculate_billing_amount_for_timesheet()
@@ -795,13 +904,19 @@ class SalesInvoice(SellingController):
self.total_billing_hours = timesheet_sum("billing_hours")
def get_warehouse(self):
- user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile`
- where ifnull(user,'') = %s and company = %s""", (frappe.session['user'], self.company))
+ user_pos_profile = frappe.db.sql(
+ """select name, warehouse from `tabPOS Profile`
+ where ifnull(user,'') = %s and company = %s""",
+ (frappe.session["user"], self.company),
+ )
warehouse = user_pos_profile[0][1] if user_pos_profile else None
if not warehouse:
- global_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile`
- where (user is null or user = '') and company = %s""", self.company)
+ global_pos_profile = frappe.db.sql(
+ """select name, warehouse from `tabPOS Profile`
+ where (user is null or user = '') and company = %s""",
+ self.company,
+ )
if global_pos_profile:
warehouse = global_pos_profile[0][1]
@@ -815,14 +930,16 @@ class SalesInvoice(SellingController):
for d in self.get("items"):
if d.is_fixed_asset:
if not disposal_account:
- disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(self.company)
+ disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(
+ self.company
+ )
d.income_account = disposal_account
if not d.cost_center:
d.cost_center = depreciation_cost_center
def check_prev_docstatus(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
@@ -838,22 +955,35 @@ class SalesInvoice(SellingController):
if gl_entries:
# if POS and amount is written off, updating outstanding amt after posting all gl entries
- update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
- cint(self.redeem_loyalty_points)) else "Yes"
+ update_outstanding = (
+ "No"
+ if (cint(self.is_pos) or self.write_off_account or cint(self.redeem_loyalty_points))
+ else "Yes"
+ )
if self.docstatus == 1:
- make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
+ make_gl_entries(
+ gl_entries,
+ update_outstanding=update_outstanding,
+ merge_entries=False,
+ from_repost=from_repost,
+ )
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
- update_outstanding_amt(self.debit_to, "Customer", self.customer,
- self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
- elif self.docstatus == 2 and cint(self.update_stock) \
- and cint(auto_accounting_for_stock):
- make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
+ update_outstanding_amt(
+ self.debit_to,
+ "Customer",
+ self.customer,
+ self.doctype,
+ self.return_against if cint(self.is_return) and self.return_against else self.name,
+ )
+
+ elif self.docstatus == 2 and cint(self.update_stock) and cint(auto_accounting_for_stock):
+ make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import merge_similar_entries
@@ -883,31 +1013,46 @@ class SalesInvoice(SellingController):
def make_customer_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
- grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
- base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total)
- else self.base_grand_total, self.precision("base_grand_total"))
+ grand_total = (
+ self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
+ )
+ base_grand_total = flt(
+ self.base_rounded_total
+ if (self.base_rounding_adjustment and self.base_rounded_total)
+ else self.base_grand_total,
+ self.precision("base_grand_total"),
+ )
if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle
gl_entries.append(
- self.get_gl_dict({
- "account": self.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "due_date": self.due_date,
- "against": self.against_income_account,
- "debit": base_grand_total,
- "debit_in_account_currency": base_grand_total \
- if self.party_account_currency==self.company_currency else grand_total,
- "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype,
- "cost_center": self.cost_center,
- "project": self.project
- }, self.party_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "due_date": self.due_date,
+ "against": self.against_income_account,
+ "debit": base_grand_total,
+ "debit_in_account_currency": base_grand_total
+ if self.party_account_currency == self.company_currency
+ else grand_total,
+ "against_voucher": self.return_against
+ if cint(self.is_return) and self.return_against
+ else self.name,
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center,
+ "project": self.project,
+ },
+ self.party_account_currency,
+ item=self,
+ )
)
def make_tax_gl_entries(self, gl_entries):
- enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+ enable_discount_accounting = cint(
+ frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+ )
for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
@@ -915,33 +1060,45 @@ class SalesInvoice(SellingController):
if flt(tax.base_tax_amount_after_discount_amount):
account_currency = get_account_currency(tax.account_head)
gl_entries.append(
- self.get_gl_dict({
- "account": tax.account_head,
- "against": self.customer,
- "credit": flt(base_amount,
- tax.precision("tax_amount_after_discount_amount")),
- "credit_in_account_currency": (flt(base_amount,
- tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
- flt(amount, tax.precision("tax_amount_after_discount_amount"))),
- "cost_center": tax.cost_center
- }, account_currency, item=tax)
+ self.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "against": self.customer,
+ "credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")),
+ "credit_in_account_currency": (
+ flt(base_amount, tax.precision("base_tax_amount_after_discount_amount"))
+ if account_currency == self.company_currency
+ else flt(amount, tax.precision("tax_amount_after_discount_amount"))
+ ),
+ "cost_center": tax.cost_center,
+ },
+ account_currency,
+ item=tax,
+ )
)
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
- self.get_gl_dict({
- "account": self.unrealized_profit_loss_account,
- "against": self.customer,
- "debit": flt(self.total_taxes_and_charges),
- "debit_in_account_currency": flt(self.base_total_taxes_and_charges),
- "cost_center": self.cost_center
- }, account_currency, item=self))
+ self.get_gl_dict(
+ {
+ "account": self.unrealized_profit_loss_account,
+ "against": self.customer,
+ "debit": flt(self.total_taxes_and_charges),
+ "debit_in_account_currency": flt(self.base_total_taxes_and_charges),
+ "cost_center": self.cost_center,
+ },
+ account_currency,
+ item=self,
+ )
+ )
def make_item_gl_entries(self, gl_entries):
# income account gl entries
- enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+ enable_discount_accounting = cint(
+ frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+ )
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")):
@@ -949,8 +1106,9 @@ class SalesInvoice(SellingController):
asset = self.get_asset(item)
if self.is_return:
- fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset,
- item.base_net_amount, item.finance_book)
+ fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
+ asset, item.base_net_amount, item.finance_book
+ )
asset.db_set("disposal_date", None)
if asset.calculate_depreciation:
@@ -958,8 +1116,9 @@ class SalesInvoice(SellingController):
self.reset_depreciation_schedule(asset)
else:
- fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
- item.base_net_amount, item.finance_book)
+ fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
+ asset, item.base_net_amount, item.finance_book
+ )
asset.db_set("disposal_date", self.posting_date)
if asset.calculate_depreciation:
@@ -974,47 +1133,57 @@ class SalesInvoice(SellingController):
else:
# Do not book income for transfer within same company
if not self.is_internal_transfer():
- income_account = (item.income_account
- if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
+ income_account = (
+ item.income_account
+ if (not item.enable_deferred_revenue or self.is_return)
+ else item.deferred_revenue_account
+ )
amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
account_currency = get_account_currency(income_account)
gl_entries.append(
- self.get_gl_dict({
- "account": income_account,
- "against": self.customer,
- "credit": flt(base_amount, item.precision("base_net_amount")),
- "credit_in_account_currency": (flt(base_amount, item.precision("base_net_amount"))
- if account_currency==self.company_currency
- else flt(amount, item.precision("net_amount"))),
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": income_account,
+ "against": self.customer,
+ "credit": flt(base_amount, item.precision("base_net_amount")),
+ "credit_in_account_currency": (
+ flt(base_amount, item.precision("base_net_amount"))
+ if account_currency == self.company_currency
+ else flt(amount, item.precision("net_amount"))
+ ),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ account_currency,
+ item=item,
+ )
)
# expense account gl entries
- if cint(self.update_stock) and \
- erpnext.is_perpetual_inventory_enabled(self.company):
+ if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
gl_entries += super(SalesInvoice, self).get_gl_entries()
def get_asset(self, item):
- if item.get('asset'):
+ if item.get("asset"):
asset = frappe.get_doc("Asset", item.asset)
else:
- frappe.throw(_(
- "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
- title=_("Missing Asset")
+ frappe.throw(
+ _("Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
+ title=_("Missing Asset"),
)
self.check_finance_books(item, asset)
return asset
def check_finance_books(self, item, asset):
- if (len(asset.finance_books) > 1 and not item.finance_book
- and asset.finance_books[0].finance_book):
- frappe.throw(_("Select finance book for the item {0} at row {1}")
- .format(item.item_code, item.idx))
+ if (
+ len(asset.finance_books) > 1 and not item.finance_book and asset.finance_books[0].finance_book
+ ):
+ frappe.throw(
+ _("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
+ )
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
@@ -1034,14 +1203,12 @@ class SalesInvoice(SellingController):
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
- 'Asset Repair',
- filters = {'asset': asset.name},
- fields = ['name', 'increase_in_asset_life']
+ "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
- asset_repair = frappe.get_doc('Asset Repair', repair.name)
+ asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
@@ -1051,8 +1218,8 @@ class SalesInvoice(SellingController):
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
row = -1
- finance_book = asset.get('schedules')[0].get('finance_book')
- for schedule in asset.get('schedules'):
+ finance_book = asset.get("schedules")[0].get("finance_book")
+ for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
@@ -1060,8 +1227,9 @@ class SalesInvoice(SellingController):
row += 1
if schedule.schedule_date == posting_date_of_original_invoice:
- if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice) \
- or self.sale_happens_in_the_future(posting_date_of_original_invoice):
+ if not self.sale_was_made_on_original_schedule_date(
+ asset, schedule, row, posting_date_of_original_invoice
+ ) or self.sale_happens_in_the_future(posting_date_of_original_invoice):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
@@ -1076,14 +1244,17 @@ class SalesInvoice(SellingController):
asset.save()
def get_posting_date_of_sales_invoice(self):
- return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date')
+ return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
- def sale_was_made_on_original_schedule_date(self, asset, schedule, row, posting_date_of_original_invoice):
- for finance_book in asset.get('finance_books'):
+ def sale_was_made_on_original_schedule_date(
+ self, asset, schedule, row, posting_date_of_original_invoice
+ ):
+ for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
- orginal_schedule_date = add_months(finance_book.depreciation_start_date,
- row * cint(finance_book.frequency_of_depreciation))
+ orginal_schedule_date = add_months(
+ finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
+ )
if orginal_schedule_date == posting_date_of_original_invoice:
return True
@@ -1104,7 +1275,9 @@ class SalesInvoice(SellingController):
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):
- self._enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+ self._enable_discount_accounting = cint(
+ frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+ )
return self._enable_discount_accounting
@@ -1112,36 +1285,46 @@ class SalesInvoice(SellingController):
if self.is_return:
asset.set_status()
else:
- asset.set_status("Sold" if self.docstatus==1 else None)
+ asset.set_status("Sold" if self.docstatus == 1 else None)
def make_loyalty_point_redemption_gle(self, gl_entries):
if cint(self.redeem_loyalty_points):
gl_entries.append(
- self.get_gl_dict({
- "account": self.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "against": "Expense account - " + cstr(self.loyalty_redemption_account) + " for the Loyalty Program",
- "credit": self.loyalty_amount,
- "against_voucher": self.return_against if cint(self.is_return) else self.name,
- "against_voucher_type": self.doctype,
- "cost_center": self.cost_center
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "against": "Expense account - "
+ + cstr(self.loyalty_redemption_account)
+ + " for the Loyalty Program",
+ "credit": self.loyalty_amount,
+ "against_voucher": self.return_against if cint(self.is_return) else self.name,
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": self.loyalty_redemption_account,
- "cost_center": self.cost_center or self.loyalty_redemption_cost_center,
- "against": self.customer,
- "debit": self.loyalty_amount,
- "remark": "Loyalty Points redeemed by the customer"
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.loyalty_redemption_account,
+ "cost_center": self.cost_center or self.loyalty_redemption_cost_center,
+ "against": self.customer,
+ "debit": self.loyalty_amount,
+ "remark": "Loyalty Points redeemed by the customer",
+ },
+ item=self,
+ )
)
def make_pos_gl_entries(self, gl_entries):
if cint(self.is_pos):
- skip_change_gl_entries = not cint(frappe.db.get_single_value('Accounts Settings', 'post_change_gl_entries'))
+ skip_change_gl_entries = not cint(
+ frappe.db.get_single_value("Accounts Settings", "post_change_gl_entries")
+ )
for payment_mode in self.payments:
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
@@ -1150,32 +1333,42 @@ class SalesInvoice(SellingController):
if payment_mode.amount:
# POS, make payment entries
gl_entries.append(
- self.get_gl_dict({
- "account": self.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "against": payment_mode.account,
- "credit": payment_mode.base_amount,
- "credit_in_account_currency": payment_mode.base_amount \
- if self.party_account_currency==self.company_currency \
+ self.get_gl_dict(
+ {
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "against": payment_mode.account,
+ "credit": payment_mode.base_amount,
+ "credit_in_account_currency": payment_mode.base_amount
+ if self.party_account_currency == self.company_currency
else payment_mode.amount,
- "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype,
- "cost_center": self.cost_center
- }, self.party_account_currency, item=self)
+ "against_voucher": self.return_against
+ if cint(self.is_return) and self.return_against
+ else self.name,
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center,
+ },
+ self.party_account_currency,
+ item=self,
+ )
)
payment_mode_account_currency = get_account_currency(payment_mode.account)
gl_entries.append(
- self.get_gl_dict({
- "account": payment_mode.account,
- "against": self.customer,
- "debit": payment_mode.base_amount,
- "debit_in_account_currency": payment_mode.base_amount \
- if payment_mode_account_currency==self.company_currency \
+ self.get_gl_dict(
+ {
+ "account": payment_mode.account,
+ "against": self.customer,
+ "debit": payment_mode.base_amount,
+ "debit_in_account_currency": payment_mode.base_amount
+ if payment_mode_account_currency == self.company_currency
else payment_mode.amount,
- "cost_center": self.cost_center
- }, payment_mode_account_currency, item=self)
+ "cost_center": self.cost_center,
+ },
+ payment_mode_account_currency,
+ item=self,
+ )
)
if not skip_change_gl_entries:
@@ -1185,28 +1378,38 @@ class SalesInvoice(SellingController):
if self.change_amount:
if self.account_for_change_amount:
gl_entries.append(
- self.get_gl_dict({
- "account": self.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "against": self.account_for_change_amount,
- "debit": flt(self.base_change_amount),
- "debit_in_account_currency": flt(self.base_change_amount) \
- if self.party_account_currency==self.company_currency else flt(self.change_amount),
- "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype,
- "cost_center": self.cost_center,
- "project": self.project
- }, self.party_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "against": self.account_for_change_amount,
+ "debit": flt(self.base_change_amount),
+ "debit_in_account_currency": flt(self.base_change_amount)
+ if self.party_account_currency == self.company_currency
+ else flt(self.change_amount),
+ "against_voucher": self.return_against
+ if cint(self.is_return) and self.return_against
+ else self.name,
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center,
+ "project": self.project,
+ },
+ self.party_account_currency,
+ item=self,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": self.account_for_change_amount,
- "against": self.customer,
- "credit": self.base_change_amount,
- "cost_center": self.cost_center
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.account_for_change_amount,
+ "against": self.customer,
+ "credit": self.base_change_amount,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
)
else:
frappe.throw(_("Select change amount account"), title="Mandatory Field")
@@ -1215,61 +1418,84 @@ class SalesInvoice(SellingController):
# write off entries, applicable if only pos
if self.write_off_account and flt(self.write_off_amount, self.precision("write_off_amount")):
write_off_account_currency = get_account_currency(self.write_off_account)
- default_cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
+ default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
gl_entries.append(
- self.get_gl_dict({
- "account": self.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "against": self.write_off_account,
- "credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
- "credit_in_account_currency": (flt(self.base_write_off_amount,
- self.precision("base_write_off_amount")) if self.party_account_currency==self.company_currency
- else flt(self.write_off_amount, self.precision("write_off_amount"))),
- "against_voucher": self.return_against if cint(self.is_return) else self.name,
- "against_voucher_type": self.doctype,
- "cost_center": self.cost_center,
- "project": self.project
- }, self.party_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "against": self.write_off_account,
+ "credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
+ "credit_in_account_currency": (
+ flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
+ if self.party_account_currency == self.company_currency
+ else flt(self.write_off_amount, self.precision("write_off_amount"))
+ ),
+ "against_voucher": self.return_against if cint(self.is_return) else self.name,
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center,
+ "project": self.project,
+ },
+ self.party_account_currency,
+ item=self,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": self.write_off_account,
- "against": self.customer,
- "debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
- "debit_in_account_currency": (flt(self.base_write_off_amount,
- self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency
- else flt(self.write_off_amount, self.precision("write_off_amount"))),
- "cost_center": self.cost_center or self.write_off_cost_center or default_cost_center
- }, write_off_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.write_off_account,
+ "against": self.customer,
+ "debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
+ "debit_in_account_currency": (
+ flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
+ if write_off_account_currency == self.company_currency
+ else flt(self.write_off_amount, self.precision("write_off_amount"))
+ ),
+ "cost_center": self.cost_center or self.write_off_cost_center or default_cost_center,
+ },
+ write_off_account_currency,
+ item=self,
+ )
)
def make_gle_for_rounding_adjustment(self, gl_entries):
- if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment \
- and not self.is_internal_transfer():
- round_off_account, round_off_cost_center = \
- get_round_off_account_and_cost_center(self.company)
+ if (
+ flt(self.rounding_adjustment, self.precision("rounding_adjustment"))
+ and self.base_rounding_adjustment
+ and not self.is_internal_transfer()
+ ):
+ round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
gl_entries.append(
- self.get_gl_dict({
- "account": round_off_account,
- "against": self.customer,
- "credit_in_account_currency": flt(self.rounding_adjustment,
- self.precision("rounding_adjustment")),
- "credit": flt(self.base_rounding_adjustment,
- self.precision("base_rounding_adjustment")),
- "cost_center": self.cost_center or round_off_cost_center,
- }, item=self))
+ self.get_gl_dict(
+ {
+ "account": round_off_account,
+ "against": self.customer,
+ "credit_in_account_currency": flt(
+ self.rounding_adjustment, self.precision("rounding_adjustment")
+ ),
+ "credit": flt(self.base_rounding_adjustment, self.precision("base_rounding_adjustment")),
+ "cost_center": self.cost_center or round_off_cost_center,
+ },
+ item=self,
+ )
+ )
def update_billing_status_in_dn(self, update_modified=True):
updated_delivery_notes = []
for d in self.get("items"):
if d.dn_detail:
- billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
- where dn_detail=%s and docstatus=1""", d.dn_detail)
+ billed_amt = frappe.db.sql(
+ """select sum(amount) from `tabSales Invoice Item`
+ where dn_detail=%s and docstatus=1""",
+ d.dn_detail,
+ )
billed_amt = billed_amt and billed_amt[0][0] or 0
- frappe.db.set_value("Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified)
+ frappe.db.set_value(
+ "Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified
+ )
updated_delivery_notes.append(d.delivery_note)
elif d.so_detail:
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
@@ -1284,7 +1510,7 @@ class SalesInvoice(SellingController):
self.due_date = None
def update_serial_no(self, in_cancel=False):
- """ update Sales Invoice refrence in Serial No """
+ """update Sales Invoice refrence in Serial No"""
invoice = None if (in_cancel or self.is_return) else self.name
if in_cancel and self.is_return:
invoice = self.return_against
@@ -1294,26 +1520,25 @@ class SalesInvoice(SellingController):
continue
for serial_no in get_serial_nos(item.serial_no):
- if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
- frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
+ if serial_no and frappe.db.get_value("Serial No", serial_no, "item_code") == item.item_code:
+ frappe.db.set_value("Serial No", serial_no, "sales_invoice", invoice)
def validate_serial_numbers(self):
"""
- validate serial number agains Delivery Note and Sales Invoice
+ validate serial number agains Delivery Note and Sales Invoice
"""
self.set_serial_no_against_delivery_note()
self.validate_serial_against_delivery_note()
def set_serial_no_against_delivery_note(self):
for item in self.items:
- if item.serial_no and item.delivery_note and \
- item.qty != len(get_serial_nos(item.serial_no)):
+ if item.serial_no and item.delivery_note and item.qty != len(get_serial_nos(item.serial_no)):
item.serial_no = get_delivery_note_serial_no(item.item_code, item.qty, item.delivery_note)
def validate_serial_against_delivery_note(self):
"""
- validate if the serial numbers in Sales Invoice Items are same as in
- Delivery Note Item
+ validate if the serial numbers in Sales Invoice Items are same as in
+ Delivery Note Item
"""
for item in self.items:
@@ -1332,14 +1557,18 @@ class SalesInvoice(SellingController):
serial_no_msg = ", ".join(frappe.bold(d) for d in serial_no_diff)
msg = _("Row #{0}: The following Serial Nos are not present in Delivery Note {1}:").format(
- item.idx, dn_link)
+ item.idx, dn_link
+ )
msg += " " + serial_no_msg
frappe.throw(msg=msg, title=_("Serial Nos Mismatch"))
if item.serial_no and cint(item.qty) != len(si_serial_nos):
- frappe.throw(_("Row #{0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
- item.idx, item.qty, item.item_code, len(si_serial_nos)))
+ frappe.throw(
+ _("Row #{0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
+ item.idx, item.qty, item.item_code, len(si_serial_nos)
+ )
+ )
def update_project(self):
if self.project:
@@ -1347,7 +1576,6 @@ class SalesInvoice(SellingController):
project.update_billed_amount()
project.db_update()
-
def verify_payment_amount_is_positive(self):
for entry in self.payments:
if entry.amount < 0:
@@ -1363,66 +1591,86 @@ class SalesInvoice(SellingController):
returned_amount = self.get_returned_amount()
current_amount = flt(self.grand_total) - cint(self.loyalty_amount)
eligible_amount = current_amount - returned_amount
- lp_details = get_loyalty_program_details_with_points(self.customer, company=self.company,
- current_transaction_amount=current_amount, loyalty_program=self.loyalty_program,
- expiry_date=self.posting_date, include_expired_entry=True)
- if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
- (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
+ lp_details = get_loyalty_program_details_with_points(
+ self.customer,
+ company=self.company,
+ current_transaction_amount=current_amount,
+ loyalty_program=self.loyalty_program,
+ expiry_date=self.posting_date,
+ include_expired_entry=True,
+ )
+ if (
+ lp_details
+ and getdate(lp_details.from_date) <= getdate(self.posting_date)
+ and (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date))
+ ):
collection_factor = lp_details.collection_factor if lp_details.collection_factor else 1.0
- points_earned = cint(eligible_amount/collection_factor)
+ points_earned = cint(eligible_amount / collection_factor)
- doc = frappe.get_doc({
- "doctype": "Loyalty Point Entry",
- "company": self.company,
- "loyalty_program": lp_details.loyalty_program,
- "loyalty_program_tier": lp_details.tier_name,
- "customer": self.customer,
- "invoice_type": self.doctype,
- "invoice": self.name,
- "loyalty_points": points_earned,
- "purchase_amount": eligible_amount,
- "expiry_date": add_days(self.posting_date, lp_details.expiry_duration),
- "posting_date": self.posting_date
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Loyalty Point Entry",
+ "company": self.company,
+ "loyalty_program": lp_details.loyalty_program,
+ "loyalty_program_tier": lp_details.tier_name,
+ "customer": self.customer,
+ "invoice_type": self.doctype,
+ "invoice": self.name,
+ "loyalty_points": points_earned,
+ "purchase_amount": eligible_amount,
+ "expiry_date": add_days(self.posting_date, lp_details.expiry_duration),
+ "posting_date": self.posting_date,
+ }
+ )
doc.flags.ignore_permissions = 1
doc.save()
self.set_loyalty_program_tier()
# valdite the redemption and then delete the loyalty points earned on cancel of the invoice
def delete_loyalty_point_entry(self):
- lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where invoice=%s",
- (self.name), as_dict=1)
+ lp_entry = frappe.db.sql(
+ "select name from `tabLoyalty Point Entry` where invoice=%s", (self.name), as_dict=1
+ )
- if not lp_entry: return
- against_lp_entry = frappe.db.sql('''select name, invoice from `tabLoyalty Point Entry`
- where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
+ if not lp_entry:
+ return
+ against_lp_entry = frappe.db.sql(
+ """select name, invoice from `tabLoyalty Point Entry`
+ where redeem_against=%s""",
+ (lp_entry[0].name),
+ as_dict=1,
+ )
if against_lp_entry:
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
frappe.throw(
- _('''{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}''')
- .format(self.doctype, self.doctype, invoice_list)
+ _(
+ """{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}"""
+ ).format(self.doctype, self.doctype, invoice_list)
)
else:
- frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
+ frappe.db.sql("""delete from `tabLoyalty Point Entry` where invoice=%s""", (self.name))
# Set loyalty program
self.set_loyalty_program_tier()
def set_loyalty_program_tier(self):
- lp_details = get_loyalty_program_details_with_points(self.customer, company=self.company,
- loyalty_program=self.loyalty_program, include_expired_entry=True)
+ lp_details = get_loyalty_program_details_with_points(
+ self.customer,
+ company=self.company,
+ loyalty_program=self.loyalty_program,
+ include_expired_entry=True,
+ )
frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
def get_returned_amount(self):
from frappe.query_builder.functions import Coalesce, Sum
+
doc = frappe.qb.DocType(self.doctype)
returned_amount = (
frappe.qb.from_(doc)
.select(Sum(doc.grand_total))
.where(
- (doc.docstatus == 1)
- & (doc.is_return == 1)
- & (Coalesce(doc.return_against, '') == self.name)
+ (doc.docstatus == 1) & (doc.is_return == 1) & (Coalesce(doc.return_against, "") == self.name)
)
).run()
@@ -1434,7 +1682,10 @@ class SalesInvoice(SellingController):
get_loyalty_point_entries,
get_redemption_details,
)
- loyalty_point_entries = get_loyalty_point_entries(self.customer, self.loyalty_program, self.company, self.posting_date)
+
+ loyalty_point_entries = get_loyalty_point_entries(
+ self.customer, self.loyalty_program, self.company, self.posting_date
+ )
redemption_details = get_redemption_details(self.customer, self.loyalty_program, self.company)
points_to_redeem = self.loyalty_points
@@ -1448,30 +1699,32 @@ class SalesInvoice(SellingController):
redeemed_points = points_to_redeem
else:
redeemed_points = available_points
- doc = frappe.get_doc({
- "doctype": "Loyalty Point Entry",
- "company": self.company,
- "loyalty_program": self.loyalty_program,
- "loyalty_program_tier": lp_entry.loyalty_program_tier,
- "customer": self.customer,
- "invoice_type": self.doctype,
- "invoice": self.name,
- "redeem_against": lp_entry.name,
- "loyalty_points": -1*redeemed_points,
- "purchase_amount": self.grand_total,
- "expiry_date": lp_entry.expiry_date,
- "posting_date": self.posting_date
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Loyalty Point Entry",
+ "company": self.company,
+ "loyalty_program": self.loyalty_program,
+ "loyalty_program_tier": lp_entry.loyalty_program_tier,
+ "customer": self.customer,
+ "invoice_type": self.doctype,
+ "invoice": self.name,
+ "redeem_against": lp_entry.name,
+ "loyalty_points": -1 * redeemed_points,
+ "purchase_amount": self.grand_total,
+ "expiry_date": lp_entry.expiry_date,
+ "posting_date": self.posting_date,
+ }
+ )
doc.flags.ignore_permissions = 1
doc.save()
points_to_redeem -= redeemed_points
- if points_to_redeem < 1: # since points_to_redeem is integer
+ if points_to_redeem < 1: # since points_to_redeem is integer
break
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
- if self.get('amended_from'):
- self.status = 'Draft'
+ if self.get("amended_from"):
+ self.status = "Draft"
return
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
@@ -1482,7 +1735,7 @@ class SalesInvoice(SellingController):
status = "Cancelled"
elif self.docstatus == 1:
if self.is_internal_transfer():
- self.status = 'Internal Transfer'
+ self.status = "Internal Transfer"
elif is_overdue(self, total):
self.status = "Overdue"
elif 0 < outstanding_amount < total:
@@ -1490,11 +1743,17 @@ class SalesInvoice(SellingController):
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid"
# Check if outstanding amount is 0 due to credit note issued against invoice
- elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
+ elif (
+ outstanding_amount <= 0
+ and self.is_return == 0
+ and frappe.db.get_value(
+ "Sales Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1}
+ )
+ ):
self.status = "Credit Note Issued"
elif self.is_return == 1:
self.status = "Return"
- elif outstanding_amount<=0:
+ elif outstanding_amount <= 0:
self.status = "Paid"
else:
self.status = "Submitted"
@@ -1510,34 +1769,29 @@ class SalesInvoice(SellingController):
self.status = "Draft"
if update:
- self.db_set('status', self.status, update_modified = update_modified)
+ self.db_set("status", self.status, update_modified=update_modified)
def get_total_in_party_account_currency(doc):
- total_fieldname = (
- "grand_total"
- if doc.disable_rounded_total
- else "rounded_total"
- )
+ total_fieldname = "grand_total" if doc.disable_rounded_total else "rounded_total"
if doc.party_account_currency != doc.currency:
total_fieldname = "base_" + total_fieldname
return flt(doc.get(total_fieldname), doc.precision(total_fieldname))
+
def is_overdue(doc, total):
outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
if outstanding_amount <= 0:
return
today = getdate()
- if doc.get('is_pos') or not doc.get('payment_schedule'):
+ if doc.get("is_pos") or not doc.get("payment_schedule"):
return getdate(doc.due_date) < today
# calculate payable amount till date
payment_amount_field = (
- "base_payment_amount"
- if doc.party_account_currency != doc.currency
- else "payment_amount"
+ "base_payment_amount" if doc.party_account_currency != doc.currency else "payment_amount"
)
payable_amount = sum(
@@ -1552,7 +1806,8 @@ def is_overdue(doc, total):
def get_discounting_status(sales_invoice):
status = None
- invoice_discounting_list = frappe.db.sql("""
+ invoice_discounting_list = frappe.db.sql(
+ """
select status
from `tabInvoice Discounting` id, `tabDiscounted Invoice` d
where
@@ -1560,7 +1815,9 @@ def get_discounting_status(sales_invoice):
and d.sales_invoice=%s
and id.docstatus=1
and status in ('Disbursed', 'Settled')
- """, sales_invoice)
+ """,
+ sales_invoice,
+ )
for d in invoice_discounting_list:
status = d[0]
@@ -1569,6 +1826,7 @@ def get_discounting_status(sales_invoice):
return status
+
def validate_inter_company_party(doctype, party, company, inter_company_reference):
if not party:
return
@@ -1597,10 +1855,19 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
frappe.throw(_("Invalid Company for Inter Company Transaction."))
elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party:
- companies = frappe.get_all("Allowed To Transact With", fields=["company"], filters={"parenttype": partytype, "parent": party})
+ companies = frappe.get_all(
+ "Allowed To Transact With",
+ fields=["company"],
+ filters={"parenttype": partytype, "parent": party},
+ )
companies = [d.company for d in companies]
if not company in companies:
- frappe.throw(_("{0} not allowed to transact with {1}. Please change the Company.").format(partytype, company))
+ frappe.throw(
+ _("{0} not allowed to transact with {1}. Please change the Company.").format(
+ partytype, company
+ )
+ )
+
def update_linked_doc(doctype, name, inter_company_reference):
@@ -1610,8 +1877,8 @@ def update_linked_doc(doctype, name, inter_company_reference):
ref_field = "inter_company_order_reference"
if inter_company_reference:
- frappe.db.set_value(doctype, inter_company_reference,\
- ref_field, name)
+ frappe.db.set_value(doctype, inter_company_reference, ref_field, name)
+
def unlink_inter_company_doc(doctype, name, inter_company_reference):
@@ -1626,44 +1893,54 @@ def unlink_inter_company_doc(doctype, name, inter_company_reference):
frappe.db.set_value(doctype, name, ref_field, "")
frappe.db.set_value(ref_doc, inter_company_reference, ref_field, "")
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
+
list_context = get_list_context(context)
- list_context.update({
- 'show_sidebar': True,
- 'show_search': True,
- 'no_breadcrumbs': True,
- 'title': _('Invoices'),
- })
+ list_context.update(
+ {
+ "show_sidebar": True,
+ "show_search": True,
+ "no_breadcrumbs": True,
+ "title": _("Invoices"),
+ }
+ )
return list_context
+
@frappe.whitelist()
def get_bank_cash_account(mode_of_payment, company):
- account = frappe.db.get_value("Mode of Payment Account",
- {"parent": mode_of_payment, "company": company}, "default_account")
+ account = frappe.db.get_value(
+ "Mode of Payment Account", {"parent": mode_of_payment, "company": company}, "default_account"
+ )
if not account:
- frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
- .format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
- return {
- "account": account
- }
+ frappe.throw(
+ _("Please set default Cash or Bank account in Mode of Payment {0}").format(
+ get_link_to_form("Mode of Payment", mode_of_payment)
+ ),
+ title=_("Missing Account"),
+ )
+ return {"account": account}
+
@frappe.whitelist()
def make_maintenance_schedule(source_name, target_doc=None):
- doclist = get_mapped_doc("Sales Invoice", source_name, {
- "Sales Invoice": {
- "doctype": "Maintenance Schedule",
- "validation": {
- "docstatus": ["=", 1]
- }
+ doclist = get_mapped_doc(
+ "Sales Invoice",
+ source_name,
+ {
+ "Sales Invoice": {"doctype": "Maintenance Schedule", "validation": {"docstatus": ["=", 1]}},
+ "Sales Invoice Item": {
+ "doctype": "Maintenance Schedule Item",
+ },
},
- "Sales Invoice Item": {
- "doctype": "Maintenance Schedule Item",
- },
- }, target_doc)
+ target_doc,
+ )
return doclist
+
@frappe.whitelist()
def make_delivery_note(source_name, target_doc=None):
def set_missing_values(source, target):
@@ -1678,83 +1955,104 @@ def make_delivery_note(source_name, target_doc=None):
target_doc.base_amount = target_doc.qty * flt(source_doc.base_rate)
target_doc.amount = target_doc.qty * flt(source_doc.rate)
- doclist = get_mapped_doc("Sales Invoice", source_name, {
- "Sales Invoice": {
- "doctype": "Delivery Note",
- "validation": {
- "docstatus": ["=", 1]
- }
- },
- "Sales Invoice Item": {
- "doctype": "Delivery Note Item",
- "field_map": {
- "name": "si_detail",
- "parent": "against_sales_invoice",
- "serial_no": "serial_no",
- "sales_order": "against_sales_order",
- "so_detail": "so_detail",
- "cost_center": "cost_center"
+ doclist = get_mapped_doc(
+ "Sales Invoice",
+ source_name,
+ {
+ "Sales Invoice": {"doctype": "Delivery Note", "validation": {"docstatus": ["=", 1]}},
+ "Sales Invoice Item": {
+ "doctype": "Delivery Note Item",
+ "field_map": {
+ "name": "si_detail",
+ "parent": "against_sales_invoice",
+ "serial_no": "serial_no",
+ "sales_order": "against_sales_order",
+ "so_detail": "so_detail",
+ "cost_center": "cost_center",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: doc.delivered_by_supplier != 1,
},
- "postprocess": update_item,
- "condition": lambda doc: doc.delivered_by_supplier!=1
- },
- "Sales Taxes and Charges": {
- "doctype": "Sales Taxes and Charges",
- "add_if_empty": True
- },
- "Sales Team": {
- "doctype": "Sales Team",
- "field_map": {
- "incentives": "incentives"
+ "Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
+ "Sales Team": {
+ "doctype": "Sales Team",
+ "field_map": {"incentives": "incentives"},
+ "add_if_empty": True,
},
- "add_if_empty": True
- }
- }, target_doc, set_missing_values)
+ },
+ target_doc,
+ set_missing_values,
+ )
- doclist.set_onload('ignore_price_list', True)
+ doclist.set_onload("ignore_price_list", True)
return doclist
+
@frappe.whitelist()
def make_sales_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
return make_return_doc("Sales Invoice", source_name, target_doc)
+
def set_account_for_mode_of_payment(self):
for data in self.payments:
if not data.account:
data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account")
+
def get_inter_company_details(doc, doctype):
if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"]:
- parties = frappe.db.get_all("Supplier", fields=["name"], filters={"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company})
+ parties = frappe.db.get_all(
+ "Supplier",
+ fields=["name"],
+ filters={"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company},
+ )
company = frappe.get_cached_value("Customer", doc.customer, "represents_company")
if not parties:
- frappe.throw(_('No Supplier found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
+ frappe.throw(
+ _("No Supplier found for Inter Company Transactions which represents company {0}").format(
+ frappe.bold(doc.company)
+ )
+ )
party = get_internal_party(parties, "Supplier", doc)
else:
- parties = frappe.db.get_all("Customer", fields=["name"], filters={"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company})
+ parties = frappe.db.get_all(
+ "Customer",
+ fields=["name"],
+ filters={"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company},
+ )
company = frappe.get_cached_value("Supplier", doc.supplier, "represents_company")
if not parties:
- frappe.throw(_('No Customer found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
+ frappe.throw(
+ _("No Customer found for Inter Company Transactions which represents company {0}").format(
+ frappe.bold(doc.company)
+ )
+ )
party = get_internal_party(parties, "Customer", doc)
- return {
- "party": party,
- "company": company
- }
+ return {"party": party, "company": company}
+
def get_internal_party(parties, link_doctype, doc):
if len(parties) == 1:
- party = parties[0].name
+ party = parties[0].name
else:
# If more than one Internal Supplier/Customer, get supplier/customer on basis of address
- if doc.get('company_address') or doc.get('shipping_address'):
- party = frappe.db.get_value("Dynamic Link", {"parent": doc.get('company_address') or doc.get('shipping_address'),
- "parenttype": "Address", "link_doctype": link_doctype}, "link_name")
+ if doc.get("company_address") or doc.get("shipping_address"):
+ party = frappe.db.get_value(
+ "Dynamic Link",
+ {
+ "parent": doc.get("company_address") or doc.get("shipping_address"),
+ "parenttype": "Address",
+ "link_doctype": link_doctype,
+ },
+ "link_name",
+ )
if not party:
party = parties[0].name
@@ -1763,11 +2061,18 @@ def get_internal_party(parties, link_doctype, doc):
return party
+
def validate_inter_company_transaction(doc, doctype):
details = get_inter_company_details(doc, doctype)
- price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
- valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
+ price_list = (
+ doc.selling_price_list
+ if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"]
+ else doc.buying_price_list
+ )
+ valid_price_list = frappe.db.get_value(
+ "Price List", {"name": price_list, "buying": 1, "selling": 1}
+ )
if not valid_price_list and not doc.is_internal_transfer():
frappe.throw(_("Selected Price List should have buying and selling fields checked."))
@@ -1777,28 +2082,32 @@ def validate_inter_company_transaction(doc, doctype):
frappe.throw(_("No {0} found for Inter Company Transactions.").format(partytype))
company = details.get("company")
- default_currency = frappe.get_cached_value('Company', company, "default_currency")
+ default_currency = frappe.get_cached_value("Company", company, "default_currency")
if default_currency != doc.currency:
- frappe.throw(_("Company currencies of both the companies should match for Inter Company Transactions."))
+ frappe.throw(
+ _("Company currencies of both the companies should match for Inter Company Transactions.")
+ )
return
+
@frappe.whitelist()
def make_inter_company_purchase_invoice(source_name, target_doc=None):
return make_inter_company_transaction("Sales Invoice", source_name, target_doc)
+
def make_inter_company_transaction(doctype, source_name, target_doc=None):
if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
- source_document_warehouse_field = 'target_warehouse'
- target_document_warehouse_field = 'from_warehouse'
+ source_document_warehouse_field = "target_warehouse"
+ target_document_warehouse_field = "from_warehouse"
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
- source_document_warehouse_field = 'from_warehouse'
- target_document_warehouse_field = 'target_warehouse'
+ source_document_warehouse_field = "from_warehouse"
+ target_document_warehouse_field = "target_warehouse"
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
@@ -1810,7 +2119,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
def update_details(source_doc, target_doc, source_parent):
target_doc.inter_company_invoice_reference = source_doc.name
if target_doc.doctype in ["Purchase Invoice", "Purchase Order"]:
- currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
+ currency = frappe.db.get_value("Supplier", details.get("party"), "default_currency")
target_doc.company = details.get("company")
target_doc.supplier = details.get("party")
target_doc.is_internal_supplier = 1
@@ -1818,130 +2127,176 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
target_doc.buying_price_list = source_doc.selling_price_list
# Invert Addresses
- update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
- update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
+ update_address(target_doc, "supplier_address", "address_display", source_doc.company_address)
+ update_address(
+ target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
+ )
if currency:
target_doc.currency = currency
- update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
- doctype=target_doc.doctype, party_address=target_doc.supplier_address,
- company_address=target_doc.shipping_address)
+ update_taxes(
+ target_doc,
+ party=target_doc.supplier,
+ party_type="Supplier",
+ company=target_doc.company,
+ doctype=target_doc.doctype,
+ party_address=target_doc.supplier_address,
+ company_address=target_doc.shipping_address,
+ )
else:
- currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
+ currency = frappe.db.get_value("Customer", details.get("party"), "default_currency")
target_doc.company = details.get("company")
target_doc.customer = details.get("party")
target_doc.selling_price_list = source_doc.buying_price_list
- update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
- update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
- update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
+ update_address(
+ target_doc, "company_address", "company_address_display", source_doc.supplier_address
+ )
+ update_address(
+ target_doc, "shipping_address_name", "shipping_address", source_doc.shipping_address
+ )
+ update_address(target_doc, "customer_address", "address_display", source_doc.shipping_address)
if currency:
target_doc.currency = currency
- update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
- doctype=target_doc.doctype, party_address=target_doc.customer_address,
- company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
+ update_taxes(
+ target_doc,
+ party=target_doc.customer,
+ party_type="Customer",
+ company=target_doc.company,
+ doctype=target_doc.doctype,
+ party_address=target_doc.customer_address,
+ company_address=target_doc.company_address,
+ shipping_address_name=target_doc.shipping_address_name,
+ )
item_field_map = {
"doctype": target_doctype + " Item",
- "field_no_map": [
- "income_account",
- "expense_account",
- "cost_center",
- "warehouse"
- ],
+ "field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],
"field_map": {
- 'rate': 'rate',
- }
+ "rate": "rate",
+ },
}
if doctype in ["Sales Invoice", "Sales Order"]:
- item_field_map["field_map"].update({
- "name": target_detail_field,
- })
+ item_field_map["field_map"].update(
+ {
+ "name": target_detail_field,
+ }
+ )
- if source_doc.get('update_stock'):
- item_field_map["field_map"].update({
- source_document_warehouse_field: target_document_warehouse_field,
- 'batch_no': 'batch_no',
- 'serial_no': 'serial_no'
- })
+ if source_doc.get("update_stock"):
+ item_field_map["field_map"].update(
+ {
+ source_document_warehouse_field: target_document_warehouse_field,
+ "batch_no": "batch_no",
+ "serial_no": "serial_no",
+ }
+ )
- doclist = get_mapped_doc(doctype, source_name, {
- doctype: {
- "doctype": target_doctype,
- "postprocess": update_details,
- "set_target_warehouse": "set_from_warehouse",
- "field_no_map": [
- "taxes_and_charges",
- "set_warehouse",
- "shipping_address"
- ]
+ doclist = get_mapped_doc(
+ doctype,
+ source_name,
+ {
+ doctype: {
+ "doctype": target_doctype,
+ "postprocess": update_details,
+ "set_target_warehouse": "set_from_warehouse",
+ "field_no_map": ["taxes_and_charges", "set_warehouse", "shipping_address"],
+ },
+ doctype + " Item": item_field_map,
},
- doctype +" Item": item_field_map
-
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
def set_purchase_references(doc):
# add internal PO or PR links if any
if doc.is_internal_transfer():
- if doc.doctype == 'Purchase Receipt':
+ if doc.doctype == "Purchase Receipt":
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
if so_item_map:
- pd_item_map, parent_child_map, warehouse_map = \
- get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
+ pd_item_map, parent_child_map, warehouse_map = get_pd_details(
+ "Purchase Order Item", so_item_map, "sales_order_item"
+ )
update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map)
- elif doc.doctype == 'Purchase Invoice':
+ elif doc.doctype == "Purchase Invoice":
dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference)
# First check for Purchase receipt
if list(dn_item_map.values()):
- pd_item_map, parent_child_map, warehouse_map = \
- get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item')
+ pd_item_map, parent_child_map, warehouse_map = get_pd_details(
+ "Purchase Receipt Item", dn_item_map, "delivery_note_item"
+ )
- update_pi_items(doc, 'pr_detail', 'purchase_receipt',
- dn_item_map, pd_item_map, parent_child_map, warehouse_map)
+ update_pi_items(
+ doc,
+ "pr_detail",
+ "purchase_receipt",
+ dn_item_map,
+ pd_item_map,
+ parent_child_map,
+ warehouse_map,
+ )
if list(so_item_map.values()):
- pd_item_map, parent_child_map, warehouse_map = \
- get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
+ pd_item_map, parent_child_map, warehouse_map = get_pd_details(
+ "Purchase Order Item", so_item_map, "sales_order_item"
+ )
- update_pi_items(doc, 'po_detail', 'purchase_order',
- so_item_map, pd_item_map, parent_child_map, warehouse_map)
+ update_pi_items(
+ doc, "po_detail", "purchase_order", so_item_map, pd_item_map, parent_child_map, warehouse_map
+ )
-def update_pi_items(doc, detail_field, parent_field, sales_item_map,
- purchase_item_map, parent_child_map, warehouse_map):
- for item in doc.get('items'):
+
+def update_pi_items(
+ doc,
+ detail_field,
+ parent_field,
+ sales_item_map,
+ purchase_item_map,
+ parent_child_map,
+ warehouse_map,
+):
+ for item in doc.get("items"):
item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item)))
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
if doc.update_stock:
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
+
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
- for item in doc.get('items'):
+ for item in doc.get("items"):
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
+
def get_delivery_note_details(internal_reference):
- si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
- filters={'parent': internal_reference})
+ si_item_details = frappe.get_all(
+ "Delivery Note Item", fields=["name", "so_detail"], filters={"parent": internal_reference}
+ )
return {d.name: d.so_detail for d in si_item_details if d.so_detail}
+
def get_sales_invoice_details(internal_reference):
dn_item_map = {}
so_item_map = {}
- si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail',
- 'dn_detail'], filters={'parent': internal_reference})
+ si_item_details = frappe.get_all(
+ "Sales Invoice Item",
+ fields=["name", "so_detail", "dn_detail"],
+ filters={"parent": internal_reference},
+ )
for d in si_item_details:
if d.dn_detail:
@@ -1951,13 +2306,17 @@ def get_sales_invoice_details(internal_reference):
return dn_item_map, so_item_map
+
def get_pd_details(doctype, sd_detail_map, sd_detail_field):
pd_item_map = {}
accepted_warehouse_map = {}
parent_child_map = {}
- pd_item_details = frappe.get_all(doctype,
- fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))})
+ pd_item_details = frappe.get_all(
+ doctype,
+ fields=[sd_detail_field, "name", "warehouse", "parent"],
+ filters={sd_detail_field: ("in", list(sd_detail_map.values()))},
+ )
for d in pd_item_details:
pd_item_map.setdefault(d.get(sd_detail_field), d.name)
@@ -1966,16 +2325,33 @@ def get_pd_details(doctype, sd_detail_map, sd_detail_field):
return pd_item_map, parent_child_map, accepted_warehouse_map
-def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None,
- company_address=None, shipping_address_name=None, master_doctype=None):
+
+def update_taxes(
+ doc,
+ party=None,
+ party_type=None,
+ company=None,
+ doctype=None,
+ party_address=None,
+ company_address=None,
+ shipping_address_name=None,
+ master_doctype=None,
+):
# Update Party Details
- party_details = get_party_details(party=party, party_type=party_type, company=company,
- doctype=doctype, party_address=party_address, company_address=company_address,
- shipping_address=shipping_address_name)
+ party_details = get_party_details(
+ party=party,
+ party_type=party_type,
+ company=company,
+ doctype=doctype,
+ party_address=party_address,
+ company_address=company_address,
+ shipping_address=shipping_address_name,
+ )
# Update taxes and charges if any
- doc.taxes_and_charges = party_details.get('taxes_and_charges')
- doc.set('taxes', party_details.get('taxes'))
+ doc.taxes_and_charges = party_details.get("taxes_and_charges")
+ doc.set("taxes", party_details.get("taxes"))
+
def update_address(doc, address_field, address_display_field, address_name):
doc.set(address_field, address_name)
@@ -1986,53 +2362,61 @@ def update_address(doc, address_field, address_display_field, address_name):
doc.set(address_display_field, get_address_display(doc.get(address_field)))
+
@frappe.whitelist()
def get_loyalty_programs(customer):
- ''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
+ """sets applicable loyalty program to the customer or returns a list of applicable programs"""
from erpnext.selling.doctype.customer.customer import get_loyalty_programs
- customer = frappe.get_doc('Customer', customer)
- if customer.loyalty_program: return [customer.loyalty_program]
+ customer = frappe.get_doc("Customer", customer)
+ if customer.loyalty_program:
+ return [customer.loyalty_program]
lp_details = get_loyalty_programs(customer)
if len(lp_details) == 1:
- frappe.db.set(customer, 'loyalty_program', lp_details[0])
+ frappe.db.set(customer, "loyalty_program", lp_details[0])
return lp_details
else:
return lp_details
+
def on_doctype_update():
frappe.db.add_index("Sales Invoice", ["customer", "is_return", "return_against"])
+
@frappe.whitelist()
def create_invoice_discounting(source_name, target_doc=None):
invoice = frappe.get_doc("Sales Invoice", source_name)
invoice_discounting = frappe.new_doc("Invoice Discounting")
invoice_discounting.company = invoice.company
- invoice_discounting.append("invoices", {
- "sales_invoice": source_name,
- "customer": invoice.customer,
- "posting_date": invoice.posting_date,
- "outstanding_amount": invoice.outstanding_amount
- })
+ invoice_discounting.append(
+ "invoices",
+ {
+ "sales_invoice": source_name,
+ "customer": invoice.customer,
+ "posting_date": invoice.posting_date,
+ "outstanding_amount": invoice.outstanding_amount,
+ },
+ )
return invoice_discounting
+
def update_multi_mode_option(doc, pos_profile):
def append_payment(payment_mode):
- payment = doc.append('payments', {})
+ payment = doc.append("payments", {})
payment.default = payment_mode.default
payment.mode_of_payment = payment_mode.mop
payment.account = payment_mode.default_account
payment.type = payment_mode.type
- doc.set('payments', [])
+ doc.set("payments", [])
invalid_modes = []
- mode_of_payments = [d.mode_of_payment for d in pos_profile.get('payments')]
+ mode_of_payments = [d.mode_of_payment for d in pos_profile.get("payments")]
mode_of_payments_info = get_mode_of_payments_info(mode_of_payments, doc.company)
- for row in pos_profile.get('payments'):
+ for row in pos_profile.get("payments"):
payment_mode = mode_of_payments_info.get(row.mode_of_payment)
if not payment_mode:
invalid_modes.append(get_link_to_form("Mode of Payment", row.mode_of_payment))
@@ -2048,12 +2432,17 @@ def update_multi_mode_option(doc, pos_profile):
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
+
def get_all_mode_of_payments(doc):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select mpa.default_account, mpa.parent, mp.type as type
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
- {'company': doc.company}, as_dict=1)
+ {"company": doc.company},
+ as_dict=1,
+ )
+
def get_mode_of_payments_info(mode_of_payments, company):
data = frappe.db.sql(
@@ -2069,16 +2458,24 @@ def get_mode_of_payments_info(mode_of_payments, company):
mp.name in %s
group by
mp.name
- """, (company, mode_of_payments), as_dict=1)
+ """,
+ (company, mode_of_payments),
+ as_dict=1,
+ )
+
+ return {row.get("mop"): row for row in data}
- return {row.get('mop'): row for row in data}
def get_mode_of_payment_info(mode_of_payment, company):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select mpa.default_account, mpa.parent, mp.type as type
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
- (company, mode_of_payment), as_dict=1)
+ (company, mode_of_payment),
+ as_dict=1,
+ )
+
@frappe.whitelist()
def create_dunning(source_name, target_doc=None):
@@ -2088,41 +2485,58 @@ def create_dunning(source_name, target_doc=None):
calculate_interest_and_amount,
get_dunning_letter_text,
)
+
def set_missing_values(source, target):
target.sales_invoice = source_name
target.outstanding_amount = source.outstanding_amount
overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days
target.overdue_days = overdue_days
- if frappe.db.exists('Dunning Type', {'start_day': [
- '<', overdue_days], 'end_day': ['>=', overdue_days]}):
- dunning_type = frappe.get_doc('Dunning Type', {'start_day': [
- '<', overdue_days], 'end_day': ['>=', overdue_days]})
+ if frappe.db.exists(
+ "Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
+ ):
+ dunning_type = frappe.get_doc(
+ "Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
+ )
target.dunning_type = dunning_type.name
target.rate_of_interest = dunning_type.rate_of_interest
target.dunning_fee = dunning_type.dunning_fee
- letter_text = get_dunning_letter_text(dunning_type = dunning_type.name, doc = target.as_dict())
+ letter_text = get_dunning_letter_text(dunning_type=dunning_type.name, doc=target.as_dict())
if letter_text:
- target.body_text = letter_text.get('body_text')
- target.closing_text = letter_text.get('closing_text')
- target.language = letter_text.get('language')
- amounts = calculate_interest_and_amount(target.posting_date, target.outstanding_amount,
- target.rate_of_interest, target.dunning_fee, target.overdue_days)
- target.interest_amount = amounts.get('interest_amount')
- target.dunning_amount = amounts.get('dunning_amount')
- target.grand_total = amounts.get('grand_total')
+ target.body_text = letter_text.get("body_text")
+ target.closing_text = letter_text.get("closing_text")
+ target.language = letter_text.get("language")
+ amounts = calculate_interest_and_amount(
+ target.posting_date,
+ target.outstanding_amount,
+ target.rate_of_interest,
+ target.dunning_fee,
+ target.overdue_days,
+ )
+ target.interest_amount = amounts.get("interest_amount")
+ target.dunning_amount = amounts.get("dunning_amount")
+ target.grand_total = amounts.get("grand_total")
- doclist = get_mapped_doc("Sales Invoice", source_name, {
- "Sales Invoice": {
- "doctype": "Dunning",
- }
- }, target_doc, set_missing_values)
+ doclist = get_mapped_doc(
+ "Sales Invoice",
+ source_name,
+ {
+ "Sales Invoice": {
+ "doctype": "Dunning",
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
def check_if_return_invoice_linked_with_payment_entry(self):
# If a Return invoice is linked with payment entry along with other invoices,
# the cancellation of the Return causes allocated amount to be greater than paid
- if not frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
+ if not frappe.db.get_single_value(
+ "Accounts Settings", "unlink_payment_on_cancellation_of_invoice"
+ ):
return
payment_entries = []
@@ -2131,7 +2545,8 @@ def check_if_return_invoice_linked_with_payment_entry(self):
else:
invoice = self.name
- payment_entries = frappe.db.sql_list("""
+ payment_entries = frappe.db.sql_list(
+ """
SELECT
t1.name
FROM
@@ -2141,7 +2556,9 @@ def check_if_return_invoice_linked_with_payment_entry(self):
and t1.docstatus = 1
and t2.reference_name = %s
and t2.allocated_amount < 0
- """, invoice)
+ """,
+ invoice,
+ )
links_to_pe = []
if payment_entries:
@@ -2150,7 +2567,9 @@ def check_if_return_invoice_linked_with_payment_entry(self):
if len(payment_entry.references) > 1:
links_to_pe.append(payment_entry.name)
if links_to_pe:
- payment_entries_link = [get_link_to_form('Payment Entry', name, label=name) for name in links_to_pe]
+ payment_entries_link = [
+ get_link_to_form("Payment Entry", name, label=name) for name in links_to_pe
+ ]
message = _("Please cancel and amend the Payment Entry")
message += " " + ", ".join(payment_entries_link) + " "
message += _("to unallocate the amount of this Return Invoice before cancelling it.")
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
index 5cdc8dae25..b83d6a575e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
@@ -3,34 +3,29 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'sales_invoice',
- 'non_standard_fieldnames': {
- 'Delivery Note': 'against_sales_invoice',
- 'Journal Entry': 'reference_name',
- 'Payment Entry': 'reference_name',
- 'Payment Request': 'reference_name',
- 'Sales Invoice': 'return_against',
- 'Auto Repeat': 'reference_document',
+ "fieldname": "sales_invoice",
+ "non_standard_fieldnames": {
+ "Delivery Note": "against_sales_invoice",
+ "Journal Entry": "reference_name",
+ "Payment Entry": "reference_name",
+ "Payment Request": "reference_name",
+ "Sales Invoice": "return_against",
+ "Auto Repeat": "reference_document",
},
- 'internal_links': {
- 'Sales Order': ['items', 'sales_order']
- },
- 'transactions': [
+ "internal_links": {"Sales Order": ["items", "sales_order"]},
+ "transactions": [
{
- 'label': _('Payment'),
- 'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting', 'Dunning']
+ "label": _("Payment"),
+ "items": [
+ "Payment Entry",
+ "Payment Request",
+ "Journal Entry",
+ "Invoice Discounting",
+ "Dunning",
+ ],
},
- {
- 'label': _('Reference'),
- 'items': ['Timesheet', 'Delivery Note', 'Sales Order']
- },
- {
- 'label': _('Returns'),
- 'items': ['Sales Invoice']
- },
- {
- 'label': _('Subscription'),
- 'items': ['Auto Repeat']
- },
- ]
+ {"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]},
+ {"label": _("Returns"), "items": ["Sales Invoice"]},
+ {"label": _("Subscription"), "items": ["Auto Repeat"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 62c3508ab0..808b766fce 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -59,23 +59,25 @@ class TestSalesInvoice(unittest.TestCase):
w2 = frappe.get_doc(w.doctype, w.name)
import time
+
time.sleep(1)
w.save()
import time
+
time.sleep(1)
self.assertRaises(frappe.TimestampMismatchError, w2.save)
def test_sales_invoice_change_naming_series(self):
si = frappe.copy_doc(test_records[2])
si.insert()
- si.naming_series = 'TEST-'
+ si.naming_series = "TEST-"
self.assertRaises(frappe.CannotChangeConstantError, si.save)
si = frappe.copy_doc(test_records[1])
si.insert()
- si.naming_series = 'TEST-'
+ si.naming_series = "TEST-"
self.assertRaises(frappe.CannotChangeConstantError, si.save)
@@ -91,15 +93,21 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
expected_values = {
- "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
- "base_price_list_rate", "base_rate", "base_amount"],
+ "keys": [
+ "price_list_rate",
+ "discount_percentage",
+ "rate",
+ "amount",
+ "base_price_list_rate",
+ "base_rate",
+ "base_amount",
+ ],
"_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500],
"_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750],
}
# check if children are saved
- self.assertEqual(len(si.get("items")),
- len(expected_values)-1)
+ self.assertEqual(len(si.get("items")), len(expected_values) - 1)
# check if item values are calculated
for d in si.get("items"):
@@ -120,7 +128,7 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account S&H Education Cess - _TC": [1.4, 1619.2],
"_Test Account CST - _TC": [32.38, 1651.58],
"_Test Account VAT - _TC": [156.25, 1807.83],
- "_Test Account Discount - _TC": [-180.78, 1627.05]
+ "_Test Account Discount - _TC": [-180.78, 1627.05],
}
for d in si.get("taxes"):
@@ -150,7 +158,7 @@ class TestSalesInvoice(unittest.TestCase):
pe.submit()
unlink_payment_on_cancel_of_invoice(0)
- si = frappe.get_doc('Sales Invoice', si.name)
+ si = frappe.get_doc("Sales Invoice", si.name)
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
@@ -161,25 +169,30 @@ class TestSalesInvoice(unittest.TestCase):
si2 = create_sales_invoice(rate=300)
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
-
pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC")
- pe.append('references', {
- 'reference_doctype': 'Sales Invoice',
- 'reference_name': si2.name,
- 'total_amount': si2.grand_total,
- 'outstanding_amount': si2.outstanding_amount,
- 'allocated_amount': si2.outstanding_amount
- })
+ pe.append(
+ "references",
+ {
+ "reference_doctype": "Sales Invoice",
+ "reference_name": si2.name,
+ "total_amount": si2.grand_total,
+ "outstanding_amount": si2.outstanding_amount,
+ "allocated_amount": si2.outstanding_amount,
+ },
+ )
- pe.append('references', {
- 'reference_doctype': 'Sales Invoice',
- 'reference_name': si3.name,
- 'total_amount': si3.grand_total,
- 'outstanding_amount': si3.outstanding_amount,
- 'allocated_amount': si3.outstanding_amount
- })
+ pe.append(
+ "references",
+ {
+ "reference_doctype": "Sales Invoice",
+ "reference_name": si3.name,
+ "total_amount": si3.grand_total,
+ "outstanding_amount": si3.outstanding_amount,
+ "allocated_amount": si3.outstanding_amount,
+ },
+ )
- pe.reference_no = 'Test001'
+ pe.reference_no = "Test001"
pe.reference_date = nowdate()
pe.save()
pe.submit()
@@ -190,7 +203,6 @@ class TestSalesInvoice(unittest.TestCase):
si1.load_from_db()
self.assertRaises(PaymentEntryUnlinkError, si1.cancel)
-
def test_sales_invoice_calculation_export_currency(self):
si = frappe.copy_doc(test_records[2])
si.currency = "USD"
@@ -205,14 +217,21 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
expected_values = {
- "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
- "base_price_list_rate", "base_rate", "base_amount"],
+ "keys": [
+ "price_list_rate",
+ "discount_percentage",
+ "rate",
+ "amount",
+ "base_price_list_rate",
+ "base_rate",
+ "base_amount",
+ ],
"_Test Item Home Desktop 100": [1, 0, 1, 10, 50, 50, 500],
"_Test Item Home Desktop 200": [3, 0, 3, 15, 150, 150, 750],
}
# check if children are saved
- self.assertEqual(len(si.get("items")), len(expected_values)-1)
+ self.assertEqual(len(si.get("items")), len(expected_values) - 1)
# check if item values are calculated
for d in si.get("items"):
@@ -235,7 +254,7 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account S&H Education Cess - _TC": [1.5, 1619.5, 0.03, 32.39],
"_Test Account CST - _TC": [32.5, 1652, 0.65, 33.04],
"_Test Account VAT - _TC": [156.5, 1808.5, 3.13, 36.17],
- "_Test Account Discount - _TC": [-181.0, 1627.5, -3.62, 32.55]
+ "_Test Account Discount - _TC": [-181.0, 1627.5, -3.62, 32.55],
}
for d in si.get("taxes"):
@@ -247,22 +266,28 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_invoice_with_discount_and_inclusive_tax(self):
si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": "_Test Account Service Tax - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Service Tax",
- "rate": 14,
- 'included_in_print_rate': 1
- })
- si.append("taxes", {
- "charge_type": "On Item Quantity",
- "account_head": "_Test Account Education Cess - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "description": "CESS",
- "rate": 5,
- 'included_in_print_rate': 1
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 14,
+ "included_in_print_rate": 1,
+ },
+ )
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Item Quantity",
+ "account_head": "_Test Account Education Cess - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "CESS",
+ "rate": 5,
+ "included_in_print_rate": 1,
+ },
+ )
si.insert()
# with inclusive tax
@@ -274,7 +299,7 @@ class TestSalesInvoice(unittest.TestCase):
# additional discount
si.discount_amount = 100
- si.apply_discount_on = 'Net Total'
+ si.apply_discount_on = "Net Total"
si.payment_schedule = []
si.save()
@@ -287,7 +312,7 @@ class TestSalesInvoice(unittest.TestCase):
# additional discount on grand total
si.discount_amount = 100
- si.apply_discount_on = 'Grand Total'
+ si.apply_discount_on = "Grand Total"
si.payment_schedule = []
si.save()
@@ -299,14 +324,17 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_invoice_discount_amount(self):
si = frappe.copy_doc(test_records[3])
si.discount_amount = 104.94
- si.append("taxes", {
- "charge_type": "On Previous Row Amount",
- "account_head": "_Test Account Service Tax - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Service Tax",
- "rate": 10,
- "row_id": 8,
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Previous Row Amount",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 10,
+ "row_id": 8,
+ },
+ )
si.insert()
expected_values = [
@@ -322,7 +350,7 @@ class TestSalesInvoice(unittest.TestCase):
"net_rate": 46.54,
"net_amount": 465.37,
"base_net_rate": 46.54,
- "base_net_amount": 465.37
+ "base_net_amount": 465.37,
},
{
"item_code": "_Test Item Home Desktop 200",
@@ -336,12 +364,12 @@ class TestSalesInvoice(unittest.TestCase):
"net_rate": 139.62,
"net_amount": 698.08,
"base_net_rate": 139.62,
- "base_net_amount": 698.08
- }
+ "base_net_amount": 698.08,
+ },
]
# check if children are saved
- self.assertEqual(len(si.get("items")), len(expected_values))
+ self.assertEqual(len(si.get("items")), len(expected_values))
# check if item values are calculated
for i, d in enumerate(si.get("items")):
@@ -363,7 +391,7 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account Customs Duty - _TC": [125, 116.35, 1585.40],
"_Test Account Shipping Charges - _TC": [100, 100, 1685.40],
"_Test Account Discount - _TC": [-180.33, -168.54, 1516.86],
- "_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.01]
+ "_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.01],
}
for d in si.get("taxes"):
@@ -378,38 +406,48 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
si = frappe.copy_doc(test_records[3])
si.discount_amount = 104.94
- si.append("taxes", {
- "doctype": "Sales Taxes and Charges",
- "charge_type": "On Previous Row Amount",
- "account_head": "_Test Account Service Tax - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Service Tax",
- "rate": 10,
- "row_id": 8
- })
+ si.append(
+ "taxes",
+ {
+ "doctype": "Sales Taxes and Charges",
+ "charge_type": "On Previous Row Amount",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 10,
+ "row_id": 8,
+ },
+ )
si.insert()
si.submit()
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- [si.debit_to, 1500, 0.0],
- [test_records[3]["items"][0]["income_account"], 0.0, 1163.45],
- [test_records[3]["taxes"][0]["account_head"], 0.0, 130.31],
- [test_records[3]["taxes"][1]["account_head"], 0.0, 2.61],
- [test_records[3]["taxes"][2]["account_head"], 0.0, 1.30],
- [test_records[3]["taxes"][3]["account_head"], 0.0, 25.95],
- [test_records[3]["taxes"][4]["account_head"], 0.0, 145.43],
- [test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
- [test_records[3]["taxes"][6]["account_head"], 0.0, 100],
- [test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
- ["_Test Account Service Tax - _TC", 16.85, 0.0],
- ["Round Off - _TC", 0.01, 0.0]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ [si.debit_to, 1500, 0.0],
+ [test_records[3]["items"][0]["income_account"], 0.0, 1163.45],
+ [test_records[3]["taxes"][0]["account_head"], 0.0, 130.31],
+ [test_records[3]["taxes"][1]["account_head"], 0.0, 2.61],
+ [test_records[3]["taxes"][2]["account_head"], 0.0, 1.30],
+ [test_records[3]["taxes"][3]["account_head"], 0.0, 25.95],
+ [test_records[3]["taxes"][4]["account_head"], 0.0, 145.43],
+ [test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
+ [test_records[3]["taxes"][6]["account_head"], 0.0, 100],
+ [test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
+ ["_Test Account Service Tax - _TC", 16.85, 0.0],
+ ["Round Off - _TC", 0.01, 0.0],
+ ]
+ )
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
@@ -419,8 +457,11 @@ class TestSalesInvoice(unittest.TestCase):
# cancel
si.cancel()
- gle = frappe.db.sql("""select * from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
+ gle = frappe.db.sql(
+ """select * from `tabGL Entry`
+ where voucher_type='Sales Invoice' and voucher_no=%s""",
+ si.name,
+ )
self.assertTrue(gle)
@@ -432,14 +473,17 @@ class TestSalesInvoice(unittest.TestCase):
item_row_copy.qty = qty
si.append("items", item_row_copy)
- si.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 19
- })
+ si.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 19,
+ },
+ )
si.insert()
self.assertEqual(si.net_total, 4600)
@@ -454,10 +498,10 @@ class TestSalesInvoice(unittest.TestCase):
item_row = si.get("items")[0]
add_items = [
- (54, '_Test Account Excise Duty @ 12 - _TC'),
- (288, '_Test Account Excise Duty @ 15 - _TC'),
- (144, '_Test Account Excise Duty @ 20 - _TC'),
- (430, '_Test Item Tax Template 1 - _TC')
+ (54, "_Test Account Excise Duty @ 12 - _TC"),
+ (288, "_Test Account Excise Duty @ 15 - _TC"),
+ (144, "_Test Account Excise Duty @ 20 - _TC"),
+ (430, "_Test Item Tax Template 1 - _TC"),
]
for qty, item_tax_template in add_items:
item_row_copy = copy.deepcopy(item_row)
@@ -465,30 +509,39 @@ class TestSalesInvoice(unittest.TestCase):
item_row_copy.item_tax_template = item_tax_template
si.append("items", item_row_copy)
- si.append("taxes", {
- "account_head": "_Test Account Excise Duty - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Excise Duty",
- "doctype": "Sales Taxes and Charges",
- "rate": 11
- })
- si.append("taxes", {
- "account_head": "_Test Account Education Cess - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Education Cess",
- "doctype": "Sales Taxes and Charges",
- "rate": 0
- })
- si.append("taxes", {
- "account_head": "_Test Account S&H Education Cess - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "S&H Education Cess",
- "doctype": "Sales Taxes and Charges",
- "rate": 3
- })
+ si.append(
+ "taxes",
+ {
+ "account_head": "_Test Account Excise Duty - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Excise Duty",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 11,
+ },
+ )
+ si.append(
+ "taxes",
+ {
+ "account_head": "_Test Account Education Cess - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Education Cess",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 0,
+ },
+ )
+ si.append(
+ "taxes",
+ {
+ "account_head": "_Test Account S&H Education Cess - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "S&H Education Cess",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 3,
+ },
+ )
si.insert()
self.assertEqual(si.net_total, 4600)
@@ -518,14 +571,17 @@ class TestSalesInvoice(unittest.TestCase):
si.apply_discount_on = "Net Total"
si.discount_amount = 75.0
- si.append("taxes", {
- "account_head": "_Test Account VAT - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "VAT",
- "doctype": "Sales Taxes and Charges",
- "rate": 24
- })
+ si.append(
+ "taxes",
+ {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 24,
+ },
+ )
si.insert()
self.assertEqual(si.total, 975)
@@ -539,7 +595,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_inclusive_rate_validations(self):
si = frappe.copy_doc(test_records[2])
for i, tax in enumerate(si.get("taxes")):
- tax.idx = i+1
+ tax.idx = i + 1
si.get("items")[0].price_list_rate = 62.5
si.get("items")[0].price_list_rate = 191
@@ -559,14 +615,43 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
expected_values = {
- "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
- "base_price_list_rate", "base_rate", "base_amount", "net_rate", "net_amount"],
- "_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 62.5, 62.5, 625.0, 50, 499.97600115194473],
- "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 190.66, 190.66, 953.3, 150, 749.9968530500239],
+ "keys": [
+ "price_list_rate",
+ "discount_percentage",
+ "rate",
+ "amount",
+ "base_price_list_rate",
+ "base_rate",
+ "base_amount",
+ "net_rate",
+ "net_amount",
+ ],
+ "_Test Item Home Desktop 100": [
+ 62.5,
+ 0,
+ 62.5,
+ 625.0,
+ 62.5,
+ 62.5,
+ 625.0,
+ 50,
+ 499.97600115194473,
+ ],
+ "_Test Item Home Desktop 200": [
+ 190.66,
+ 0,
+ 190.66,
+ 953.3,
+ 190.66,
+ 190.66,
+ 953.3,
+ 150,
+ 749.9968530500239,
+ ],
}
# check if children are saved
- self.assertEqual(len(si.get("items")), len(expected_values)-1)
+ self.assertEqual(len(si.get("items")), len(expected_values) - 1)
# check if item values are calculated
for d in si.get("items"):
@@ -587,7 +672,7 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account VAT - _TC": [156.25, 1578.30],
"_Test Account Customs Duty - _TC": [125, 1703.30],
"_Test Account Shipping Charges - _TC": [100, 1803.30],
- "_Test Account Discount - _TC": [-180.33, 1622.97]
+ "_Test Account Discount - _TC": [-180.33, 1622.97],
}
for d in si.get("taxes"):
@@ -625,7 +710,7 @@ class TestSalesInvoice(unittest.TestCase):
"net_rate": 40,
"net_amount": 399.9808009215558,
"base_net_rate": 2000,
- "base_net_amount": 19999
+ "base_net_amount": 19999,
},
{
"item_code": "_Test Item Home Desktop 200",
@@ -639,8 +724,8 @@ class TestSalesInvoice(unittest.TestCase):
"net_rate": 118.01,
"net_amount": 590.0531205155963,
"base_net_rate": 5900.5,
- "base_net_amount": 29502.5
- }
+ "base_net_amount": 29502.5,
+ },
]
# check if children are saved
@@ -665,8 +750,8 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account CST - _TC": [1104, 56312.0, 22.08, 1126.24],
"_Test Account VAT - _TC": [6187.5, 62499.5, 123.75, 1249.99],
"_Test Account Customs Duty - _TC": [4950.0, 67449.5, 99.0, 1348.99],
- "_Test Account Shipping Charges - _TC": [ 100, 67549.5, 2, 1350.99],
- "_Test Account Discount - _TC": [ -6755, 60794.5, -135.10, 1215.89]
+ "_Test Account Shipping Charges - _TC": [100, 67549.5, 2, 1350.99],
+ "_Test Account Discount - _TC": [-6755, 60794.5, -135.10, 1215.89],
}
for d in si.get("taxes"):
@@ -678,7 +763,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.rounding_adjustment, 0.01)
self.assertEqual(si.base_rounding_adjustment, 0.50)
-
def test_outstanding(self):
w = self.make()
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
@@ -698,11 +782,11 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), 162.0)
- link_data = get_dynamic_link_map().get('Sales Invoice', [])
+ link_data = get_dynamic_link_map().get("Sales Invoice", [])
link_doctypes = [d.parent for d in link_data]
# test case for dynamic link order
- self.assertTrue(link_doctypes.index('GL Entry') > link_doctypes.index('Journal Entry Account'))
+ self.assertTrue(link_doctypes.index("GL Entry") > link_doctypes.index("Journal Entry Account"))
jv.cancel()
self.assertEqual(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), 562.0)
@@ -712,18 +796,25 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
si.submit()
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- [si.debit_to, 630.0, 0.0],
- [test_records[1]["items"][0]["income_account"], 0.0, 500.0],
- [test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
- [test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ [si.debit_to, 630.0, 0.0],
+ [test_records[1]["items"][0]["income_account"], 0.0, 500.0],
+ [test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
+ [test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
+ ]
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
@@ -733,25 +824,49 @@ class TestSalesInvoice(unittest.TestCase):
# cancel
si.cancel()
- gle = frappe.db.sql("""select * from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
+ gle = frappe.db.sql(
+ """select * from `tabGL Entry`
+ where voucher_type='Sales Invoice' and voucher_no=%s""",
+ si.name,
+ )
self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
- make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
- expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
+ make_pos_profile(
+ company="_Test Company with perpetual inventory",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ write_off_account="_Test Write Off - TCP1",
+ )
- pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ item_code="_Test FG Item",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ )
- pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
- income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
+ pos = create_sales_invoice(
+ company="_Test Company with perpetual inventory",
+ debit_to="Debtors - TCP1",
+ item_code="_Test FG Item",
+ warehouse="Stores - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ do_not_save=True,
+ )
pos.is_pos = 1
pos.update_stock = 1
- pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
- pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50})
+ pos.append(
+ "payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - TCP1", "amount": 50}
+ )
+ pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - TCP1", "amount": 50})
taxes = get_taxes_and_charges()
pos.taxes = []
@@ -771,20 +886,19 @@ class TestSalesInvoice(unittest.TestCase):
pos_profile = make_pos_profile()
pos_profile.payments = []
- pos_profile.append('payments', {
- 'default': 1,
- 'mode_of_payment': 'Cash'
- })
+ pos_profile.append("payments", {"default": 1, "mode_of_payment": "Cash"})
pos_profile.save()
- pos = create_sales_invoice(qty = 10, do_not_save=True)
+ pos = create_sales_invoice(qty=10, do_not_save=True)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
- pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
- pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
+ pos.append(
+ "payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
+ )
+ pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500})
pos.insert()
pos.submit()
@@ -793,25 +907,43 @@ class TestSalesInvoice(unittest.TestCase):
pos_return.insert()
pos_return.submit()
- self.assertEqual(pos_return.get('payments')[0].amount, -1000)
+ self.assertEqual(pos_return.get("payments")[0].amount, -1000)
def test_pos_change_amount(self):
- make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
- expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
+ make_pos_profile(
+ company="_Test Company with perpetual inventory",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ write_off_account="_Test Write Off - TCP1",
+ )
- make_purchase_receipt(company= "_Test Company with perpetual inventory",
- item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
+ make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ item_code="_Test FG Item",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ )
- pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
- debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
- income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
- cost_center = "Main - TCP1", do_not_save=True)
+ pos = create_sales_invoice(
+ company="_Test Company with perpetual inventory",
+ debit_to="Debtors - TCP1",
+ item_code="_Test FG Item",
+ warehouse="Stores - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ do_not_save=True,
+ )
pos.is_pos = 1
pos.update_stock = 1
- pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
- pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
+ pos.append(
+ "payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - TCP1", "amount": 50}
+ )
+ pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - TCP1", "amount": 60})
pos.change_amount = 5.0
pos.insert()
@@ -821,18 +953,34 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(pos.write_off_amount, -5)
def test_pos_with_no_gl_entry_for_change_amount(self):
- frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 0)
+ frappe.db.set_value("Accounts Settings", None, "post_change_gl_entries", 0)
- make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
- expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
+ make_pos_profile(
+ company="_Test Company with perpetual inventory",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ write_off_account="_Test Write Off - TCP1",
+ )
- make_purchase_receipt(company= "_Test Company with perpetual inventory",
- item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
+ make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ item_code="_Test FG Item",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ )
- pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
- debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
- income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
- cost_center = "Main - TCP1", do_not_save=True)
+ pos = create_sales_invoice(
+ company="_Test Company with perpetual inventory",
+ debit_to="Debtors - TCP1",
+ item_code="_Test FG Item",
+ warehouse="Stores - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ do_not_save=True,
+ )
pos.is_pos = 1
pos.update_stock = 1
@@ -842,8 +990,10 @@ class TestSalesInvoice(unittest.TestCase):
for tax in taxes:
pos.append("taxes", tax)
- pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
- pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
+ pos.append(
+ "payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - TCP1", "amount": 50}
+ )
+ pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - TCP1", "amount": 60})
pos.insert()
pos.submit()
@@ -853,40 +1003,50 @@ class TestSalesInvoice(unittest.TestCase):
self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True)
- frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 1)
+ frappe.db.set_value("Accounts Settings", None, "post_change_gl_entries", 1)
def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False):
if validate_without_change_gle:
cash_amount -= pos.change_amount
# check stock ledger entries
- sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
+ sle = frappe.db.sql(
+ """select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
- si.name, as_dict=1)[0]
+ si.name,
+ as_dict=1,
+ )[0]
self.assertTrue(sle)
- self.assertEqual([sle.item_code, sle.warehouse, sle.actual_qty],
- ['_Test FG Item', 'Stores - TCP1', -1.0])
+ self.assertEqual(
+ [sle.item_code, sle.warehouse, sle.actual_qty], ["_Test FG Item", "Stores - TCP1", -1.0]
+ )
# check gl entries
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc, debit asc, credit asc""", si.name, as_dict=1)
+ order by account asc, debit asc, credit asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- stock_in_hand = get_inventory_account('_Test Company with perpetual inventory')
- expected_gl_entries = sorted([
- [si.debit_to, 100.0, 0.0],
- [pos.items[0].income_account, 0.0, 89.09],
- ['Round Off - TCP1', 0.0, 0.01],
- [pos.taxes[0].account_head, 0.0, 10.69],
- [pos.taxes[1].account_head, 0.0, 0.21],
- [stock_in_hand, 0.0, abs(sle.stock_value_difference)],
- [pos.items[0].expense_account, abs(sle.stock_value_difference), 0.0],
- [si.debit_to, 0.0, 50.0],
- [si.debit_to, 0.0, cash_amount],
- ["_Test Bank - TCP1", 50, 0.0],
- ["Cash - TCP1", cash_amount, 0.0]
- ])
+ stock_in_hand = get_inventory_account("_Test Company with perpetual inventory")
+ expected_gl_entries = sorted(
+ [
+ [si.debit_to, 100.0, 0.0],
+ [pos.items[0].income_account, 0.0, 89.09],
+ ["Round Off - TCP1", 0.0, 0.01],
+ [pos.taxes[0].account_head, 0.0, 10.69],
+ [pos.taxes[1].account_head, 0.0, 0.21],
+ [stock_in_hand, 0.0, abs(sle.stock_value_difference)],
+ [pos.items[0].expense_account, abs(sle.stock_value_difference), 0.0],
+ [si.debit_to, 0.0, 50.0],
+ [si.debit_to, 0.0, cash_amount],
+ ["_Test Bank - TCP1", 50, 0.0],
+ ["Cash - TCP1", cash_amount, 0.0],
+ ]
+ )
for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)):
self.assertEqual(expected_gl_entries[i][0], gle.account)
@@ -894,8 +1054,11 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[i][2], gle.credit)
si.cancel()
- gle = frappe.db.sql("""select * from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
+ gle = frappe.db.sql(
+ """select * from `tabGL Entry`
+ where voucher_type='Sales Invoice' and voucher_no=%s""",
+ si.name,
+ )
self.assertTrue(gle)
@@ -915,21 +1078,29 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, si.submit)
def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self):
- si = create_sales_invoice(company="_Test Company with perpetual inventory", debit_to = "Debtors - TCP1",
- income_account="Sales - TCP1", cost_center = "Main - TCP1", do_not_save=True)
+ si = create_sales_invoice(
+ company="_Test Company with perpetual inventory",
+ debit_to="Debtors - TCP1",
+ income_account="Sales - TCP1",
+ cost_center="Main - TCP1",
+ do_not_save=True,
+ )
si.get("items")[0].item_code = None
si.insert()
si.submit()
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- ["Debtors - TCP1", 100.0, 0.0],
- ["Sales - TCP1", 0.0, 100.0]
- ])
+ expected_values = dict(
+ (d[0], d) for d in [["Debtors - TCP1", 100.0, 0.0], ["Sales - TCP1", 0.0, 100.0]]
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
@@ -938,25 +1109,32 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_invoice_gl_entry_with_perpetual_inventory_non_stock_item(self):
si = create_sales_invoice(item="_Test Non Stock Item")
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- [si.debit_to, 100.0, 0.0],
- [test_records[1]["items"][0]["income_account"], 0.0, 100.0]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ [si.debit_to, 100.0, 0.0],
+ [test_records[1]["items"][0]["income_account"], 0.0, 100.0],
+ ]
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
-
def _insert_purchase_receipt(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
test_records as pr_test_records,
)
+
pr = frappe.copy_doc(pr_test_records[0])
pr.naming_series = "_T-Purchase Receipt-"
pr.insert()
@@ -966,6 +1144,7 @@ class TestSalesInvoice(unittest.TestCase):
from erpnext.stock.doctype.delivery_note.test_delivery_note import (
test_records as dn_test_records,
)
+
dn = frappe.copy_doc(dn_test_records[0])
dn.naming_series = "_T-Delivery Note-"
dn.insert()
@@ -983,24 +1162,37 @@ class TestSalesInvoice(unittest.TestCase):
si = frappe.copy_doc(test_records[0])
si.allocate_advances_automatically = 0
- si.append("advances", {
- "doctype": "Sales Invoice Advance",
- "reference_type": "Journal Entry",
- "reference_name": jv.name,
- "reference_row": jv.get("accounts")[0].name,
- "advance_amount": 400,
- "allocated_amount": 300,
- "remarks": jv.remark
- })
+ si.append(
+ "advances",
+ {
+ "doctype": "Sales Invoice Advance",
+ "reference_type": "Journal Entry",
+ "reference_name": jv.name,
+ "reference_row": jv.get("accounts")[0].name,
+ "advance_amount": 400,
+ "allocated_amount": 300,
+ "remarks": jv.remark,
+ },
+ )
si.insert()
si.submit()
si.load_from_db()
- self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
- where reference_name=%s""", si.name))
+ self.assertTrue(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
+ where reference_name=%s""",
+ si.name,
+ )
+ )
- self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
- where reference_name=%s and credit_in_account_currency=300""", si.name))
+ self.assertTrue(
+ frappe.db.sql(
+ """select name from `tabJournal Entry Account`
+ where reference_name=%s and credit_in_account_currency=300""",
+ si.name,
+ )
+ )
self.assertEqual(si.outstanding_amount, 262.0)
@@ -1022,29 +1214,34 @@ class TestSalesInvoice(unittest.TestCase):
si.submit()
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
- self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
- "delivery_document_no"), si.name)
+ self.assertEqual(
+ frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no"), si.name
+ )
return si
def test_serialized_cancel(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
si = self.test_serialized()
si.cancel()
serial_nos = get_serial_nos(si.get("items")[0].serial_no)
- self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
- self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0],
- "delivery_document_no"))
+ self.assertEqual(
+ frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC"
+ )
+ self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no"))
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"))
def test_serialize_status(self):
- serial_no = frappe.get_doc({
- "doctype": "Serial No",
- "item_code": "_Test Serialized Item With Series",
- "serial_no": make_autoname("SR", "Serial No")
- })
+ serial_no = frappe.get_doc(
+ {
+ "doctype": "Serial No",
+ "item_code": "_Test Serialized Item With Series",
+ "serial_no": make_autoname("SR", "Serial No"),
+ }
+ )
serial_no.save()
si = frappe.copy_doc(test_records[0])
@@ -1058,8 +1255,8 @@ class TestSalesInvoice(unittest.TestCase):
def test_serial_numbers_against_delivery_note(self):
"""
- check if the sales invoice item serial numbers and the delivery note items
- serial numbers are same
+ check if the sales invoice item serial numbers and the delivery note items
+ serial numbers are same
"""
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -1079,40 +1276,84 @@ class TestSalesInvoice(unittest.TestCase):
def test_return_sales_invoice(self):
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
- actual_qty_0 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
+ actual_qty_0 = get_qty_after_transaction(item_code="_Test Item", warehouse="Stores - TCP1")
- si = create_sales_invoice(qty = 5, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1")
+ si = create_sales_invoice(
+ qty=5,
+ rate=500,
+ update_stock=1,
+ company="_Test Company with perpetual inventory",
+ debit_to="Debtors - TCP1",
+ item_code="_Test Item",
+ warehouse="Stores - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
-
- actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
+ actual_qty_1 = get_qty_after_transaction(item_code="_Test Item", warehouse="Stores - TCP1")
self.assertEqual(actual_qty_0 - 5, actual_qty_1)
# outgoing_rate
- outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Sales Invoice",
- "voucher_no": si.name}, "stock_value_difference") / 5
+ outgoing_rate = (
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Sales Invoice", "voucher_no": si.name},
+ "stock_value_difference",
+ )
+ / 5
+ )
# return entry
- si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1")
+ si1 = create_sales_invoice(
+ is_return=1,
+ return_against=si.name,
+ qty=-2,
+ rate=500,
+ update_stock=1,
+ company="_Test Company with perpetual inventory",
+ debit_to="Debtors - TCP1",
+ item_code="_Test Item",
+ warehouse="Stores - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
- actual_qty_2 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
+ actual_qty_2 = get_qty_after_transaction(item_code="_Test Item", warehouse="Stores - TCP1")
self.assertEqual(actual_qty_1 + 2, actual_qty_2)
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Sales Invoice", "voucher_no": si1.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
- stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory', si1.items[0].warehouse)
+ stock_in_hand_account = get_inventory_account(
+ "_Test Company with perpetual inventory", si1.items[0].warehouse
+ )
# Check gl entry
- gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
- "voucher_no": si1.name, "account": stock_in_hand_account}, "debit")
+ gle_warehouse_amount = frappe.db.get_value(
+ "GL Entry",
+ {"voucher_type": "Sales Invoice", "voucher_no": si1.name, "account": stock_in_hand_account},
+ "debit",
+ )
self.assertEqual(gle_warehouse_amount, stock_value_difference)
- party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
- "voucher_no": si1.name, "account": "Debtors - TCP1", "party": "_Test Customer"}, "credit")
+ party_credited = frappe.db.get_value(
+ "GL Entry",
+ {
+ "voucher_type": "Sales Invoice",
+ "voucher_no": si1.name,
+ "account": "Debtors - TCP1",
+ "party": "_Test Customer",
+ },
+ "credit",
+ )
self.assertEqual(party_credited, 1000)
@@ -1125,40 +1366,54 @@ class TestSalesInvoice(unittest.TestCase):
asset = create_asset(item_code="Macbook Pro")
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
- return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
+ return_si = create_sales_invoice(
+ is_return=1,
+ return_against=si.name,
+ item_code="Macbook Pro",
+ asset=asset.name,
+ qty=-1,
+ rate=90000,
+ )
disposal_account = frappe.get_cached_value("Company", "_Test Company", "disposal_account")
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
loss_for_si = frappe.get_all(
"GL Entry",
- filters = {
- "voucher_no": si.name,
- "account": disposal_account
- },
- fields = ["credit", "debit"]
+ filters={"voucher_no": si.name, "account": disposal_account},
+ fields=["credit", "debit"],
)[0]
loss_for_return_si = frappe.get_all(
"GL Entry",
- filters = {
- "voucher_no": return_si.name,
- "account": disposal_account
- },
- fields = ["credit", "debit"]
+ filters={"voucher_no": return_si.name, "account": disposal_account},
+ fields=["credit", "debit"],
)[0]
- self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
- self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
+ self.assertEqual(loss_for_si["credit"], loss_for_return_si["debit"])
+ self.assertEqual(loss_for_si["debit"], loss_for_return_si["credit"])
def test_incoming_rate_for_stand_alone_credit_note(self):
- return_si = create_sales_invoice(is_return=1, update_stock=1, qty=-1, rate=90000, incoming_rate=10,
- company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', debit_to='Debtors - TCP1',
- income_account='Sales - TCP1', expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1')
+ return_si = create_sales_invoice(
+ is_return=1,
+ update_stock=1,
+ qty=-1,
+ rate=90000,
+ incoming_rate=10,
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ debit_to="Debtors - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
- incoming_rate = frappe.db.get_value('Stock Ledger Entry', {'voucher_no': return_si.name}, 'incoming_rate')
- debit_amount = frappe.db.get_value('GL Entry',
- {'voucher_no': return_si.name, 'account': 'Stock In Hand - TCP1'}, 'debit')
+ incoming_rate = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": return_si.name}, "incoming_rate"
+ )
+ debit_amount = frappe.db.get_value(
+ "GL Entry", {"voucher_no": return_si.name, "account": "Stock In Hand - TCP1"}, "debit"
+ )
self.assertEqual(debit_amount, 10.0)
self.assertEqual(incoming_rate, 10.0)
@@ -1170,16 +1425,25 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
expected_values = {
- "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
- "base_price_list_rate", "base_rate", "base_amount",
- "net_rate", "base_net_rate", "net_amount", "base_net_amount"],
+ "keys": [
+ "price_list_rate",
+ "discount_percentage",
+ "rate",
+ "amount",
+ "base_price_list_rate",
+ "base_rate",
+ "base_amount",
+ "net_rate",
+ "base_net_rate",
+ "net_amount",
+ "base_net_amount",
+ ],
"_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500, 25, 25, 250, 250],
"_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750, 75, 75, 375, 375],
}
# check if children are saved
- self.assertEqual(len(si.get("items")),
- len(expected_values)-1)
+ self.assertEqual(len(si.get("items")), len(expected_values) - 1)
# check if item values are calculated
for d in si.get("items"):
@@ -1194,16 +1458,19 @@ class TestSalesInvoice(unittest.TestCase):
# check tax calculation
expected_values = {
- "keys": ["tax_amount", "tax_amount_after_discount_amount",
- "base_tax_amount_after_discount_amount"],
+ "keys": [
+ "tax_amount",
+ "tax_amount_after_discount_amount",
+ "base_tax_amount_after_discount_amount",
+ ],
"_Test Account Shipping Charges - _TC": [100, 100, 100],
"_Test Account Customs Duty - _TC": [62.5, 62.5, 62.5],
"_Test Account Excise Duty - _TC": [70, 70, 70],
"_Test Account Education Cess - _TC": [1.4, 1.4, 1.4],
- "_Test Account S&H Education Cess - _TC": [.7, 0.7, 0.7],
+ "_Test Account S&H Education Cess - _TC": [0.7, 0.7, 0.7],
"_Test Account CST - _TC": [17.19, 17.19, 17.19],
"_Test Account VAT - _TC": [78.13, 78.13, 78.13],
- "_Test Account Discount - _TC": [-95.49, -95.49, -95.49]
+ "_Test Account Discount - _TC": [-95.49, -95.49, -95.49],
}
for d in si.get("taxes"):
@@ -1211,19 +1478,26 @@ class TestSalesInvoice(unittest.TestCase):
if expected_values.get(d.account_head):
self.assertEqual(d.get(k), expected_values[d.account_head][i])
-
self.assertEqual(si.total_taxes_and_charges, 234.43)
self.assertEqual(si.base_grand_total, 859.43)
self.assertEqual(si.grand_total, 859.43)
def test_multi_currency_gle(self):
- si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="USD", conversion_rate=50)
+ si = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -1233,26 +1507,35 @@ class TestSalesInvoice(unittest.TestCase):
"debit": 5000,
"debit_in_account_currency": 100,
"credit": 0,
- "credit_in_account_currency": 0
+ "credit_in_account_currency": 0,
},
"Sales - _TC": {
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
- "credit_in_account_currency": 5000
- }
+ "credit_in_account_currency": 5000,
+ },
}
- for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
+ for field in (
+ "account_currency",
+ "debit",
+ "debit_in_account_currency",
+ "credit",
+ "credit_in_account_currency",
+ ):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
# cancel
si.cancel()
- gle = frappe.db.sql("""select name from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry`
+ where voucher_type='Sales Invoice' and voucher_no=%s""",
+ si.name,
+ )
self.assertTrue(gle)
@@ -1260,32 +1543,52 @@ class TestSalesInvoice(unittest.TestCase):
# Customer currency = USD
# Transaction currency cannot be INR
- si1 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- do_not_save=True)
+ si1 = create_sales_invoice(
+ customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", do_not_save=True
+ )
self.assertRaises(InvalidCurrency, si1.save)
# Transaction currency cannot be EUR
- si2 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="EUR", conversion_rate=80, do_not_save=True)
+ si2 = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="EUR",
+ conversion_rate=80,
+ do_not_save=True,
+ )
self.assertRaises(InvalidCurrency, si2.save)
# Transaction currency only allowed in USD
- si3 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
- currency="USD", conversion_rate=50)
+ si3 = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=50,
+ )
# Party Account currency must be in USD, as there is existing GLE with USD
- si4 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
- currency="USD", conversion_rate=50, do_not_submit=True)
+ si4 = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable - _TC",
+ currency="USD",
+ conversion_rate=50,
+ do_not_submit=True,
+ )
self.assertRaises(InvalidAccountCurrency, si4.submit)
# Party Account currency must be in USD, force customer currency as there is no GLE
si3.cancel()
- si5 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
- currency="USD", conversion_rate=50, do_not_submit=True)
+ si5 = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable - _TC",
+ currency="USD",
+ conversion_rate=50,
+ do_not_submit=True,
+ )
self.assertRaises(InvalidAccountCurrency, si5.submit)
@@ -1293,12 +1596,12 @@ class TestSalesInvoice(unittest.TestCase):
si = create_sales_invoice(item_code="_Test Item", qty=1, do_not_submit=True)
price_list_rate = flt(100) * flt(si.plc_conversion_rate)
si.items[0].price_list_rate = price_list_rate
- si.items[0].margin_type = 'Percentage'
+ si.items[0].margin_type = "Percentage"
si.items[0].margin_rate_or_amount = 25
si.items[0].discount_amount = 0.0
si.items[0].discount_percentage = 0.0
si.save()
- self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate))
+ self.assertEqual(si.get("items")[0].rate, flt((price_list_rate * 25) / 100 + price_list_rate))
def test_outstanding_amount_after_advance_jv_cancelation(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
@@ -1306,89 +1609,107 @@ class TestSalesInvoice(unittest.TestCase):
)
jv = frappe.copy_doc(jv_test_records[0])
- jv.accounts[0].is_advance = 'Yes'
+ jv.accounts[0].is_advance = "Yes"
jv.insert()
jv.submit()
si = frappe.copy_doc(test_records[0])
- si.append("advances", {
- "doctype": "Sales Invoice Advance",
- "reference_type": "Journal Entry",
- "reference_name": jv.name,
- "reference_row": jv.get("accounts")[0].name,
- "advance_amount": 400,
- "allocated_amount": 300,
- "remarks": jv.remark
- })
+ si.append(
+ "advances",
+ {
+ "doctype": "Sales Invoice Advance",
+ "reference_type": "Journal Entry",
+ "reference_name": jv.name,
+ "reference_row": jv.get("accounts")[0].name,
+ "advance_amount": 400,
+ "allocated_amount": 300,
+ "remarks": jv.remark,
+ },
+ )
si.insert()
si.submit()
si.load_from_db()
- #check outstanding after advance allocation
- self.assertEqual(flt(si.outstanding_amount),
- flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")))
+ # check outstanding after advance allocation
+ self.assertEqual(
+ flt(si.outstanding_amount),
+ flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")),
+ )
- #added to avoid Document has been modified exception
+ # added to avoid Document has been modified exception
jv = frappe.get_doc("Journal Entry", jv.name)
jv.cancel()
si.load_from_db()
- #check outstanding after advance cancellation
- self.assertEqual(flt(si.outstanding_amount),
- flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")))
+ # check outstanding after advance cancellation
+ self.assertEqual(
+ flt(si.outstanding_amount),
+ flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")),
+ )
def test_outstanding_amount_after_advance_payment_entry_cancelation(self):
- pe = frappe.get_doc({
- "doctype": "Payment Entry",
- "payment_type": "Receive",
- "party_type": "Customer",
- "party": "_Test Customer",
- "company": "_Test Company",
- "paid_from_account_currency": "INR",
- "paid_to_account_currency": "INR",
- "source_exchange_rate": 1,
- "target_exchange_rate": 1,
- "reference_no": "1",
- "reference_date": nowdate(),
- "received_amount": 300,
- "paid_amount": 300,
- "paid_from": "_Test Receivable - _TC",
- "paid_to": "_Test Cash - _TC"
- })
+ pe = frappe.get_doc(
+ {
+ "doctype": "Payment Entry",
+ "payment_type": "Receive",
+ "party_type": "Customer",
+ "party": "_Test Customer",
+ "company": "_Test Company",
+ "paid_from_account_currency": "INR",
+ "paid_to_account_currency": "INR",
+ "source_exchange_rate": 1,
+ "target_exchange_rate": 1,
+ "reference_no": "1",
+ "reference_date": nowdate(),
+ "received_amount": 300,
+ "paid_amount": 300,
+ "paid_from": "_Test Receivable - _TC",
+ "paid_to": "_Test Cash - _TC",
+ }
+ )
pe.insert()
pe.submit()
si = frappe.copy_doc(test_records[0])
si.is_pos = 0
- si.append("advances", {
- "doctype": "Sales Invoice Advance",
- "reference_type": "Payment Entry",
- "reference_name": pe.name,
- "advance_amount": 300,
- "allocated_amount": 300,
- "remarks": pe.remarks
- })
+ si.append(
+ "advances",
+ {
+ "doctype": "Sales Invoice Advance",
+ "reference_type": "Payment Entry",
+ "reference_name": pe.name,
+ "advance_amount": 300,
+ "allocated_amount": 300,
+ "remarks": pe.remarks,
+ },
+ )
si.insert()
si.submit()
si.load_from_db()
- #check outstanding after advance allocation
- self.assertEqual(flt(si.outstanding_amount),
- flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")))
+ # check outstanding after advance allocation
+ self.assertEqual(
+ flt(si.outstanding_amount),
+ flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")),
+ )
- #added to avoid Document has been modified exception
+ # added to avoid Document has been modified exception
pe = frappe.get_doc("Payment Entry", pe.name)
pe.cancel()
si.load_from_db()
- #check outstanding after advance cancellation
- self.assertEqual(flt(si.outstanding_amount),
- flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")))
+ # check outstanding after advance cancellation
+ self.assertEqual(
+ flt(si.outstanding_amount),
+ flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")),
+ )
def test_multiple_uom_in_selling(self):
- frappe.db.sql("""delete from `tabItem Price`
- where price_list='_Test Price List' and item_code='_Test Item'""")
+ frappe.db.sql(
+ """delete from `tabItem Price`
+ where price_list='_Test Price List' and item_code='_Test Item'"""
+ )
item_price = frappe.new_doc("Item Price")
item_price.price_list = "_Test Price List"
item_price.item_code = "_Test Item"
@@ -1402,9 +1723,18 @@ class TestSalesInvoice(unittest.TestCase):
si.save()
expected_values = {
- "keys": ["price_list_rate", "stock_uom", "uom", "conversion_factor", "rate", "amount",
- "base_price_list_rate", "base_rate", "base_amount"],
- "_Test Item": [1000, "_Test UOM", "_Test UOM 1", 10.0, 1000, 1000, 1000, 1000, 1000]
+ "keys": [
+ "price_list_rate",
+ "stock_uom",
+ "uom",
+ "conversion_factor",
+ "rate",
+ "amount",
+ "base_price_list_rate",
+ "base_rate",
+ "base_amount",
+ ],
+ "_Test Item": [1000, "_Test UOM", "_Test UOM 1", 10.0, 1000, 1000, 1000, 1000, 1000],
}
# check if the conversion_factor and price_list_rate is calculated according to uom
@@ -1419,23 +1749,10 @@ class TestSalesInvoice(unittest.TestCase):
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
expected_itemised_tax = {
- "_Test Item": {
- "Service Tax": {
- "tax_rate": 10.0,
- "tax_amount": 1000.0
- }
- },
- "_Test Item 2": {
- "Service Tax": {
- "tax_rate": 10.0,
- "tax_amount": 500.0
- }
- }
- }
- expected_itemised_taxable_amount = {
- "_Test Item": 10000.0,
- "_Test Item 2": 5000.0
+ "_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}},
+ "_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}},
}
+ expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0}
self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
@@ -1450,23 +1767,10 @@ class TestSalesInvoice(unittest.TestCase):
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
expected_itemised_tax = {
- "_Test Item": {
- "Service Tax": {
- "tax_rate": 10.0,
- "tax_amount": 1000.0
- }
- },
- "_Test Item 2": {
- "Service Tax": {
- "tax_rate": 10.0,
- "tax_amount": 500.0
- }
- }
- }
- expected_itemised_taxable_amount = {
- "_Test Item": 10000.0,
- "_Test Item 2": 5000.0
+ "_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}},
+ "_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}},
}
+ expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0}
self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
@@ -1475,59 +1779,73 @@ class TestSalesInvoice(unittest.TestCase):
def create_si_to_test_tax_breakup(self):
si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
- si.append("items", {
- "item_code": "_Test Item",
- "gst_hsn_code": "999800",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 100,
- "rate": 50,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC"
- })
- si.append("items", {
- "item_code": "_Test Item 2",
- "gst_hsn_code": "999800",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 100,
- "rate": 50,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC"
- })
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "gst_hsn_code": "999800",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 100,
+ "rate": 50,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item 2",
+ "gst_hsn_code": "999800",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 100,
+ "rate": 50,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": "_Test Account Service Tax - _TC",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Service Tax",
- "rate": 10
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 10,
+ },
+ )
si.insert()
return si
def test_company_monthly_sales(self):
- existing_current_month_sales = frappe.get_cached_value('Company', "_Test Company", "total_monthly_sales")
+ existing_current_month_sales = frappe.get_cached_value(
+ "Company", "_Test Company", "total_monthly_sales"
+ )
si = create_sales_invoice()
- current_month_sales = frappe.get_cached_value('Company', "_Test Company", "total_monthly_sales")
+ current_month_sales = frappe.get_cached_value("Company", "_Test Company", "total_monthly_sales")
self.assertEqual(current_month_sales, existing_current_month_sales + si.base_grand_total)
si.cancel()
- current_month_sales = frappe.get_cached_value('Company', "_Test Company", "total_monthly_sales")
+ current_month_sales = frappe.get_cached_value("Company", "_Test Company", "total_monthly_sales")
self.assertEqual(current_month_sales, existing_current_month_sales)
def test_rounding_adjustment(self):
si = create_sales_invoice(rate=24900, do_not_save=True)
for tax in ["Tax 1", "Tax2"]:
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": "_Test Account Service Tax - _TC",
- "description": tax,
- "rate": 14,
- "cost_center": "_Test Cost Center - _TC",
- "included_in_print_rate": 1
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "description": tax,
+ "rate": 14,
+ "cost_center": "_Test Cost Center - _TC",
+ "included_in_print_rate": 1,
+ },
+ )
si.save()
si.submit()
self.assertEqual(si.net_total, 19453.13)
@@ -1535,16 +1853,23 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.total_taxes_and_charges, 5446.88)
self.assertEqual(si.rounding_adjustment, -0.01)
- expected_values = dict((d[0], d) for d in [
- [si.debit_to, 24900, 0.0],
- ["_Test Account Service Tax - _TC", 0.0, 5446.88],
- ["Sales - _TC", 0.0, 19453.13],
- ["Round Off - _TC", 0.01, 0.0]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ [si.debit_to, 24900, 0.0],
+ ["_Test Account Service Tax - _TC", 0.0, 5446.88],
+ ["Sales - _TC", 0.0, 19453.13],
+ ["Round Off - _TC", 0.01, 0.0],
+ ]
+ )
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
@@ -1554,24 +1879,30 @@ class TestSalesInvoice(unittest.TestCase):
def test_rounding_adjustment_2(self):
si = create_sales_invoice(rate=400, do_not_save=True)
for rate in [400, 600, 100]:
- si.append("items", {
- "item_code": "_Test Item",
- "gst_hsn_code": "999800",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 1,
- "rate": rate,
- "income_account": "Sales - _TC",
- "cost_center": "_Test Cost Center - _TC"
- })
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "gst_hsn_code": "999800",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "rate": rate,
+ "income_account": "Sales - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
for tax_account in ["_Test Account VAT - _TC", "_Test Account Service Tax - _TC"]:
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": tax_account,
- "description": tax_account,
- "rate": 9,
- "cost_center": "_Test Cost Center - _TC",
- "included_in_print_rate": 1
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": tax_account,
+ "description": tax_account,
+ "rate": 9,
+ "cost_center": "_Test Cost Center - _TC",
+ "included_in_print_rate": 1,
+ },
+ )
si.save()
si.submit()
self.assertEqual(si.net_total, 1271.19)
@@ -1579,16 +1910,23 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.total_taxes_and_charges, 228.82)
self.assertEqual(si.rounding_adjustment, -0.01)
- expected_values = dict((d[0], d) for d in [
- [si.debit_to, 1500, 0.0],
- ["_Test Account Service Tax - _TC", 0.0, 114.41],
- ["_Test Account VAT - _TC", 0.0, 114.41],
- ["Sales - _TC", 0.0, 1271.18]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ [si.debit_to, 1500, 0.0],
+ ["_Test Account Service Tax - _TC", 0.0, 114.41],
+ ["_Test Account VAT - _TC", 0.0, 114.41],
+ ["Sales - _TC", 0.0, 1271.18],
+ ]
+ )
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
@@ -1599,24 +1937,30 @@ class TestSalesInvoice(unittest.TestCase):
si = create_sales_invoice(do_not_save=True)
si.items = []
for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
- si.append("items", {
- "item_code": "_Test Item",
- "gst_hsn_code": "999800",
- "warehouse": "_Test Warehouse - _TC",
- "qty": d[1],
- "rate": d[0],
- "income_account": "Sales - _TC",
- "cost_center": "_Test Cost Center - _TC"
- })
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "gst_hsn_code": "999800",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": d[1],
+ "rate": d[0],
+ "income_account": "Sales - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
for tax_account in ["_Test Account VAT - _TC", "_Test Account Service Tax - _TC"]:
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": tax_account,
- "description": tax_account,
- "rate": 6,
- "cost_center": "_Test Cost Center - _TC",
- "included_in_print_rate": 1
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": tax_account,
+ "description": tax_account,
+ "rate": 6,
+ "cost_center": "_Test Cost Center - _TC",
+ "included_in_print_rate": 1,
+ },
+ )
si.save()
si.submit()
self.assertEqual(si.net_total, 4007.16)
@@ -1624,31 +1968,40 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.total_taxes_and_charges, 480.86)
self.assertEqual(si.rounding_adjustment, -0.02)
- expected_values = dict((d[0], d) for d in [
- [si.debit_to, 4488.0, 0.0],
- ["_Test Account Service Tax - _TC", 0.0, 240.43],
- ["_Test Account VAT - _TC", 0.0, 240.43],
- ["Sales - _TC", 0.0, 4007.15],
- ["Round Off - _TC", 0.01, 0]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ [si.debit_to, 4488.0, 0.0],
+ ["_Test Account Service Tax - _TC", 0.0, 240.43],
+ ["_Test Account VAT - _TC", 0.0, 240.43],
+ ["Sales - _TC", 0.0, 4007.15],
+ ["Round Off - _TC", 0.01, 0],
+ ]
+ )
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
debit_credit_diff = 0
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
- debit_credit_diff += (gle.debit - gle.credit)
+ debit_credit_diff += gle.debit - gle.credit
self.assertEqual(debit_credit_diff, 0)
def test_sales_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
- shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test")
+ shipping_rule = create_shipping_rule(
+ shipping_rule_type="Selling", shipping_rule_name="Shipping Rule - Sales Invoice Test"
+ )
si = frappe.copy_doc(test_records[2])
@@ -1661,29 +2014,32 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.total_taxes_and_charges, 468.85)
self.assertEqual(si.grand_total, 1718.85)
-
-
def test_create_invoice_without_terms(self):
si = create_sales_invoice(do_not_save=1)
- self.assertFalse(si.get('payment_schedule'))
+ self.assertFalse(si.get("payment_schedule"))
si.insert()
- self.assertTrue(si.get('payment_schedule'))
+ self.assertTrue(si.get("payment_schedule"))
def test_duplicate_due_date_in_terms(self):
si = create_sales_invoice(do_not_save=1)
- si.append('payment_schedule', dict(due_date='2017-01-01', invoice_portion=50.00, payment_amount=50))
- si.append('payment_schedule', dict(due_date='2017-01-01', invoice_portion=50.00, payment_amount=50))
+ si.append(
+ "payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50)
+ )
+ si.append(
+ "payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50)
+ )
self.assertRaises(frappe.ValidationError, si.insert)
def test_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
- si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
+ si = create_sales_invoice(item_code="_Test Item", qty=(5 * -1), rate=500, is_return=1)
- outstanding_amount = get_outstanding_amount(si.doctype,
- si.name, "Debtors - _TC", si.customer, "Customer")
+ outstanding_amount = get_outstanding_amount(
+ si.doctype, si.name, "Debtors - _TC", si.customer, "Customer"
+ )
self.assertEqual(si.outstanding_amount, outstanding_amount)
@@ -1698,30 +2054,31 @@ class TestSalesInvoice(unittest.TestCase):
pe.insert()
pe.submit()
- si_doc = frappe.get_doc('Sales Invoice', si.name)
+ si_doc = frappe.get_doc("Sales Invoice", si.name)
self.assertEqual(si_doc.outstanding_amount, 0)
def test_sales_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
- 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")
self.assertEqual(si.cost_center, cost_center)
expected_values = {
- "Debtors - _TC": {
- "cost_center": cost_center
- },
- "Sales - _TC": {
- "cost_center": cost_center
- }
+ "Debtors - _TC": {"cost_center": cost_center},
+ "Sales - _TC": {"cost_center": cost_center},
}
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -1731,16 +2088,20 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_invoice_with_project_link(self):
from erpnext.projects.doctype.project.test_project import make_project
- project = make_project({
- 'project_name': 'Sales Invoice Project',
- 'project_template_name': 'Test Project Template',
- 'start_date': '2020-01-01'
- })
- item_project = make_project({
- 'project_name': 'Sales Invoice Item Project',
- 'project_template_name': 'Test Project Template',
- 'start_date': '2019-06-01'
- })
+ project = make_project(
+ {
+ "project_name": "Sales Invoice Project",
+ "project_template_name": "Test Project Template",
+ "start_date": "2020-01-01",
+ }
+ )
+ item_project = make_project(
+ {
+ "project_name": "Sales Invoice Item Project",
+ "project_template_name": "Test Project Template",
+ "start_date": "2019-06-01",
+ }
+ )
sales_invoice = create_sales_invoice(do_not_save=1)
sales_invoice.items[0].project = item_project.name
@@ -1749,18 +2110,18 @@ class TestSalesInvoice(unittest.TestCase):
sales_invoice.submit()
expected_values = {
- "Debtors - _TC": {
- "project": project.name
- },
- "Sales - _TC": {
- "project": item_project.name
- }
+ "Debtors - _TC": {"project": project.name},
+ "Sales - _TC": {"project": item_project.name},
}
- gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, project, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", sales_invoice.name, as_dict=1)
+ order by account asc""",
+ sales_invoice.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -1769,21 +2130,21 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_invoice_without_cost_center(self):
cost_center = "_Test Cost Center - _TC"
- si = create_sales_invoice(debit_to="Debtors - _TC")
+ si = create_sales_invoice(debit_to="Debtors - _TC")
expected_values = {
- "Debtors - _TC": {
- "cost_center": None
- },
- "Sales - _TC": {
- "cost_center": cost_center
- }
+ "Debtors - _TC": {"cost_center": None},
+ "Sales - _TC": {"cost_center": cost_center},
}
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
- order by account asc""", si.name, as_dict=1)
+ order by account asc""",
+ si.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
@@ -1791,8 +2152,11 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_deferred_revenue(self):
- deferred_account = create_account(account_name="Deferred Revenue",
- parent_account="Current Liabilities - _TC", company="_Test Company")
+ deferred_account = create_account(
+ account_name="Deferred Revenue",
+ parent_account="Current Liabilities - _TC",
+ company="_Test Company",
+ )
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_revenue = 1
@@ -1808,14 +2172,16 @@ class TestSalesInvoice(unittest.TestCase):
si.save()
si.submit()
- pda1 = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date=nowdate(),
- start_date="2019-01-01",
- end_date="2019-03-31",
- type="Income",
- company="_Test Company"
- ))
+ pda1 = frappe.get_doc(
+ dict(
+ doctype="Process Deferred Accounting",
+ posting_date=nowdate(),
+ start_date="2019-01-01",
+ end_date="2019-03-31",
+ type="Income",
+ company="_Test Company",
+ )
+ )
pda1.insert()
pda1.submit()
@@ -1826,17 +2192,20 @@ class TestSalesInvoice(unittest.TestCase):
[deferred_account, 43.08, 0.0, "2019-02-28"],
["Sales - _TC", 0.0, 43.08, "2019-02-28"],
[deferred_account, 23.07, 0.0, "2019-03-15"],
- ["Sales - _TC", 0.0, 23.07, "2019-03-15"]
+ ["Sales - _TC", 0.0, 23.07, "2019-03-15"],
]
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
def test_fixed_deferred_revenue(self):
- deferred_account = create_account(account_name="Deferred Revenue",
- parent_account="Current Liabilities - _TC", company="_Test Company")
+ deferred_account = create_account(
+ account_name="Deferred Revenue",
+ parent_account="Current Liabilities - _TC",
+ company="_Test Company",
+ )
- acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- acc_settings.book_deferred_entries_based_on = 'Months'
+ acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
+ acc_settings.book_deferred_entries_based_on = "Months"
acc_settings.save()
item = create_item("_Test Item for Deferred Accounting")
@@ -1845,7 +2214,9 @@ class TestSalesInvoice(unittest.TestCase):
item.no_of_months = 12
item.save()
- si = create_sales_invoice(item=item.name, posting_date="2019-01-16", rate=50000, do_not_submit=True)
+ si = create_sales_invoice(
+ item=item.name, posting_date="2019-01-16", rate=50000, do_not_submit=True
+ )
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-16"
si.items[0].service_end_date = "2019-03-31"
@@ -1853,14 +2224,16 @@ class TestSalesInvoice(unittest.TestCase):
si.save()
si.submit()
- pda1 = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date='2019-03-31',
- start_date="2019-01-01",
- end_date="2019-03-31",
- type="Income",
- company="_Test Company"
- ))
+ pda1 = frappe.get_doc(
+ dict(
+ doctype="Process Deferred Accounting",
+ posting_date="2019-03-31",
+ start_date="2019-01-01",
+ end_date="2019-03-31",
+ type="Income",
+ company="_Test Company",
+ )
+ )
pda1.insert()
pda1.submit()
@@ -1871,13 +2244,13 @@ class TestSalesInvoice(unittest.TestCase):
[deferred_account, 20000.0, 0.0, "2019-02-28"],
["Sales - _TC", 0.0, 20000.0, "2019-02-28"],
[deferred_account, 20000.0, 0.0, "2019-03-31"],
- ["Sales - _TC", 0.0, 20000.0, "2019-03-31"]
+ ["Sales - _TC", 0.0, 20000.0, "2019-03-31"],
]
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
- acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- acc_settings.book_deferred_entries_based_on = 'Days'
+ acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
+ acc_settings.book_deferred_entries_based_on = "Days"
acc_settings.save()
def test_inter_company_transaction(self):
@@ -1886,45 +2259,47 @@ class TestSalesInvoice(unittest.TestCase):
create_internal_customer(
customer_name="_Test Internal Customer",
represents_company="_Test Company 1",
- allowed_to_interact_with="Wind Power LLC"
+ allowed_to_interact_with="Wind Power LLC",
)
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
- supplier = frappe.get_doc({
- "supplier_group": "_Test Supplier Group",
- "supplier_name": "_Test Internal Supplier",
- "doctype": "Supplier",
- "is_internal_supplier": 1,
- "represents_company": "Wind Power LLC"
- })
+ supplier = frappe.get_doc(
+ {
+ "supplier_group": "_Test Supplier Group",
+ "supplier_name": "_Test Internal Supplier",
+ "doctype": "Supplier",
+ "is_internal_supplier": 1,
+ "represents_company": "Wind Power LLC",
+ }
+ )
- supplier.append("companies", {
- "company": "_Test Company 1"
- })
+ supplier.append("companies", {"company": "_Test Company 1"})
supplier.insert()
si = create_sales_invoice(
- company = "Wind Power LLC",
- customer = "_Test Internal Customer",
- debit_to = "Debtors - WP",
- warehouse = "Stores - WP",
- income_account = "Sales - WP",
- expense_account = "Cost of Goods Sold - WP",
- cost_center = "Main - WP",
- currency = "USD",
- do_not_save = 1
+ company="Wind Power LLC",
+ customer="_Test Internal Customer",
+ debit_to="Debtors - WP",
+ warehouse="Stores - WP",
+ income_account="Sales - WP",
+ expense_account="Cost of Goods Sold - WP",
+ cost_center="Main - WP",
+ currency="USD",
+ do_not_save=1,
)
si.selling_price_list = "_Test Price List Rest of the World"
si.submit()
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
- target_doc.items[0].update({
- "expense_account": "Cost of Goods Sold - _TC1",
- "cost_center": "Main - _TC1",
- "warehouse": "Stores - _TC1"
- })
+ target_doc.items[0].update(
+ {
+ "expense_account": "Cost of Goods Sold - _TC1",
+ "cost_center": "Main - _TC1",
+ "warehouse": "Stores - _TC1",
+ }
+ )
target_doc.submit()
self.assertEqual(target_doc.company, "_Test Company 1")
@@ -1936,57 +2311,66 @@ class TestSalesInvoice(unittest.TestCase):
old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
- old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled('_Test Company 1')
- frappe.local.enable_perpetual_inventory['_Test Company 1'] = 1
-
- frappe.db.set_value("Company", '_Test Company 1', "stock_received_but_not_billed", "Stock Received But Not Billed - _TC1")
- frappe.db.set_value("Company", '_Test Company 1', "expenses_included_in_valuation", "Expenses Included In Valuation - _TC1")
+ old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company 1")
+ frappe.local.enable_perpetual_inventory["_Test Company 1"] = 1
+ frappe.db.set_value(
+ "Company",
+ "_Test Company 1",
+ "stock_received_but_not_billed",
+ "Stock Received But Not Billed - _TC1",
+ )
+ frappe.db.set_value(
+ "Company",
+ "_Test Company 1",
+ "expenses_included_in_valuation",
+ "Expenses Included In Valuation - _TC1",
+ )
if not frappe.db.exists("Customer", "_Test Internal Customer"):
- customer = frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": "_Test Internal Customer",
- "customer_type": "Individual",
- "doctype": "Customer",
- "territory": "_Test Territory",
- "is_internal_customer": 1,
- "represents_company": "_Test Company 1"
- })
+ customer = frappe.get_doc(
+ {
+ "customer_group": "_Test Customer Group",
+ "customer_name": "_Test Internal Customer",
+ "customer_type": "Individual",
+ "doctype": "Customer",
+ "territory": "_Test Territory",
+ "is_internal_customer": 1,
+ "represents_company": "_Test Company 1",
+ }
+ )
- customer.append("companies", {
- "company": "Wind Power LLC"
- })
+ customer.append("companies", {"company": "Wind Power LLC"})
customer.insert()
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
- supplier = frappe.get_doc({
- "supplier_group": "_Test Supplier Group",
- "supplier_name": "_Test Internal Supplier",
- "doctype": "Supplier",
- "is_internal_supplier": 1,
- "represents_company": "Wind Power LLC"
- })
+ supplier = frappe.get_doc(
+ {
+ "supplier_group": "_Test Supplier Group",
+ "supplier_name": "_Test Internal Supplier",
+ "doctype": "Supplier",
+ "is_internal_supplier": 1,
+ "represents_company": "Wind Power LLC",
+ }
+ )
- supplier.append("companies", {
- "company": "_Test Company 1"
- })
+ supplier.append("companies", {"company": "_Test Company 1"})
supplier.insert()
# begin test
si = create_sales_invoice(
- company = "Wind Power LLC",
- customer = "_Test Internal Customer",
- debit_to = "Debtors - WP",
- warehouse = "Stores - WP",
- income_account = "Sales - WP",
- expense_account = "Cost of Goods Sold - WP",
- cost_center = "Main - WP",
- currency = "USD",
- update_stock = 1,
- do_not_save = 1
+ company="Wind Power LLC",
+ customer="_Test Internal Customer",
+ debit_to="Debtors - WP",
+ warehouse="Stores - WP",
+ income_account="Sales - WP",
+ expense_account="Cost of Goods Sold - WP",
+ cost_center="Main - WP",
+ currency="USD",
+ update_stock=1,
+ do_not_save=1,
)
si.selling_price_list = "_Test Price List Rest of the World"
si.submit()
@@ -2007,19 +2391,19 @@ class TestSalesInvoice(unittest.TestCase):
target_doc.save()
# after warehouse is set, linked account or default inventory account is set
- self.assertEqual(target_doc.items[0].expense_account, 'Stock In Hand - _TC1')
+ self.assertEqual(target_doc.items[0].expense_account, "Stock In Hand - _TC1")
# tear down
- frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
+ frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
def test_sle_for_target_warehouse(self):
se = make_stock_entry(
item_code="138-CMS Shoe",
target="Finished Goods - _TC",
- company = "_Test Company",
+ company="_Test Company",
qty=1,
- basic_rate=500
+ basic_rate=500,
)
si = frappe.copy_doc(test_records[0])
@@ -2031,8 +2415,9 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
si.submit()
- sles = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": si.name},
- fields=["name", "actual_qty"])
+ sles = frappe.get_all(
+ "Stock Ledger Entry", filters={"voucher_no": si.name}, fields=["name", "actual_qty"]
+ )
# check if both SLEs are created
self.assertEqual(len(sles), 2)
@@ -2046,82 +2431,92 @@ class TestSalesInvoice(unittest.TestCase):
## Create internal transfer account
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
- account = create_account(account_name="Unrealized Profit",
- parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
+ account = create_account(
+ account_name="Unrealized Profit",
+ parent_account="Current Liabilities - TCP1",
+ company="_Test Company with perpetual inventory",
+ )
- frappe.db.set_value('Company', '_Test Company with perpetual inventory',
- 'unrealized_profit_loss_account', account)
+ frappe.db.set_value(
+ "Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
+ )
- customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
- "_Test Company with perpetual inventory")
+ customer = create_internal_customer(
+ "_Test Internal Customer 2",
+ "_Test Company with perpetual inventory",
+ "_Test Company with perpetual inventory",
+ )
- create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
- "_Test Company with perpetual inventory")
+ create_internal_supplier(
+ "_Test Internal Supplier 2",
+ "_Test Company with perpetual inventory",
+ "_Test Company with perpetual inventory",
+ )
si = create_sales_invoice(
- company = "_Test Company with perpetual inventory",
- customer = customer,
- debit_to = "Debtors - TCP1",
- warehouse = "Stores - TCP1",
- income_account = "Sales - TCP1",
- expense_account = "Cost of Goods Sold - TCP1",
- cost_center = "Main - TCP1",
- currency = "INR",
- do_not_save = 1
+ company="_Test Company with perpetual inventory",
+ customer=customer,
+ debit_to="Debtors - TCP1",
+ warehouse="Stores - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ currency="INR",
+ do_not_save=1,
)
si.selling_price_list = "_Test Price List Rest of the World"
si.update_stock = 1
- si.items[0].target_warehouse = 'Work In Progress - TCP1'
+ si.items[0].target_warehouse = "Work In Progress - TCP1"
# Add stock to stores for succesful stock transfer
make_stock_entry(
- target="Stores - TCP1",
- company = "_Test Company with perpetual inventory",
- qty=1,
- basic_rate=100
+ target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=1, basic_rate=100
)
add_taxes(si)
si.save()
rate = 0.0
- for d in si.get('items'):
- rate = get_incoming_rate({
- "item_code": d.item_code,
- "warehouse": d.warehouse,
- "posting_date": si.posting_date,
- "posting_time": si.posting_time,
- "qty": -1 * flt(d.get('stock_qty')),
- "serial_no": d.serial_no,
- "company": si.company,
- "voucher_type": 'Sales Invoice',
- "voucher_no": si.name,
- "allow_zero_valuation": d.get("allow_zero_valuation")
- }, raise_error_if_no_rate=False)
+ for d in si.get("items"):
+ rate = get_incoming_rate(
+ {
+ "item_code": d.item_code,
+ "warehouse": d.warehouse,
+ "posting_date": si.posting_date,
+ "posting_time": si.posting_time,
+ "qty": -1 * flt(d.get("stock_qty")),
+ "serial_no": d.serial_no,
+ "company": si.company,
+ "voucher_type": "Sales Invoice",
+ "voucher_no": si.name,
+ "allow_zero_valuation": d.get("allow_zero_valuation"),
+ },
+ raise_error_if_no_rate=False,
+ )
rate = flt(rate, 2)
si.submit()
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
- target_doc.company = '_Test Company with perpetual inventory'
- target_doc.items[0].warehouse = 'Finished Goods - TCP1'
+ target_doc.company = "_Test Company with perpetual inventory"
+ target_doc.items[0].warehouse = "Finished Goods - TCP1"
add_taxes(target_doc)
target_doc.save()
target_doc.submit()
- tax_amount = flt(rate * (12/100), 2)
+ tax_amount = flt(rate * (12 / 100), 2)
si_gl_entries = [
["_Test Account Excise Duty - TCP1", 0.0, tax_amount, nowdate()],
- ["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()]
+ ["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()],
]
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
pi_gl_entries = [
- ["_Test Account Excise Duty - TCP1", tax_amount , 0.0, nowdate()],
- ["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()]
+ ["_Test Account Excise Duty - TCP1", tax_amount, 0.0, nowdate()],
+ ["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()],
]
# Sale and Purchase both should be at valuation rate
@@ -2137,43 +2532,46 @@ class TestSalesInvoice(unittest.TestCase):
data = get_ewb_data("Sales Invoice", [si.name])
- self.assertEqual(data['version'], '1.0.0421')
- self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')
- self.assertEqual(data['billLists'][0]['fromTrdName'], '_Test Company')
- self.assertEqual(data['billLists'][0]['toTrdName'], '_Test Customer')
- self.assertEqual(data['billLists'][0]['vehicleType'], 'R')
- self.assertEqual(data['billLists'][0]['totalValue'], 60000)
- self.assertEqual(data['billLists'][0]['cgstValue'], 5400)
- self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
- self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
- self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
- self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
- self.assertEqual(data['billLists'][0]['fromStateCode'],27)
+ self.assertEqual(data["version"], "1.0.0421")
+ self.assertEqual(data["billLists"][0]["fromGstin"], "27AAECE4835E1ZR")
+ self.assertEqual(data["billLists"][0]["fromTrdName"], "_Test Company")
+ self.assertEqual(data["billLists"][0]["toTrdName"], "_Test Customer")
+ self.assertEqual(data["billLists"][0]["vehicleType"], "R")
+ self.assertEqual(data["billLists"][0]["totalValue"], 60000)
+ self.assertEqual(data["billLists"][0]["cgstValue"], 5400)
+ self.assertEqual(data["billLists"][0]["sgstValue"], 5400)
+ self.assertEqual(data["billLists"][0]["vehicleNo"], "KA12KA1234")
+ self.assertEqual(data["billLists"][0]["itemList"][0]["taxableAmount"], 60000)
+ self.assertEqual(data["billLists"][0]["actualFromStateCode"], 7)
+ self.assertEqual(data["billLists"][0]["fromStateCode"], 27)
def test_einvoice_submission_without_irn(self):
# init
- einvoice_settings = frappe.get_doc('E Invoice Settings')
+ einvoice_settings = frappe.get_doc("E Invoice Settings")
einvoice_settings.enable = 1
einvoice_settings.applicable_from = nowdate()
- einvoice_settings.append('credentials', {
- 'company': '_Test Company',
- 'gstin': '27AAECE4835E1ZR',
- 'username': 'test',
- 'password': 'test'
- })
+ einvoice_settings.append(
+ "credentials",
+ {
+ "company": "_Test Company",
+ "gstin": "27AAECE4835E1ZR",
+ "username": "test",
+ "password": "test",
+ },
+ )
einvoice_settings.save()
country = frappe.flags.country
- frappe.flags.country = 'India'
+ frappe.flags.country = "India"
si = make_sales_invoice_for_ewaybill()
self.assertRaises(frappe.ValidationError, si.submit)
- si.irn = 'test_irn'
+ si.irn = "test_irn"
si.submit()
# reset
- einvoice_settings = frappe.get_doc('E Invoice Settings')
+ einvoice_settings = frappe.get_doc("E Invoice Settings")
einvoice_settings.enable = 0
frappe.flags.country = country
@@ -2185,15 +2583,15 @@ class TestSalesInvoice(unittest.TestCase):
si.save()
einvoice = make_einvoice(si)
- self.assertTrue(einvoice['EwbDtls'])
+ self.assertTrue(einvoice["EwbDtls"])
validate_totals(einvoice)
- si.apply_discount_on = 'Net Total'
+ si.apply_discount_on = "Net Total"
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
- [d.set('included_in_print_rate', 1) for d in si.taxes]
+ [d.set("included_in_print_rate", 1) for d in si.taxes]
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
@@ -2201,29 +2599,39 @@ class TestSalesInvoice(unittest.TestCase):
def test_item_tax_net_range(self):
item = create_item("T Shirt")
- item.set('taxes', [])
- item.append("taxes", {
- "item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
- "minimum_net_rate": 0,
- "maximum_net_rate": 500
- })
+ item.set("taxes", [])
+ item.append(
+ "taxes",
+ {
+ "item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
+ "minimum_net_rate": 0,
+ "maximum_net_rate": 500,
+ },
+ )
- item.append("taxes", {
- "item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
- "minimum_net_rate": 501,
- "maximum_net_rate": 1000
- })
+ item.append(
+ "taxes",
+ {
+ "item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
+ "minimum_net_rate": 501,
+ "maximum_net_rate": 1000,
+ },
+ )
item.save()
- sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True)
- self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
+ sales_invoice = create_sales_invoice(item="T Shirt", rate=700, do_not_submit=True)
+ self.assertEqual(
+ sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC"
+ )
# Apply discount
- sales_invoice.apply_discount_on = 'Net Total'
+ sales_invoice.apply_discount_on = "Net Total"
sales_invoice.discount_amount = 300
sales_invoice.save()
- self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
+ self.assertEqual(
+ sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC"
+ )
def test_sales_invoice_with_discount_accounting_enabled(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
@@ -2232,14 +2640,17 @@ class TestSalesInvoice(unittest.TestCase):
enable_discount_accounting()
- discount_account = create_account(account_name="Discount Account",
- parent_account="Indirect Expenses - _TC", company="_Test Company")
+ discount_account = create_account(
+ account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC",
+ company="_Test Company",
+ )
si = create_sales_invoice(discount_account=discount_account, discount_percentage=10, rate=90)
expected_gle = [
["Debtors - _TC", 90.0, 0.0, nowdate()],
["Discount Account - _TC", 10.0, 0.0, nowdate()],
- ["Sales - _TC", 0.0, 100.0, nowdate()]
+ ["Sales - _TC", 0.0, 100.0, nowdate()],
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
@@ -2251,27 +2662,33 @@ class TestSalesInvoice(unittest.TestCase):
)
enable_discount_accounting()
- additional_discount_account = create_account(account_name="Discount Account",
- parent_account="Indirect Expenses - _TC", company="_Test Company")
+ additional_discount_account = create_account(
+ account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC",
+ company="_Test Company",
+ )
- si = create_sales_invoice(parent_cost_center='Main - _TC', do_not_save=1)
+ si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
si.apply_discount_on = "Grand Total"
si.additional_discount_account = additional_discount_account
si.additional_discount_percentage = 20
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": "_Test Account VAT - _TC",
- "cost_center": "Main - _TC",
- "description": "Test",
- "rate": 10
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account VAT - _TC",
+ "cost_center": "Main - _TC",
+ "description": "Test",
+ "rate": 10,
+ },
+ )
si.submit()
expected_gle = [
["_Test Account VAT - _TC", 0.0, 10.0, nowdate()],
["Debtors - _TC", 88, 0.0, nowdate()],
["Discount Account - _TC", 22.0, 0.0, nowdate()],
- ["Sales - _TC", 0.0, 100.0, nowdate()]
+ ["Sales - _TC", 0.0, 100.0, nowdate()],
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
@@ -2279,20 +2696,22 @@ class TestSalesInvoice(unittest.TestCase):
def test_asset_depreciation_on_sale_with_pro_rata(self):
"""
- Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
+ Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
"""
create_asset_data()
asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1)
post_depreciation_entries(getdate("2021-09-30"))
- create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30"))
+ create_sales_invoice(
+ item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30")
+ )
asset.load_from_db()
expected_values = [
["2020-06-30", 1366.12, 1366.12],
["2021-06-30", 20000.0, 21366.12],
- ["2021-09-30", 5041.1, 26407.22]
+ ["2021-09-30", 5041.1, 26407.22],
]
for i, schedule in enumerate(asset.schedules):
@@ -2303,23 +2722,28 @@ class TestSalesInvoice(unittest.TestCase):
def test_asset_depreciation_on_sale_without_pro_rata(self):
"""
- Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale.
+ Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale.
"""
create_asset_data()
- asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1,
- available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3,
- expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1)
+ asset = create_asset(
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date=getdate("2019-12-31"),
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date=getdate("2020-12-31"),
+ submit=1,
+ )
post_depreciation_entries(getdate("2021-09-30"))
- create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-12-31"))
+ create_sales_invoice(
+ item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-12-31")
+ )
asset.load_from_db()
- expected_values = [
- ["2020-12-31", 30000, 30000],
- ["2021-12-31", 30000, 60000]
- ]
+ expected_values = [["2020-12-31", 30000, 30000], ["2021-12-31", 30000, 60000]]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
@@ -2334,7 +2758,9 @@ class TestSalesInvoice(unittest.TestCase):
asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1)
post_depreciation_entries(getdate("2021-09-30"))
- si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30"))
+ si = create_sales_invoice(
+ item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30")
+ )
return_si = make_return_doc("Sales Invoice", si.name)
return_si.submit()
asset.load_from_db()
@@ -2344,8 +2770,8 @@ class TestSalesInvoice(unittest.TestCase):
["2021-06-30", 20000.0, 21366.12, True],
["2022-06-30", 20000.0, 41366.12, False],
["2023-06-30", 20000.0, 61366.12, False],
- ["2024-06-30", 20000.0, 81366.12, False],
- ["2025-06-06", 18633.88, 100000.0, False]
+ ["2024-06-30", 20000.0, 81366.12, False],
+ ["2025-06-06", 18633.88, 100000.0, False],
]
for i, schedule in enumerate(asset.schedules):
@@ -2370,30 +2796,34 @@ class TestSalesInvoice(unittest.TestCase):
party_link = create_party_link("Supplier", supplier, customer)
# enable common party accounting
- frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
+ frappe.db.set_value("Accounts Settings", None, "enable_common_party_accounting", 1)
# create a sales invoice
si = create_sales_invoice(customer=customer, parent_cost_center="_Test Cost Center - _TC")
# check outstanding of sales invoice
si.reload()
- self.assertEqual(si.status, 'Paid')
+ self.assertEqual(si.status, "Paid")
self.assertEqual(flt(si.outstanding_amount), 0.0)
# check creation of journal entry
- jv = frappe.get_all('Journal Entry Account', {
- 'account': si.debit_to,
- 'party_type': 'Customer',
- 'party': si.customer,
- 'reference_type': si.doctype,
- 'reference_name': si.name
- }, pluck='credit_in_account_currency')
+ jv = frappe.get_all(
+ "Journal Entry Account",
+ {
+ "account": si.debit_to,
+ "party_type": "Customer",
+ "party": si.customer,
+ "reference_type": si.doctype,
+ "reference_name": si.name,
+ },
+ pluck="credit_in_account_currency",
+ )
self.assertTrue(jv)
self.assertEqual(jv[0], si.grand_total)
party_link.delete()
- frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0)
+ frappe.db.set_value("Accounts Settings", None, "enable_common_party_accounting", 0)
def test_payment_statuses(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
@@ -2403,16 +2833,14 @@ class TestSalesInvoice(unittest.TestCase):
# Test Overdue
si = create_sales_invoice(do_not_submit=True)
si.payment_schedule = []
- si.append("payment_schedule", {
- "due_date": add_days(today, -5),
- "invoice_portion": 50,
- "payment_amount": si.grand_total / 2
- })
- si.append("payment_schedule", {
- "due_date": add_days(today, 5),
- "invoice_portion": 50,
- "payment_amount": si.grand_total / 2
- })
+ si.append(
+ "payment_schedule",
+ {"due_date": add_days(today, -5), "invoice_portion": 50, "payment_amount": si.grand_total / 2},
+ )
+ si.append(
+ "payment_schedule",
+ {"due_date": add_days(today, 5), "invoice_portion": 50, "payment_amount": si.grand_total / 2},
+ )
si.submit()
self.assertEqual(si.status, "Overdue")
@@ -2451,21 +2879,23 @@ class TestSalesInvoice(unittest.TestCase):
# Sales Invoice with Payment Schedule
si_with_payment_schedule = create_sales_invoice(do_not_submit=True)
- si_with_payment_schedule.extend("payment_schedule", [
- {
- "due_date": add_days(today, -5),
- "invoice_portion": 50,
- "payment_amount": si_with_payment_schedule.grand_total / 2
- },
- {
- "due_date": add_days(today, 5),
- "invoice_portion": 50,
- "payment_amount": si_with_payment_schedule.grand_total / 2
- }
- ])
+ si_with_payment_schedule.extend(
+ "payment_schedule",
+ [
+ {
+ "due_date": add_days(today, -5),
+ "invoice_portion": 50,
+ "payment_amount": si_with_payment_schedule.grand_total / 2,
+ },
+ {
+ "due_date": add_days(today, 5),
+ "invoice_portion": 50,
+ "payment_amount": si_with_payment_schedule.grand_total / 2,
+ },
+ ],
+ )
si_with_payment_schedule.submit()
-
for invoice in (si, si_with_payment_schedule):
invoice.db_set("status", "Unpaid")
update_invoice_status()
@@ -2477,24 +2907,27 @@ class TestSalesInvoice(unittest.TestCase):
invoice.reload()
self.assertEqual(invoice.status, "Overdue and Discounted")
-
def test_sales_commission(self):
si = frappe.copy_doc(test_records[2])
- frappe.db.set_value('Item', si.get('items')[0].item_code, 'grant_commission', 1)
- frappe.db.set_value('Item', si.get('items')[1].item_code, 'grant_commission', 0)
+ frappe.db.set_value("Item", si.get("items")[0].item_code, "grant_commission", 1)
+ frappe.db.set_value("Item", si.get("items")[1].item_code, "grant_commission", 0)
- item = copy.deepcopy(si.get('items')[0])
- item.update({
- "qty": 1,
- "rate": 500,
- })
+ item = copy.deepcopy(si.get("items")[0])
+ item.update(
+ {
+ "qty": 1,
+ "rate": 500,
+ }
+ )
- item = copy.deepcopy(si.get('items')[1])
- item.update({
- "qty": 1,
- "rate": 500,
- })
+ item = copy.deepcopy(si.get("items")[1])
+ item.update(
+ {
+ "qty": 1,
+ "rate": 500,
+ }
+ )
# Test valid values
for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):
@@ -2510,7 +2943,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, si.save)
def test_sales_invoice_submission_post_account_freezing_date(self):
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
+ frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", add_days(getdate(), 1))
si = create_sales_invoice(do_not_save=True)
si.posting_date = add_days(getdate(), 1)
si.save()
@@ -2519,17 +2952,19 @@ class TestSalesInvoice(unittest.TestCase):
si.posting_date = getdate()
si.submit()
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
+ frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_over_billing_case_against_delivery_note(self):
- '''
- Test a case where duplicating the item with qty = 1 in the invoice
- allows overbilling even if it is disabled
- '''
+ """
+ Test a case where duplicating the item with qty = 1 in the invoice
+ allows overbilling even if it is disabled
+ """
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
- over_billing_allowance = frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance')
- frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', 0)
+ over_billing_allowance = frappe.db.get_single_value(
+ "Accounts Settings", "over_billing_allowance"
+ )
+ frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
dn = create_delivery_note()
dn.submit()
@@ -2537,7 +2972,7 @@ class TestSalesInvoice(unittest.TestCase):
si = make_sales_invoice(dn.name)
# make a copy of first item and add it to invoice
item_copy = frappe.copy_doc(si.items[0])
- si.append('items', item_copy)
+ si.append("items", item_copy)
si.save()
with self.assertRaises(frappe.ValidationError) as err:
@@ -2545,13 +2980,16 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue("cannot overbill" in str(err.exception).lower())
- frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
+ frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance)
def test_multi_currency_deferred_revenue_via_journal_entry(self):
- deferred_account = create_account(account_name="Deferred Revenue",
- parent_account="Current Liabilities - _TC", company="_Test Company")
+ deferred_account = create_account(
+ account_name="Deferred Revenue",
+ parent_account="Current Liabilities - _TC",
+ company="_Test Company",
+ )
- acc_settings = frappe.get_single('Accounts Settings')
+ acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 1
acc_settings.submit_journal_entries = 1
acc_settings.save()
@@ -2561,12 +2999,19 @@ class TestSalesInvoice(unittest.TestCase):
item.deferred_revenue_account = deferred_account
item.save()
- si = create_sales_invoice(customer='_Test Customer USD', currency='USD',
- item=item.name, qty=1, rate=100, conversion_rate=60, do_not_save=True)
+ si = create_sales_invoice(
+ customer="_Test Customer USD",
+ currency="USD",
+ item=item.name,
+ qty=1,
+ rate=100,
+ conversion_rate=60,
+ do_not_save=True,
+ )
si.set_posting_time = 1
- si.posting_date = '2019-01-01'
- si.debit_to = '_Test Receivable USD - _TC'
+ si.posting_date = "2019-01-01"
+ si.debit_to = "_Test Receivable USD - _TC"
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-01"
si.items[0].service_end_date = "2019-03-30"
@@ -2574,16 +3019,18 @@ class TestSalesInvoice(unittest.TestCase):
si.save()
si.submit()
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
+ frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", getdate("2019-01-31"))
- pda1 = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date=nowdate(),
- start_date="2019-01-01",
- end_date="2019-03-31",
- type="Income",
- company="_Test Company"
- ))
+ pda1 = frappe.get_doc(
+ dict(
+ doctype="Process Deferred Accounting",
+ posting_date=nowdate(),
+ start_date="2019-01-01",
+ end_date="2019-03-31",
+ type="Income",
+ company="_Test Company",
+ )
+ )
pda1.insert()
pda1.submit()
@@ -2594,13 +3041,17 @@ class TestSalesInvoice(unittest.TestCase):
["Sales - _TC", 0.0, 1887.64, "2019-02-28"],
[deferred_account, 1887.64, 0.0, "2019-02-28"],
["Sales - _TC", 0.0, 2022.47, "2019-03-15"],
- [deferred_account, 2022.47, 0.0, "2019-03-15"]
+ [deferred_account, 2022.47, 0.0, "2019-03-15"],
]
- gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+ gl_entries = gl_entries = frappe.db.sql(
+ """select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
- order by posting_date asc, account asc""", (si.items[0].name, si.posting_date), as_dict=1)
+ order by posting_date asc, account asc""",
+ (si.items[0].name, si.posting_date),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
@@ -2608,152 +3059,164 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][2], gle.debit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
- acc_settings = frappe.get_single('Accounts Settings')
+ acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
+ frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_standalone_serial_no_return(self):
- si = create_sales_invoice(item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1)
+ si = create_sales_invoice(
+ item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
+ )
si.reload()
self.assertTrue(si.items[0].serial_no)
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
- si.naming_series = 'INV-2020-.#####'
+ si.naming_series = "INV-2020-.#####"
si.items = []
- si.append("items", {
- "item_code": "_Test Item",
- "uom": "Nos",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 2000,
- "rate": 12,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
- })
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "uom": "Nos",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 2000,
+ "rate": 12,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
- si.append("items", {
- "item_code": "_Test Item 2",
- "uom": "Nos",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 420,
- "rate": 15,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
- })
+ si.append(
+ "items",
+ {
+ "item_code": "_Test Item 2",
+ "uom": "Nos",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 420,
+ "rate": 15,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
return si
+
def make_test_address_for_ewaybill():
- if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
- address = frappe.get_doc({
- "address_line1": "_Test Address Line 1",
- "address_line2": "_Test Address Line 2",
- "address_title": "_Test Address for Eway bill",
- "address_type": "Billing",
- "city": "_Test City",
- "state": "Test State",
- "country": "India",
- "doctype": "Address",
- "is_primary_address": 1,
- "phone": "+910000000000",
- "gstin": "27AAECE4835E1ZR",
- "gst_state": "Maharashtra",
- "gst_state_number": "27",
- "pincode": "401108"
- }).insert()
+ if not frappe.db.exists("Address", "_Test Address for Eway bill-Billing"):
+ address = frappe.get_doc(
+ {
+ "address_line1": "_Test Address Line 1",
+ "address_line2": "_Test Address Line 2",
+ "address_title": "_Test Address for Eway bill",
+ "address_type": "Billing",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "doctype": "Address",
+ "is_primary_address": 1,
+ "phone": "+910000000000",
+ "gstin": "27AAECE4835E1ZR",
+ "gst_state": "Maharashtra",
+ "gst_state_number": "27",
+ "pincode": "401108",
+ }
+ ).insert()
- address.append("links", {
- "link_doctype": "Company",
- "link_name": "_Test Company"
- })
+ address.append("links", {"link_doctype": "Company", "link_name": "_Test Company"})
address.save()
- if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Billing'):
- address = frappe.get_doc({
- "address_line1": "_Test Address Line 1",
- "address_line2": "_Test Address Line 2",
- "address_title": "_Test Customer-Address for Eway bill",
- "address_type": "Billing",
- "city": "_Test City",
- "state": "Test State",
- "country": "India",
- "doctype": "Address",
- "is_primary_address": 1,
- "phone": "+910000000000",
- "gstin": "27AACCM7806M1Z3",
- "gst_state": "Maharashtra",
- "gst_state_number": "27",
- "pincode": "410038"
- }).insert()
+ if not frappe.db.exists("Address", "_Test Customer-Address for Eway bill-Billing"):
+ address = frappe.get_doc(
+ {
+ "address_line1": "_Test Address Line 1",
+ "address_line2": "_Test Address Line 2",
+ "address_title": "_Test Customer-Address for Eway bill",
+ "address_type": "Billing",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "doctype": "Address",
+ "is_primary_address": 1,
+ "phone": "+910000000000",
+ "gstin": "27AACCM7806M1Z3",
+ "gst_state": "Maharashtra",
+ "gst_state_number": "27",
+ "pincode": "410038",
+ }
+ ).insert()
- address.append("links", {
- "link_doctype": "Customer",
- "link_name": "_Test Customer"
- })
+ address.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
address.save()
- if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
- address = frappe.get_doc({
- "address_line1": "_Test Address Line 1",
- "address_line2": "_Test Address Line 2",
- "address_title": "_Test Customer-Address for Eway bill",
- "address_type": "Shipping",
- "city": "_Test City",
- "state": "Test State",
- "country": "India",
- "doctype": "Address",
- "is_primary_address": 1,
- "phone": "+910000000000",
- "gst_state": "Maharashtra",
- "gst_state_number": "27",
- "pincode": "410098"
- }).insert()
+ if not frappe.db.exists("Address", "_Test Customer-Address for Eway bill-Shipping"):
+ address = frappe.get_doc(
+ {
+ "address_line1": "_Test Address Line 1",
+ "address_line2": "_Test Address Line 2",
+ "address_title": "_Test Customer-Address for Eway bill",
+ "address_type": "Shipping",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "doctype": "Address",
+ "is_primary_address": 1,
+ "phone": "+910000000000",
+ "gst_state": "Maharashtra",
+ "gst_state_number": "27",
+ "pincode": "410098",
+ }
+ ).insert()
- address.append("links", {
- "link_doctype": "Customer",
- "link_name": "_Test Customer"
- })
+ address.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
address.save()
- if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'):
- address = frappe.get_doc({
- "address_line1": "_Test Dispatch Address Line 1",
- "address_line2": "_Test Dispatch Address Line 2",
- "address_title": "_Test Dispatch-Address for Eway bill",
- "address_type": "Shipping",
- "city": "_Test City",
- "state": "Test State",
- "country": "India",
- "doctype": "Address",
- "is_primary_address": 0,
- "phone": "+910000000000",
- "gstin": "07AAACC1206D1ZI",
- "gst_state": "Delhi",
- "gst_state_number": "07",
- "pincode": "1100101"
- }).insert()
+ if not frappe.db.exists("Address", "_Test Dispatch-Address for Eway bill-Shipping"):
+ address = frappe.get_doc(
+ {
+ "address_line1": "_Test Dispatch Address Line 1",
+ "address_line2": "_Test Dispatch Address Line 2",
+ "address_title": "_Test Dispatch-Address for Eway bill",
+ "address_type": "Shipping",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "doctype": "Address",
+ "is_primary_address": 0,
+ "phone": "+910000000000",
+ "gstin": "07AAACC1206D1ZI",
+ "gst_state": "Delhi",
+ "gst_state_number": "07",
+ "pincode": "1100101",
+ }
+ ).insert()
address.save()
+
def make_test_transporter_for_ewaybill():
- if not frappe.db.exists('Supplier', '_Test Transporter'):
- frappe.get_doc({
- "doctype": "Supplier",
- "supplier_name": "_Test Transporter",
- "country": "India",
- "supplier_group": "_Test Supplier Group",
- "supplier_type": "Company",
- "is_transporter": 1
- }).insert()
+ if not frappe.db.exists("Supplier", "_Test Transporter"):
+ frappe.get_doc(
+ {
+ "doctype": "Supplier",
+ "supplier_name": "_Test Transporter",
+ "country": "India",
+ "supplier_group": "_Test Supplier Group",
+ "supplier_type": "Company",
+ "is_transporter": 1,
+ }
+ ).insert()
+
def make_sales_invoice_for_ewaybill():
make_test_address_for_ewaybill()
@@ -2764,20 +3227,23 @@ def make_sales_invoice_for_ewaybill():
gst_account = frappe.get_all(
"GST Account",
fields=["cgst_account", "sgst_account", "igst_account"],
- filters = {"company": "_Test Company"}
+ filters={"company": "_Test Company"},
)
if not gst_account:
- gst_settings.append("gst_accounts", {
- "company": "_Test Company",
- "cgst_account": "Output Tax CGST - _TC",
- "sgst_account": "Output Tax SGST - _TC",
- "igst_account": "Output Tax IGST - _TC",
- })
+ gst_settings.append(
+ "gst_accounts",
+ {
+ "company": "_Test Company",
+ "cgst_account": "Output Tax CGST - _TC",
+ "sgst_account": "Output Tax SGST - _TC",
+ "igst_account": "Output Tax IGST - _TC",
+ },
+ )
gst_settings.save()
- si = create_sales_invoice(do_not_save=1, rate='60000')
+ si = create_sales_invoice(do_not_save=1, rate="60000")
si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing"
@@ -2786,32 +3252,43 @@ def make_sales_invoice_for_ewaybill():
si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping"
si.vehicle_no = "KA12KA1234"
si.gst_category = "Registered Regular"
- si.mode_of_transport = 'Road'
- si.transporter = '_Test Transporter'
+ si.mode_of_transport = "Road"
+ si.transporter = "_Test Transporter"
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": "Output Tax CGST - _TC",
- "cost_center": "Main - _TC",
- "description": "CGST @ 9.0",
- "rate": 9
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "Output Tax CGST - _TC",
+ "cost_center": "Main - _TC",
+ "description": "CGST @ 9.0",
+ "rate": 9,
+ },
+ )
- si.append("taxes", {
- "charge_type": "On Net Total",
- "account_head": "Output Tax SGST - _TC",
- "cost_center": "Main - _TC",
- "description": "SGST @ 9.0",
- "rate": 9
- })
+ si.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "Output Tax SGST - _TC",
+ "cost_center": "Main - _TC",
+ "description": "SGST @ 9.0",
+ "rate": 9,
+ },
+ )
return si
+
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
- gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
- order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
+ order by posting_date asc, account asc""",
+ (voucher_no, posting_date),
+ as_dict=1,
+ )
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
@@ -2819,6 +3296,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
doc.assertEqual(expected_gle[i][2], gle.credit)
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
+
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)
@@ -2833,32 +3311,35 @@ def create_sales_invoice(**args):
si.is_pos = args.is_pos
si.is_return = args.is_return
si.return_against = args.return_against
- si.currency=args.currency or "INR"
+ si.currency = args.currency or "INR"
si.conversion_rate = args.conversion_rate or 1
si.naming_series = args.naming_series or "T-SINV-"
si.cost_center = args.parent_cost_center
- si.append("items", {
- "item_code": args.item or args.item_code or "_Test Item",
- "item_name": args.item_name or "_Test Item",
- "description": args.description or "_Test Item",
- "gst_hsn_code": "999800",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 1,
- "uom": args.uom or "Nos",
- "stock_uom": args.uom or "Nos",
- "rate": args.rate if args.get("rate") is not None else 100,
- "price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100,
- "income_account": args.income_account or "Sales - _TC",
- "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
- "discount_account": args.discount_account or None,
- "discount_amount": args.discount_amount or 0,
- "asset": args.asset or None,
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "serial_no": args.serial_no,
- "conversion_factor": 1,
- "incoming_rate": args.incoming_rate or 0
- })
+ si.append(
+ "items",
+ {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "item_name": args.item_name or "_Test Item",
+ "description": args.description or "_Test Item",
+ "gst_hsn_code": "999800",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 1,
+ "uom": args.uom or "Nos",
+ "stock_uom": args.uom or "Nos",
+ "rate": args.rate if args.get("rate") is not None else 100,
+ "price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100,
+ "income_account": args.income_account or "Sales - _TC",
+ "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
+ "discount_account": args.discount_account or None,
+ "discount_amount": args.discount_amount or 0,
+ "asset": args.asset or None,
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "serial_no": args.serial_no,
+ "conversion_factor": 1,
+ "incoming_rate": args.incoming_rate or 0,
+ },
+ )
if not args.do_not_save:
si.insert()
@@ -2871,6 +3352,7 @@ def create_sales_invoice(**args):
return si
+
def create_sales_invoice_against_cost_center(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)
@@ -2886,20 +3368,23 @@ def create_sales_invoice_against_cost_center(**args):
si.is_pos = args.is_pos
si.is_return = args.is_return
si.return_against = args.return_against
- si.currency=args.currency or "INR"
+ si.currency = args.currency or "INR"
si.conversion_rate = args.conversion_rate or 1
- si.append("items", {
- "item_code": args.item or args.item_code or "_Test Item",
- "gst_hsn_code": "999800",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 1,
- "rate": args.rate or 100,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "serial_no": args.serial_no
- })
+ si.append(
+ "items",
+ {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "gst_hsn_code": "999800",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 1,
+ "rate": args.rate or 100,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "serial_no": args.serial_no,
+ },
+ )
if not args.do_not_save:
si.insert()
@@ -2914,59 +3399,69 @@ def create_sales_invoice_against_cost_center(**args):
test_dependencies = ["Journal Entry", "Contact", "Address"]
-test_records = frappe.get_test_records('Sales Invoice')
+test_records = frappe.get_test_records("Sales Invoice")
+
def get_outstanding_amount(against_voucher_type, against_voucher, account, party, party_type):
- bal = flt(frappe.db.sql("""
+ bal = flt(
+ frappe.db.sql(
+ """
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry`
where against_voucher_type=%s and against_voucher=%s
and account = %s and party = %s and party_type = %s""",
- (against_voucher_type, against_voucher, account, party, party_type))[0][0] or 0.0)
+ (against_voucher_type, against_voucher, account, party, party_type),
+ )[0][0]
+ or 0.0
+ )
- if against_voucher_type == 'Purchase Invoice':
+ if against_voucher_type == "Purchase Invoice":
bal = bal * -1
return bal
+
def get_taxes_and_charges():
- return [{
- "account_head": "_Test Account Excise Duty - TCP1",
- "charge_type": "On Net Total",
- "cost_center": "Main - TCP1",
- "description": "Excise Duty",
- "doctype": "Sales Taxes and Charges",
- "idx": 1,
- "included_in_print_rate": 1,
- "parentfield": "taxes",
- "rate": 12
- },
- {
- "account_head": "_Test Account Education Cess - TCP1",
- "charge_type": "On Previous Row Amount",
- "cost_center": "Main - TCP1",
- "description": "Education Cess",
- "doctype": "Sales Taxes and Charges",
- "idx": 2,
- "included_in_print_rate": 1,
- "parentfield": "taxes",
- "rate": 2,
- "row_id": 1
- }]
+ return [
+ {
+ "account_head": "_Test Account Excise Duty - TCP1",
+ "charge_type": "On Net Total",
+ "cost_center": "Main - TCP1",
+ "description": "Excise Duty",
+ "doctype": "Sales Taxes and Charges",
+ "idx": 1,
+ "included_in_print_rate": 1,
+ "parentfield": "taxes",
+ "rate": 12,
+ },
+ {
+ "account_head": "_Test Account Education Cess - TCP1",
+ "charge_type": "On Previous Row Amount",
+ "cost_center": "Main - TCP1",
+ "description": "Education Cess",
+ "doctype": "Sales Taxes and Charges",
+ "idx": 2,
+ "included_in_print_rate": 1,
+ "parentfield": "taxes",
+ "rate": 2,
+ "row_id": 1,
+ },
+ ]
+
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
- supplier = frappe.get_doc({
- "supplier_group": "_Test Supplier Group",
- "supplier_name": supplier_name,
- "doctype": "Supplier",
- "is_internal_supplier": 1,
- "represents_company": represents_company
- })
+ supplier = frappe.get_doc(
+ {
+ "supplier_group": "_Test Supplier Group",
+ "supplier_name": supplier_name,
+ "doctype": "Supplier",
+ "is_internal_supplier": 1,
+ "represents_company": represents_company,
+ }
+ )
- supplier.append("companies", {
- "company": allowed_to_interact_with
- })
+ supplier.append("companies", {"company": allowed_to_interact_with})
supplier.insert()
supplier_name = supplier.name
@@ -2975,11 +3470,15 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
return supplier_name
+
def add_taxes(doc):
- doc.append('taxes', {
- 'account_head': '_Test Account Excise Duty - TCP1',
- "charge_type": "On Net Total",
- "cost_center": "Main - TCP1",
- "description": "Excise Duty",
- "rate": 12
- })
+ doc.append(
+ "taxes",
+ {
+ "account_head": "_Test Account Excise Duty - TCP1",
+ "charge_type": "On Net Total",
+ "cost_center": "Main - TCP1",
+ "description": "Excise Duty",
+ "rate": 12,
+ },
+ )
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
index 8043a1b66f..d9009bae4c 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
@@ -21,13 +21,14 @@ class SalesTaxesandChargesTemplate(Document):
def autoname(self):
if self.company and self.title:
- abbr = frappe.get_cached_value('Company', self.company, 'abbr')
- self.name = '{0} - {1}'.format(self.title, abbr)
+ abbr = frappe.get_cached_value("Company", self.company, "abbr")
+ self.name = "{0} - {1}".format(self.title, abbr)
def set_missing_values(self):
for data in self.taxes:
- if data.charge_type == 'On Net Total' and flt(data.rate) == 0.0:
- data.rate = frappe.db.get_value('Account', data.account_head, 'tax_rate')
+ if data.charge_type == "On Net Total" and flt(data.rate) == 0.0:
+ data.rate = frappe.db.get_value("Account", data.account_head, "tax_rate")
+
def valdiate_taxes_and_charges_template(doc):
# default should not be disabled
@@ -35,9 +36,13 @@ def valdiate_taxes_and_charges_template(doc):
# doc.is_default = 1
if doc.is_default == 1:
- frappe.db.sql("""update `tab{0}` set is_default = 0
- where is_default = 1 and name != %s and company = %s""".format(doc.doctype),
- (doc.name, doc.company))
+ frappe.db.sql(
+ """update `tab{0}` set is_default = 0
+ where is_default = 1 and name != %s and company = %s""".format(
+ doc.doctype
+ ),
+ (doc.name, doc.company),
+ )
validate_disabled(doc)
@@ -50,13 +55,27 @@ def valdiate_taxes_and_charges_template(doc):
validate_cost_center(tax, doc)
validate_inclusive_tax(tax, doc)
+
def validate_disabled(doc):
if doc.is_default and doc.disabled:
frappe.throw(_("Disabled template must not be default template"))
+
def validate_for_tax_category(doc):
if not doc.tax_category:
return
- if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
- frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
+ if frappe.db.exists(
+ doc.doctype,
+ {
+ "company": doc.company,
+ "tax_category": doc.tax_category,
+ "disabled": 0,
+ "name": ["!=", doc.name],
+ },
+ ):
+ frappe.throw(
+ _(
+ "A template with tax category {0} already exists. Only one template is allowed with each tax category"
+ ).format(frappe.bold(doc.tax_category))
+ )
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py
index bc1fd8ec32..6432acaae9 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py
@@ -3,20 +3,14 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'taxes_and_charges',
- 'non_standard_fieldnames': {
- 'Tax Rule': 'sales_tax_template',
- 'Subscription': 'sales_tax_template',
- 'Restaurant': 'default_tax_template'
+ "fieldname": "taxes_and_charges",
+ "non_standard_fieldnames": {
+ "Tax Rule": "sales_tax_template",
+ "Subscription": "sales_tax_template",
+ "Restaurant": "default_tax_template",
},
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Sales Invoice', 'Sales Order', 'Delivery Note']
- },
- {
- 'label': _('References'),
- 'items': ['POS Profile', 'Subscription', 'Restaurant', 'Tax Rule']
- }
- ]
+ "transactions": [
+ {"label": _("Transactions"), "items": ["Sales Invoice", "Sales Order", "Delivery Note"]},
+ {"label": _("References"), "items": ["POS Profile", "Subscription", "Restaurant", "Tax Rule"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py
index 7b13c6c692..972b773501 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py
@@ -5,7 +5,8 @@ import unittest
import frappe
-test_records = frappe.get_test_records('Sales Taxes and Charges Template')
+test_records = frappe.get_test_records("Sales Taxes and Charges Template")
+
class TestSalesTaxesandChargesTemplate(unittest.TestCase):
pass
diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.py b/erpnext/accounts/doctype/share_transfer/share_transfer.py
index b543ad8204..4f49843c1e 100644
--- a/erpnext/accounts/doctype/share_transfer/share_transfer.py
+++ b/erpnext/accounts/doctype/share_transfer/share_transfer.py
@@ -10,95 +10,115 @@ from frappe.model.naming import make_autoname
from frappe.utils import nowdate
-class ShareDontExists(ValidationError): pass
+class ShareDontExists(ValidationError):
+ pass
+
class ShareTransfer(Document):
def on_submit(self):
- if self.transfer_type == 'Issue':
+ if self.transfer_type == "Issue":
shareholder = self.get_company_shareholder()
- shareholder.append('share_balance', {
- 'share_type': self.share_type,
- 'from_no': self.from_no,
- 'to_no': self.to_no,
- 'rate': self.rate,
- 'amount': self.amount,
- 'no_of_shares': self.no_of_shares,
- 'is_company': 1,
- 'current_state': 'Issued'
- })
+ shareholder.append(
+ "share_balance",
+ {
+ "share_type": self.share_type,
+ "from_no": self.from_no,
+ "to_no": self.to_no,
+ "rate": self.rate,
+ "amount": self.amount,
+ "no_of_shares": self.no_of_shares,
+ "is_company": 1,
+ "current_state": "Issued",
+ },
+ )
shareholder.save()
doc = self.get_shareholder_doc(self.to_shareholder)
- doc.append('share_balance', {
- 'share_type': self.share_type,
- 'from_no': self.from_no,
- 'to_no': self.to_no,
- 'rate': self.rate,
- 'amount': self.amount,
- 'no_of_shares': self.no_of_shares
- })
+ doc.append(
+ "share_balance",
+ {
+ "share_type": self.share_type,
+ "from_no": self.from_no,
+ "to_no": self.to_no,
+ "rate": self.rate,
+ "amount": self.amount,
+ "no_of_shares": self.no_of_shares,
+ },
+ )
doc.save()
- elif self.transfer_type == 'Purchase':
+ elif self.transfer_type == "Purchase":
self.remove_shares(self.from_shareholder)
self.remove_shares(self.get_company_shareholder().name)
- elif self.transfer_type == 'Transfer':
+ elif self.transfer_type == "Transfer":
self.remove_shares(self.from_shareholder)
doc = self.get_shareholder_doc(self.to_shareholder)
- doc.append('share_balance', {
- 'share_type': self.share_type,
- 'from_no': self.from_no,
- 'to_no': self.to_no,
- 'rate': self.rate,
- 'amount': self.amount,
- 'no_of_shares': self.no_of_shares
- })
+ doc.append(
+ "share_balance",
+ {
+ "share_type": self.share_type,
+ "from_no": self.from_no,
+ "to_no": self.to_no,
+ "rate": self.rate,
+ "amount": self.amount,
+ "no_of_shares": self.no_of_shares,
+ },
+ )
doc.save()
def on_cancel(self):
- if self.transfer_type == 'Issue':
+ if self.transfer_type == "Issue":
compnay_shareholder = self.get_company_shareholder()
self.remove_shares(compnay_shareholder.name)
self.remove_shares(self.to_shareholder)
- elif self.transfer_type == 'Purchase':
+ elif self.transfer_type == "Purchase":
compnay_shareholder = self.get_company_shareholder()
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
- from_shareholder.append('share_balance', {
- 'share_type': self.share_type,
- 'from_no': self.from_no,
- 'to_no': self.to_no,
- 'rate': self.rate,
- 'amount': self.amount,
- 'no_of_shares': self.no_of_shares
- })
+ from_shareholder.append(
+ "share_balance",
+ {
+ "share_type": self.share_type,
+ "from_no": self.from_no,
+ "to_no": self.to_no,
+ "rate": self.rate,
+ "amount": self.amount,
+ "no_of_shares": self.no_of_shares,
+ },
+ )
from_shareholder.save()
- compnay_shareholder.append('share_balance', {
- 'share_type': self.share_type,
- 'from_no': self.from_no,
- 'to_no': self.to_no,
- 'rate': self.rate,
- 'amount': self.amount,
- 'no_of_shares': self.no_of_shares
- })
+ compnay_shareholder.append(
+ "share_balance",
+ {
+ "share_type": self.share_type,
+ "from_no": self.from_no,
+ "to_no": self.to_no,
+ "rate": self.rate,
+ "amount": self.amount,
+ "no_of_shares": self.no_of_shares,
+ },
+ )
compnay_shareholder.save()
- elif self.transfer_type == 'Transfer':
+ elif self.transfer_type == "Transfer":
self.remove_shares(self.to_shareholder)
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
- from_shareholder.append('share_balance', {
- 'share_type': self.share_type,
- 'from_no': self.from_no,
- 'to_no': self.to_no,
- 'rate': self.rate,
- 'amount': self.amount,
- 'no_of_shares': self.no_of_shares
- })
+ from_shareholder.append(
+ "share_balance",
+ {
+ "share_type": self.share_type,
+ "from_no": self.from_no,
+ "to_no": self.to_no,
+ "rate": self.rate,
+ "amount": self.amount,
+ "no_of_shares": self.no_of_shares,
+ },
+ )
from_shareholder.save()
def validate(self):
@@ -106,90 +126,96 @@ class ShareTransfer(Document):
self.basic_validations()
self.folio_no_validation()
- if self.transfer_type == 'Issue':
+ if self.transfer_type == "Issue":
# validate share doesn't exist in company
ret_val = self.share_exists(self.get_company_shareholder().name)
- if ret_val in ('Complete', 'Partial'):
- frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError)
+ if ret_val in ("Complete", "Partial"):
+ frappe.throw(_("The shares already exist"), frappe.DuplicateEntryError)
else:
# validate share exists with from_shareholder
ret_val = self.share_exists(self.from_shareholder)
- if ret_val in ('Outside', 'Partial'):
- frappe.throw(_("The shares don't exist with the {0}")
- .format(self.from_shareholder), ShareDontExists)
+ if ret_val in ("Outside", "Partial"):
+ frappe.throw(
+ _("The shares don't exist with the {0}").format(self.from_shareholder), ShareDontExists
+ )
def basic_validations(self):
- if self.transfer_type == 'Purchase':
- self.to_shareholder = ''
+ if self.transfer_type == "Purchase":
+ self.to_shareholder = ""
if not self.from_shareholder:
- frappe.throw(_('The field From Shareholder cannot be blank'))
+ frappe.throw(_("The field From Shareholder cannot be blank"))
if not self.from_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
if not self.asset_account:
- frappe.throw(_('The field Asset Account cannot be blank'))
- elif (self.transfer_type == 'Issue'):
- self.from_shareholder = ''
+ frappe.throw(_("The field Asset Account cannot be blank"))
+ elif self.transfer_type == "Issue":
+ self.from_shareholder = ""
if not self.to_shareholder:
- frappe.throw(_('The field To Shareholder cannot be blank'))
+ frappe.throw(_("The field To Shareholder cannot be blank"))
if not self.to_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
if not self.asset_account:
- frappe.throw(_('The field Asset Account cannot be blank'))
+ frappe.throw(_("The field Asset Account cannot be blank"))
else:
if not self.from_shareholder or not self.to_shareholder:
- frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
+ frappe.throw(_("The fields From Shareholder and To Shareholder cannot be blank"))
if not self.to_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
if not self.equity_or_liability_account:
- frappe.throw(_('The field Equity/Liability Account cannot be blank'))
+ frappe.throw(_("The field Equity/Liability Account cannot be blank"))
if self.from_shareholder == self.to_shareholder:
- frappe.throw(_('The seller and the buyer cannot be the same'))
+ frappe.throw(_("The seller and the buyer cannot be the same"))
if self.no_of_shares != self.to_no - self.from_no + 1:
- frappe.throw(_('The number of shares and the share numbers are inconsistent'))
+ frappe.throw(_("The number of shares and the share numbers are inconsistent"))
if not self.amount:
self.amount = self.rate * self.no_of_shares
if self.amount != self.rate * self.no_of_shares:
- frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated'))
+ frappe.throw(
+ _("There are inconsistencies between the rate, no of shares and the amount calculated")
+ )
def share_exists(self, shareholder):
doc = self.get_shareholder_doc(shareholder)
for entry in doc.share_balance:
- if entry.share_type != self.share_type or \
- entry.from_no > self.to_no or \
- entry.to_no < self.from_no:
- continue # since query lies outside bounds
- elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #both inside
- return 'Complete' # absolute truth!
+ if (
+ entry.share_type != self.share_type or entry.from_no > self.to_no or entry.to_no < self.from_no
+ ):
+ continue # since query lies outside bounds
+ elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: # both inside
+ return "Complete" # absolute truth!
elif entry.from_no <= self.from_no <= self.to_no:
- return 'Partial'
+ return "Partial"
elif entry.from_no <= self.to_no <= entry.to_no:
- return 'Partial'
+ return "Partial"
- return 'Outside'
+ return "Outside"
def folio_no_validation(self):
- shareholder_fields = ['from_shareholder', 'to_shareholder']
+ shareholder_fields = ["from_shareholder", "to_shareholder"]
for shareholder_field in shareholder_fields:
shareholder_name = self.get(shareholder_field)
if not shareholder_name:
continue
doc = self.get_shareholder_doc(shareholder_name)
if doc.company != self.company:
- frappe.throw(_('The shareholder does not belong to this company'))
+ frappe.throw(_("The shareholder does not belong to this company"))
if not doc.folio_no:
- doc.folio_no = self.from_folio_no \
- if (shareholder_field == 'from_shareholder') else self.to_folio_no
+ doc.folio_no = (
+ self.from_folio_no if (shareholder_field == "from_shareholder") else self.to_folio_no
+ )
doc.save()
else:
- if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder_field == 'from_shareholder') else self.to_folio_no):
- frappe.throw(_('The folio numbers are not matching'))
+ if doc.folio_no and doc.folio_no != (
+ self.from_folio_no if (shareholder_field == "from_shareholder") else self.to_folio_no
+ ):
+ frappe.throw(_("The folio numbers are not matching"))
def autoname_folio(self, shareholder, is_company=False):
if is_company:
doc = self.get_company_shareholder()
else:
doc = self.get_shareholder_doc(shareholder)
- doc.folio_no = make_autoname('FN.#####')
+ doc.folio_no = make_autoname("FN.#####")
doc.save()
return doc.folio_no
@@ -197,106 +223,120 @@ class ShareTransfer(Document):
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
# Shares exist for sure
# Iterate over all entries and modify entry if in entry
- doc = frappe.get_doc('Shareholder', shareholder)
+ doc = frappe.get_doc("Shareholder", shareholder)
current_entries = doc.share_balance
new_entries = []
for entry in current_entries:
# use spaceage logic here
- if entry.share_type != self.share_type or \
- entry.from_no > self.to_no or \
- entry.to_no < self.from_no:
+ if (
+ entry.share_type != self.share_type or entry.from_no > self.to_no or entry.to_no < self.from_no
+ ):
new_entries.append(entry)
- continue # since query lies outside bounds
+ continue # since query lies outside bounds
elif entry.from_no <= self.from_no and entry.to_no >= self.to_no:
- #split
+ # split
if entry.from_no == self.from_no:
if entry.to_no == self.to_no:
- pass #nothing to append
+ pass # nothing to append
else:
- new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
+ new_entries.append(self.return_share_balance_entry(self.to_no + 1, entry.to_no, entry.rate))
else:
if entry.to_no == self.to_no:
- new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
+ new_entries.append(
+ self.return_share_balance_entry(entry.from_no, self.from_no - 1, entry.rate)
+ )
else:
- new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
- new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
+ new_entries.append(
+ self.return_share_balance_entry(entry.from_no, self.from_no - 1, entry.rate)
+ )
+ new_entries.append(self.return_share_balance_entry(self.to_no + 1, entry.to_no, entry.rate))
elif entry.from_no >= self.from_no and entry.to_no <= self.to_no:
# split and check
- pass #nothing to append
+ pass # nothing to append
elif self.from_no <= entry.from_no <= self.to_no and entry.to_no >= self.to_no:
- new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
+ new_entries.append(self.return_share_balance_entry(self.to_no + 1, entry.to_no, entry.rate))
elif self.from_no <= entry.to_no <= self.to_no and entry.from_no <= self.from_no:
- new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
+ new_entries.append(
+ self.return_share_balance_entry(entry.from_no, self.from_no - 1, entry.rate)
+ )
else:
new_entries.append(entry)
doc.share_balance = []
for entry in new_entries:
- doc.append('share_balance', entry)
+ doc.append("share_balance", entry)
doc.save()
def return_share_balance_entry(self, from_no, to_no, rate):
# return an entry as a dict
return {
- 'share_type' : self.share_type,
- 'from_no' : from_no,
- 'to_no' : to_no,
- 'rate' : rate,
- 'amount' : self.rate * (to_no - from_no + 1),
- 'no_of_shares' : to_no - from_no + 1
+ "share_type": self.share_type,
+ "from_no": from_no,
+ "to_no": to_no,
+ "rate": rate,
+ "amount": self.rate * (to_no - from_no + 1),
+ "no_of_shares": to_no - from_no + 1,
}
def get_shareholder_doc(self, shareholder):
# Get Shareholder doc based on the Shareholder name
if shareholder:
- query_filters = {'name': shareholder}
+ query_filters = {"name": shareholder}
- name = frappe.db.get_value('Shareholder', {'name': shareholder}, 'name')
+ name = frappe.db.get_value("Shareholder", {"name": shareholder}, "name")
- return frappe.get_doc('Shareholder', name)
+ return frappe.get_doc("Shareholder", name)
def get_company_shareholder(self):
# Get company doc or create one if not present
- company_shareholder = frappe.db.get_value('Shareholder',
- {
- 'company': self.company,
- 'is_company': 1
- }, 'name')
+ company_shareholder = frappe.db.get_value(
+ "Shareholder", {"company": self.company, "is_company": 1}, "name"
+ )
if company_shareholder:
- return frappe.get_doc('Shareholder', company_shareholder)
+ return frappe.get_doc("Shareholder", company_shareholder)
else:
- shareholder = frappe.get_doc({
- 'doctype': 'Shareholder',
- 'title': self.company,
- 'company': self.company,
- 'is_company': 1
- })
+ shareholder = frappe.get_doc(
+ {"doctype": "Shareholder", "title": self.company, "company": self.company, "is_company": 1}
+ )
shareholder.insert()
return shareholder
+
@frappe.whitelist()
-def make_jv_entry( company, account, amount, payment_account,\
- credit_applicant_type, credit_applicant, debit_applicant_type, debit_applicant):
- journal_entry = frappe.new_doc('Journal Entry')
- journal_entry.voucher_type = 'Journal Entry'
+def make_jv_entry(
+ company,
+ account,
+ amount,
+ payment_account,
+ credit_applicant_type,
+ credit_applicant,
+ debit_applicant_type,
+ debit_applicant,
+):
+ journal_entry = frappe.new_doc("Journal Entry")
+ journal_entry.voucher_type = "Journal Entry"
journal_entry.company = company
journal_entry.posting_date = nowdate()
account_amt_list = []
- account_amt_list.append({
- "account": account,
- "debit_in_account_currency": amount,
- "party_type": debit_applicant_type,
- "party": debit_applicant,
- })
- account_amt_list.append({
- "account": payment_account,
- "credit_in_account_currency": amount,
- "party_type": credit_applicant_type,
- "party": credit_applicant,
- })
+ account_amt_list.append(
+ {
+ "account": account,
+ "debit_in_account_currency": amount,
+ "party_type": debit_applicant_type,
+ "party": debit_applicant,
+ }
+ )
+ account_amt_list.append(
+ {
+ "account": payment_account,
+ "credit_in_account_currency": amount,
+ "party_type": credit_applicant_type,
+ "party": credit_applicant,
+ }
+ )
journal_entry.set("accounts", account_amt_list)
return journal_entry.as_dict()
diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py
index bc3a52167d..9731074360 100644
--- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py
+++ b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py
@@ -9,6 +9,7 @@ from erpnext.accounts.doctype.share_transfer.share_transfer import ShareDontExis
test_dependencies = ["Share Type", "Shareholder"]
+
class TestShareTransfer(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabShare Transfer`")
@@ -26,7 +27,7 @@ class TestShareTransfer(unittest.TestCase):
"rate": 10,
"company": "_Test Company",
"asset_account": "Cash - _TC",
- "equity_or_liability_account": "Creditors - _TC"
+ "equity_or_liability_account": "Creditors - _TC",
},
{
"doctype": "Share Transfer",
@@ -40,7 +41,7 @@ class TestShareTransfer(unittest.TestCase):
"no_of_shares": 100,
"rate": 15,
"company": "_Test Company",
- "equity_or_liability_account": "Creditors - _TC"
+ "equity_or_liability_account": "Creditors - _TC",
},
{
"doctype": "Share Transfer",
@@ -54,7 +55,7 @@ class TestShareTransfer(unittest.TestCase):
"no_of_shares": 300,
"rate": 20,
"company": "_Test Company",
- "equity_or_liability_account": "Creditors - _TC"
+ "equity_or_liability_account": "Creditors - _TC",
},
{
"doctype": "Share Transfer",
@@ -68,7 +69,7 @@ class TestShareTransfer(unittest.TestCase):
"no_of_shares": 200,
"rate": 15,
"company": "_Test Company",
- "equity_or_liability_account": "Creditors - _TC"
+ "equity_or_liability_account": "Creditors - _TC",
},
{
"doctype": "Share Transfer",
@@ -82,42 +83,46 @@ class TestShareTransfer(unittest.TestCase):
"rate": 25,
"company": "_Test Company",
"asset_account": "Cash - _TC",
- "equity_or_liability_account": "Creditors - _TC"
- }
+ "equity_or_liability_account": "Creditors - _TC",
+ },
]
for d in share_transfers:
st = frappe.get_doc(d)
st.submit()
def test_invalid_share_transfer(self):
- doc = frappe.get_doc({
- "doctype": "Share Transfer",
- "transfer_type": "Transfer",
- "date": "2018-01-05",
- "from_shareholder": "SH-00003",
- "to_shareholder": "SH-00002",
- "share_type": "Equity",
- "from_no": 1,
- "to_no": 100,
- "no_of_shares": 100,
- "rate": 15,
- "company": "_Test Company",
- "equity_or_liability_account": "Creditors - _TC"
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Share Transfer",
+ "transfer_type": "Transfer",
+ "date": "2018-01-05",
+ "from_shareholder": "SH-00003",
+ "to_shareholder": "SH-00002",
+ "share_type": "Equity",
+ "from_no": 1,
+ "to_no": 100,
+ "no_of_shares": 100,
+ "rate": 15,
+ "company": "_Test Company",
+ "equity_or_liability_account": "Creditors - _TC",
+ }
+ )
self.assertRaises(ShareDontExists, doc.insert)
- doc = frappe.get_doc({
- "doctype": "Share Transfer",
- "transfer_type": "Purchase",
- "date": "2018-01-02",
- "from_shareholder": "SH-00001",
- "share_type": "Equity",
- "from_no": 1,
- "to_no": 200,
- "no_of_shares": 200,
- "rate": 15,
- "company": "_Test Company",
- "asset_account": "Cash - _TC",
- "equity_or_liability_account": "Creditors - _TC"
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Share Transfer",
+ "transfer_type": "Purchase",
+ "date": "2018-01-02",
+ "from_shareholder": "SH-00001",
+ "share_type": "Equity",
+ "from_no": 1,
+ "to_no": 200,
+ "no_of_shares": 200,
+ "rate": 15,
+ "company": "_Test Company",
+ "asset_account": "Cash - _TC",
+ "equity_or_liability_account": "Creditors - _TC",
+ }
+ )
self.assertRaises(ShareDontExists, doc.insert)
diff --git a/erpnext/accounts/doctype/share_type/share_type_dashboard.py b/erpnext/accounts/doctype/share_type/share_type_dashboard.py
index d5551d1247..19604b332a 100644
--- a/erpnext/accounts/doctype/share_type/share_type_dashboard.py
+++ b/erpnext/accounts/doctype/share_type/share_type_dashboard.py
@@ -3,11 +3,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'share_type',
- 'transactions': [
- {
- 'label': _('References'),
- 'items': ['Share Transfer', 'Shareholder']
- }
- ]
+ "fieldname": "share_type",
+ "transactions": [{"label": _("References"), "items": ["Share Transfer", "Shareholder"]}],
}
diff --git a/erpnext/accounts/doctype/shareholder/shareholder.py b/erpnext/accounts/doctype/shareholder/shareholder.py
index 8a0fa85a69..b0e2493f7a 100644
--- a/erpnext/accounts/doctype/shareholder/shareholder.py
+++ b/erpnext/accounts/doctype/shareholder/shareholder.py
@@ -15,7 +15,7 @@ class Shareholder(Document):
load_address_and_contact(self)
def on_trash(self):
- delete_contact_and_address('Shareholder', self.name)
+ delete_contact_and_address("Shareholder", self.name)
def before_save(self):
for entry in self.share_balance:
diff --git a/erpnext/accounts/doctype/shareholder/shareholder_dashboard.py b/erpnext/accounts/doctype/shareholder/shareholder_dashboard.py
index c01ac23f1e..fa9d431c19 100644
--- a/erpnext/accounts/doctype/shareholder/shareholder_dashboard.py
+++ b/erpnext/accounts/doctype/shareholder/shareholder_dashboard.py
@@ -1,12 +1,6 @@
def get_data():
return {
- 'fieldname': 'shareholder',
- 'non_standard_fieldnames': {
- 'Share Transfer': 'to_shareholder'
- },
- 'transactions': [
- {
- 'items': ['Share Transfer']
- }
- ]
+ "fieldname": "shareholder",
+ "non_standard_fieldnames": {"Share Transfer": "to_shareholder"},
+ "transactions": [{"items": ["Share Transfer"]}],
}
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index 7e5129911e..1d79503a05 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -12,9 +12,17 @@ from frappe.utils import flt, fmt_money
import erpnext
-class OverlappingConditionError(frappe.ValidationError): pass
-class FromGreaterThanToError(frappe.ValidationError): pass
-class ManyBlankToValuesError(frappe.ValidationError): pass
+class OverlappingConditionError(frappe.ValidationError):
+ pass
+
+
+class FromGreaterThanToError(frappe.ValidationError):
+ pass
+
+
+class ManyBlankToValuesError(frappe.ValidationError):
+ pass
+
class ShippingRule(Document):
def validate(self):
@@ -35,15 +43,19 @@ class ShippingRule(Document):
if not d.to_value:
zero_to_values.append(d)
elif d.from_value >= d.to_value:
- throw(_("From value must be less than to value in row {0}").format(d.idx), FromGreaterThanToError)
+ throw(
+ _("From value must be less than to value in row {0}").format(d.idx), FromGreaterThanToError
+ )
# check if more than two or more rows has To Value = 0
if len(zero_to_values) >= 2:
- throw(_('There can only be one Shipping Rule Condition with 0 or blank value for "To Value"'),
- ManyBlankToValuesError)
+ throw(
+ _('There can only be one Shipping Rule Condition with 0 or blank value for "To Value"'),
+ ManyBlankToValuesError,
+ )
def apply(self, doc):
- '''Apply shipping rule on given doc. Called from accounts controller'''
+ """Apply shipping rule on given doc. Called from accounts controller"""
shipping_amount = 0.0
by_value = False
@@ -52,15 +64,15 @@ class ShippingRule(Document):
# validate country only if there is address
self.validate_countries(doc)
- if self.calculate_based_on == 'Net Total':
+ if self.calculate_based_on == "Net Total":
value = doc.base_net_total
by_value = True
- elif self.calculate_based_on == 'Net Weight':
+ elif self.calculate_based_on == "Net Weight":
value = doc.total_net_weight
by_value = True
- elif self.calculate_based_on == 'Fixed':
+ elif self.calculate_based_on == "Fixed":
shipping_amount = self.shipping_amount
# shipping amount by value, apply conditions
@@ -75,7 +87,9 @@ class ShippingRule(Document):
def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"):
- if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)):
+ if not condition.to_value or (
+ flt(condition.from_value) <= flt(value) <= flt(condition.to_value)
+ ):
return condition.shipping_amount
return 0.0
@@ -83,27 +97,31 @@ class ShippingRule(Document):
def validate_countries(self, doc):
# validate applicable countries
if self.countries:
- shipping_country = doc.get_shipping_address().get('country')
+ shipping_country = doc.get_shipping_address().get("country")
if not shipping_country:
- frappe.throw(_('Shipping Address does not have country, which is required for this Shipping Rule'))
+ frappe.throw(
+ _("Shipping Address does not have country, which is required for this Shipping Rule")
+ )
if shipping_country not in [d.country for d in self.countries]:
- frappe.throw(_('Shipping rule not applicable for country {0} in Shipping Address').format(shipping_country))
+ frappe.throw(
+ _("Shipping rule not applicable for country {0} in Shipping Address").format(shipping_country)
+ )
def add_shipping_rule_to_tax_table(self, doc, shipping_amount):
shipping_charge = {
"charge_type": "Actual",
"account_head": self.account,
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
}
if self.shipping_rule_type == "Selling":
# check if not applied on purchase
- if not doc.meta.get_field('taxes').options == 'Sales Taxes and Charges':
- frappe.throw(_('Shipping rule only applicable for Selling'))
+ if not doc.meta.get_field("taxes").options == "Sales Taxes and Charges":
+ frappe.throw(_("Shipping rule only applicable for Selling"))
shipping_charge["doctype"] = "Sales Taxes and Charges"
else:
# check if not applied on sales
- if not doc.meta.get_field('taxes').options == 'Purchase Taxes and Charges':
- frappe.throw(_('Shipping rule only applicable for Buying'))
+ if not doc.meta.get_field("taxes").options == "Purchase Taxes and Charges":
+ frappe.throw(_("Shipping rule only applicable for Buying"))
shipping_charge["doctype"] = "Purchase Taxes and Charges"
shipping_charge["category"] = "Valuation and Total"
@@ -127,19 +145,19 @@ class ShippingRule(Document):
def validate_overlapping_shipping_rule_conditions(self):
def overlap_exists_between(num_range1, num_range2):
"""
- num_range1 and num_range2 are two ranges
- ranges are represented as a tuple e.g. range 100 to 300 is represented as (100, 300)
- if condition num_range1 = 100 to 300
- then condition num_range2 can only be like 50 to 99 or 301 to 400
- hence, non-overlapping condition = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2)
+ num_range1 and num_range2 are two ranges
+ ranges are represented as a tuple e.g. range 100 to 300 is represented as (100, 300)
+ if condition num_range1 = 100 to 300
+ then condition num_range2 can only be like 50 to 99 or 301 to 400
+ hence, non-overlapping condition = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2)
"""
(x1, x2), (y1, y2) = num_range1, num_range2
separate = (x1 <= x2 <= y1 <= y2) or (y1 <= y2 <= x1 <= x2)
- return (not separate)
+ return not separate
overlaps = []
for i in range(0, len(self.conditions)):
- for j in range(i+1, len(self.conditions)):
+ for j in range(i + 1, len(self.conditions)):
d1, d2 = self.conditions[i], self.conditions[j]
if d1.as_dict() != d2.as_dict():
# in our case, to_value can be zero, hence pass the from_value if so
@@ -153,7 +171,12 @@ class ShippingRule(Document):
msgprint(_("Overlapping conditions found between:"))
messages = []
for d1, d2 in overlaps:
- messages.append("%s-%s = %s " % (d1.from_value, d1.to_value, fmt_money(d1.shipping_amount, currency=company_currency)) +
- _("and") + " %s-%s = %s" % (d2.from_value, d2.to_value, fmt_money(d2.shipping_amount, currency=company_currency)))
+ messages.append(
+ "%s-%s = %s "
+ % (d1.from_value, d1.to_value, fmt_money(d1.shipping_amount, currency=company_currency))
+ + _("and")
+ + " %s-%s = %s"
+ % (d2.from_value, d2.to_value, fmt_money(d2.shipping_amount, currency=company_currency))
+ )
msgprint("\n".join(messages), raise_exception=OverlappingConditionError)
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule_dashboard.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule_dashboard.py
index fc70621159..60ce120c54 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule_dashboard.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule_dashboard.py
@@ -3,22 +3,11 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'shipping_rule',
- 'non_standard_fieldnames': {
- 'Payment Entry': 'party_name'
- },
- 'transactions': [
- {
- 'label': _('Pre Sales'),
- 'items': ['Quotation', 'Supplier Quotation']
- },
- {
- 'label': _('Sales'),
- 'items': ['Sales Order', 'Delivery Note', 'Sales Invoice']
- },
- {
- 'label': _('Purchase'),
- 'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
- }
- ]
+ "fieldname": "shipping_rule",
+ "non_standard_fieldnames": {"Payment Entry": "party_name"},
+ "transactions": [
+ {"label": _("Pre Sales"), "items": ["Quotation", "Supplier Quotation"]},
+ {"label": _("Sales"), "items": ["Sales Order", "Delivery Note", "Sales Invoice"]},
+ {"label": _("Purchase"), "items": ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
index c06dae0970..a24e834c57 100644
--- a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
@@ -11,18 +11,19 @@ from erpnext.accounts.doctype.shipping_rule.shipping_rule import (
OverlappingConditionError,
)
-test_records = frappe.get_test_records('Shipping Rule')
+test_records = frappe.get_test_records("Shipping Rule")
+
class TestShippingRule(unittest.TestCase):
def test_from_greater_than_to(self):
shipping_rule = frappe.copy_doc(test_records[0])
- shipping_rule.name = test_records[0].get('name')
+ shipping_rule.name = test_records[0].get("name")
shipping_rule.get("conditions")[0].from_value = 101
self.assertRaises(FromGreaterThanToError, shipping_rule.insert)
def test_many_zero_to_values(self):
shipping_rule = frappe.copy_doc(test_records[0])
- shipping_rule.name = test_records[0].get('name')
+ shipping_rule.name = test_records[0].get("name")
shipping_rule.get("conditions")[0].to_value = 0
self.assertRaises(ManyBlankToValuesError, shipping_rule.insert)
@@ -35,48 +36,58 @@ class TestShippingRule(unittest.TestCase):
((50, 150), (50, 150)),
]:
shipping_rule = frappe.copy_doc(test_records[0])
- shipping_rule.name = test_records[0].get('name')
+ shipping_rule.name = test_records[0].get("name")
shipping_rule.get("conditions")[0].from_value = range_a[0]
shipping_rule.get("conditions")[0].to_value = range_a[1]
shipping_rule.get("conditions")[1].from_value = range_b[0]
shipping_rule.get("conditions")[1].to_value = range_b[1]
self.assertRaises(OverlappingConditionError, shipping_rule.insert)
+
def create_shipping_rule(shipping_rule_type, shipping_rule_name):
if frappe.db.exists("Shipping Rule", shipping_rule_name):
return frappe.get_doc("Shipping Rule", shipping_rule_name)
sr = frappe.new_doc("Shipping Rule")
- sr.account = "_Test Account Shipping Charges - _TC"
- sr.calculate_based_on = "Net Total"
+ sr.account = "_Test Account Shipping Charges - _TC"
+ sr.calculate_based_on = "Net Total"
sr.company = "_Test Company"
sr.cost_center = "_Test Cost Center - _TC"
sr.label = shipping_rule_name
sr.name = shipping_rule_name
sr.shipping_rule_type = shipping_rule_type
- sr.append("conditions", {
+ sr.append(
+ "conditions",
+ {
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
- "to_value": 100
- })
- sr.append("conditions", {
+ "to_value": 100,
+ },
+ )
+ sr.append(
+ "conditions",
+ {
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
- "to_value": 200
- })
- sr.append("conditions", {
+ "to_value": 200,
+ },
+ )
+ sr.append(
+ "conditions",
+ {
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"shipping_amount": 200.0,
- "to_value": 2000
- })
+ "to_value": 2000,
+ },
+ )
sr.insert(ignore_permissions=True)
sr.submit()
return sr
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 467d4a1334..f6dd86afa2 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -60,7 +60,11 @@ class Subscription(Document):
"""
_current_invoice_start = None
- if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
+ if (
+ self.is_new_subscription()
+ and self.trial_period_end
+ and getdate(self.trial_period_end) > getdate(self.start_date)
+ ):
_current_invoice_start = add_days(self.trial_period_end, 1)
elif self.trial_period_start and self.is_trialling():
_current_invoice_start = self.trial_period_start
@@ -102,7 +106,7 @@ class Subscription(Document):
if self.follow_calendar_months:
billing_info = self.get_billing_cycle_and_interval()
- billing_interval_count = billing_info[0]['billing_interval_count']
+ billing_interval_count = billing_info[0]["billing_interval_count"]
calendar_months = get_calendar_months(billing_interval_count)
calendar_month = 0
current_invoice_end_month = getdate(_current_invoice_end).month
@@ -112,12 +116,13 @@ class Subscription(Document):
if month <= current_invoice_end_month:
calendar_month = month
- if cint(calendar_month - billing_interval_count) <= 0 and \
- getdate(date).month != 1:
+ if cint(calendar_month - billing_interval_count) <= 0 and getdate(date).month != 1:
calendar_month = 12
current_invoice_end_year -= 1
- _current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' + cstr(calendar_month) + '-01')
+ _current_invoice_end = get_last_day(
+ cstr(current_invoice_end_year) + "-" + cstr(calendar_month) + "-01"
+ )
if self.end_date and getdate(_current_invoice_end) > getdate(self.end_date):
_current_invoice_end = self.end_date
@@ -131,7 +136,7 @@ class Subscription(Document):
same billing interval
"""
if billing_cycle_data and len(billing_cycle_data) != 1:
- frappe.throw(_('You can only have Plans with the same billing cycle in a Subscription'))
+ frappe.throw(_("You can only have Plans with the same billing cycle in a Subscription"))
def get_billing_cycle_and_interval(self):
"""
@@ -141,10 +146,11 @@ class Subscription(Document):
"""
plan_names = [plan.plan for plan in self.plans]
billing_info = frappe.db.sql(
- 'select distinct `billing_interval`, `billing_interval_count` '
- 'from `tabSubscription Plan` '
- 'where name in %s',
- (plan_names,), as_dict=1
+ "select distinct `billing_interval`, `billing_interval_count` "
+ "from `tabSubscription Plan` "
+ "where name in %s",
+ (plan_names,),
+ as_dict=1,
)
return billing_info
@@ -161,19 +167,19 @@ class Subscription(Document):
if billing_info:
data = dict()
- interval = billing_info[0]['billing_interval']
- interval_count = billing_info[0]['billing_interval_count']
- if interval not in ['Day', 'Week']:
- data['days'] = -1
- if interval == 'Day':
- data['days'] = interval_count - 1
- elif interval == 'Month':
- data['months'] = interval_count
- elif interval == 'Year':
- data['years'] = interval_count
+ interval = billing_info[0]["billing_interval"]
+ interval_count = billing_info[0]["billing_interval_count"]
+ if interval not in ["Day", "Week"]:
+ data["days"] = -1
+ if interval == "Day":
+ data["days"] = interval_count - 1
+ elif interval == "Month":
+ data["months"] = interval_count
+ elif interval == "Year":
+ data["years"] = interval_count
# todo: test week
- elif interval == 'Week':
- data['days'] = interval_count * 7 - 1
+ elif interval == "Week":
+ data["days"] = interval_count * 7 - 1
return data
@@ -184,27 +190,27 @@ class Subscription(Document):
Used when the `Subscription` needs to decide what to do after the current generated
invoice is past it's due date and grace period.
"""
- subscription_settings = frappe.get_single('Subscription Settings')
- if self.status == 'Past Due Date' and self.is_past_grace_period():
- self.status = 'Cancelled' if cint(subscription_settings.cancel_after_grace) else 'Unpaid'
+ subscription_settings = frappe.get_single("Subscription Settings")
+ if self.status == "Past Due Date" and self.is_past_grace_period():
+ self.status = "Cancelled" if cint(subscription_settings.cancel_after_grace) else "Unpaid"
def set_subscription_status(self):
"""
Sets the status of the `Subscription`
"""
if self.is_trialling():
- self.status = 'Trialling'
- elif self.status == 'Active' and self.end_date and getdate() > getdate(self.end_date):
- self.status = 'Completed'
+ self.status = "Trialling"
+ elif self.status == "Active" and self.end_date and getdate() > getdate(self.end_date):
+ self.status = "Completed"
elif self.is_past_grace_period():
- subscription_settings = frappe.get_single('Subscription Settings')
- self.status = 'Cancelled' if cint(subscription_settings.cancel_after_grace) else 'Unpaid'
+ subscription_settings = frappe.get_single("Subscription Settings")
+ self.status = "Cancelled" if cint(subscription_settings.cancel_after_grace) else "Unpaid"
elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
- self.status = 'Past Due Date'
+ self.status = "Past Due Date"
elif not self.has_outstanding_invoice():
- self.status = 'Active'
+ self.status = "Active"
elif self.is_new_subscription():
- self.status = 'Active'
+ self.status = "Active"
self.save()
def is_trialling(self):
@@ -231,7 +237,7 @@ class Subscription(Document):
"""
current_invoice = self.get_current_invoice()
if self.current_invoice_is_past_due(current_invoice):
- subscription_settings = frappe.get_single('Subscription Settings')
+ subscription_settings = frappe.get_single("Subscription Settings")
grace_period = cint(subscription_settings.grace_period)
return getdate() > add_days(current_invoice.due_date, grace_period)
@@ -252,15 +258,15 @@ class Subscription(Document):
"""
Returns the most recent generated invoice.
"""
- doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
+ doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
if len(self.invoices):
current = self.invoices[-1]
- if frappe.db.exists(doctype, current.get('invoice')):
- doc = frappe.get_doc(doctype, current.get('invoice'))
+ if frappe.db.exists(doctype, current.get("invoice")):
+ doc = frappe.get_doc(doctype, current.get("invoice"))
return doc
else:
- frappe.throw(_('Invoice {0} no longer exists').format(current.get('invoice')))
+ frappe.throw(_("Invoice {0} no longer exists").format(current.get("invoice")))
def is_new_subscription(self):
"""
@@ -273,7 +279,7 @@ class Subscription(Document):
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
self.validate_end_date()
self.validate_to_follow_calendar_months()
- self.cost_center = erpnext.get_default_cost_center(self.get('company'))
+ self.cost_center = erpnext.get_default_cost_center(self.get("company"))
def validate_trial_period(self):
"""
@@ -281,30 +287,34 @@ class Subscription(Document):
"""
if self.trial_period_start and self.trial_period_end:
if getdate(self.trial_period_end) < getdate(self.trial_period_start):
- frappe.throw(_('Trial Period End Date Cannot be before Trial Period Start Date'))
+ frappe.throw(_("Trial Period End Date Cannot be before Trial Period Start Date"))
if self.trial_period_start and not self.trial_period_end:
- frappe.throw(_('Both Trial Period Start Date and Trial Period End Date must be set'))
+ frappe.throw(_("Both Trial Period Start Date and Trial Period End Date must be set"))
if self.trial_period_start and getdate(self.trial_period_start) > getdate(self.start_date):
- frappe.throw(_('Trial Period Start date cannot be after Subscription Start Date'))
+ frappe.throw(_("Trial Period Start date cannot be after Subscription Start Date"))
def validate_end_date(self):
billing_cycle_info = self.get_billing_cycle_data()
end_date = add_to_date(self.start_date, **billing_cycle_info)
if self.end_date and getdate(self.end_date) <= getdate(end_date):
- frappe.throw(_('Subscription End Date must be after {0} as per the subscription plan').format(end_date))
+ frappe.throw(
+ _("Subscription End Date must be after {0} as per the subscription plan").format(end_date)
+ )
def validate_to_follow_calendar_months(self):
if self.follow_calendar_months:
billing_info = self.get_billing_cycle_and_interval()
if not self.end_date:
- frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
+ frappe.throw(_("Subscription End Date is mandatory to follow calendar months"))
- if billing_info[0]['billing_interval'] != 'Month':
- frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months'))
+ if billing_info[0]["billing_interval"] != "Month":
+ frappe.throw(
+ _("Billing Interval in Subscription Plan must be Month to follow calendar months")
+ )
def after_insert(self):
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
@@ -316,13 +326,10 @@ class Subscription(Document):
saves the `Subscription`.
"""
- doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
+ doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
invoice = self.create_invoice(prorate)
- self.append('invoices', {
- 'document_type': doctype,
- 'invoice': invoice.name
- })
+ self.append("invoices", {"document_type": doctype, "invoice": invoice.name})
self.save()
@@ -332,28 +339,33 @@ class Subscription(Document):
"""
Creates a `Invoice`, submits it and returns it
"""
- doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
+ doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
invoice = frappe.new_doc(doctype)
# For backward compatibility
# Earlier subscription didn't had any company field
- company = self.get('company') or get_default_company()
+ company = self.get("company") or get_default_company()
if not company:
- frappe.throw(_("Company is mandatory was generating invoice. Please set default company in Global Defaults"))
+ frappe.throw(
+ _("Company is mandatory was generating invoice. Please set default company in Global Defaults")
+ )
invoice.company = company
invoice.set_posting_time = 1
- invoice.posting_date = self.current_invoice_start if self.generate_invoice_at_period_start \
+ invoice.posting_date = (
+ self.current_invoice_start
+ if self.generate_invoice_at_period_start
else self.current_invoice_end
+ )
invoice.cost_center = self.cost_center
- if doctype == 'Sales Invoice':
+ if doctype == "Sales Invoice":
invoice.customer = self.party
else:
invoice.supplier = self.party
- if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
+ if frappe.db.get_value("Supplier", self.party, "tax_withholding_category"):
invoice.apply_tds = 1
### Add party currency to invoice
@@ -364,23 +376,21 @@ class Subscription(Document):
for dimension in accounting_dimensions:
if self.get(dimension):
- invoice.update({
- dimension: self.get(dimension)
- })
+ invoice.update({dimension: self.get(dimension)})
# Subscription is better suited for service items. I won't update `update_stock`
# for that reason
items_list = self.get_items_from_plans(self.plans, prorate)
for item in items_list:
- item['cost_center'] = self.cost_center
- invoice.append('items', item)
+ item["cost_center"] = self.cost_center
+ invoice.append("items", item)
# Taxes
- tax_template = ''
+ tax_template = ""
- if doctype == 'Sales Invoice' and self.sales_tax_template:
+ if doctype == "Sales Invoice" and self.sales_tax_template:
tax_template = self.sales_tax_template
- if doctype == 'Purchase Invoice' and self.purchase_tax_template:
+ if doctype == "Purchase Invoice" and self.purchase_tax_template:
tax_template = self.purchase_tax_template
if tax_template:
@@ -390,11 +400,11 @@ class Subscription(Document):
# Due date
if self.days_until_due:
invoice.append(
- 'payment_schedule',
+ "payment_schedule",
{
- 'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
- 'invoice_portion': 100
- }
+ "due_date": add_days(invoice.posting_date, cint(self.days_until_due)),
+ "invoice_portion": 100,
+ },
)
# Discounts
@@ -409,7 +419,7 @@ class Subscription(Document):
if self.additional_discount_percentage or self.additional_discount_amount:
discount_on = self.apply_additional_discount
- invoice.apply_discount_on = discount_on if discount_on else 'Grand Total'
+ invoice.apply_discount_on = discount_on if discount_on else "Grand Total"
# Subscription period
invoice.from_date = self.current_invoice_start
@@ -430,44 +440,62 @@ class Subscription(Document):
Returns the `Item`s linked to `Subscription Plan`
"""
if prorate:
- prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start,
- self.generate_invoice_at_period_start)
+ prorate_factor = get_prorata_factor(
+ self.current_invoice_end, self.current_invoice_start, self.generate_invoice_at_period_start
+ )
items = []
party = self.party
for plan in plans:
- plan_doc = frappe.get_doc('Subscription Plan', plan.plan)
+ plan_doc = frappe.get_doc("Subscription Plan", plan.plan)
item_code = plan_doc.item
- if self.party == 'Customer':
- deferred_field = 'enable_deferred_revenue'
+ if self.party == "Customer":
+ deferred_field = "enable_deferred_revenue"
else:
- deferred_field = 'enable_deferred_expense'
+ deferred_field = "enable_deferred_expense"
- deferred = frappe.db.get_value('Item', item_code, deferred_field)
+ deferred = frappe.db.get_value("Item", item_code, deferred_field)
if not prorate:
- item = {'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, party,
- self.current_invoice_start, self.current_invoice_end), 'cost_center': plan_doc.cost_center}
+ item = {
+ "item_code": item_code,
+ "qty": plan.qty,
+ "rate": get_plan_rate(
+ plan.plan, plan.qty, party, self.current_invoice_start, self.current_invoice_end
+ ),
+ "cost_center": plan_doc.cost_center,
+ }
else:
- item = {'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, party,
- self.current_invoice_start, self.current_invoice_end, prorate_factor), 'cost_center': plan_doc.cost_center}
+ item = {
+ "item_code": item_code,
+ "qty": plan.qty,
+ "rate": get_plan_rate(
+ plan.plan,
+ plan.qty,
+ party,
+ self.current_invoice_start,
+ self.current_invoice_end,
+ prorate_factor,
+ ),
+ "cost_center": plan_doc.cost_center,
+ }
if deferred:
- item.update({
- deferred_field: deferred,
- 'service_start_date': self.current_invoice_start,
- 'service_end_date': self.current_invoice_end
- })
+ item.update(
+ {
+ deferred_field: deferred,
+ "service_start_date": self.current_invoice_start,
+ "service_end_date": self.current_invoice_end,
+ }
+ )
accounting_dimensions = get_accounting_dimensions()
for dimension in accounting_dimensions:
if plan_doc.get(dimension):
- item.update({
- dimension: plan_doc.get(dimension)
- })
+ item.update({dimension: plan_doc.get(dimension)})
items.append(item)
@@ -480,9 +508,9 @@ class Subscription(Document):
1. `process_for_active`
2. `process_for_past_due`
"""
- if self.status == 'Active':
+ if self.status == "Active":
self.process_for_active()
- elif self.status in ['Past Due Date', 'Unpaid']:
+ elif self.status in ["Past Due Date", "Unpaid"]:
self.process_for_past_due_date()
self.set_subscription_status()
@@ -490,8 +518,10 @@ class Subscription(Document):
self.save()
def is_postpaid_to_invoice(self):
- return getdate() > getdate(self.current_invoice_end) or \
- (getdate() >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start))
+ return getdate() > getdate(self.current_invoice_end) or (
+ getdate() >= getdate(self.current_invoice_end)
+ and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)
+ )
def is_prepaid_to_invoice(self):
if not self.generate_invoice_at_period_start:
@@ -507,9 +537,13 @@ class Subscription(Document):
invoice = self.get_current_invoice()
if not (_current_start_date and _current_end_date):
- _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True)
+ _current_start_date, _current_end_date = self.update_subscription_period(
+ date=add_days(self.current_invoice_end, 1), return_date=True
+ )
- if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date):
+ if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(
+ _current_end_date
+ ):
return True
return False
@@ -524,10 +558,11 @@ class Subscription(Document):
3. Change the `Subscription` status to 'Cancelled'
"""
- if not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \
- and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
+ if not self.is_current_invoice_generated(
+ self.current_invoice_start, self.current_invoice_end
+ ) and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
- prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
+ prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
self.generate_invoice(prorate)
if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice():
@@ -543,7 +578,7 @@ class Subscription(Document):
if self.end_date and getdate() < getdate(self.end_date):
return
- self.status = 'Cancelled'
+ self.status = "Cancelled"
if not self.cancelation_date:
self.cancelation_date = nowdate()
@@ -558,10 +593,10 @@ class Subscription(Document):
"""
current_invoice = self.get_current_invoice()
if not current_invoice:
- frappe.throw(_('Current invoice {0} is missing').format(current_invoice.invoice))
+ frappe.throw(_("Current invoice {0} is missing").format(current_invoice.invoice))
else:
if not self.has_outstanding_invoice():
- self.status = 'Active'
+ self.status = "Active"
else:
self.set_status_grace_period()
@@ -569,31 +604,33 @@ class Subscription(Document):
self.update_subscription_period(add_days(self.current_invoice_end, 1))
# Generate invoices periodically even if current invoice are unpaid
- if self.generate_new_invoices_past_due_date and not \
- self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \
- and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
+ if (
+ self.generate_new_invoices_past_due_date
+ and not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end)
+ and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice())
+ ):
- prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
+ prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
self.generate_invoice(prorate)
-
@staticmethod
def is_paid(invoice):
"""
Return `True` if the given invoice is paid
"""
- return invoice.status == 'Paid'
+ return invoice.status == "Paid"
def has_outstanding_invoice(self):
"""
Returns `True` if the most recent invoice for the `Subscription` is not paid
"""
- doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
+ doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
current_invoice = self.get_current_invoice()
invoice_list = [d.invoice for d in self.invoices]
- outstanding_invoices = frappe.get_all(doctype, fields=['name'],
- filters={'status': ('!=', 'Paid'), 'name': ('in', invoice_list)})
+ outstanding_invoices = frappe.get_all(
+ doctype, fields=["name"], filters={"status": ("!=", "Paid"), "name": ("in", invoice_list)}
+ )
if outstanding_invoices:
return True
@@ -605,10 +642,12 @@ class Subscription(Document):
This sets the subscription as cancelled. It will stop invoices from being generated
but it will not affect already created invoices.
"""
- if self.status != 'Cancelled':
- to_generate_invoice = True if self.status == 'Active' and not self.generate_invoice_at_period_start else False
- to_prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
- self.status = 'Cancelled'
+ if self.status != "Cancelled":
+ to_generate_invoice = (
+ True if self.status == "Active" and not self.generate_invoice_at_period_start else False
+ )
+ to_prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
+ self.status = "Cancelled"
self.cancelation_date = nowdate()
if to_generate_invoice:
self.generate_invoice(prorate=to_prorate)
@@ -620,19 +659,20 @@ class Subscription(Document):
subscription and the `Subscription` will lose all the history of generated invoices
it has.
"""
- if self.status == 'Cancelled':
- self.status = 'Active'
- self.db_set('start_date', nowdate())
+ if self.status == "Cancelled":
+ self.status = "Active"
+ self.db_set("start_date", nowdate())
self.update_subscription_period(nowdate())
self.invoices = []
self.save()
else:
- frappe.throw(_('You cannot restart a Subscription that is not cancelled.'))
+ frappe.throw(_("You cannot restart a Subscription that is not cancelled."))
def get_precision(self):
invoice = self.get_current_invoice()
if invoice:
- return invoice.precision('grand_total')
+ return invoice.precision("grand_total")
+
def get_calendar_months(billing_interval):
calendar_months = []
@@ -643,6 +683,7 @@ def get_calendar_months(billing_interval):
return calendar_months
+
def get_prorata_factor(period_end, period_start, is_prepaid):
if is_prepaid:
prorate_factor = 1
@@ -667,7 +708,7 @@ def get_all_subscriptions():
"""
Returns all `Subscription` documents
"""
- return frappe.db.get_all('Subscription', {'status': ('!=','Cancelled')})
+ return frappe.db.get_all("Subscription", {"status": ("!=", "Cancelled")})
def process(data):
@@ -676,7 +717,7 @@ def process(data):
"""
if data:
try:
- subscription = frappe.get_doc('Subscription', data['name'])
+ subscription = frappe.get_doc("Subscription", data["name"])
subscription.process()
frappe.db.commit()
except frappe.ValidationError:
@@ -692,7 +733,7 @@ def cancel_subscription(name):
Cancels a `Subscription`. This will stop the `Subscription` from further invoicing the
`Subscriber` but all already outstanding invoices will not be affected.
"""
- subscription = frappe.get_doc('Subscription', name)
+ subscription = frappe.get_doc("Subscription", name)
subscription.cancel_subscription()
@@ -702,7 +743,7 @@ def restart_subscription(name):
Restarts a cancelled `Subscription`. The `Subscription` will 'forget' the history of
all invoices it has generated
"""
- subscription = frappe.get_doc('Subscription', name)
+ subscription = frappe.get_doc("Subscription", name)
subscription.restart_subscription()
@@ -711,5 +752,5 @@ def get_subscription_updates(name):
"""
Use this to get the latest state of the given `Subscription`
"""
- subscription = frappe.get_doc('Subscription', name)
+ subscription = frappe.get_doc("Subscription", name)
subscription.process()
diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py
index 6f67bc5128..eb17daa282 100644
--- a/erpnext/accounts/doctype/subscription/test_subscription.py
+++ b/erpnext/accounts/doctype/subscription/test_subscription.py
@@ -18,104 +18,111 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto
test_dependencies = ("UOM", "Item Group", "Item")
+
def create_plan():
- if not frappe.db.exists('Subscription Plan', '_Test Plan Name'):
- plan = frappe.new_doc('Subscription Plan')
- plan.plan_name = '_Test Plan Name'
- plan.item = '_Test Non Stock Item'
+ if not frappe.db.exists("Subscription Plan", "_Test Plan Name"):
+ plan = frappe.new_doc("Subscription Plan")
+ plan.plan_name = "_Test Plan Name"
+ plan.item = "_Test Non Stock Item"
plan.price_determination = "Fixed Rate"
plan.cost = 900
- plan.billing_interval = 'Month'
+ plan.billing_interval = "Month"
plan.billing_interval_count = 1
plan.insert()
- if not frappe.db.exists('Subscription Plan', '_Test Plan Name 2'):
- plan = frappe.new_doc('Subscription Plan')
- plan.plan_name = '_Test Plan Name 2'
- plan.item = '_Test Non Stock Item'
+ if not frappe.db.exists("Subscription Plan", "_Test Plan Name 2"):
+ plan = frappe.new_doc("Subscription Plan")
+ plan.plan_name = "_Test Plan Name 2"
+ plan.item = "_Test Non Stock Item"
plan.price_determination = "Fixed Rate"
plan.cost = 1999
- plan.billing_interval = 'Month'
+ plan.billing_interval = "Month"
plan.billing_interval_count = 1
plan.insert()
- if not frappe.db.exists('Subscription Plan', '_Test Plan Name 3'):
- plan = frappe.new_doc('Subscription Plan')
- plan.plan_name = '_Test Plan Name 3'
- plan.item = '_Test Non Stock Item'
+ if not frappe.db.exists("Subscription Plan", "_Test Plan Name 3"):
+ plan = frappe.new_doc("Subscription Plan")
+ plan.plan_name = "_Test Plan Name 3"
+ plan.item = "_Test Non Stock Item"
plan.price_determination = "Fixed Rate"
plan.cost = 1999
- plan.billing_interval = 'Day'
+ plan.billing_interval = "Day"
plan.billing_interval_count = 14
plan.insert()
# Defined a quarterly Subscription Plan
- if not frappe.db.exists('Subscription Plan', '_Test Plan Name 4'):
- plan = frappe.new_doc('Subscription Plan')
- plan.plan_name = '_Test Plan Name 4'
- plan.item = '_Test Non Stock Item'
+ if not frappe.db.exists("Subscription Plan", "_Test Plan Name 4"):
+ plan = frappe.new_doc("Subscription Plan")
+ plan.plan_name = "_Test Plan Name 4"
+ plan.item = "_Test Non Stock Item"
plan.price_determination = "Monthly Rate"
plan.cost = 20000
- plan.billing_interval = 'Month'
+ plan.billing_interval = "Month"
plan.billing_interval_count = 3
plan.insert()
- if not frappe.db.exists('Subscription Plan', '_Test Plan Multicurrency'):
- plan = frappe.new_doc('Subscription Plan')
- plan.plan_name = '_Test Plan Multicurrency'
- plan.item = '_Test Non Stock Item'
+ if not frappe.db.exists("Subscription Plan", "_Test Plan Multicurrency"):
+ plan = frappe.new_doc("Subscription Plan")
+ plan.plan_name = "_Test Plan Multicurrency"
+ plan.item = "_Test Non Stock Item"
plan.price_determination = "Fixed Rate"
plan.cost = 50
- plan.currency = 'USD'
- plan.billing_interval = 'Month'
+ plan.currency = "USD"
+ plan.billing_interval = "Month"
plan.billing_interval_count = 1
plan.insert()
+
def create_parties():
- if not frappe.db.exists('Supplier', '_Test Supplier'):
- supplier = frappe.new_doc('Supplier')
- supplier.supplier_name = '_Test Supplier'
- supplier.supplier_group = 'All Supplier Groups'
+ if not frappe.db.exists("Supplier", "_Test Supplier"):
+ supplier = frappe.new_doc("Supplier")
+ supplier.supplier_name = "_Test Supplier"
+ supplier.supplier_group = "All Supplier Groups"
supplier.insert()
- if not frappe.db.exists('Customer', '_Test Subscription Customer'):
- customer = frappe.new_doc('Customer')
- customer.customer_name = '_Test Subscription Customer'
- customer.billing_currency = 'USD'
- customer.append('accounts', {
- 'company': '_Test Company',
- 'account': '_Test Receivable USD - _TC'
- })
+ if not frappe.db.exists("Customer", "_Test Subscription Customer"):
+ customer = frappe.new_doc("Customer")
+ customer.customer_name = "_Test Subscription Customer"
+ customer.billing_currency = "USD"
+ customer.append(
+ "accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"}
+ )
customer.insert()
+
class TestSubscription(unittest.TestCase):
def setUp(self):
create_plan()
create_parties()
def test_create_subscription_with_trial_with_correct_period(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
subscription.trial_period_start = nowdate()
subscription.trial_period_end = add_months(nowdate(), 1)
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
self.assertEqual(subscription.trial_period_start, nowdate())
self.assertEqual(subscription.trial_period_end, add_months(nowdate(), 1))
- self.assertEqual(add_days(subscription.trial_period_end, 1), get_date_str(subscription.current_invoice_start))
- self.assertEqual(add_to_date(subscription.current_invoice_start, months=1, days=-1), get_date_str(subscription.current_invoice_end))
+ self.assertEqual(
+ add_days(subscription.trial_period_end, 1), get_date_str(subscription.current_invoice_start)
+ )
+ self.assertEqual(
+ add_to_date(subscription.current_invoice_start, months=1, days=-1),
+ get_date_str(subscription.current_invoice_end),
+ )
self.assertEqual(subscription.invoices, [])
- self.assertEqual(subscription.status, 'Trialling')
+ self.assertEqual(subscription.status, "Trialling")
subscription.delete()
def test_create_subscription_without_trial_with_correct_period(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
self.assertEqual(subscription.trial_period_start, None)
@@ -124,190 +131,190 @@ class TestSubscription(unittest.TestCase):
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
# No invoice is created
self.assertEqual(len(subscription.invoices), 0)
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
subscription.delete()
def test_create_subscription_trial_with_wrong_dates(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
subscription.trial_period_end = nowdate()
subscription.trial_period_start = add_days(nowdate(), 30)
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
self.assertRaises(frappe.ValidationError, subscription.save)
subscription.delete()
def test_create_subscription_multi_with_different_billing_fails(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
subscription.trial_period_end = nowdate()
subscription.trial_period_start = add_days(nowdate(), 30)
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.append('plans', {'plan': '_Test Plan Name 3', 'qty': 1})
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+ subscription.append("plans", {"plan": "_Test Plan Name 3", "qty": 1})
self.assertRaises(frappe.ValidationError, subscription.save)
subscription.delete()
def test_invoice_is_generated_at_end_of_billing_period(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.start_date = '2018-01-01'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.start_date = "2018-01-01"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.insert()
- self.assertEqual(subscription.status, 'Active')
- self.assertEqual(subscription.current_invoice_start, '2018-01-01')
- self.assertEqual(subscription.current_invoice_end, '2018-01-31')
+ self.assertEqual(subscription.status, "Active")
+ self.assertEqual(subscription.current_invoice_start, "2018-01-01")
+ self.assertEqual(subscription.current_invoice_end, "2018-01-31")
subscription.process()
self.assertEqual(len(subscription.invoices), 1)
- self.assertEqual(subscription.current_invoice_start, '2018-01-01')
+ self.assertEqual(subscription.current_invoice_start, "2018-01-01")
subscription.process()
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
subscription.delete()
def test_status_goes_back_to_active_after_invoice_is_paid(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start_date = '2018-01-01'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+ subscription.start_date = "2018-01-01"
subscription.insert()
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
self.assertEqual(len(subscription.invoices), 1)
# Status is unpaid as Days until Due is zero and grace period is Zero
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
subscription.get_current_invoice()
current_invoice = subscription.get_current_invoice()
self.assertIsNotNone(current_invoice)
- current_invoice.db_set('outstanding_amount', 0)
- current_invoice.db_set('status', 'Paid')
+ current_invoice.db_set("outstanding_amount", 0)
+ current_invoice.db_set("status", "Paid")
subscription.process()
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
self.assertEqual(subscription.current_invoice_start, add_months(subscription.start_date, 1))
self.assertEqual(len(subscription.invoices), 1)
subscription.delete()
def test_subscription_cancel_after_grace_period(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 1
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start_date = '2018-01-01'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+ subscription.start_date = "2018-01-01"
subscription.insert()
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
# This should change status to Cancelled since grace period is 0
# And is backdated subscription so subscription will be cancelled after processing
- self.assertEqual(subscription.status, 'Cancelled')
+ self.assertEqual(subscription.status, "Cancelled")
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_subscription_unpaid_after_grace_period(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 0
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start_date = '2018-01-01'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+ subscription.start_date = "2018-01-01"
subscription.insert()
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
# Status is unpaid as Days until Due is zero and grace period is Zero
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_subscription_invoice_days_until_due(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.days_until_due = 10
subscription.start_date = add_months(nowdate(), -1)
subscription.insert()
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
self.assertEqual(len(subscription.invoices), 1)
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
subscription.delete()
def test_subscription_is_past_due_doesnt_change_within_grace_period(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
grace_period = settings.grace_period
settings.grace_period = 1000
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.start_date = add_days(nowdate(), -1000)
subscription.insert()
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
- self.assertEqual(subscription.status, 'Past Due Date')
+ self.assertEqual(subscription.status, "Past Due Date")
subscription.process()
# Grace period is 1000 days so status should remain as Past Due Date
- self.assertEqual(subscription.status, 'Past Due Date')
+ self.assertEqual(subscription.status, "Past Due Date")
subscription.process()
- self.assertEqual(subscription.status, 'Past Due Date')
+ self.assertEqual(subscription.status, "Past Due Date")
subscription.process()
- self.assertEqual(subscription.status, 'Past Due Date')
+ self.assertEqual(subscription.status, "Past Due Date")
settings.grace_period = grace_period
settings.save()
subscription.delete()
def test_subscription_remains_active_during_invoice_period(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
- subscription.process() # no changes expected
+ subscription.process() # no changes expected
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
self.assertEqual(len(subscription.invoices), 0)
- subscription.process() # no changes expected still
- self.assertEqual(subscription.status, 'Active')
+ subscription.process() # no changes expected still
+ self.assertEqual(subscription.status, "Active")
self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
self.assertEqual(len(subscription.invoices), 0)
- subscription.process() # no changes expected yet still
- self.assertEqual(subscription.status, 'Active')
+ subscription.process() # no changes expected yet still
+ self.assertEqual(subscription.status, "Active")
self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
self.assertEqual(len(subscription.invoices), 0)
@@ -315,30 +322,30 @@ class TestSubscription(unittest.TestCase):
subscription.delete()
def test_subscription_cancelation(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
subscription.cancel_subscription()
- self.assertEqual(subscription.status, 'Cancelled')
+ self.assertEqual(subscription.status, "Cancelled")
subscription.delete()
def test_subscription_cancellation_invoices(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
to_prorate = settings.prorate
settings.prorate = 1
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
subscription.cancel_subscription()
# Invoice must have been generated
@@ -346,33 +353,39 @@ class TestSubscription(unittest.TestCase):
invoice = subscription.get_current_invoice()
diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1)
- plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1)
- prorate_factor = flt(diff/plan_days)
+ plan_days = flt(
+ date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1
+ )
+ prorate_factor = flt(diff / plan_days)
self.assertEqual(
flt(
- get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start,
- subscription.generate_invoice_at_period_start),
- 2),
- flt(prorate_factor, 2)
+ get_prorata_factor(
+ subscription.current_invoice_end,
+ subscription.current_invoice_start,
+ subscription.generate_invoice_at_period_start,
+ ),
+ 2,
+ ),
+ flt(prorate_factor, 2),
)
self.assertEqual(flt(invoice.grand_total, 2), flt(prorate_factor * 900, 2))
- self.assertEqual(subscription.status, 'Cancelled')
+ self.assertEqual(subscription.status, "Cancelled")
subscription.delete()
settings.prorate = to_prorate
settings.save()
def test_subscription_cancellation_invoices_with_prorata_false(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
to_prorate = settings.prorate
settings.prorate = 0
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
@@ -385,21 +398,23 @@ class TestSubscription(unittest.TestCase):
subscription.delete()
def test_subscription_cancellation_invoices_with_prorata_true(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
to_prorate = settings.prorate
settings.prorate = 1
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1)
- plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1)
+ plan_days = flt(
+ date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1
+ )
prorate_factor = flt(diff / plan_days)
self.assertEqual(flt(invoice.grand_total, 2), flt(prorate_factor * 900, 2))
@@ -410,30 +425,30 @@ class TestSubscription(unittest.TestCase):
subscription.delete()
def test_subcription_cancellation_and_process(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 1
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start_date = '2018-01-01'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+ subscription.start_date = "2018-01-01"
subscription.insert()
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
invoices = len(subscription.invoices)
subscription.cancel_subscription()
- self.assertEqual(subscription.status, 'Cancelled')
+ self.assertEqual(subscription.status, "Cancelled")
self.assertEqual(len(subscription.invoices), invoices)
subscription.process()
- self.assertEqual(subscription.status, 'Cancelled')
+ self.assertEqual(subscription.status, "Cancelled")
self.assertEqual(len(subscription.invoices), invoices)
subscription.process()
- self.assertEqual(subscription.status, 'Cancelled')
+ self.assertEqual(subscription.status, "Cancelled")
self.assertEqual(len(subscription.invoices), invoices)
settings.cancel_after_grace = default_grace_period_action
@@ -441,36 +456,36 @@ class TestSubscription(unittest.TestCase):
subscription.delete()
def test_subscription_restart_and_process(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
default_grace_period_action = settings.cancel_after_grace
settings.grace_period = 0
settings.cancel_after_grace = 0
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start_date = '2018-01-01'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+ subscription.start_date = "2018-01-01"
subscription.insert()
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
# Status is unpaid as Days until Due is zero and grace period is Zero
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
subscription.cancel_subscription()
- self.assertEqual(subscription.status, 'Cancelled')
+ self.assertEqual(subscription.status, "Cancelled")
subscription.restart_subscription()
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
self.assertEqual(len(subscription.invoices), 0)
subscription.process()
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
self.assertEqual(len(subscription.invoices), 0)
subscription.process()
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
self.assertEqual(len(subscription.invoices), 0)
settings.cancel_after_grace = default_grace_period_action
@@ -478,42 +493,42 @@ class TestSubscription(unittest.TestCase):
subscription.delete()
def test_subscription_unpaid_back_to_active(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 0
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start_date = '2018-01-01'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+ subscription.start_date = "2018-01-01"
subscription.insert()
- subscription.process() # generate first invoice
+ subscription.process() # generate first invoice
# This should change status to Unpaid since grace period is 0
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
invoice = subscription.get_current_invoice()
- invoice.db_set('outstanding_amount', 0)
- invoice.db_set('status', 'Paid')
+ invoice.db_set("outstanding_amount", 0)
+ invoice.db_set("status", "Paid")
subscription.process()
- self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.status, "Active")
# A new invoice is generated
subscription.process()
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_restart_active_subscription(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
self.assertRaises(frappe.ValidationError, subscription.restart_subscription)
@@ -521,44 +536,44 @@ class TestSubscription(unittest.TestCase):
subscription.delete()
def test_subscription_invoice_discount_percentage(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
subscription.additional_discount_percentage = 10
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
self.assertEqual(invoice.additional_discount_percentage, 10)
- self.assertEqual(invoice.apply_discount_on, 'Grand Total')
+ self.assertEqual(invoice.apply_discount_on, "Grand Total")
subscription.delete()
def test_subscription_invoice_discount_amount(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
subscription.additional_discount_amount = 11
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
self.assertEqual(invoice.discount_amount, 11)
- self.assertEqual(invoice.apply_discount_on, 'Grand Total')
+ self.assertEqual(invoice.apply_discount_on, "Grand Total")
subscription.delete()
def test_prepaid_subscriptions(self):
# Create a non pre-billed subscription, processing should not create
# invoices.
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
subscription.process()
@@ -573,16 +588,16 @@ class TestSubscription(unittest.TestCase):
self.assertEqual(len(subscription.invoices), 1)
def test_prepaid_subscriptions_with_prorate_true(self):
- settings = frappe.get_single('Subscription Settings')
+ settings = frappe.get_single("Subscription Settings")
to_prorate = settings.prorate
settings.prorate = 1
settings.save()
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Customer'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Customer"
subscription.generate_invoice_at_period_start = True
- subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
subscription.save()
subscription.process()
subscription.cancel_subscription()
@@ -602,38 +617,38 @@ class TestSubscription(unittest.TestCase):
subscription.delete()
def test_subscription_with_follow_calendar_months(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Supplier'
- subscription.party = '_Test Supplier'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Supplier"
+ subscription.party = "_Test Supplier"
subscription.generate_invoice_at_period_start = 1
subscription.follow_calendar_months = 1
# select subscription start date as '2018-01-15'
- subscription.start_date = '2018-01-15'
- subscription.end_date = '2018-07-15'
- subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1})
+ subscription.start_date = "2018-01-15"
+ subscription.end_date = "2018-07-15"
+ subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
subscription.save()
# even though subscription starts at '2018-01-15' and Billing interval is Month and count 3
# First invoice will end at '2018-03-31' instead of '2018-04-14'
- self.assertEqual(get_date_str(subscription.current_invoice_end), '2018-03-31')
+ self.assertEqual(get_date_str(subscription.current_invoice_end), "2018-03-31")
def test_subscription_generate_invoice_past_due(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Supplier'
- subscription.party = '_Test Supplier'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Supplier"
+ subscription.party = "_Test Supplier"
subscription.generate_invoice_at_period_start = 1
subscription.generate_new_invoices_past_due_date = 1
# select subscription start date as '2018-01-15'
- subscription.start_date = '2018-01-01'
- subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1})
+ subscription.start_date = "2018-01-01"
+ subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
subscription.save()
# Process subscription and create first invoice
# Subscription status will be unpaid since due date has already passed
subscription.process()
self.assertEqual(len(subscription.invoices), 1)
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
# Now the Subscription is unpaid
# Even then new invoice should be created as we have enabled `generate_new_invoices_past_due_date` in
@@ -643,39 +658,39 @@ class TestSubscription(unittest.TestCase):
self.assertEqual(len(subscription.invoices), 2)
def test_subscription_without_generate_invoice_past_due(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Supplier'
- subscription.party = '_Test Supplier'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Supplier"
+ subscription.party = "_Test Supplier"
subscription.generate_invoice_at_period_start = 1
# select subscription start date as '2018-01-15'
- subscription.start_date = '2018-01-01'
- subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1})
+ subscription.start_date = "2018-01-01"
+ subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
subscription.save()
# Process subscription and create first invoice
# Subscription status will be unpaid since due date has already passed
subscription.process()
self.assertEqual(len(subscription.invoices), 1)
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
subscription.process()
self.assertEqual(len(subscription.invoices), 1)
def test_multicurrency_subscription(self):
- subscription = frappe.new_doc('Subscription')
- subscription.party_type = 'Customer'
- subscription.party = '_Test Subscription Customer'
+ subscription = frappe.new_doc("Subscription")
+ subscription.party_type = "Customer"
+ subscription.party = "_Test Subscription Customer"
subscription.generate_invoice_at_period_start = 1
- subscription.company = '_Test Company'
+ subscription.company = "_Test Company"
# select subscription start date as '2018-01-15'
- subscription.start_date = '2018-01-01'
- subscription.append('plans', {'plan': '_Test Plan Multicurrency', 'qty': 1})
+ subscription.start_date = "2018-01-01"
+ subscription.append("plans", {"plan": "_Test Plan Multicurrency", "qty": 1})
subscription.save()
subscription.process()
self.assertEqual(len(subscription.invoices), 1)
- self.assertEqual(subscription.status, 'Unpaid')
+ self.assertEqual(subscription.status, "Unpaid")
# Check the currency of the created invoice
- currency = frappe.db.get_value('Sales Invoice', subscription.invoices[0].invoice, 'currency')
- self.assertEqual(currency, 'USD')
\ No newline at end of file
+ currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].invoice, "currency")
+ self.assertEqual(currency, "USD")
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
index 1285343d19..a95e0a9c2d 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
@@ -16,10 +16,13 @@ class SubscriptionPlan(Document):
def validate_interval_count(self):
if self.billing_interval_count < 1:
- frappe.throw(_('Billing Interval Count cannot be less than 1'))
+ frappe.throw(_("Billing Interval Count cannot be less than 1"))
+
@frappe.whitelist()
-def get_plan_rate(plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1):
+def get_plan_rate(
+ plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1
+):
plan = frappe.get_doc("Subscription Plan", plan)
if plan.price_determination == "Fixed Rate":
return plan.cost * prorate_factor
@@ -30,13 +33,19 @@ def get_plan_rate(plan, quantity=1, customer=None, start_date=None, end_date=Non
else:
customer_group = None
- price = get_price(item_code=plan.item, price_list=plan.price_list, customer_group=customer_group, company=None, qty=quantity)
+ price = get_price(
+ item_code=plan.item,
+ price_list=plan.price_list,
+ customer_group=customer_group,
+ company=None,
+ qty=quantity,
+ )
if not price:
return 0
else:
return price.price_list_rate * prorate_factor
- elif plan.price_determination == 'Monthly Rate':
+ elif plan.price_determination == "Monthly Rate":
start_date = getdate(start_date)
end_date = getdate(end_date)
@@ -44,15 +53,21 @@ def get_plan_rate(plan, quantity=1, customer=None, start_date=None, end_date=Non
cost = plan.cost * no_of_months
# Adjust cost if start or end date is not month start or end
- prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
+ prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
if prorate:
- prorate_factor = flt(date_diff(start_date, get_first_day(start_date)) / date_diff(
- get_last_day(start_date), get_first_day(start_date)), 1)
+ prorate_factor = flt(
+ date_diff(start_date, get_first_day(start_date))
+ / date_diff(get_last_day(start_date), get_first_day(start_date)),
+ 1,
+ )
- prorate_factor += flt(date_diff(get_last_day(end_date), end_date) / date_diff(
- get_last_day(end_date), get_first_day(end_date)), 1)
+ prorate_factor += flt(
+ date_diff(get_last_day(end_date), end_date)
+ / date_diff(get_last_day(end_date), get_first_day(end_date)),
+ 1,
+ )
- cost -= (plan.cost * prorate_factor)
+ cost -= plan.cost * prorate_factor
return cost
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan_dashboard.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan_dashboard.py
index d076e39964..7df76cde80 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan_dashboard.py
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan_dashboard.py
@@ -3,15 +3,7 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'subscription_plan',
- 'non_standard_fieldnames': {
- 'Payment Request': 'plan',
- 'Subscription': 'plan'
- },
- 'transactions': [
- {
- 'label': _('References'),
- 'items': ['Payment Request', 'Subscription']
- }
- ]
+ "fieldname": "subscription_plan",
+ "non_standard_fieldnames": {"Payment Request": "plan", "Subscription": "plan"},
+ "transactions": [{"label": _("References"), "items": ["Payment Request", "Subscription"]}],
}
diff --git a/erpnext/accounts/doctype/tax_category/tax_category_dashboard.py b/erpnext/accounts/doctype/tax_category/tax_category_dashboard.py
index 4bdb70a480..17a275ebc3 100644
--- a/erpnext/accounts/doctype/tax_category/tax_category_dashboard.py
+++ b/erpnext/accounts/doctype/tax_category/tax_category_dashboard.py
@@ -3,27 +3,12 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'tax_category',
- 'transactions': [
- {
- 'label': _('Pre Sales'),
- 'items': ['Quotation', 'Supplier Quotation']
- },
- {
- 'label': _('Sales'),
- 'items': ['Sales Invoice', 'Delivery Note', 'Sales Order']
- },
- {
- 'label': _('Purchase'),
- 'items': ['Purchase Invoice', 'Purchase Receipt']
- },
- {
- 'label': _('Party'),
- 'items': ['Customer', 'Supplier']
- },
- {
- 'label': _('Taxes'),
- 'items': ['Item', 'Tax Rule']
- }
- ]
+ "fieldname": "tax_category",
+ "transactions": [
+ {"label": _("Pre Sales"), "items": ["Quotation", "Supplier Quotation"]},
+ {"label": _("Sales"), "items": ["Sales Invoice", "Delivery Note", "Sales Order"]},
+ {"label": _("Purchase"), "items": ["Purchase Invoice", "Purchase Receipt"]},
+ {"label": _("Party"), "items": ["Customer", "Supplier"]},
+ {"label": _("Taxes"), "items": ["Item", "Tax Rule"]},
+ ],
}
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py
index 2d94bc376a..27b78e9fab 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py
@@ -14,9 +14,17 @@ from frappe.utils.nestedset import get_root_of
from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups
-class IncorrectCustomerGroup(frappe.ValidationError): pass
-class IncorrectSupplierType(frappe.ValidationError): pass
-class ConflictingTaxRule(frappe.ValidationError): pass
+class IncorrectCustomerGroup(frappe.ValidationError):
+ pass
+
+
+class IncorrectSupplierType(frappe.ValidationError):
+ pass
+
+
+class ConflictingTaxRule(frappe.ValidationError):
+ pass
+
class TaxRule(Document):
def __setup__(self):
@@ -29,7 +37,7 @@ class TaxRule(Document):
self.validate_use_for_shopping_cart()
def validate_tax_template(self):
- if self.tax_type== "Sales":
+ if self.tax_type == "Sales":
self.purchase_tax_template = self.supplier = self.supplier_group = None
if self.customer:
self.customer_group = None
@@ -49,28 +57,28 @@ class TaxRule(Document):
def validate_filters(self):
filters = {
- "tax_type": self.tax_type,
- "customer": self.customer,
- "customer_group": self.customer_group,
- "supplier": self.supplier,
- "supplier_group": self.supplier_group,
- "item": self.item,
- "item_group": self.item_group,
- "billing_city": self.billing_city,
- "billing_county": self.billing_county,
- "billing_state": self.billing_state,
- "billing_zipcode": self.billing_zipcode,
- "billing_country": self.billing_country,
- "shipping_city": self.shipping_city,
- "shipping_county": self.shipping_county,
- "shipping_state": self.shipping_state,
- "shipping_zipcode": self.shipping_zipcode,
- "shipping_country": self.shipping_country,
- "tax_category": self.tax_category,
- "company": self.company
+ "tax_type": self.tax_type,
+ "customer": self.customer,
+ "customer_group": self.customer_group,
+ "supplier": self.supplier,
+ "supplier_group": self.supplier_group,
+ "item": self.item,
+ "item_group": self.item_group,
+ "billing_city": self.billing_city,
+ "billing_county": self.billing_county,
+ "billing_state": self.billing_state,
+ "billing_zipcode": self.billing_zipcode,
+ "billing_country": self.billing_country,
+ "shipping_city": self.shipping_city,
+ "shipping_county": self.shipping_county,
+ "shipping_state": self.shipping_state,
+ "shipping_zipcode": self.shipping_zipcode,
+ "shipping_country": self.shipping_country,
+ "tax_category": self.tax_category,
+ "company": self.company,
}
- conds=""
+ conds = ""
for d in filters:
if conds:
conds += " and "
@@ -80,85 +88,112 @@ class TaxRule(Document):
conds += """ and ((from_date > '{from_date}' and from_date < '{to_date}') or
(to_date > '{from_date}' and to_date < '{to_date}') or
('{from_date}' > from_date and '{from_date}' < to_date) or
- ('{from_date}' = from_date and '{to_date}' = to_date))""".format(from_date=self.from_date, to_date=self.to_date)
+ ('{from_date}' = from_date and '{to_date}' = to_date))""".format(
+ from_date=self.from_date, to_date=self.to_date
+ )
elif self.from_date and not self.to_date:
- conds += """ and to_date > '{from_date}'""".format(from_date = self.from_date)
+ conds += """ and to_date > '{from_date}'""".format(from_date=self.from_date)
elif self.to_date and not self.from_date:
- conds += """ and from_date < '{to_date}'""".format(to_date = self.to_date)
+ conds += """ and from_date < '{to_date}'""".format(to_date=self.to_date)
- tax_rule = frappe.db.sql("select name, priority \
- from `tabTax Rule` where {0} and name != '{1}'".format(conds, self.name), as_dict=1)
+ tax_rule = frappe.db.sql(
+ "select name, priority \
+ from `tabTax Rule` where {0} and name != '{1}'".format(
+ conds, self.name
+ ),
+ as_dict=1,
+ )
if tax_rule:
if tax_rule[0].priority == self.priority:
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
def validate_use_for_shopping_cart(self):
- '''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one'''
- if (not self.use_for_shopping_cart
- and cint(frappe.db.get_single_value('E Commerce Settings', 'enabled'))
- and not frappe.db.get_value('Tax Rule', {'use_for_shopping_cart': 1, 'name': ['!=', self.name]})):
+ """If shopping cart is enabled and no tax rule exists for shopping cart, enable this one"""
+ if (
+ not self.use_for_shopping_cart
+ and cint(frappe.db.get_single_value("E Commerce Settings", "enabled"))
+ and not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1, "name": ["!=", self.name]})
+ ):
self.use_for_shopping_cart = 1
- frappe.msgprint(_("Enabling 'Use for Shopping Cart', as Shopping Cart is enabled and there should be at least one Tax Rule for Shopping Cart"))
+ frappe.msgprint(
+ _(
+ "Enabling 'Use for Shopping Cart', as Shopping Cart is enabled and there should be at least one Tax Rule for Shopping Cart"
+ )
+ )
+
@frappe.whitelist()
def get_party_details(party, party_type, args=None):
out = {}
billing_address, shipping_address = None, None
if args:
- if args.get('billing_address'):
- billing_address = frappe.get_doc('Address', args.get('billing_address'))
- if args.get('shipping_address'):
- shipping_address = frappe.get_doc('Address', args.get('shipping_address'))
+ if args.get("billing_address"):
+ billing_address = frappe.get_doc("Address", args.get("billing_address"))
+ if args.get("shipping_address"):
+ shipping_address = frappe.get_doc("Address", args.get("shipping_address"))
else:
billing_address_name = get_default_address(party_type, party)
- shipping_address_name = get_default_address(party_type, party, 'is_shipping_address')
+ shipping_address_name = get_default_address(party_type, party, "is_shipping_address")
if billing_address_name:
- billing_address = frappe.get_doc('Address', billing_address_name)
+ billing_address = frappe.get_doc("Address", billing_address_name)
if shipping_address_name:
- shipping_address = frappe.get_doc('Address', shipping_address_name)
+ shipping_address = frappe.get_doc("Address", shipping_address_name)
if billing_address:
- out["billing_city"]= billing_address.city
- out["billing_county"]= billing_address.county
- out["billing_state"]= billing_address.state
- out["billing_zipcode"]= billing_address.pincode
- out["billing_country"]= billing_address.country
+ out["billing_city"] = billing_address.city
+ out["billing_county"] = billing_address.county
+ out["billing_state"] = billing_address.state
+ out["billing_zipcode"] = billing_address.pincode
+ out["billing_country"] = billing_address.country
if shipping_address:
- out["shipping_city"]= shipping_address.city
- out["shipping_county"]= shipping_address.county
- out["shipping_state"]= shipping_address.state
- out["shipping_zipcode"]= shipping_address.pincode
- out["shipping_country"]= shipping_address.country
+ out["shipping_city"] = shipping_address.city
+ out["shipping_county"] = shipping_address.county
+ out["shipping_state"] = shipping_address.state
+ out["shipping_zipcode"] = shipping_address.pincode
+ out["shipping_country"] = shipping_address.country
return out
+
def get_tax_template(posting_date, args):
"""Get matching tax rule"""
args = frappe._dict(args)
- conditions = ["""(from_date is null or from_date <= '{0}')
- and (to_date is null or to_date >= '{0}')""".format(posting_date)]
+ conditions = [
+ """(from_date is null or from_date <= '{0}')
+ and (to_date is null or to_date >= '{0}')""".format(
+ posting_date
+ )
+ ]
- conditions.append("ifnull(tax_category, '') = {0}".format(frappe.db.escape(cstr(args.get("tax_category")))))
- if 'tax_category' in args.keys():
- del args['tax_category']
+ conditions.append(
+ "ifnull(tax_category, '') = {0}".format(frappe.db.escape(cstr(args.get("tax_category"))))
+ )
+ if "tax_category" in args.keys():
+ del args["tax_category"]
for key, value in args.items():
- if key=="use_for_shopping_cart":
+ if key == "use_for_shopping_cart":
conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0))
- elif key == 'customer_group':
- if not value: value = get_root_of("Customer Group")
+ elif key == "customer_group":
+ if not value:
+ value = get_root_of("Customer Group")
customer_group_condition = get_customer_group_condition(value)
conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition))
else:
conditions.append("ifnull({0}, '') in ('', {1})".format(key, frappe.db.escape(cstr(value))))
- tax_rule = frappe.db.sql("""select * from `tabTax Rule`
- where {0}""".format(" and ".join(conditions)), as_dict = True)
+ tax_rule = frappe.db.sql(
+ """select * from `tabTax Rule`
+ where {0}""".format(
+ " and ".join(conditions)
+ ),
+ as_dict=True,
+ )
if not tax_rule:
return None
@@ -166,28 +201,34 @@ def get_tax_template(posting_date, args):
for rule in tax_rule:
rule.no_of_keys_matched = 0
for key in args:
- if rule.get(key): rule.no_of_keys_matched += 1
+ if rule.get(key):
+ rule.no_of_keys_matched += 1
def cmp(a, b):
# refernce: https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
return int(a > b) - int(a < b)
- rule = sorted(tax_rule,
- key = functools.cmp_to_key(lambda b, a:
- cmp(a.no_of_keys_matched, b.no_of_keys_matched) or
- cmp(a.priority, b.priority)))[0]
+ rule = sorted(
+ tax_rule,
+ key=functools.cmp_to_key(
+ lambda b, a: cmp(a.no_of_keys_matched, b.no_of_keys_matched) or cmp(a.priority, b.priority)
+ ),
+ )[0]
tax_template = rule.sales_tax_template or rule.purchase_tax_template
doctype = "{0} Taxes and Charges Template".format(rule.tax_type)
- if frappe.db.get_value(doctype, tax_template, 'disabled')==1:
+ if frappe.db.get_value(doctype, tax_template, "disabled") == 1:
return None
return tax_template
+
def get_customer_group_condition(customer_group):
condition = ""
- customer_groups = ["%s"%(frappe.db.escape(d.name)) for d in get_parent_customer_groups(customer_group)]
+ customer_groups = [
+ "%s" % (frappe.db.escape(d.name)) for d in get_parent_customer_groups(customer_group)
+ ]
if customer_groups:
- condition = ",".join(['%s'] * len(customer_groups))%(tuple(customer_groups))
+ condition = ",".join(["%s"] * len(customer_groups)) % (tuple(customer_groups))
return condition
diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
index d5ac9b20c5..848e05424b 100644
--- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
@@ -9,8 +9,7 @@ from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule, get_t
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
-test_records = frappe.get_test_records('Tax Rule')
-
+test_records = frappe.get_test_records("Tax Rule")
class TestTaxRule(unittest.TestCase):
@@ -26,40 +25,70 @@ class TestTaxRule(unittest.TestCase):
frappe.db.sql("delete from `tabTax Rule`")
def test_conflict(self):
- tax_rule1 = make_tax_rule(customer= "_Test Customer",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1)
+ tax_rule1 = make_tax_rule(
+ customer="_Test Customer",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ )
tax_rule1.save()
- tax_rule2 = make_tax_rule(customer= "_Test Customer",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1)
+ tax_rule2 = make_tax_rule(
+ customer="_Test Customer",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ )
self.assertRaises(ConflictingTaxRule, tax_rule2.save)
def test_conflict_with_non_overlapping_dates(self):
- tax_rule1 = make_tax_rule(customer= "_Test Customer",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01")
+ tax_rule1 = make_tax_rule(
+ customer="_Test Customer",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ from_date="2015-01-01",
+ )
tax_rule1.save()
- tax_rule2 = make_tax_rule(customer= "_Test Customer",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, to_date = "2013-01-01")
+ tax_rule2 = make_tax_rule(
+ customer="_Test Customer",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ to_date="2013-01-01",
+ )
tax_rule2.save()
self.assertTrue(tax_rule2.name)
def test_for_parent_customer_group(self):
- tax_rule1 = make_tax_rule(customer_group= "All Customer Groups",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01")
+ tax_rule1 = make_tax_rule(
+ customer_group="All Customer Groups",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ from_date="2015-01-01",
+ )
tax_rule1.save()
- self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":1}),
- "_Test Sales Taxes and Charges Template - _TC")
+ self.assertEqual(
+ get_tax_template("2015-01-01", {"customer_group": "Commercial", "use_for_shopping_cart": 1}),
+ "_Test Sales Taxes and Charges Template - _TC",
+ )
def test_conflict_with_overlapping_dates(self):
- tax_rule1 = make_tax_rule(customer= "_Test Customer",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01", to_date = "2015-01-05")
+ tax_rule1 = make_tax_rule(
+ customer="_Test Customer",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ from_date="2015-01-01",
+ to_date="2015-01-05",
+ )
tax_rule1.save()
- tax_rule2 = make_tax_rule(customer= "_Test Customer",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-03", to_date = "2015-01-09")
+ tax_rule2 = make_tax_rule(
+ customer="_Test Customer",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ from_date="2015-01-03",
+ to_date="2015-01-09",
+ )
self.assertRaises(ConflictingTaxRule, tax_rule2.save)
@@ -67,93 +96,186 @@ class TestTaxRule(unittest.TestCase):
tax_rule = make_tax_rule()
self.assertEqual(tax_rule.purchase_tax_template, None)
-
def test_select_tax_rule_based_on_customer(self):
- make_tax_rule(customer= "_Test Customer",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ save=1,
+ )
- make_tax_rule(customer= "_Test Customer 1",
- sales_tax_template = "_Test Sales Taxes and Charges Template 1 - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer 1",
+ sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
+ save=1,
+ )
- make_tax_rule(customer= "_Test Customer 2",
- sales_tax_template = "_Test Sales Taxes and Charges Template 2 - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer 2",
+ sales_tax_template="_Test Sales Taxes and Charges Template 2 - _TC",
+ save=1,
+ )
- self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer 2"}),
- "_Test Sales Taxes and Charges Template 2 - _TC")
+ self.assertEqual(
+ get_tax_template("2015-01-01", {"customer": "_Test Customer 2"}),
+ "_Test Sales Taxes and Charges Template 2 - _TC",
+ )
def test_select_tax_rule_based_on_tax_category(self):
- make_tax_rule(customer="_Test Customer", tax_category="_Test Tax Category 1",
- sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ tax_category="_Test Tax Category 1",
+ sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
+ save=1,
+ )
- make_tax_rule(customer="_Test Customer", tax_category="_Test Tax Category 2",
- sales_tax_template="_Test Sales Taxes and Charges Template 2 - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ tax_category="_Test Tax Category 2",
+ sales_tax_template="_Test Sales Taxes and Charges Template 2 - _TC",
+ save=1,
+ )
self.assertFalse(get_tax_template("2015-01-01", {"customer": "_Test Customer"}))
- self.assertEqual(get_tax_template("2015-01-01", {"customer": "_Test Customer", "tax_category": "_Test Tax Category 1"}),
- "_Test Sales Taxes and Charges Template 1 - _TC")
- self.assertEqual(get_tax_template("2015-01-01", {"customer": "_Test Customer", "tax_category": "_Test Tax Category 2"}),
- "_Test Sales Taxes and Charges Template 2 - _TC")
+ self.assertEqual(
+ get_tax_template(
+ "2015-01-01", {"customer": "_Test Customer", "tax_category": "_Test Tax Category 1"}
+ ),
+ "_Test Sales Taxes and Charges Template 1 - _TC",
+ )
+ self.assertEqual(
+ get_tax_template(
+ "2015-01-01", {"customer": "_Test Customer", "tax_category": "_Test Tax Category 2"}
+ ),
+ "_Test Sales Taxes and Charges Template 2 - _TC",
+ )
- make_tax_rule(customer="_Test Customer", tax_category="",
- sales_tax_template="_Test Sales Taxes and Charges Template - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ tax_category="",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ save=1,
+ )
- self.assertEqual(get_tax_template("2015-01-01", {"customer": "_Test Customer"}),
- "_Test Sales Taxes and Charges Template - _TC")
+ self.assertEqual(
+ get_tax_template("2015-01-01", {"customer": "_Test Customer"}),
+ "_Test Sales Taxes and Charges Template - _TC",
+ )
def test_select_tax_rule_based_on_better_match(self):
- make_tax_rule(customer= "_Test Customer", billing_city = "Test City", billing_state = "Test State",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ billing_city="Test City",
+ billing_state="Test State",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ save=1,
+ )
- make_tax_rule(customer= "_Test Customer", billing_city = "Test City1", billing_state = "Test State",
- sales_tax_template = "_Test Sales Taxes and Charges Template 1 - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ billing_city="Test City1",
+ billing_state="Test State",
+ sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
+ save=1,
+ )
- self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City", "billing_state": "Test State"}),
- "_Test Sales Taxes and Charges Template - _TC")
+ self.assertEqual(
+ get_tax_template(
+ "2015-01-01",
+ {"customer": "_Test Customer", "billing_city": "Test City", "billing_state": "Test State"},
+ ),
+ "_Test Sales Taxes and Charges Template - _TC",
+ )
def test_select_tax_rule_based_on_state_match(self):
- make_tax_rule(customer= "_Test Customer", shipping_state = "Test State",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ shipping_state="Test State",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ save=1,
+ )
- make_tax_rule(customer= "_Test Customer", shipping_state = "Test State12",
- sales_tax_template = "_Test Sales Taxes and Charges Template 1 - _TC", priority=2, save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ shipping_state="Test State12",
+ sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
+ priority=2,
+ save=1,
+ )
- self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "shipping_state": "Test State"}),
- "_Test Sales Taxes and Charges Template - _TC")
+ self.assertEqual(
+ get_tax_template("2015-01-01", {"customer": "_Test Customer", "shipping_state": "Test State"}),
+ "_Test Sales Taxes and Charges Template - _TC",
+ )
def test_select_tax_rule_based_on_better_priority(self):
- make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority=1, save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ billing_city="Test City",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ priority=1,
+ save=1,
+ )
- make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
- sales_tax_template = "_Test Sales Taxes and Charges Template 1 - _TC", priority=2, save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ billing_city="Test City",
+ sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
+ priority=2,
+ save=1,
+ )
- self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City"}),
- "_Test Sales Taxes and Charges Template 1 - _TC")
+ self.assertEqual(
+ get_tax_template("2015-01-01", {"customer": "_Test Customer", "billing_city": "Test City"}),
+ "_Test Sales Taxes and Charges Template 1 - _TC",
+ )
def test_select_tax_rule_based_cross_matching_keys(self):
- make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ billing_city="Test City",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ save=1,
+ )
- make_tax_rule(customer= "_Test Customer 1", billing_city = "Test City 1",
- sales_tax_template = "_Test Sales Taxes and Charges Template 1 - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer 1",
+ billing_city="Test City 1",
+ sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
+ save=1,
+ )
- self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
- None)
+ self.assertEqual(
+ get_tax_template("2015-01-01", {"customer": "_Test Customer", "billing_city": "Test City 1"}),
+ None,
+ )
def test_select_tax_rule_based_cross_partially_keys(self):
- make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ billing_city="Test City",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ save=1,
+ )
- make_tax_rule(billing_city = "Test City 1",
- sales_tax_template = "_Test Sales Taxes and Charges Template 1 - _TC", save=1)
+ make_tax_rule(
+ billing_city="Test City 1",
+ sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
+ save=1,
+ )
- self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
- "_Test Sales Taxes and Charges Template 1 - _TC")
+ self.assertEqual(
+ get_tax_template("2015-01-01", {"customer": "_Test Customer", "billing_city": "Test City 1"}),
+ "_Test Sales Taxes and Charges Template 1 - _TC",
+ )
def test_taxes_fetch_via_tax_rule(self):
- make_tax_rule(customer= "_Test Customer", billing_city = "_Test City",
- sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
+ make_tax_rule(
+ customer="_Test Customer",
+ billing_city="_Test City",
+ sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
+ save=1,
+ )
# create opportunity for customer
opportunity = make_opportunity(with_items=1)
@@ -168,7 +290,6 @@ class TestTaxRule(unittest.TestCase):
self.assertTrue(len(quotation.taxes) > 0)
-
def make_tax_rule(**args):
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 5bb9b93185..35c2f8494f 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -16,7 +16,7 @@ class TaxWithholdingCategory(Document):
def validate_dates(self):
last_date = None
- for d in self.get('rates'):
+ for d in self.get("rates"):
if getdate(d.from_date) >= getdate(d.to_date):
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))
@@ -26,25 +26,30 @@ class TaxWithholdingCategory(Document):
def validate_accounts(self):
existing_accounts = []
- for d in self.get('accounts'):
- if d.get('account') in existing_accounts:
- frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get('account'))))
+ for d in self.get("accounts"):
+ if d.get("account") in existing_accounts:
+ frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
- existing_accounts.append(d.get('account'))
+ existing_accounts.append(d.get("account"))
def validate_thresholds(self):
- for d in self.get('rates'):
+ for d in self.get("rates"):
if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold:
- frappe.throw(_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(d.idx))
+ frappe.throw(
+ _("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(
+ d.idx
+ )
+ )
+
def get_party_details(inv):
- party_type, party = '', ''
+ party_type, party = "", ""
- if inv.doctype == 'Sales Invoice':
- party_type = 'Customer'
+ if inv.doctype == "Sales Invoice":
+ party_type = "Customer"
party = inv.customer
else:
- party_type = 'Supplier'
+ party_type = "Supplier"
party = inv.supplier
if not party:
@@ -52,65 +57,71 @@ def get_party_details(inv):
return party_type, party
+
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
- pan_no = ''
+ pan_no = ""
parties = []
party_type, party = get_party_details(inv)
has_pan_field = frappe.get_meta(party_type).has_field("pan")
if not tax_withholding_category:
if has_pan_field:
- fields = ['tax_withholding_category', 'pan']
+ fields = ["tax_withholding_category", "pan"]
else:
- fields = ['tax_withholding_category']
+ fields = ["tax_withholding_category"]
tax_withholding_details = frappe.db.get_value(party_type, party, fields, as_dict=1)
- tax_withholding_category = tax_withholding_details.get('tax_withholding_category')
- pan_no = tax_withholding_details.get('pan')
+ tax_withholding_category = tax_withholding_details.get("tax_withholding_category")
+ pan_no = tax_withholding_details.get("pan")
if not tax_withholding_category:
return
# if tax_withholding_category passed as an argument but not pan_no
if not pan_no and has_pan_field:
- pan_no = frappe.db.get_value(party_type, party, 'pan')
+ pan_no = frappe.db.get_value(party_type, party, "pan")
# Get others suppliers with the same PAN No
if pan_no:
- parties = frappe.get_all(party_type, filters={ 'pan': pan_no }, pluck='name')
+ parties = frappe.get_all(party_type, filters={"pan": pan_no}, pluck="name")
if not parties:
parties.append(party)
- posting_date = inv.get('posting_date') or inv.get('transaction_date')
+ posting_date = inv.get("posting_date") or inv.get("transaction_date")
tax_details = get_tax_withholding_details(tax_withholding_category, posting_date, inv.company)
if not tax_details:
- frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
- .format(tax_withholding_category, inv.company))
+ frappe.throw(
+ _("Please set associated account in Tax Withholding Category {0} against Company {1}").format(
+ tax_withholding_category, inv.company
+ )
+ )
- if party_type == 'Customer' and not tax_details.cumulative_threshold:
+ if party_type == "Customer" and not tax_details.cumulative_threshold:
# TCS is only chargeable on sum of invoiced value
- frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
- .format(tax_withholding_category, inv.company, party))
+ frappe.throw(
+ _(
+ "Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value."
+ ).format(tax_withholding_category, inv.company, party)
+ )
tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount(
- party_type, parties,
- inv, tax_details,
- posting_date, pan_no
+ party_type, parties, inv, tax_details, posting_date, pan_no
)
- if party_type == 'Supplier':
+ if party_type == "Supplier":
tax_row = get_tax_row_for_tds(tax_details, tax_amount)
else:
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
- if inv.doctype == 'Purchase Invoice':
+ if inv.doctype == "Purchase Invoice":
return tax_row, tax_deducted_on_advances
else:
return tax_row
+
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
@@ -118,19 +129,24 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company)
for account_detail in tax_withholding.accounts:
if company == account_detail.company:
- return frappe._dict({
- "tax_withholding_category": tax_withholding_category,
- "account_head": account_detail.account,
- "rate": tax_rate_detail.tax_withholding_rate,
- "from_date": tax_rate_detail.from_date,
- "to_date": tax_rate_detail.to_date,
- "threshold": tax_rate_detail.single_threshold,
- "cumulative_threshold": tax_rate_detail.cumulative_threshold,
- "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
- "consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
- "tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
- "round_off_tax_amount": tax_withholding.round_off_tax_amount
- })
+ return frappe._dict(
+ {
+ "tax_withholding_category": tax_withholding_category,
+ "account_head": account_detail.account,
+ "rate": tax_rate_detail.tax_withholding_rate,
+ "from_date": tax_rate_detail.from_date,
+ "to_date": tax_rate_detail.to_date,
+ "threshold": tax_rate_detail.single_threshold,
+ "cumulative_threshold": tax_rate_detail.cumulative_threshold,
+ "description": tax_withholding.category_name
+ if tax_withholding.category_name
+ else tax_withholding_category,
+ "consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
+ "tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
+ "round_off_tax_amount": tax_withholding.round_off_tax_amount,
+ }
+ )
+
def get_tax_withholding_rates(tax_withholding, posting_date):
# returns the row that matches with the fiscal year from posting date
@@ -140,13 +156,14 @@ def get_tax_withholding_rates(tax_withholding, posting_date):
frappe.throw(_("No Tax Withholding data found for the current posting date."))
+
def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted):
row = {
"category": "Total",
"charge_type": "Actual",
"tax_amount": tax_amount,
"description": tax_details.description,
- "account_head": tax_details.account_head
+ "account_head": tax_details.account_head,
}
if tax_deducted:
@@ -156,20 +173,20 @@ def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted):
taxes_excluding_tcs = [d for d in inv.taxes if d.account_head != tax_details.account_head]
if taxes_excluding_tcs:
# chargeable amount is the total amount after other charges are applied
- row.update({
- "charge_type": "On Previous Row Total",
- "row_id": len(taxes_excluding_tcs),
- "rate": tax_details.rate
- })
+ row.update(
+ {
+ "charge_type": "On Previous Row Total",
+ "row_id": len(taxes_excluding_tcs),
+ "rate": tax_details.rate,
+ }
+ )
else:
# if only TCS is to be charged, then net total is chargeable amount
- row.update({
- "charge_type": "On Net Total",
- "rate": tax_details.rate
- })
+ row.update({"charge_type": "On Net Total", "rate": tax_details.rate})
return row
+
def get_tax_row_for_tds(tax_details, tax_amount):
return {
"category": "Total",
@@ -177,29 +194,39 @@ def get_tax_row_for_tds(tax_details, tax_amount):
"tax_amount": tax_amount,
"add_deduct_tax": "Deduct",
"description": tax_details.description,
- "account_head": tax_details.account_head
+ "account_head": tax_details.account_head,
}
+
def get_lower_deduction_certificate(tax_details, pan_no):
- ldc_name = frappe.db.get_value('Lower Deduction Certificate',
+ ldc_name = frappe.db.get_value(
+ "Lower Deduction Certificate",
{
- 'pan_no': pan_no,
- 'tax_withholding_category': tax_details.tax_withholding_category,
- 'valid_from': ('>=', tax_details.from_date),
- 'valid_upto': ('<=', tax_details.to_date)
- }, 'name')
+ "pan_no": pan_no,
+ "tax_withholding_category": tax_details.tax_withholding_category,
+ "valid_from": (">=", tax_details.from_date),
+ "valid_upto": ("<=", tax_details.to_date),
+ },
+ "name",
+ )
if ldc_name:
- return frappe.get_doc('Lower Deduction Certificate', ldc_name)
+ return frappe.get_doc("Lower Deduction Certificate", ldc_name)
+
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type)
- advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date,
- to_date=tax_details.to_date, party_type=party_type)
+ advance_vouchers = get_advance_vouchers(
+ parties,
+ company=inv.company,
+ from_date=tax_details.from_date,
+ to_date=tax_details.to_date,
+ party_type=party_type,
+ )
taxable_vouchers = vouchers + advance_vouchers
tax_deducted_on_advances = 0
- if inv.doctype == 'Purchase Invoice':
+ if inv.doctype == "Purchase Invoice":
tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details)
tax_deducted = 0
@@ -207,18 +234,20 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
tax_amount = 0
- if party_type == 'Supplier':
+ if party_type == "Supplier":
ldc = get_lower_deduction_certificate(tax_details, pan_no)
if tax_deducted:
net_total = inv.net_total
if ldc:
- tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total)
+ tax_amount = get_tds_amount_from_ldc(
+ ldc, parties, pan_no, tax_details, posting_date, net_total
+ )
else:
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
else:
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)
- elif party_type == 'Customer':
+ elif party_type == "Customer":
if tax_deducted:
# if already TCS is charged, then amount will be calculated based on 'Previous Row Total'
tax_amount = 0
@@ -232,27 +261,28 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
return tax_amount, tax_deducted, tax_deducted_on_advances
-def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
- dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
- doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice'
+
+def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
+ dr_or_cr = "credit" if party_type == "Supplier" else "debit"
+ doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
filters = {
- 'company': company,
- frappe.scrub(party_type): ['in', parties],
- 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
- 'is_opening': 'No',
- 'docstatus': 1
+ "company": company,
+ frappe.scrub(party_type): ["in", parties],
+ "posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
+ "is_opening": "No",
+ "docstatus": 1,
}
- if not tax_details.get('consider_party_ledger_amount') and doctype != "Sales Invoice":
- filters.update({
- 'apply_tds': 1,
- 'tax_withholding_category': tax_details.get('tax_withholding_category')
- })
+ if not tax_details.get("consider_party_ledger_amount") and doctype != "Sales Invoice":
+ filters.update(
+ {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
+ )
invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""]
- journal_entries = frappe.db.sql("""
+ journal_entries = frappe.db.sql(
+ """
SELECT j.name
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
WHERE
@@ -261,52 +291,60 @@ def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
AND j.posting_date between %s and %s
AND ja.{dr_or_cr} > 0
AND ja.party in %s
- """.format(dr_or_cr=dr_or_cr), (tax_details.from_date, tax_details.to_date, tuple(parties)), as_list=1)
+ """.format(
+ dr_or_cr=dr_or_cr
+ ),
+ (tax_details.from_date, tax_details.to_date, tuple(parties)),
+ as_list=1,
+ )
if journal_entries:
journal_entries = journal_entries[0]
return invoices + journal_entries
-def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'):
+
+def get_advance_vouchers(
+ parties, company=None, from_date=None, to_date=None, party_type="Supplier"
+):
# for advance vouchers, debit and credit is reversed
- dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit'
+ dr_or_cr = "debit" if party_type == "Supplier" else "credit"
filters = {
- dr_or_cr: ['>', 0],
- 'is_opening': 'No',
- 'is_cancelled': 0,
- 'party_type': party_type,
- 'party': ['in', parties],
- 'against_voucher': ['is', 'not set']
+ dr_or_cr: [">", 0],
+ "is_opening": "No",
+ "is_cancelled": 0,
+ "party_type": party_type,
+ "party": ["in", parties],
+ "against_voucher": ["is", "not set"],
}
if company:
- filters['company'] = company
+ filters["company"] = company
if from_date and to_date:
- filters['posting_date'] = ['between', (from_date, to_date)]
+ filters["posting_date"] = ["between", (from_date, to_date)]
+
+ return frappe.get_all("GL Entry", filters=filters, distinct=1, pluck="voucher_no") or [""]
- return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
- advances = [d.reference_name for d in inv.get('advances')]
+ advances = [d.reference_name for d in inv.get("advances")]
tax_info = []
if advances:
pe = frappe.qb.DocType("Payment Entry").as_("pe")
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
- tax_info = frappe.qb.from_(at).inner_join(pe).on(
- pe.name == at.parent
- ).select(
- at.parent, at.name, at.tax_amount, at.allocated_amount
- ).where(
- pe.tax_withholding_category == tax_details.get('tax_withholding_category')
- ).where(
- at.parent.isin(advances)
- ).where(
- at.account_head == tax_details.account_head
- ).run(as_dict=True)
+ tax_info = (
+ frappe.qb.from_(at)
+ .inner_join(pe)
+ .on(pe.name == at.parent)
+ .select(at.parent, at.name, at.tax_amount, at.allocated_amount)
+ .where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
+ .where(at.parent.isin(advances))
+ .where(at.account_head == tax_details.account_head)
+ .run(as_dict=True)
+ )
return tax_info
@@ -314,59 +352,74 @@ def get_taxes_deducted_on_advances_allocated(inv, tax_details):
def get_deducted_tax(taxable_vouchers, tax_details):
# check if TDS / TCS account is already charged on taxable vouchers
filters = {
- 'is_cancelled': 0,
- 'credit': ['>', 0],
- 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
- 'account': tax_details.account_head,
- 'voucher_no': ['in', taxable_vouchers],
+ "is_cancelled": 0,
+ "credit": [">", 0],
+ "posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
+ "account": tax_details.account_head,
+ "voucher_no": ["in", taxable_vouchers],
}
field = "credit"
- entries = frappe.db.get_all('GL Entry', filters, pluck=field)
+ entries = frappe.db.get_all("GL Entry", filters, pluck=field)
return sum(entries)
+
def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
tds_amount = 0
- invoice_filters = {
- 'name': ('in', vouchers),
- 'docstatus': 1,
- 'apply_tds': 1
- }
+ invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
- field = 'sum(net_total)'
+ field = "sum(net_total)"
if cint(tax_details.consider_party_ledger_amount):
- invoice_filters.pop('apply_tds', None)
- field = 'sum(grand_total)'
+ invoice_filters.pop("apply_tds", None)
+ field = "sum(grand_total)"
- supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
+ supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
- supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
- 'parent': ('in', vouchers), 'docstatus': 1,
- 'party': ('in', parties), 'reference_type': ('!=', 'Purchase Invoice')
- }, 'sum(credit_in_account_currency)') or 0.0
+ supp_jv_credit_amt = (
+ frappe.db.get_value(
+ "Journal Entry Account",
+ {
+ "parent": ("in", vouchers),
+ "docstatus": 1,
+ "party": ("in", parties),
+ "reference_type": ("!=", "Purchase Invoice"),
+ },
+ "sum(credit_in_account_currency)",
+ )
+ or 0.0
+ )
supp_credit_amt += supp_jv_credit_amt
supp_credit_amt += inv.net_total
- debit_note_amount = get_debit_note_amount(parties, tax_details.from_date, tax_details.to_date, inv.company)
+ debit_note_amount = get_debit_note_amount(
+ parties, tax_details.from_date, tax_details.to_date, inv.company
+ )
supp_credit_amt -= debit_note_amount
- threshold = tax_details.get('threshold', 0)
- cumulative_threshold = tax_details.get('cumulative_threshold', 0)
+ threshold = tax_details.get("threshold", 0)
+ cumulative_threshold = tax_details.get("cumulative_threshold", 0)
- if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
- if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
+ if (threshold and inv.net_total >= threshold) or (
+ cumulative_threshold and supp_credit_amt >= cumulative_threshold
+ ):
+ if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
+ tax_details.tax_on_excess_amount
+ ):
# Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach
- net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
+ net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0
net_total += inv.net_total
supp_credit_amt = net_total - cumulative_threshold
if ldc and is_valid_certificate(
- ldc.valid_from, ldc.valid_upto,
- inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
- inv.net_total, ldc.certificate_limit
+ ldc.valid_from,
+ ldc.valid_upto,
+ inv.get("posting_date") or inv.get("transaction_date"),
+ tax_deducted,
+ inv.net_total,
+ ldc.certificate_limit,
):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
else:
@@ -374,98 +427,127 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
return tds_amount
+
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
tcs_amount = 0
# sum of debit entries made from sales invoices
- invoiced_amt = frappe.db.get_value('GL Entry', {
- 'is_cancelled': 0,
- 'party': ['in', parties],
- 'company': inv.company,
- 'voucher_no': ['in', vouchers],
- }, 'sum(debit)') or 0.0
+ invoiced_amt = (
+ frappe.db.get_value(
+ "GL Entry",
+ {
+ "is_cancelled": 0,
+ "party": ["in", parties],
+ "company": inv.company,
+ "voucher_no": ["in", vouchers],
+ },
+ "sum(debit)",
+ )
+ or 0.0
+ )
# sum of credit entries made from PE / JV with unset 'against voucher'
- advance_amt = frappe.db.get_value('GL Entry', {
- 'is_cancelled': 0,
- 'party': ['in', parties],
- 'company': inv.company,
- 'voucher_no': ['in', adv_vouchers],
- }, 'sum(credit)') or 0.0
+ advance_amt = (
+ frappe.db.get_value(
+ "GL Entry",
+ {
+ "is_cancelled": 0,
+ "party": ["in", parties],
+ "company": inv.company,
+ "voucher_no": ["in", adv_vouchers],
+ },
+ "sum(credit)",
+ )
+ or 0.0
+ )
# sum of credit entries made from sales invoice
- credit_note_amt = sum(frappe.db.get_all('GL Entry', {
- 'is_cancelled': 0,
- 'credit': ['>', 0],
- 'party': ['in', parties],
- 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
- 'company': inv.company,
- 'voucher_type': 'Sales Invoice',
- }, pluck='credit'))
+ credit_note_amt = sum(
+ frappe.db.get_all(
+ "GL Entry",
+ {
+ "is_cancelled": 0,
+ "credit": [">", 0],
+ "party": ["in", parties],
+ "posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
+ "company": inv.company,
+ "voucher_type": "Sales Invoice",
+ },
+ pluck="credit",
+ )
+ )
- cumulative_threshold = tax_details.get('cumulative_threshold', 0)
+ cumulative_threshold = tax_details.get("cumulative_threshold", 0)
current_invoice_total = get_invoice_total_without_tcs(inv, tax_details)
total_invoiced_amt = current_invoice_total + invoiced_amt + advance_amt - credit_note_amt
- if (cumulative_threshold and total_invoiced_amt >= cumulative_threshold):
+ if cumulative_threshold and total_invoiced_amt >= cumulative_threshold:
chargeable_amt = total_invoiced_amt - cumulative_threshold
tcs_amount = chargeable_amt * tax_details.rate / 100 if chargeable_amt > 0 else 0
return tcs_amount
+
def get_invoice_total_without_tcs(inv, tax_details):
tcs_tax_row = [d for d in inv.taxes if d.account_head == tax_details.account_head]
tcs_tax_row_amount = tcs_tax_row[0].base_tax_amount if tcs_tax_row else 0
return inv.grand_total - tcs_tax_row_amount
+
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
tds_amount = 0
- limit_consumed = frappe.db.get_value('Purchase Invoice', {
- 'supplier': ('in', parties),
- 'apply_tds': 1,
- 'docstatus': 1
- }, 'sum(net_total)')
+ limit_consumed = frappe.db.get_value(
+ "Purchase Invoice",
+ {"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
+ "sum(net_total)",
+ )
if is_valid_certificate(
- ldc.valid_from, ldc.valid_upto,
- posting_date, limit_consumed,
- net_total, ldc.certificate_limit
+ ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total, ldc.certificate_limit
):
- tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
+ tds_amount = get_ltds_amount(
+ net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details
+ )
return tds_amount
+
def get_debit_note_amount(suppliers, from_date, to_date, company=None):
filters = {
- 'supplier': ['in', suppliers],
- 'is_return': 1,
- 'docstatus': 1,
- 'posting_date': ['between', (from_date, to_date)]
+ "supplier": ["in", suppliers],
+ "is_return": 1,
+ "docstatus": 1,
+ "posting_date": ["between", (from_date, to_date)],
}
- fields = ['abs(sum(net_total)) as net_total']
+ fields = ["abs(sum(net_total)) as net_total"]
if company:
- filters['company'] = company
+ filters["company"] = company
+
+ return frappe.get_all("Purchase Invoice", filters, fields)[0].get("net_total") or 0.0
- return frappe.get_all('Purchase Invoice', filters, fields)[0].get('net_total') or 0.0
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
- return current_amount * rate/100
+ return current_amount * rate / 100
else:
- ltds_amount = (certificate_limit - deducted_amount)
+ ltds_amount = certificate_limit - deducted_amount
tds_amount = current_amount - ltds_amount
- return ltds_amount * rate/100 + tds_amount * tax_details.rate/100
+ return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
-def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit):
+
+def is_valid_certificate(
+ valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit
+):
valid = False
- if ((getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and
- certificate_limit > deducted_amount):
+ if (
+ getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)
+ ) and certificate_limit > deducted_amount:
valid = True
return valid
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category_dashboard.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category_dashboard.py
index 256d4ac217..8a510ea023 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category_dashboard.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'tax_withholding_category',
- 'transactions': [
- {
- 'items': ['Supplier']
- }
- ]
- }
+ return {"fieldname": "tax_withholding_category", "transactions": [{"items": ["Supplier"]}]}
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index a3fcf7da7a..3059f8d64b 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -10,6 +10,7 @@ from erpnext.accounts.utils import get_fiscal_year
test_dependencies = ["Supplier Group", "Customer Group"]
+
class TestTaxWithholdingCategory(unittest.TestCase):
@classmethod
def setUpClass(self):
@@ -21,18 +22,20 @@ class TestTaxWithholdingCategory(unittest.TestCase):
cancel_invoices()
def test_cumulative_threshold_tds(self):
- frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS")
+ frappe.db.set_value(
+ "Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS"
+ )
invoices = []
# create invoices for lower than single threshold tax rate
for _ in range(2):
- pi = create_purchase_invoice(supplier = "Test TDS Supplier")
+ pi = create_purchase_invoice(supplier="Test TDS Supplier")
pi.submit()
invoices.append(pi)
# create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold
- pi = create_purchase_invoice(supplier = "Test TDS Supplier")
+ pi = create_purchase_invoice(supplier="Test TDS Supplier")
pi.submit()
# assert equal tax deduction on total invoice amount uptil now
@@ -41,21 +44,23 @@ class TestTaxWithholdingCategory(unittest.TestCase):
invoices.append(pi)
# TDS is already deducted, so from onward system will deduct the TDS on every invoice
- pi = create_purchase_invoice(supplier = "Test TDS Supplier", rate=5000)
+ pi = create_purchase_invoice(supplier="Test TDS Supplier", rate=5000)
pi.submit()
# assert equal tax deduction on total invoice amount uptil now
self.assertEqual(pi.taxes_and_charges_deducted, 500)
invoices.append(pi)
- #delete invoices to avoid clashing
+ # delete invoices to avoid clashing
for d in invoices:
d.cancel()
def test_single_threshold_tds(self):
invoices = []
- frappe.db.set_value("Supplier", "Test TDS Supplier1", "tax_withholding_category", "Single Threshold TDS")
- pi = create_purchase_invoice(supplier = "Test TDS Supplier1", rate = 20000)
+ frappe.db.set_value(
+ "Supplier", "Test TDS Supplier1", "tax_withholding_category", "Single Threshold TDS"
+ )
+ pi = create_purchase_invoice(supplier="Test TDS Supplier1", rate=20000)
pi.submit()
invoices.append(pi)
@@ -63,7 +68,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
self.assertEqual(pi.grand_total, 18000)
# check gl entry for the purchase invoice
- gl_entries = frappe.db.get_all('GL Entry', filters={'voucher_no': pi.name}, fields=["*"])
+ gl_entries = frappe.db.get_all("GL Entry", filters={"voucher_no": pi.name}, fields=["*"])
self.assertEqual(len(gl_entries), 3)
for d in gl_entries:
if d.account == pi.credit_to:
@@ -75,7 +80,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
else:
raise ValueError("Account head does not match.")
- pi = create_purchase_invoice(supplier = "Test TDS Supplier1")
+ pi = create_purchase_invoice(supplier="Test TDS Supplier1")
pi.submit()
invoices.append(pi)
@@ -88,17 +93,19 @@ class TestTaxWithholdingCategory(unittest.TestCase):
def test_tax_withholding_category_checks(self):
invoices = []
- frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
+ frappe.db.set_value(
+ "Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category"
+ )
# First Invoice with no tds check
- pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
+ pi = create_purchase_invoice(supplier="Test TDS Supplier3", rate=20000, do_not_save=True)
pi.apply_tds = 0
pi.save()
pi.submit()
invoices.append(pi)
# Second Invoice will apply TDS checked
- pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
+ pi1 = create_purchase_invoice(supplier="Test TDS Supplier3", rate=20000)
pi1.submit()
invoices.append(pi1)
@@ -110,82 +117,89 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in invoices:
d.cancel()
-
def test_cumulative_threshold_tcs(self):
- frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
+ frappe.db.set_value(
+ "Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"
+ )
invoices = []
# create invoices for lower than single threshold tax rate
for _ in range(2):
- si = create_sales_invoice(customer = "Test TCS Customer")
+ si = create_sales_invoice(customer="Test TCS Customer")
si.submit()
invoices.append(si)
# create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold
- si = create_sales_invoice(customer = "Test TCS Customer", rate=12000)
+ si = create_sales_invoice(customer="Test TCS Customer", rate=12000)
si.submit()
# assert tax collection on total invoice amount created until now
- tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
+ tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == "TCS - _TC"])
self.assertEqual(tcs_charged, 200)
self.assertEqual(si.grand_total, 12200)
invoices.append(si)
# TCS is already collected once, so going forward system will collect TCS on every invoice
- si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
+ si = create_sales_invoice(customer="Test TCS Customer", rate=5000)
si.submit()
- tcs_charged = sum(d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC')
+ tcs_charged = sum(d.base_tax_amount for d in si.taxes if d.account_head == "TCS - _TC")
self.assertEqual(tcs_charged, 500)
invoices.append(si)
- #delete invoices to avoid clashing
+ # delete invoices to avoid clashing
for d in invoices:
d.cancel()
def test_tds_calculation_on_net_total(self):
- frappe.db.set_value("Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS")
+ frappe.db.set_value(
+ "Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
+ )
invoices = []
- pi = create_purchase_invoice(supplier = "Test TDS Supplier4", rate = 20000, do_not_save=True)
- pi.append('taxes', {
- "category": "Total",
- "charge_type": "Actual",
- "account_head": '_Test Account VAT - _TC',
- "cost_center": 'Main - _TC',
- "tax_amount": 1000,
- "description": "Test",
- "add_deduct_tax": "Add"
-
- })
+ pi = create_purchase_invoice(supplier="Test TDS Supplier4", rate=20000, do_not_save=True)
+ pi.append(
+ "taxes",
+ {
+ "category": "Total",
+ "charge_type": "Actual",
+ "account_head": "_Test Account VAT - _TC",
+ "cost_center": "Main - _TC",
+ "tax_amount": 1000,
+ "description": "Test",
+ "add_deduct_tax": "Add",
+ },
+ )
pi.save()
pi.submit()
invoices.append(pi)
# Second Invoice will apply TDS checked
- pi1 = create_purchase_invoice(supplier = "Test TDS Supplier4", rate = 20000)
+ pi1 = create_purchase_invoice(supplier="Test TDS Supplier4", rate=20000)
pi1.submit()
invoices.append(pi1)
self.assertEqual(pi1.taxes[0].tax_amount, 4000)
- #delete invoices to avoid clashing
+ # delete invoices to avoid clashing
for d in invoices:
d.cancel()
def test_multi_category_single_supplier(self):
- frappe.db.set_value("Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category")
+ frappe.db.set_value(
+ "Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"
+ )
invoices = []
- pi = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 500, do_not_save=True)
+ pi = create_purchase_invoice(supplier="Test TDS Supplier5", rate=500, do_not_save=True)
pi.tax_withholding_category = "Test Service Category"
pi.save()
pi.submit()
invoices.append(pi)
# Second Invoice will apply TDS checked
- pi1 = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 2500, do_not_save=True)
+ pi1 = create_purchase_invoice(supplier="Test TDS Supplier5", rate=2500, do_not_save=True)
pi1.tax_withholding_category = "Test Goods Category"
pi1.save()
pi1.submit()
@@ -193,258 +207,294 @@ class TestTaxWithholdingCategory(unittest.TestCase):
self.assertEqual(pi1.taxes[0].tax_amount, 250)
- #delete invoices to avoid clashing
+ # delete invoices to avoid clashing
for d in invoices:
d.cancel()
-def cancel_invoices():
- purchase_invoices = frappe.get_all("Purchase Invoice", {
- 'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
- 'docstatus': 1
- }, pluck="name")
- sales_invoices = frappe.get_all("Sales Invoice", {
- 'customer': 'Test TCS Customer',
- 'docstatus': 1
- }, pluck="name")
+def cancel_invoices():
+ purchase_invoices = frappe.get_all(
+ "Purchase Invoice",
+ {
+ "supplier": ["in", ["Test TDS Supplier", "Test TDS Supplier1", "Test TDS Supplier2"]],
+ "docstatus": 1,
+ },
+ pluck="name",
+ )
+
+ sales_invoices = frappe.get_all(
+ "Sales Invoice", {"customer": "Test TCS Customer", "docstatus": 1}, pluck="name"
+ )
for d in purchase_invoices:
- frappe.get_doc('Purchase Invoice', d).cancel()
+ frappe.get_doc("Purchase Invoice", d).cancel()
for d in sales_invoices:
- frappe.get_doc('Sales Invoice', d).cancel()
+ frappe.get_doc("Sales Invoice", d).cancel()
+
def create_purchase_invoice(**args):
# return sales invoice doc object
- item = frappe.db.get_value('Item', {'item_name': 'TDS Item'}, "name")
+ item = frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name")
args = frappe._dict(args)
- pi = frappe.get_doc({
- "doctype": "Purchase Invoice",
- "posting_date": today(),
- "apply_tds": 0 if args.do_not_apply_tds else 1,
- "supplier": args.supplier,
- "company": '_Test Company',
- "taxes_and_charges": "",
- "currency": "INR",
- "credit_to": "Creditors - _TC",
- "taxes": [],
- "items": [{
- 'doctype': 'Purchase Invoice Item',
- 'item_code': item,
- 'qty': args.qty or 1,
- 'rate': args.rate or 10000,
- 'cost_center': 'Main - _TC',
- 'expense_account': 'Stock Received But Not Billed - _TC'
- }]
- })
+ pi = frappe.get_doc(
+ {
+ "doctype": "Purchase Invoice",
+ "posting_date": today(),
+ "apply_tds": 0 if args.do_not_apply_tds else 1,
+ "supplier": args.supplier,
+ "company": "_Test Company",
+ "taxes_and_charges": "",
+ "currency": "INR",
+ "credit_to": "Creditors - _TC",
+ "taxes": [],
+ "items": [
+ {
+ "doctype": "Purchase Invoice Item",
+ "item_code": item,
+ "qty": args.qty or 1,
+ "rate": args.rate or 10000,
+ "cost_center": "Main - _TC",
+ "expense_account": "Stock Received But Not Billed - _TC",
+ }
+ ],
+ }
+ )
pi.save()
return pi
+
def create_sales_invoice(**args):
# return sales invoice doc object
- item = frappe.db.get_value('Item', {'item_name': 'TCS Item'}, "name")
+ item = frappe.db.get_value("Item", {"item_name": "TCS Item"}, "name")
args = frappe._dict(args)
- si = frappe.get_doc({
- "doctype": "Sales Invoice",
- "posting_date": today(),
- "customer": args.customer,
- "company": '_Test Company',
- "taxes_and_charges": "",
- "currency": "INR",
- "debit_to": "Debtors - _TC",
- "taxes": [],
- "items": [{
- 'doctype': 'Sales Invoice Item',
- 'item_code': item,
- 'qty': args.qty or 1,
- 'rate': args.rate or 10000,
- 'cost_center': 'Main - _TC',
- 'expense_account': 'Cost of Goods Sold - _TC',
- 'warehouse': args.warehouse or '_Test Warehouse - _TC'
- }]
- })
+ si = frappe.get_doc(
+ {
+ "doctype": "Sales Invoice",
+ "posting_date": today(),
+ "customer": args.customer,
+ "company": "_Test Company",
+ "taxes_and_charges": "",
+ "currency": "INR",
+ "debit_to": "Debtors - _TC",
+ "taxes": [],
+ "items": [
+ {
+ "doctype": "Sales Invoice Item",
+ "item_code": item,
+ "qty": args.qty or 1,
+ "rate": args.rate or 10000,
+ "cost_center": "Main - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ }
+ ],
+ }
+ )
si.save()
return si
+
def create_records():
# create a new suppliers
- for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3',
- 'Test TDS Supplier4', 'Test TDS Supplier5']:
- if frappe.db.exists('Supplier', name):
+ for name in [
+ "Test TDS Supplier",
+ "Test TDS Supplier1",
+ "Test TDS Supplier2",
+ "Test TDS Supplier3",
+ "Test TDS Supplier4",
+ "Test TDS Supplier5",
+ ]:
+ if frappe.db.exists("Supplier", name):
continue
- frappe.get_doc({
- "supplier_group": "_Test Supplier Group",
- "supplier_name": name,
- "doctype": "Supplier",
- }).insert()
+ frappe.get_doc(
+ {
+ "supplier_group": "_Test Supplier Group",
+ "supplier_name": name,
+ "doctype": "Supplier",
+ }
+ ).insert()
- for name in ['Test TCS Customer']:
- if frappe.db.exists('Customer', name):
+ for name in ["Test TCS Customer"]:
+ if frappe.db.exists("Customer", name):
continue
- frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": name,
- "doctype": "Customer"
- }).insert()
+ frappe.get_doc(
+ {"customer_group": "_Test Customer Group", "customer_name": name, "doctype": "Customer"}
+ ).insert()
# create item
- if not frappe.db.exists('Item', "TDS Item"):
- frappe.get_doc({
- "doctype": "Item",
- "item_code": "TDS Item",
- "item_name": "TDS Item",
- "item_group": "All Item Groups",
- "is_stock_item": 0,
- }).insert()
+ if not frappe.db.exists("Item", "TDS Item"):
+ frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": "TDS Item",
+ "item_name": "TDS Item",
+ "item_group": "All Item Groups",
+ "is_stock_item": 0,
+ }
+ ).insert()
- if not frappe.db.exists('Item', "TCS Item"):
- frappe.get_doc({
- "doctype": "Item",
- "item_code": "TCS Item",
- "item_name": "TCS Item",
- "item_group": "All Item Groups",
- "is_stock_item": 1
- }).insert()
+ if not frappe.db.exists("Item", "TCS Item"):
+ frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": "TCS Item",
+ "item_name": "TCS Item",
+ "item_group": "All Item Groups",
+ "is_stock_item": 1,
+ }
+ ).insert()
# create tds account
if not frappe.db.exists("Account", "TDS - _TC"):
- frappe.get_doc({
- 'doctype': 'Account',
- 'company': '_Test Company',
- 'account_name': 'TDS',
- 'parent_account': 'Tax Assets - _TC',
- 'report_type': 'Balance Sheet',
- 'root_type': 'Asset'
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "account_name": "TDS",
+ "parent_account": "Tax Assets - _TC",
+ "report_type": "Balance Sheet",
+ "root_type": "Asset",
+ }
+ ).insert()
# create tcs account
if not frappe.db.exists("Account", "TCS - _TC"):
- frappe.get_doc({
- 'doctype': 'Account',
- 'company': '_Test Company',
- 'account_name': 'TCS',
- 'parent_account': 'Duties and Taxes - _TC',
- 'report_type': 'Balance Sheet',
- 'root_type': 'Liability'
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "account_name": "TCS",
+ "parent_account": "Duties and Taxes - _TC",
+ "report_type": "Balance Sheet",
+ "root_type": "Liability",
+ }
+ ).insert()
+
def create_tax_with_holding_category():
fiscal_year = get_fiscal_year(today(), company="_Test Company")
# Cumulative threshold
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"):
- frappe.get_doc({
- "doctype": "Tax Withholding Category",
- "name": "Cumulative Threshold TDS",
- "category_name": "10% TDS",
- "rates": [{
- 'from_date': fiscal_year[1],
- 'to_date': fiscal_year[2],
- 'tax_withholding_rate': 10,
- 'single_threshold': 0,
- 'cumulative_threshold': 30000.00
- }],
- "accounts": [{
- 'company': '_Test Company',
- 'account': 'TDS - _TC'
- }]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Tax Withholding Category",
+ "name": "Cumulative Threshold TDS",
+ "category_name": "10% TDS",
+ "rates": [
+ {
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
+ "tax_withholding_rate": 10,
+ "single_threshold": 0,
+ "cumulative_threshold": 30000.00,
+ }
+ ],
+ "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
+ }
+ ).insert()
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"):
- frappe.get_doc({
- "doctype": "Tax Withholding Category",
- "name": "Cumulative Threshold TCS",
- "category_name": "10% TCS",
- "rates": [{
- 'from_date': fiscal_year[1],
- 'to_date': fiscal_year[2],
- 'tax_withholding_rate': 10,
- 'single_threshold': 0,
- 'cumulative_threshold': 30000.00
- }],
- "accounts": [{
- 'company': '_Test Company',
- 'account': 'TCS - _TC'
- }]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Tax Withholding Category",
+ "name": "Cumulative Threshold TCS",
+ "category_name": "10% TCS",
+ "rates": [
+ {
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
+ "tax_withholding_rate": 10,
+ "single_threshold": 0,
+ "cumulative_threshold": 30000.00,
+ }
+ ],
+ "accounts": [{"company": "_Test Company", "account": "TCS - _TC"}],
+ }
+ ).insert()
# Single thresold
if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"):
- frappe.get_doc({
- "doctype": "Tax Withholding Category",
- "name": "Single Threshold TDS",
- "category_name": "10% TDS",
- "rates": [{
- 'from_date': fiscal_year[1],
- 'to_date': fiscal_year[2],
- 'tax_withholding_rate': 10,
- 'single_threshold': 20000.00,
- 'cumulative_threshold': 0
- }],
- "accounts": [{
- 'company': '_Test Company',
- 'account': 'TDS - _TC'
- }]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Tax Withholding Category",
+ "name": "Single Threshold TDS",
+ "category_name": "10% TDS",
+ "rates": [
+ {
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
+ "tax_withholding_rate": 10,
+ "single_threshold": 20000.00,
+ "cumulative_threshold": 0,
+ }
+ ],
+ "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
+ }
+ ).insert()
if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
- frappe.get_doc({
- "doctype": "Tax Withholding Category",
- "name": "New TDS Category",
- "category_name": "New TDS Category",
- "round_off_tax_amount": 1,
- "consider_party_ledger_amount": 1,
- "tax_on_excess_amount": 1,
- "rates": [{
- 'from_date': fiscal_year[1],
- 'to_date': fiscal_year[2],
- 'tax_withholding_rate': 10,
- 'single_threshold': 0,
- 'cumulative_threshold': 30000
- }],
- "accounts": [{
- 'company': '_Test Company',
- 'account': 'TDS - _TC'
- }]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Tax Withholding Category",
+ "name": "New TDS Category",
+ "category_name": "New TDS Category",
+ "round_off_tax_amount": 1,
+ "consider_party_ledger_amount": 1,
+ "tax_on_excess_amount": 1,
+ "rates": [
+ {
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
+ "tax_withholding_rate": 10,
+ "single_threshold": 0,
+ "cumulative_threshold": 30000,
+ }
+ ],
+ "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
+ }
+ ).insert()
if not frappe.db.exists("Tax Withholding Category", "Test Service Category"):
- frappe.get_doc({
- "doctype": "Tax Withholding Category",
- "name": "Test Service Category",
- "category_name": "Test Service Category",
- "rates": [{
- 'from_date': fiscal_year[1],
- 'to_date': fiscal_year[2],
- 'tax_withholding_rate': 10,
- 'single_threshold': 2000,
- 'cumulative_threshold': 2000
- }],
- "accounts": [{
- 'company': '_Test Company',
- 'account': 'TDS - _TC'
- }]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Tax Withholding Category",
+ "name": "Test Service Category",
+ "category_name": "Test Service Category",
+ "rates": [
+ {
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
+ "tax_withholding_rate": 10,
+ "single_threshold": 2000,
+ "cumulative_threshold": 2000,
+ }
+ ],
+ "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
+ }
+ ).insert()
if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"):
- frappe.get_doc({
- "doctype": "Tax Withholding Category",
- "name": "Test Goods Category",
- "category_name": "Test Goods Category",
- "rates": [{
- 'from_date': fiscal_year[1],
- 'to_date': fiscal_year[2],
- 'tax_withholding_rate': 10,
- 'single_threshold': 2000,
- 'cumulative_threshold': 2000
- }],
- "accounts": [{
- 'company': '_Test Company',
- 'account': 'TDS - _TC'
- }]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Tax Withholding Category",
+ "name": "Test Goods Category",
+ "category_name": "Test Goods Category",
+ "rates": [
+ {
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
+ "tax_withholding_rate": 10,
+ "single_threshold": 2000,
+ "cumulative_threshold": 2000,
+ }
+ ],
+ "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
+ }
+ ).insert()
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 0cd5e86a8c..50f37be27b 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -16,9 +16,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
-class ClosedAccountingPeriod(frappe.ValidationError): pass
+class ClosedAccountingPeriod(frappe.ValidationError):
+ pass
-def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
+
+def make_gl_entries(
+ gl_map,
+ cancel=False,
+ adv_adj=False,
+ merge_entries=True,
+ update_outstanding="Yes",
+ from_repost=False,
+):
if gl_map:
if not cancel:
validate_accounting_period(gl_map)
@@ -27,12 +36,18 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
# Post GL Map proccess there may no be any GL Entries
elif gl_map:
- frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
+ frappe.throw(
+ _(
+ "Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."
+ )
+ )
else:
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
+
def validate_accounting_period(gl_map):
- accounting_periods = frappe.db.sql(""" SELECT
+ accounting_periods = frappe.db.sql(
+ """ SELECT
ap.name as name
FROM
`tabAccounting Period` ap, `tabClosed Document` cd
@@ -42,15 +57,23 @@ def validate_accounting_period(gl_map):
AND cd.closed = 1
AND cd.document_type = %(voucher_type)s
AND %(date)s between ap.start_date and ap.end_date
- """, {
- 'date': gl_map[0].posting_date,
- 'company': gl_map[0].company,
- 'voucher_type': gl_map[0].voucher_type
- }, as_dict=1)
+ """,
+ {
+ "date": gl_map[0].posting_date,
+ "company": gl_map[0].company,
+ "voucher_type": gl_map[0].voucher_type,
+ },
+ as_dict=1,
+ )
if accounting_periods:
- frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
- .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
+ frappe.throw(
+ _(
+ "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
+ ).format(frappe.bold(accounting_periods[0].name)),
+ ClosedAccountingPeriod,
+ )
+
def process_gl_map(gl_map, merge_entries=True, precision=None):
if not gl_map:
@@ -65,8 +88,11 @@ def process_gl_map(gl_map, merge_entries=True, precision=None):
return gl_map
+
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
- cost_center_allocation = get_cost_center_allocation_data(gl_map[0]["company"], gl_map[0]["posting_date"])
+ cost_center_allocation = get_cost_center_allocation_data(
+ gl_map[0]["company"], gl_map[0]["posting_date"]
+ )
if not cost_center_allocation:
return gl_map
@@ -85,12 +111,15 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
return new_gl_map
+
def get_cost_center_allocation_data(company, posting_date):
par = frappe.qb.DocType("Cost Center Allocation")
child = frappe.qb.DocType("Cost Center Allocation Percentage")
records = (
- frappe.qb.from_(par).inner_join(child).on(par.name == child.parent)
+ frappe.qb.from_(par)
+ .inner_join(child)
+ .on(par.name == child.parent)
.select(par.main_cost_center, child.cost_center, child.percentage)
.where(par.docstatus == 1)
.where(par.company == company)
@@ -100,11 +129,13 @@ def get_cost_center_allocation_data(company, posting_date):
cc_allocation = frappe._dict()
for d in records:
- cc_allocation.setdefault(d.main_cost_center, frappe._dict())\
- .setdefault(d.cost_center, d.percentage)
+ cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
+ d.cost_center, d.percentage
+ )
return cc_allocation
+
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
accounting_dimensions = get_accounting_dimensions()
@@ -113,12 +144,14 @@ def merge_similar_entries(gl_map, precision=None):
# to that entry
same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
if same_head:
- same_head.debit = flt(same_head.debit) + flt(entry.debit)
- same_head.debit_in_account_currency = \
- flt(same_head.debit_in_account_currency) + flt(entry.debit_in_account_currency)
+ same_head.debit = flt(same_head.debit) + flt(entry.debit)
+ same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
+ entry.debit_in_account_currency
+ )
same_head.credit = flt(same_head.credit) + flt(entry.credit)
- same_head.credit_in_account_currency = \
- flt(same_head.credit_in_account_currency) + flt(entry.credit_in_account_currency)
+ same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
+ entry.credit_in_account_currency
+ )
else:
merged_gl_map.append(entry)
@@ -129,14 +162,25 @@ def merge_similar_entries(gl_map, precision=None):
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
# filter zero debit and credit entries
- merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
+ merged_gl_map = filter(
+ lambda x: flt(x.debit, precision) != 0 or flt(x.credit, precision) != 0, merged_gl_map
+ )
merged_gl_map = list(merged_gl_map)
return merged_gl_map
+
def check_if_in_list(gle, gl_map, dimensions=None):
- account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher',
- 'cost_center', 'against_voucher_type', 'party_type', 'project', 'finance_book']
+ account_head_fieldnames = [
+ "voucher_detail_no",
+ "party",
+ "against_voucher",
+ "cost_center",
+ "against_voucher_type",
+ "party_type",
+ "project",
+ "finance_book",
+ ]
if dimensions:
account_head_fieldnames = account_head_fieldnames + dimensions
@@ -155,6 +199,7 @@ def check_if_in_list(gle, gl_map, dimensions=None):
if same_head:
return e
+
def toggle_debit_credit_if_negative(gl_map):
for entry in gl_map:
# toggle debit, credit if negative entry
@@ -163,8 +208,9 @@ def toggle_debit_credit_if_negative(gl_map):
entry.debit = 0.0
if flt(entry.debit_in_account_currency) < 0:
- entry.credit_in_account_currency = \
- flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
+ entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
+ entry.debit_in_account_currency
+ )
entry.debit_in_account_currency = 0.0
if flt(entry.credit) < 0:
@@ -172,32 +218,37 @@ def toggle_debit_credit_if_negative(gl_map):
entry.credit = 0.0
if flt(entry.credit_in_account_currency) < 0:
- entry.debit_in_account_currency = \
- flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
+ entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
+ entry.credit_in_account_currency
+ )
entry.credit_in_account_currency = 0.0
update_net_values(entry)
return gl_map
+
def update_net_values(entry):
# In some scenarios net value needs to be shown in the ledger
# This method updates net values as debit or credit
if entry.post_net_value and entry.debit and entry.credit:
if entry.debit > entry.credit:
entry.debit = entry.debit - entry.credit
- entry.debit_in_account_currency = entry.debit_in_account_currency \
- - entry.credit_in_account_currency
+ entry.debit_in_account_currency = (
+ entry.debit_in_account_currency - entry.credit_in_account_currency
+ )
entry.credit = 0
entry.credit_in_account_currency = 0
else:
entry.credit = entry.credit - entry.debit
- entry.credit_in_account_currency = entry.credit_in_account_currency \
- - entry.debit_in_account_currency
+ entry.credit_in_account_currency = (
+ entry.credit_in_account_currency - entry.debit_in_account_currency
+ )
entry.debit = 0
entry.debit_in_account_currency = 0
+
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_cwip_accounts(gl_map)
@@ -210,36 +261,52 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost)
+
def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle = frappe.new_doc("GL Entry")
gle.update(args)
gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.flags.adv_adj = adv_adj
- gle.flags.update_outstanding = update_outstanding or 'Yes'
+ gle.flags.update_outstanding = update_outstanding or "Yes"
gle.submit()
if not from_repost:
validate_expense_against_budget(args)
+
def validate_cwip_accounts(gl_map):
"""Validate that CWIP account are not used in Journal Entry"""
if gl_map and gl_map[0].voucher_type != "Journal Entry":
return
- cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting"))
+ cwip_enabled = any(
+ cint(ac.enable_cwip_accounting)
+ for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
+ )
if cwip_enabled:
- cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
- where account_type = 'Capital Work in Progress' and is_group=0""")]
+ cwip_accounts = [
+ d[0]
+ for d in frappe.db.sql(
+ """select name from tabAccount
+ where account_type = 'Capital Work in Progress' and is_group=0"""
+ )
+ ]
for entry in gl_map:
if entry.account in cwip_accounts:
frappe.throw(
- _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
+ _(
+ "Account: {0} is capital Work in progress and can not be updated by Journal Entry"
+ ).format(entry.account)
+ )
+
def round_off_debit_credit(gl_map):
- precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
- currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
+ precision = get_field_precision(
+ frappe.get_meta("GL Entry").get_field("debit"),
+ currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
+ )
debit_credit_diff = 0.0
for entry in gl_map:
@@ -252,17 +319,23 @@ def round_off_debit_credit(gl_map):
if gl_map[0]["voucher_type"] in ("Journal Entry", "Payment Entry"):
allowance = 5.0 / (10**precision)
else:
- allowance = .5
+ allowance = 0.5
if abs(debit_credit_diff) > allowance:
- frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
- .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
+ frappe.throw(
+ _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
+ gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff
+ )
+ )
elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
make_round_off_gle(gl_map, debit_credit_diff, precision)
+
def make_round_off_gle(gl_map, debit_credit_diff, precision):
- round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(gl_map[0].company)
+ round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
+ gl_map[0].company
+ )
round_off_account_exists = False
round_off_gle = frappe._dict()
for d in gl_map:
@@ -279,30 +352,33 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
return
if not round_off_gle:
- for k in ["voucher_type", "voucher_no", "company",
- "posting_date", "remarks"]:
- round_off_gle[k] = gl_map[0][k]
+ for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
+ round_off_gle[k] = gl_map[0][k]
- round_off_gle.update({
- "account": round_off_account,
- "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
- "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
- "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
- "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
- "cost_center": round_off_cost_center,
- "party_type": None,
- "party": None,
- "is_opening": "No",
- "against_voucher_type": None,
- "against_voucher": None
- })
+ round_off_gle.update(
+ {
+ "account": round_off_account,
+ "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
+ "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
+ "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
+ "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
+ "cost_center": round_off_cost_center,
+ "party_type": None,
+ "party": None,
+ "is_opening": "No",
+ "against_voucher_type": None,
+ "against_voucher": None,
+ }
+ )
if not round_off_account_exists:
gl_map.append(round_off_gle)
+
def get_round_off_account_and_cost_center(company):
- round_off_account, round_off_cost_center = frappe.get_cached_value('Company', company,
- ["round_off_account", "round_off_cost_center"]) or [None, None]
+ round_off_account, round_off_cost_center = frappe.get_cached_value(
+ "Company", company, ["round_off_account", "round_off_cost_center"]
+ ) or [None, None]
if not round_off_account:
frappe.throw(_("Please mention Round Off Account in Company"))
@@ -311,74 +387,83 @@ def get_round_off_account_and_cost_center(company):
return round_off_account, round_off_cost_center
-def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
- adv_adj=False, update_outstanding="Yes"):
+
+def make_reverse_gl_entries(
+ gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes"
+):
"""
- Get original gl entries of the voucher
- and make reverse gl entries by swapping debit and credit
+ Get original gl entries of the voucher
+ and make reverse gl entries by swapping debit and credit
"""
if not gl_entries:
gl_entry = frappe.qb.DocType("GL Entry")
- gl_entries = (frappe.qb.from_(
- gl_entry
- ).select(
- '*'
- ).where(
- gl_entry.voucher_type == voucher_type
- ).where(
- gl_entry.voucher_no == voucher_no
- ).where(
- gl_entry.is_cancelled == 0
- ).for_update()).run(as_dict=1)
+ gl_entries = (
+ frappe.qb.from_(gl_entry)
+ .select("*")
+ .where(gl_entry.voucher_type == voucher_type)
+ .where(gl_entry.voucher_no == voucher_no)
+ .where(gl_entry.is_cancelled == 0)
+ .for_update()
+ ).run(as_dict=1)
if gl_entries:
validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
- set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
+ set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
for entry in gl_entries:
new_gle = copy.deepcopy(entry)
- new_gle['name'] = None
- debit = new_gle.get('debit', 0)
- credit = new_gle.get('credit', 0)
+ new_gle["name"] = None
+ debit = new_gle.get("debit", 0)
+ credit = new_gle.get("credit", 0)
- debit_in_account_currency = new_gle.get('debit_in_account_currency', 0)
- credit_in_account_currency = new_gle.get('credit_in_account_currency', 0)
+ debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
+ credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
- new_gle['debit'] = credit
- new_gle['credit'] = debit
- new_gle['debit_in_account_currency'] = credit_in_account_currency
- new_gle['credit_in_account_currency'] = debit_in_account_currency
+ new_gle["debit"] = credit
+ new_gle["credit"] = debit
+ new_gle["debit_in_account_currency"] = credit_in_account_currency
+ new_gle["credit_in_account_currency"] = debit_in_account_currency
- new_gle['remarks'] = "On cancellation of " + new_gle['voucher_no']
- new_gle['is_cancelled'] = 1
+ new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
+ new_gle["is_cancelled"] = 1
- if new_gle['debit'] or new_gle['credit']:
+ if new_gle["debit"] or new_gle["credit"]:
make_entry(new_gle, adv_adj, "Yes")
def check_freezing_date(posting_date, adv_adj=False):
"""
- Nobody can do GL Entries where posting date is before freezing date
- except authorized person
+ Nobody can do GL Entries where posting date is before freezing date
+ except authorized person
- Administrator has all the roles so this check will be bypassed if any role is allowed to post
- Hence stop admin to bypass if accounts are freezed
+ Administrator has all the roles so this check will be bypassed if any role is allowed to post
+ Hence stop admin to bypass if accounts are freezed
"""
if not adv_adj:
- acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
+ acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
if acc_frozen_upto:
- frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
- if getdate(posting_date) <= getdate(acc_frozen_upto) \
- and (frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == 'Administrator'):
- frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
+ frozen_accounts_modifier = frappe.db.get_value(
+ "Accounts Settings", None, "frozen_accounts_modifier"
+ )
+ if getdate(posting_date) <= getdate(acc_frozen_upto) and (
+ frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
+ ):
+ frappe.throw(
+ _("You are not authorized to add or update entries before {0}").format(
+ formatdate(acc_frozen_upto)
+ )
+ )
+
def set_as_cancel(voucher_type, voucher_no):
"""
- Set is_cancelled=1 in all original gl entries for the voucher
+ Set is_cancelled=1 in all original gl entries for the voucher
"""
- frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1,
+ frappe.db.sql(
+ """UPDATE `tabGL Entry` SET is_cancelled = 1,
modified=%s, modified_by=%s
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
- (now(), frappe.session.user, voucher_type, voucher_no))
+ (now(), frappe.session.user, voucher_type, voucher_no),
+ )
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 791cd1d5e9..b0b3049d48 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -33,122 +33,227 @@ from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
-class DuplicatePartyAccountError(frappe.ValidationError): pass
+class DuplicatePartyAccountError(frappe.ValidationError):
+ pass
+
@frappe.whitelist()
-def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
- bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, fetch_payment_terms_template=True,
- party_address=None, company_address=None, shipping_address=None, pos_profile=None):
+def get_party_details(
+ party=None,
+ account=None,
+ party_type="Customer",
+ company=None,
+ posting_date=None,
+ bill_date=None,
+ price_list=None,
+ currency=None,
+ doctype=None,
+ ignore_permissions=False,
+ fetch_payment_terms_template=True,
+ party_address=None,
+ company_address=None,
+ shipping_address=None,
+ pos_profile=None,
+):
if not party:
return {}
if not frappe.db.exists(party_type, party):
frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
- return _get_party_details(party, account, party_type,
- company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions,
- fetch_payment_terms_template, party_address, company_address, shipping_address, pos_profile)
+ return _get_party_details(
+ party,
+ account,
+ party_type,
+ company,
+ posting_date,
+ bill_date,
+ price_list,
+ currency,
+ doctype,
+ ignore_permissions,
+ fetch_payment_terms_template,
+ party_address,
+ company_address,
+ shipping_address,
+ pos_profile,
+ )
-def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
- bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False,
- fetch_payment_terms_template=True, party_address=None, company_address=None, shipping_address=None, pos_profile=None):
- party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
+
+def _get_party_details(
+ party=None,
+ account=None,
+ party_type="Customer",
+ company=None,
+ posting_date=None,
+ bill_date=None,
+ price_list=None,
+ currency=None,
+ doctype=None,
+ ignore_permissions=False,
+ fetch_payment_terms_template=True,
+ party_address=None,
+ company_address=None,
+ shipping_address=None,
+ pos_profile=None,
+):
+ party_details = frappe._dict(
+ set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
+ )
party = party_details[party_type.lower()]
- if not ignore_permissions and not (frappe.has_permission(party_type, "read", party) or frappe.has_permission(party_type, "select", party)):
+ if not ignore_permissions and not (
+ frappe.has_permission(party_type, "read", party)
+ or frappe.has_permission(party_type, "select", party)
+ ):
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
party = frappe.get_doc(party_type, party)
currency = party.get("default_currency") or currency or get_company_currency(company)
- party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
+ party_address, shipping_address = set_address_details(
+ party_details,
+ party,
+ party_type,
+ doctype,
+ company,
+ party_address,
+ company_address,
+ shipping_address,
+ )
set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type)
set_price_list(party_details, party, party_type, price_list, pos_profile)
- party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
- party_address, shipping_address if party_type != "Supplier" else party_address)
+ party_details["tax_category"] = get_address_tax_category(
+ party.get("tax_category"),
+ party_address,
+ shipping_address if party_type != "Supplier" else party_address,
+ )
- tax_template = set_taxes(party.name, party_type, posting_date, company,
- customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
- billing_address=party_address, shipping_address=shipping_address)
+ tax_template = set_taxes(
+ party.name,
+ party_type,
+ posting_date,
+ company,
+ customer_group=party_details.customer_group,
+ supplier_group=party_details.supplier_group,
+ tax_category=party_details.tax_category,
+ billing_address=party_address,
+ shipping_address=shipping_address,
+ )
if tax_template:
- party_details['taxes_and_charges'] = tax_template
+ party_details["taxes_and_charges"] = tax_template
if cint(fetch_payment_terms_template):
- party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)
+ party_details["payment_terms_template"] = get_payment_terms_template(
+ party.name, party_type, company
+ )
if not party_details.get("currency"):
party_details["currency"] = currency
# sales team
- if party_type=="Customer":
- party_details["sales_team"] = [{
- "sales_person": d.sales_person,
- "allocated_percentage": d.allocated_percentage or None,
- "commission_rate": d.commission_rate
- } for d in party.get("sales_team")]
+ if party_type == "Customer":
+ party_details["sales_team"] = [
+ {
+ "sales_person": d.sales_person,
+ "allocated_percentage": d.allocated_percentage or None,
+ "commission_rate": d.commission_rate,
+ }
+ for d in party.get("sales_team")
+ ]
# supplier tax withholding category
if party_type == "Supplier" and party:
- party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
+ party_details["supplier_tds"] = frappe.get_value(
+ party_type, party.name, "tax_withholding_category"
+ )
return party_details
-def set_address_details(party_details, party, party_type, doctype=None, company=None, party_address=None, company_address=None, shipping_address=None):
- billing_address_field = "customer_address" if party_type == "Lead" \
- else party_type.lower() + "_address"
- party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
+
+def set_address_details(
+ party_details,
+ party,
+ party_type,
+ doctype=None,
+ company=None,
+ party_address=None,
+ company_address=None,
+ shipping_address=None,
+):
+ billing_address_field = (
+ "customer_address" if party_type == "Lead" else party_type.lower() + "_address"
+ )
+ party_details[billing_address_field] = party_address or get_default_address(
+ party_type, party.name
+ )
if doctype:
- party_details.update(get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]))
+ party_details.update(
+ get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
+ )
# address display
party_details.address_display = get_address_display(party_details[billing_address_field])
# shipping address
if party_type in ["Customer", "Lead"]:
- party_details.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
+ party_details.shipping_address_name = shipping_address or get_party_shipping_address(
+ party_type, party.name
+ )
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
if doctype:
- party_details.update(get_fetch_values(doctype, 'shipping_address_name', party_details.shipping_address_name))
+ party_details.update(
+ get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
+ )
if company_address:
- party_details.update({'company_address': company_address})
+ party_details.update({"company_address": company_address})
else:
party_details.update(get_company_address(company))
- if doctype and doctype in ['Delivery Note', 'Sales Invoice', 'Sales Order']:
+ if doctype and doctype in ["Delivery Note", "Sales Invoice", "Sales Order"]:
if party_details.company_address:
- party_details.update(get_fetch_values(doctype, 'company_address', party_details.company_address))
+ party_details.update(
+ get_fetch_values(doctype, "company_address", party_details.company_address)
+ )
get_regional_address_details(party_details, doctype, company)
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
if party_details.company_address:
party_details["shipping_address"] = shipping_address or party_details["company_address"]
party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
- party_details.update(get_fetch_values(doctype, 'shipping_address', party_details.shipping_address))
+ party_details.update(
+ get_fetch_values(doctype, "shipping_address", party_details.shipping_address)
+ )
get_regional_address_details(party_details, doctype, company)
return party_details.get(billing_address_field), party_details.shipping_address_name
+
@erpnext.allow_regional
def get_regional_address_details(party_details, doctype, company):
pass
+
def set_contact_details(party_details, party, party_type):
party_details.contact_person = get_default_contact(party_type, party.name)
if not party_details.contact_person:
- party_details.update({
- "contact_person": None,
- "contact_display": None,
- "contact_email": None,
- "contact_mobile": None,
- "contact_phone": None,
- "contact_designation": None,
- "contact_department": None
- })
+ party_details.update(
+ {
+ "contact_person": None,
+ "contact_display": None,
+ "contact_email": None,
+ "contact_mobile": None,
+ "contact_phone": None,
+ "contact_designation": None,
+ "contact_department": None,
+ }
+ )
else:
party_details.update(get_contact_details(party_details.contact_person))
+
def set_other_values(party_details, party, party_type):
# copy
if party_type == "Customer":
@@ -159,11 +264,13 @@ def set_other_values(party_details, party, party_type):
party_details[f] = party.get(f)
# fields prepended with default in Customer doctype
- for f in ['currency'] \
- + (['sales_partner', 'commission_rate'] if party_type=="Customer" else []):
+ for f in ["currency"] + (
+ ["sales_partner", "commission_rate"] if party_type == "Customer" else []
+ ):
if party.get("default_" + f):
party_details[f] = party.get("default_" + f)
+
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
if party.get("default_price_list"):
@@ -175,92 +282,103 @@ def get_default_price_list(party):
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
# price list
- price_list = get_permitted_documents('Price List')
+ price_list = get_permitted_documents("Price List")
# if there is only one permitted document based on user permissions, set it
if price_list and len(price_list) == 1:
price_list = price_list[0]
- elif pos and party_type == 'Customer':
- customer_price_list = frappe.get_value('Customer', party.name, 'default_price_list')
+ elif pos and party_type == "Customer":
+ customer_price_list = frappe.get_value("Customer", party.name, "default_price_list")
if customer_price_list:
price_list = customer_price_list
else:
- pos_price_list = frappe.get_value('POS Profile', pos, 'selling_price_list')
+ pos_price_list = frappe.get_value("POS Profile", pos, "selling_price_list")
price_list = pos_price_list or given_price_list
else:
price_list = get_default_price_list(party) or given_price_list
if price_list:
- party_details.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
+ party_details.price_list_currency = frappe.db.get_value(
+ "Price List", price_list, "currency", cache=True
+ )
- party_details["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
+ party_details[
+ "selling_price_list" if party.doctype == "Customer" else "buying_price_list"
+ ] = price_list
-def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype):
+def set_account_and_due_date(
+ party, account, party_type, company, posting_date, bill_date, doctype
+):
if doctype not in ["POS Invoice", "Sales Invoice", "Purchase Invoice"]:
# not an invoice
- return {
- party_type.lower(): party
- }
+ return {party_type.lower(): party}
if party:
account = get_party_account(party_type, party, company)
- account_fieldname = "debit_to" if party_type=="Customer" else "credit_to"
+ account_fieldname = "debit_to" if party_type == "Customer" else "credit_to"
out = {
party_type.lower(): party,
- account_fieldname : account,
- "due_date": get_due_date(posting_date, party_type, party, company, bill_date)
+ account_fieldname: account,
+ "due_date": get_due_date(posting_date, party_type, party, company, bill_date),
}
return out
+
@frappe.whitelist()
def get_party_account(party_type, party=None, company=None):
"""Returns the account for the given `party`.
- Will first search in party (Customer / Supplier) record, if not found,
- will search in group (Customer Group / Supplier Group),
- finally will return default."""
+ Will first search in party (Customer / Supplier) record, if not found,
+ will search in group (Customer Group / Supplier Group),
+ finally will return default."""
if not company:
frappe.throw(_("Please select a Company"))
- if not party and party_type in ['Customer', 'Supplier']:
- default_account_name = "default_receivable_account" \
- if party_type=="Customer" else "default_payable_account"
+ if not party and party_type in ["Customer", "Supplier"]:
+ default_account_name = (
+ "default_receivable_account" if party_type == "Customer" else "default_payable_account"
+ )
- return frappe.get_cached_value('Company', company, default_account_name)
+ return frappe.get_cached_value("Company", company, default_account_name)
- account = frappe.db.get_value("Party Account",
- {"parenttype": party_type, "parent": party, "company": company}, "account")
+ account = frappe.db.get_value(
+ "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account"
+ )
- if not account and party_type in ['Customer', 'Supplier']:
- party_group_doctype = "Customer Group" if party_type=="Customer" else "Supplier Group"
+ if not account and party_type in ["Customer", "Supplier"]:
+ party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group"
group = frappe.get_cached_value(party_type, party, scrub(party_group_doctype))
- account = frappe.db.get_value("Party Account",
- {"parenttype": party_group_doctype, "parent": group, "company": company}, "account")
+ account = frappe.db.get_value(
+ "Party Account",
+ {"parenttype": party_group_doctype, "parent": group, "company": company},
+ "account",
+ )
- if not account and party_type in ['Customer', 'Supplier']:
- default_account_name = "default_receivable_account" \
- if party_type=="Customer" else "default_payable_account"
- account = frappe.get_cached_value('Company', company, default_account_name)
+ if not account and party_type in ["Customer", "Supplier"]:
+ default_account_name = (
+ "default_receivable_account" if party_type == "Customer" else "default_payable_account"
+ )
+ account = frappe.get_cached_value("Company", company, default_account_name)
existing_gle_currency = get_party_gle_currency(party_type, party, company)
if existing_gle_currency:
if account:
account_currency = frappe.db.get_value("Account", account, "account_currency", cache=True)
if (account and account_currency != existing_gle_currency) or not account:
- account = get_party_gle_account(party_type, party, company)
+ account = get_party_gle_account(party_type, party, company)
return account
+
@frappe.whitelist()
def get_party_bank_account(party_type, party):
- return frappe.db.get_value('Bank Account', {
- 'party_type': party_type,
- 'party': party,
- 'is_default': 1
- })
+ return frappe.db.get_value(
+ "Bank Account", {"party_type": party_type, "party": party, "is_default": 1}
+ )
+
def get_party_account_currency(party_type, party, company):
def generator():
@@ -269,27 +387,38 @@ def get_party_account_currency(party_type, party, company):
return frappe.local_cache("party_account_currency", (party_type, party, company), generator)
+
def get_party_gle_currency(party_type, party, company):
def generator():
- existing_gle_currency = frappe.db.sql("""select account_currency from `tabGL Entry`
+ existing_gle_currency = frappe.db.sql(
+ """select account_currency from `tabGL Entry`
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
- limit 1""", { "company": company, "party_type": party_type, "party": party })
+ limit 1""",
+ {"company": company, "party_type": party_type, "party": party},
+ )
return existing_gle_currency[0][0] if existing_gle_currency else None
- return frappe.local_cache("party_gle_currency", (party_type, party, company), generator,
- regenerate_if_none=True)
+ return frappe.local_cache(
+ "party_gle_currency", (party_type, party, company), generator, regenerate_if_none=True
+ )
+
def get_party_gle_account(party_type, party, company):
def generator():
- existing_gle_account = frappe.db.sql("""select account from `tabGL Entry`
+ existing_gle_account = frappe.db.sql(
+ """select account from `tabGL Entry`
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
- limit 1""", { "company": company, "party_type": party_type, "party": party })
+ limit 1""",
+ {"company": company, "party_type": party_type, "party": party},
+ )
return existing_gle_account[0][0] if existing_gle_account else None
- return frappe.local_cache("party_gle_account", (party_type, party, company), generator,
- regenerate_if_none=True)
+ return frappe.local_cache(
+ "party_gle_account", (party_type, party, company), generator, regenerate_if_none=True
+ )
+
def validate_party_gle_currency(party_type, party, company, party_account_currency=None):
"""Validate party account currency with existing GL Entry's currency"""
@@ -299,32 +428,55 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
existing_gle_currency = get_party_gle_currency(party_type, party, company)
if existing_gle_currency and party_account_currency != existing_gle_currency:
- frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.")
- .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
+ frappe.throw(
+ _(
+ "{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}."
+ ).format(
+ frappe.bold(party_type),
+ frappe.bold(party),
+ frappe.bold(existing_gle_currency),
+ frappe.bold(company),
+ ),
+ InvalidAccountCurrency,
+ )
+
def validate_party_accounts(doc):
from erpnext.controllers.accounts_controller import validate_account_head
+
companies = []
for account in doc.get("accounts"):
if account.company in companies:
- frappe.throw(_("There can only be 1 Account per Company in {0} {1}")
- .format(doc.doctype, doc.name), DuplicatePartyAccountError)
+ frappe.throw(
+ _("There can only be 1 Account per Company in {0} {1}").format(doc.doctype, doc.name),
+ DuplicatePartyAccountError,
+ )
else:
companies.append(account.company)
- party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True)
+ party_account_currency = frappe.db.get_value(
+ "Account", account.account, "account_currency", cache=True
+ )
if frappe.db.get_default("Company"):
- company_default_currency = frappe.get_cached_value('Company',
- frappe.db.get_default("Company"), "default_currency")
+ company_default_currency = frappe.get_cached_value(
+ "Company", frappe.db.get_default("Company"), "default_currency"
+ )
else:
- company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
+ company_default_currency = frappe.db.get_value("Company", account.company, "default_currency")
validate_party_gle_currency(doc.doctype, doc.name, account.company, party_account_currency)
if doc.get("default_currency") and party_account_currency and company_default_currency:
- if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:
- frappe.throw(_("Billing currency must be equal to either default company's currency or party account currency"))
+ if (
+ doc.default_currency != party_account_currency
+ and doc.default_currency != company_default_currency
+ ):
+ frappe.throw(
+ _(
+ "Billing currency must be equal to either default company's currency or party account currency"
+ )
+ )
# validate if account is mapped for same company
validate_account_head(account.idx, account.account, account.company)
@@ -339,18 +491,23 @@ def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
template_name = get_payment_terms_template(party, party_type, company)
if template_name:
- due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
+ due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
+ "%Y-%m-%d"
+ )
else:
if party_type == "Supplier":
supplier_group = frappe.get_cached_value(party_type, party, "supplier_group")
template_name = frappe.get_cached_value("Supplier Group", supplier_group, "payment_terms")
if template_name:
- due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
+ due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
+ "%Y-%m-%d"
+ )
# If due date is calculated from bill_date, check this condition
if getdate(due_date) < getdate(posting_date):
due_date = posting_date
return due_date
+
def get_due_date_from_template(template_name, posting_date, bill_date):
"""
Inspects all `Payment Term`s from the a `Payment Terms Template` and returns the due
@@ -360,40 +517,55 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
"""
due_date = getdate(bill_date or posting_date)
- template = frappe.get_doc('Payment Terms Template', template_name)
+ template = frappe.get_doc("Payment Terms Template", template_name)
for term in template.terms:
- if term.due_date_based_on == 'Day(s) after invoice date':
+ if term.due_date_based_on == "Day(s) after invoice date":
due_date = max(due_date, add_days(due_date, term.credit_days))
- elif term.due_date_based_on == 'Day(s) after the end of the invoice month':
+ elif term.due_date_based_on == "Day(s) after the end of the invoice month":
due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days))
else:
due_date = max(due_date, add_months(get_last_day(due_date), term.credit_months))
return due_date
-def validate_due_date(posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None):
+
+def validate_due_date(
+ posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None
+):
if getdate(due_date) < getdate(posting_date):
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
else:
- if not template_name: return
+ if not template_name:
+ return
- default_due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
+ default_due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
+ "%Y-%m-%d"
+ )
if not default_due_date:
return
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
- is_credit_controller = frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
+ is_credit_controller = (
+ frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
+ )
if is_credit_controller:
- msgprint(_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)")
- .format(date_diff(due_date, default_due_date)))
+ msgprint(
+ _("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)").format(
+ date_diff(due_date, default_due_date)
+ )
+ )
else:
- frappe.throw(_("Due / Reference Date cannot be after {0}")
- .format(formatdate(default_due_date)))
+ frappe.throw(
+ _("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date))
+ )
+
@frappe.whitelist()
def get_address_tax_category(tax_category=None, billing_address=None, shipping_address=None):
- addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from")
+ addr_tax_category_from = frappe.db.get_single_value(
+ "Accounts Settings", "determine_address_tax_category_from"
+ )
if addr_tax_category_from == "Shipping Address":
if shipping_address:
tax_category = frappe.db.get_value("Address", shipping_address, "tax_category") or tax_category
@@ -403,36 +575,48 @@ def get_address_tax_category(tax_category=None, billing_address=None, shipping_a
return cstr(tax_category)
+
@frappe.whitelist()
-def set_taxes(party, party_type, posting_date, company, customer_group=None, supplier_group=None, tax_category=None,
- billing_address=None, shipping_address=None, use_for_shopping_cart=None):
+def set_taxes(
+ party,
+ party_type,
+ posting_date,
+ company,
+ customer_group=None,
+ supplier_group=None,
+ tax_category=None,
+ billing_address=None,
+ shipping_address=None,
+ use_for_shopping_cart=None,
+):
from erpnext.accounts.doctype.tax_rule.tax_rule import get_party_details, get_tax_template
- args = {
- party_type.lower(): party,
- "company": company
- }
+
+ args = {party_type.lower(): party, "company": company}
if tax_category:
- args['tax_category'] = tax_category
+ args["tax_category"] = tax_category
if customer_group:
- args['customer_group'] = customer_group
+ args["customer_group"] = customer_group
if supplier_group:
- args['supplier_group'] = supplier_group
+ args["supplier_group"] = supplier_group
if billing_address or shipping_address:
- args.update(get_party_details(party, party_type, {"billing_address": billing_address, \
- "shipping_address": shipping_address }))
+ args.update(
+ get_party_details(
+ party, party_type, {"billing_address": billing_address, "shipping_address": shipping_address}
+ )
+ )
else:
args.update(get_party_details(party, party_type))
if party_type in ("Customer", "Lead"):
args.update({"tax_type": "Sales"})
- if party_type=='Lead':
- args['customer'] = None
- del args['lead']
+ if party_type == "Lead":
+ args["customer"] = None
+ del args["lead"]
else:
args.update({"tax_type": "Purchase"})
@@ -447,25 +631,27 @@ def get_payment_terms_template(party_name, party_type, company=None):
if party_type not in ("Customer", "Supplier"):
return
template = None
- if party_type == 'Customer':
- customer = frappe.get_cached_value("Customer", party_name,
- fieldname=['payment_terms', "customer_group"], as_dict=1)
+ if party_type == "Customer":
+ customer = frappe.get_cached_value(
+ "Customer", party_name, fieldname=["payment_terms", "customer_group"], as_dict=1
+ )
template = customer.payment_terms
if not template and customer.customer_group:
- template = frappe.get_cached_value("Customer Group",
- customer.customer_group, 'payment_terms')
+ template = frappe.get_cached_value("Customer Group", customer.customer_group, "payment_terms")
else:
- supplier = frappe.get_cached_value("Supplier", party_name,
- fieldname=['payment_terms', "supplier_group"], as_dict=1)
+ supplier = frappe.get_cached_value(
+ "Supplier", party_name, fieldname=["payment_terms", "supplier_group"], as_dict=1
+ )
template = supplier.payment_terms
if not template and supplier.supplier_group:
- template = frappe.get_cached_value("Supplier Group", supplier.supplier_group, 'payment_terms')
+ template = frappe.get_cached_value("Supplier Group", supplier.supplier_group, "payment_terms")
if not template and company:
- template = frappe.get_cached_value('Company', company, fieldname='payment_terms')
+ template = frappe.get_cached_value("Company", company, fieldname="payment_terms")
return template
+
def validate_party_frozen_disabled(party_type, party_name):
if frappe.flags.ignore_party_validation:
@@ -477,7 +663,9 @@ def validate_party_frozen_disabled(party_type, party_name):
if party.disabled:
frappe.throw(_("{0} {1} is disabled").format(party_type, party_name), PartyDisabled)
elif party.get("is_frozen"):
- frozen_accounts_modifier = frappe.db.get_single_value( 'Accounts Settings', 'frozen_accounts_modifier')
+ frozen_accounts_modifier = frappe.db.get_single_value(
+ "Accounts Settings", "frozen_accounts_modifier"
+ )
if not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
@@ -485,99 +673,124 @@ def validate_party_frozen_disabled(party_type, party_name):
if frappe.db.get_value("Employee", party_name, "status") != "Active":
frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True)
+
def get_timeline_data(doctype, name):
- '''returns timeline data for the past one year'''
+ """returns timeline data for the past one year"""
from frappe.desk.form.load import get_communication_data
out = {}
- fields = 'creation, count(*)'
- after = add_years(None, -1).strftime('%Y-%m-%d')
- group_by='group by Date(creation)'
+ fields = "creation, count(*)"
+ after = add_years(None, -1).strftime("%Y-%m-%d")
+ group_by = "group by Date(creation)"
- data = get_communication_data(doctype, name, after=after, group_by='group by creation',
- fields='C.creation as creation, count(C.name)',as_dict=False)
+ data = get_communication_data(
+ doctype,
+ name,
+ after=after,
+ group_by="group by creation",
+ fields="C.creation as creation, count(C.name)",
+ as_dict=False,
+ )
# fetch and append data from Activity Log
- data += frappe.db.sql("""select {fields}
+ data += frappe.db.sql(
+ """select {fields}
from `tabActivity Log`
where (reference_doctype=%(doctype)s and reference_name=%(name)s)
or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
and status!='Success' and creation > {after}
{group_by} order by creation desc
- """.format(fields=fields, group_by=group_by, after=after), {
- "doctype": doctype,
- "name": name
- }, as_dict=False)
+ """.format(
+ fields=fields, group_by=group_by, after=after
+ ),
+ {"doctype": doctype, "name": name},
+ as_dict=False,
+ )
timeline_items = dict(data)
for date, count in timeline_items.items():
timestamp = get_timestamp(date)
- out.update({ timestamp: count })
+ out.update({timestamp: count})
return out
+
def get_dashboard_info(party_type, party, loyalty_program=None):
current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True)
- doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice"
+ doctype = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
- companies = frappe.get_all(doctype, filters={
- 'docstatus': 1,
- party_type.lower(): party
- }, distinct=1, fields=['company'])
+ companies = frappe.get_all(
+ doctype, filters={"docstatus": 1, party_type.lower(): party}, distinct=1, fields=["company"]
+ )
company_wise_info = []
- company_wise_grand_total = frappe.get_all(doctype,
+ company_wise_grand_total = frappe.get_all(
+ doctype,
filters={
- 'docstatus': 1,
+ "docstatus": 1,
party_type.lower(): party,
- 'posting_date': ('between', [current_fiscal_year.year_start_date, current_fiscal_year.year_end_date])
- },
- group_by="company",
- fields=["company", "sum(grand_total) as grand_total", "sum(base_grand_total) as base_grand_total"]
- )
+ "posting_date": (
+ "between",
+ [current_fiscal_year.year_start_date, current_fiscal_year.year_end_date],
+ ),
+ },
+ group_by="company",
+ fields=[
+ "company",
+ "sum(grand_total) as grand_total",
+ "sum(base_grand_total) as base_grand_total",
+ ],
+ )
loyalty_point_details = []
if party_type == "Customer":
- loyalty_point_details = frappe._dict(frappe.get_all("Loyalty Point Entry",
- filters={
- 'customer': party,
- 'expiry_date': ('>=', getdate()),
+ loyalty_point_details = frappe._dict(
+ frappe.get_all(
+ "Loyalty Point Entry",
+ filters={
+ "customer": party,
+ "expiry_date": (">=", getdate()),
},
group_by="company",
fields=["company", "sum(loyalty_points) as loyalty_points"],
- as_list =1
- ))
+ as_list=1,
+ )
+ )
company_wise_billing_this_year = frappe._dict()
for d in company_wise_grand_total:
company_wise_billing_this_year.setdefault(
- d.company,{
- "grand_total": d.grand_total,
- "base_grand_total": d.base_grand_total
- })
+ d.company, {"grand_total": d.grand_total, "base_grand_total": d.base_grand_total}
+ )
-
- company_wise_total_unpaid = frappe._dict(frappe.db.sql("""
+ company_wise_total_unpaid = frappe._dict(
+ frappe.db.sql(
+ """
select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry`
where party_type = %s and party=%s
and is_cancelled = 0
- group by company""", (party_type, party)))
+ group by company""",
+ (party_type, party),
+ )
+ )
for d in companies:
- company_default_currency = frappe.db.get_value("Company", d.company, 'default_currency')
+ company_default_currency = frappe.db.get_value("Company", d.company, "default_currency")
party_account_currency = get_party_account_currency(party_type, party, d.company)
- if party_account_currency==company_default_currency:
- billing_this_year = flt(company_wise_billing_this_year.get(d.company,{}).get("base_grand_total"))
+ if party_account_currency == company_default_currency:
+ billing_this_year = flt(
+ company_wise_billing_this_year.get(d.company, {}).get("base_grand_total")
+ )
else:
- billing_this_year = flt(company_wise_billing_this_year.get(d.company,{}).get("grand_total"))
+ billing_this_year = flt(company_wise_billing_this_year.get(d.company, {}).get("grand_total"))
total_unpaid = flt(company_wise_total_unpaid.get(d.company))
@@ -600,6 +813,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
return company_wise_info
+
def get_party_shipping_address(doctype, name):
"""
Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
@@ -612,50 +826,59 @@ def get_party_shipping_address(doctype, name):
:return: String
"""
out = frappe.db.sql(
- 'SELECT dl.parent '
- 'from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name '
- 'where '
- 'dl.link_doctype=%s '
- 'and dl.link_name=%s '
+ "SELECT dl.parent "
+ "from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name "
+ "where "
+ "dl.link_doctype=%s "
+ "and dl.link_name=%s "
'and dl.parenttype="Address" '
- 'and ifnull(ta.disabled, 0) = 0 and'
+ "and ifnull(ta.disabled, 0) = 0 and"
'(ta.address_type="Shipping" or ta.is_shipping_address=1) '
- 'order by ta.is_shipping_address desc, ta.address_type desc limit 1',
- (doctype, name)
+ "order by ta.is_shipping_address desc, ta.address_type desc limit 1",
+ (doctype, name),
)
if out:
return out[0][0]
else:
- return ''
+ return ""
-def get_partywise_advanced_payment_amount(party_type, posting_date = None, future_payment=0, company=None):
+
+def get_partywise_advanced_payment_amount(
+ party_type, posting_date=None, future_payment=0, company=None
+):
cond = "1=1"
if posting_date:
if future_payment:
- cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date)
+ cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' " "".format(posting_date)
else:
cond = "posting_date <= '{0}'".format(posting_date)
if company:
cond += "and company = {0}".format(frappe.db.escape(company))
- data = frappe.db.sql(""" SELECT party, sum({0}) as amount
+ data = frappe.db.sql(
+ """ SELECT party, sum({0}) as amount
FROM `tabGL Entry`
WHERE
party_type = %s and against_voucher is null
and is_cancelled = 0
- and {1} GROUP BY party"""
- .format(("credit") if party_type == "Customer" else "debit", cond) , party_type)
+ and {1} GROUP BY party""".format(
+ ("credit") if party_type == "Customer" else "debit", cond
+ ),
+ party_type,
+ )
if data:
return frappe._dict(data)
+
def get_default_contact(doctype, name):
"""
- Returns default contact for the given doctype and name.
- Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
+ Returns default contact for the given doctype and name.
+ Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
"""
- out = frappe.db.sql("""
+ out = frappe.db.sql(
+ """
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
FROM `tabDynamic Link` dl
INNER JOIN tabContact c ON c.name = dl.parent
@@ -664,7 +887,9 @@ def get_default_contact(doctype, name):
dl.link_name=%s AND
dl.parenttype = "Contact"
ORDER BY is_primary_contact DESC, is_billing_contact DESC
- """, (doctype, name))
+ """,
+ (doctype, name),
+ )
if out:
try:
return out[0][0]
diff --git a/erpnext/accounts/report/account_balance/account_balance.py b/erpnext/accounts/report/account_balance/account_balance.py
index a2c70a45f9..824a965cdc 100644
--- a/erpnext/accounts/report/account_balance/account_balance.py
+++ b/erpnext/accounts/report/account_balance/account_balance.py
@@ -14,6 +14,7 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_columns(filters):
columns = [
{
@@ -21,7 +22,7 @@ def get_columns(filters):
"fieldtype": "Link",
"fieldname": "account",
"options": "Account",
- "width": 100
+ "width": 100,
},
{
"label": _("Currency"),
@@ -29,19 +30,20 @@ def get_columns(filters):
"fieldname": "currency",
"options": "Currency",
"hidden": 1,
- "width": 50
+ "width": 50,
},
{
"label": _("Balance"),
"fieldtype": "Currency",
"fieldname": "balance",
"options": "currency",
- "width": 100
- }
+ "width": 100,
+ },
]
return columns
+
def get_conditions(filters):
conditions = {}
@@ -57,12 +59,14 @@ def get_conditions(filters):
return conditions
+
def get_data(filters):
data = []
conditions = get_conditions(filters)
- accounts = frappe.db.get_all("Account", fields=["name", "account_currency"],
- filters=conditions, order_by='name')
+ accounts = frappe.db.get_all(
+ "Account", fields=["name", "account_currency"], filters=conditions, order_by="name"
+ )
for d in accounts:
balance = get_balance_on(d.name, date=filters.report_date)
diff --git a/erpnext/accounts/report/account_balance/test_account_balance.py b/erpnext/accounts/report/account_balance/test_account_balance.py
index 73370e4ed6..13fa05d474 100644
--- a/erpnext/accounts/report/account_balance/test_account_balance.py
+++ b/erpnext/accounts/report/account_balance/test_account_balance.py
@@ -13,9 +13,9 @@ class TestAccountBalance(unittest.TestCase):
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
filters = {
- 'company': '_Test Company 2',
- 'report_date': getdate(),
- 'root_type': 'Income',
+ "company": "_Test Company 2",
+ "report_date": getdate(),
+ "root_type": "Income",
}
make_sales_invoice()
@@ -24,42 +24,45 @@ class TestAccountBalance(unittest.TestCase):
expected_data = [
{
- "account": 'Direct Income - _TC2',
- "currency": 'EUR',
+ "account": "Direct Income - _TC2",
+ "currency": "EUR",
"balance": -100.0,
},
{
- "account": 'Income - _TC2',
- "currency": 'EUR',
+ "account": "Income - _TC2",
+ "currency": "EUR",
"balance": -100.0,
},
{
- "account": 'Indirect Income - _TC2',
- "currency": 'EUR',
+ "account": "Indirect Income - _TC2",
+ "currency": "EUR",
"balance": 0.0,
},
{
- "account": 'Sales - _TC2',
- "currency": 'EUR',
+ "account": "Sales - _TC2",
+ "currency": "EUR",
"balance": -100.0,
},
{
- "account": 'Service - _TC2',
- "currency": 'EUR',
+ "account": "Service - _TC2",
+ "currency": "EUR",
"balance": 0.0,
- }
+ },
]
self.assertEqual(expected_data, report[1])
+
def make_sales_invoice():
frappe.set_user("Administrator")
- create_sales_invoice(company="_Test Company 2",
- customer = '_Test Customer 2',
- currency = 'EUR',
- warehouse = 'Finished Goods - _TC2',
- debit_to = 'Debtors - _TC2',
- income_account = 'Sales - _TC2',
- expense_account = 'Cost of Goods Sold - _TC2',
- cost_center = 'Main - _TC2')
+ create_sales_invoice(
+ company="_Test Company 2",
+ customer="_Test Customer 2",
+ currency="EUR",
+ warehouse="Finished Goods - _TC2",
+ debit_to="Debtors - _TC2",
+ income_account="Sales - _TC2",
+ expense_account="Cost of Goods Sold - _TC2",
+ cost_center="Main - _TC2",
+ )
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index a990f23cd6..7bf9539b75 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -29,6 +29,7 @@ from erpnext.accounts.utils import get_currency_precision
# 9. Report amounts are in "Party Currency" if party is selected, or company currency for multi-party
# 10. This reports is based on all GL Entries that are made against account_type "Receivable" or "Payable"
+
def execute(filters=None):
args = {
"party_type": "Customer",
@@ -36,18 +37,23 @@ def execute(filters=None):
}
return ReceivablePayableReport(filters).run(args)
+
class ReceivablePayableReport(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
self.filters.report_date = getdate(self.filters.report_date or nowdate())
- self.age_as_on = getdate(nowdate()) \
- if self.filters.report_date > getdate(nowdate()) \
+ self.age_as_on = (
+ getdate(nowdate())
+ if self.filters.report_date > getdate(nowdate())
else self.filters.report_date
+ )
def run(self, args):
self.filters.update(args)
self.set_defaults()
- self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
+ self.party_naming_by = frappe.db.get_value(
+ args.get("naming_by")[0], None, args.get("naming_by")[1]
+ )
self.get_columns()
self.get_data()
self.get_chart_data()
@@ -55,8 +61,10 @@ class ReceivablePayableReport(object):
def set_defaults(self):
if not self.filters.get("company"):
- self.filters.company = frappe.db.get_single_value('Global Defaults', 'default_company')
- self.company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency")
+ self.filters.company = frappe.db.get_single_value("Global Defaults", "default_company")
+ self.company_currency = frappe.get_cached_value(
+ "Company", self.filters.get("company"), "default_currency"
+ )
self.currency_precision = get_currency_precision() or 2
self.dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
self.party_type = self.filters.party_type
@@ -64,8 +72,8 @@ class ReceivablePayableReport(object):
self.invoices = set()
self.skip_total_row = 0
- if self.filters.get('group_by_party'):
- self.previous_party=''
+ if self.filters.get("group_by_party"):
+ self.previous_party = ""
self.total_row_map = {}
self.skip_total_row = 1
@@ -73,7 +81,7 @@ class ReceivablePayableReport(object):
self.get_gl_entries()
self.get_sales_invoices_or_customers_based_on_sales_person()
self.voucher_balance = OrderedDict()
- self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
+ self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
# Build delivery note map against all sales invoices
self.build_delivery_note_map()
@@ -100,64 +108,72 @@ class ReceivablePayableReport(object):
key = (gle.voucher_type, gle.voucher_no, gle.party)
if not key in self.voucher_balance:
self.voucher_balance[key] = frappe._dict(
- voucher_type = gle.voucher_type,
- voucher_no = gle.voucher_no,
- party = gle.party,
- posting_date = gle.posting_date,
- account_currency = gle.account_currency,
- remarks = gle.remarks if self.filters.get("show_remarks") else None,
- invoiced = 0.0,
- paid = 0.0,
- credit_note = 0.0,
- outstanding = 0.0,
- invoiced_in_account_currency = 0.0,
- paid_in_account_currency = 0.0,
- credit_note_in_account_currency = 0.0,
- outstanding_in_account_currency = 0.0
+ voucher_type=gle.voucher_type,
+ voucher_no=gle.voucher_no,
+ party=gle.party,
+ posting_date=gle.posting_date,
+ account_currency=gle.account_currency,
+ remarks=gle.remarks if self.filters.get("show_remarks") else None,
+ invoiced=0.0,
+ paid=0.0,
+ credit_note=0.0,
+ outstanding=0.0,
+ invoiced_in_account_currency=0.0,
+ paid_in_account_currency=0.0,
+ credit_note_in_account_currency=0.0,
+ outstanding_in_account_currency=0.0,
)
self.get_invoices(gle)
- if self.filters.get('group_by_party'):
+ if self.filters.get("group_by_party"):
self.init_subtotal_row(gle.party)
- if self.filters.get('group_by_party'):
- self.init_subtotal_row('Total')
+ if self.filters.get("group_by_party"):
+ self.init_subtotal_row("Total")
def get_invoices(self, gle):
- if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
+ if gle.voucher_type in ("Sales Invoice", "Purchase Invoice"):
if self.filters.get("sales_person"):
- if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \
- or gle.party in self.sales_person_records.get("Customer", []):
- self.invoices.add(gle.voucher_no)
+ if gle.voucher_no in self.sales_person_records.get(
+ "Sales Invoice", []
+ ) or gle.party in self.sales_person_records.get("Customer", []):
+ self.invoices.add(gle.voucher_no)
else:
self.invoices.add(gle.voucher_no)
def init_subtotal_row(self, party):
if not self.total_row_map.get(party):
- self.total_row_map.setdefault(party, {
- 'party': party,
- 'bold': 1
- })
+ self.total_row_map.setdefault(party, {"party": party, "bold": 1})
for field in self.get_currency_fields():
self.total_row_map[party][field] = 0.0
def get_currency_fields(self):
- return ['invoiced', 'paid', 'credit_note', 'outstanding', 'range1',
- 'range2', 'range3', 'range4', 'range5']
+ return [
+ "invoiced",
+ "paid",
+ "credit_note",
+ "outstanding",
+ "range1",
+ "range2",
+ "range3",
+ "range4",
+ "range5",
+ ]
def update_voucher_balance(self, gle):
# get the row where this balance needs to be updated
# if its a payment, it will return the linked invoice or will be considered as advance
row = self.get_voucher_balance(gle)
- if not row: return
+ if not row:
+ return
# gle_balance will be the total "debit - credit" for receivable type reports and
# and vice-versa for payable type reports
gle_balance = self.get_gle_balance(gle)
gle_balance_in_account_currency = self.get_gle_balance_in_account_currency(gle)
if gle_balance > 0:
- if gle.voucher_type in ('Journal Entry', 'Payment Entry') and gle.against_voucher:
+ if gle.voucher_type in ("Journal Entry", "Payment Entry") and gle.against_voucher:
# debit against sales / purchase invoice
row.paid -= gle_balance
row.paid_in_account_currency -= gle_balance_in_account_currency
@@ -177,7 +193,7 @@ class ReceivablePayableReport(object):
row.paid_in_account_currency -= gle_balance_in_account_currency
if gle.cost_center:
- row.cost_center = str(gle.cost_center)
+ row.cost_center = str(gle.cost_center)
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
@@ -191,14 +207,16 @@ class ReceivablePayableReport(object):
if sub_total_row:
self.data.append(sub_total_row)
self.data.append({})
- self.update_sub_total_row(sub_total_row, 'Total')
+ self.update_sub_total_row(sub_total_row, "Total")
def get_voucher_balance(self, gle):
if self.filters.get("sales_person"):
against_voucher = gle.against_voucher or gle.voucher_no
- if not (gle.party in self.sales_person_records.get("Customer", []) or \
- against_voucher in self.sales_person_records.get("Sales Invoice", [])):
- return
+ if not (
+ gle.party in self.sales_person_records.get("Customer", [])
+ or against_voucher in self.sales_person_records.get("Sales Invoice", [])
+ ):
+ return
voucher_balance = None
if gle.against_voucher:
@@ -208,13 +226,15 @@ class ReceivablePayableReport(object):
# If payment is made against credit note
# and credit note is made against a Sales Invoice
# then consider the payment against original sales invoice.
- if gle.against_voucher_type in ('Sales Invoice', 'Purchase Invoice'):
+ if gle.against_voucher_type in ("Sales Invoice", "Purchase Invoice"):
if gle.against_voucher in self.return_entries:
return_against = self.return_entries.get(gle.against_voucher)
if return_against:
against_voucher = return_against
- voucher_balance = self.voucher_balance.get((gle.against_voucher_type, against_voucher, gle.party))
+ voucher_balance = self.voucher_balance.get(
+ (gle.against_voucher_type, against_voucher, gle.party)
+ )
if not voucher_balance:
# no invoice, this is an invoice / stand-alone payment / credit note
@@ -227,13 +247,18 @@ class ReceivablePayableReport(object):
# as we can use this to filter out invoices without outstanding
for key, row in self.voucher_balance.items():
row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision)
- row.outstanding_in_account_currency = flt(row.invoiced_in_account_currency - row.paid_in_account_currency - \
- row.credit_note_in_account_currency, self.currency_precision)
+ row.outstanding_in_account_currency = flt(
+ row.invoiced_in_account_currency
+ - row.paid_in_account_currency
+ - row.credit_note_in_account_currency,
+ self.currency_precision,
+ )
row.invoice_grand_total = row.invoiced
- if (abs(row.outstanding) > 1.0/10 ** self.currency_precision) and \
- (abs(row.outstanding_in_account_currency) > 1.0/10 ** self.currency_precision):
+ if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
+ abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision
+ ):
# non-zero oustanding, we must consider this row
if self.is_invoice(row) and self.filters.based_on_payment_terms:
@@ -254,10 +279,10 @@ class ReceivablePayableReport(object):
else:
self.append_row(row)
- if self.filters.get('group_by_party'):
+ if self.filters.get("group_by_party"):
self.append_subtotal_row(self.previous_party)
if self.data:
- self.data.append(self.total_row_map.get('Total'))
+ self.data.append(self.total_row_map.get("Total"))
def append_row(self, row):
self.allocate_future_payments(row)
@@ -265,7 +290,7 @@ class ReceivablePayableReport(object):
self.set_party_details(row)
self.set_ageing(row)
- if self.filters.get('group_by_party'):
+ if self.filters.get("group_by_party"):
self.update_sub_total_row(row, row.party)
if self.previous_party and (self.previous_party != row.party):
self.append_subtotal_row(self.previous_party)
@@ -279,39 +304,49 @@ class ReceivablePayableReport(object):
invoice_details.pop("due_date", None)
row.update(invoice_details)
- if row.voucher_type == 'Sales Invoice':
+ if row.voucher_type == "Sales Invoice":
if self.filters.show_delivery_notes:
self.set_delivery_notes(row)
if self.filters.show_sales_person and row.sales_team:
row.sales_person = ", ".join(row.sales_team)
- del row['sales_team']
+ del row["sales_team"]
def set_delivery_notes(self, row):
delivery_notes = self.delivery_notes.get(row.voucher_no, [])
if delivery_notes:
- row.delivery_notes = ', '.join(delivery_notes)
+ row.delivery_notes = ", ".join(delivery_notes)
def build_delivery_note_map(self):
if self.invoices and self.filters.show_delivery_notes:
self.delivery_notes = frappe._dict()
# delivery note link inside sales invoice
- si_against_dn = frappe.db.sql("""
+ si_against_dn = frappe.db.sql(
+ """
select parent, delivery_note
from `tabSales Invoice Item`
where docstatus=1 and parent in (%s)
- """ % (','.join(['%s'] * len(self.invoices))), tuple(self.invoices), as_dict=1)
+ """
+ % (",".join(["%s"] * len(self.invoices))),
+ tuple(self.invoices),
+ as_dict=1,
+ )
for d in si_against_dn:
if d.delivery_note:
self.delivery_notes.setdefault(d.parent, set()).add(d.delivery_note)
- dn_against_si = frappe.db.sql("""
+ dn_against_si = frappe.db.sql(
+ """
select distinct parent, against_sales_invoice
from `tabDelivery Note Item`
where against_sales_invoice in (%s)
- """ % (','.join(['%s'] * len(self.invoices))), tuple(self.invoices) , as_dict=1)
+ """
+ % (",".join(["%s"] * len(self.invoices))),
+ tuple(self.invoices),
+ as_dict=1,
+ )
for d in dn_against_si:
self.delivery_notes.setdefault(d.against_sales_invoice, set()).add(d.parent)
@@ -319,39 +354,55 @@ class ReceivablePayableReport(object):
def get_invoice_details(self):
self.invoice_details = frappe._dict()
if self.party_type == "Customer":
- si_list = frappe.db.sql("""
+ si_list = frappe.db.sql(
+ """
select name, due_date, po_no
from `tabSales Invoice`
where posting_date <= %s
- """,self.filters.report_date, as_dict=1)
+ """,
+ self.filters.report_date,
+ as_dict=1,
+ )
for d in si_list:
self.invoice_details.setdefault(d.name, d)
# Get Sales Team
if self.filters.show_sales_person:
- sales_team = frappe.db.sql("""
+ sales_team = frappe.db.sql(
+ """
select parent, sales_person
from `tabSales Team`
where parenttype = 'Sales Invoice'
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
for d in sales_team:
- self.invoice_details.setdefault(d.parent, {})\
- .setdefault('sales_team', []).append(d.sales_person)
+ self.invoice_details.setdefault(d.parent, {}).setdefault("sales_team", []).append(
+ d.sales_person
+ )
if self.party_type == "Supplier":
- for pi in frappe.db.sql("""
+ for pi in frappe.db.sql(
+ """
select name, due_date, bill_no, bill_date
from `tabPurchase Invoice`
where posting_date <= %s
- """, self.filters.report_date, as_dict=1):
+ """,
+ self.filters.report_date,
+ as_dict=1,
+ ):
self.invoice_details.setdefault(pi.name, pi)
# Invoices booked via Journal Entries
- journal_entries = frappe.db.sql("""
+ journal_entries = frappe.db.sql(
+ """
select name, due_date, bill_no, bill_date
from `tabJournal Entry`
where posting_date <= %s
- """, self.filters.report_date, as_dict=1)
+ """,
+ self.filters.report_date,
+ as_dict=1,
+ )
for je in journal_entries:
if je.bill_no:
@@ -372,17 +423,18 @@ class ReceivablePayableReport(object):
# update "paid" and "oustanding" for this term
if not term.paid:
- self.allocate_closing_to_term(row, term, 'paid')
+ self.allocate_closing_to_term(row, term, "paid")
# update "credit_note" and "oustanding" for this term
if term.outstanding:
- self.allocate_closing_to_term(row, term, 'credit_note')
+ self.allocate_closing_to_term(row, term, "credit_note")
- row.payment_terms = sorted(row.payment_terms, key=lambda x: x['due_date'])
+ row.payment_terms = sorted(row.payment_terms, key=lambda x: x["due_date"])
def get_payment_terms(self, row):
# build payment_terms for row
- payment_terms_details = frappe.db.sql("""
+ payment_terms_details = frappe.db.sql(
+ """
select
si.name, si.party_account_currency, si.currency, si.conversion_rate,
ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
@@ -391,8 +443,12 @@ class ReceivablePayableReport(object):
si.name = ps.parent and
si.name = %s
order by ps.paid_amount desc, due_date
- """.format(row.voucher_type), row.voucher_no, as_dict = 1)
-
+ """.format(
+ row.voucher_type
+ ),
+ row.voucher_no,
+ as_dict=1,
+ )
original_row = frappe._dict(row)
row.payment_terms = []
@@ -406,23 +462,29 @@ class ReceivablePayableReport(object):
self.append_payment_term(row, d, term)
def append_payment_term(self, row, d, term):
- if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency:
+ if (
+ self.filters.get("customer") or self.filters.get("supplier")
+ ) and d.currency == d.party_account_currency:
invoiced = d.payment_amount
else:
invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
- row.payment_terms.append(term.update({
- "due_date": d.due_date,
- "invoiced": invoiced,
- "invoice_grand_total": row.invoiced,
- "payment_term": d.description or d.payment_term,
- "paid": d.paid_amount + d.discounted_amount,
- "credit_note": 0.0,
- "outstanding": invoiced - d.paid_amount - d.discounted_amount
- }))
+ row.payment_terms.append(
+ term.update(
+ {
+ "due_date": d.due_date,
+ "invoiced": invoiced,
+ "invoice_grand_total": row.invoiced,
+ "payment_term": d.description or d.payment_term,
+ "paid": d.paid_amount + d.discounted_amount,
+ "credit_note": 0.0,
+ "outstanding": invoiced - d.paid_amount - d.discounted_amount,
+ }
+ )
+ )
if d.paid_amount:
- row['paid'] -= d.paid_amount + d.discounted_amount
+ row["paid"] -= d.paid_amount + d.discounted_amount
def allocate_closing_to_term(self, row, term, key):
if row[key]:
@@ -437,7 +499,7 @@ class ReceivablePayableReport(object):
def allocate_extra_payments_or_credits(self, row):
# allocate extra payments / credits
additional_row = None
- for key in ('paid', 'credit_note'):
+ for key in ("paid", "credit_note"):
if row[key] > 0:
if not additional_row:
additional_row = frappe._dict(row)
@@ -445,7 +507,9 @@ class ReceivablePayableReport(object):
additional_row[key] = row[key]
if additional_row:
- additional_row.outstanding = additional_row.invoiced - additional_row.paid - additional_row.credit_note
+ additional_row.outstanding = (
+ additional_row.invoiced - additional_row.paid - additional_row.credit_note
+ )
self.append_row(additional_row)
def get_future_payments(self):
@@ -459,7 +523,8 @@ class ReceivablePayableReport(object):
self.future_payments.setdefault((d.invoice_no, d.party), []).append(d)
def get_future_payments_from_payment_entry(self):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
ref.reference_name as invoice_no,
payment_entry.party,
@@ -475,16 +540,23 @@ class ReceivablePayableReport(object):
payment_entry.docstatus < 2
and payment_entry.posting_date > %s
and payment_entry.party_type = %s
- """, (self.filters.report_date, self.party_type), as_dict=1)
+ """,
+ (self.filters.report_date, self.party_type),
+ as_dict=1,
+ )
def get_future_payments_from_journal_entry(self):
- if self.filters.get('party'):
- amount_field = ("jea.debit_in_account_currency - jea.credit_in_account_currency"
- if self.party_type == 'Supplier' else "jea.credit_in_account_currency - jea.debit_in_account_currency")
+ if self.filters.get("party"):
+ amount_field = (
+ "jea.debit_in_account_currency - jea.credit_in_account_currency"
+ if self.party_type == "Supplier"
+ else "jea.credit_in_account_currency - jea.debit_in_account_currency"
+ )
else:
- amount_field = ("jea.debit - " if self.party_type == 'Supplier' else "jea.credit")
+ amount_field = "jea.debit - " if self.party_type == "Supplier" else "jea.credit"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
jea.reference_name as invoice_no,
jea.party,
@@ -503,7 +575,12 @@ class ReceivablePayableReport(object):
and jea.reference_name is not null and jea.reference_name != ''
group by je.name, jea.reference_name
having future_amount > 0
- """.format(amount_field), (self.filters.report_date, self.party_type), as_dict=1)
+ """.format(
+ amount_field
+ ),
+ (self.filters.report_date, self.party_type),
+ as_dict=1,
+ )
def allocate_future_payments(self, row):
# future payments are captured in additional columns
@@ -525,22 +602,21 @@ class ReceivablePayableReport(object):
future.future_amount = 0
row.remaining_balance = row.outstanding - row.future_amount
- row.setdefault('future_ref', []).append(cstr(future.future_ref) + '/' + cstr(future.future_date))
+ row.setdefault("future_ref", []).append(
+ cstr(future.future_ref) + "/" + cstr(future.future_date)
+ )
if row.future_ref:
- row.future_ref = ', '.join(row.future_ref)
+ row.future_ref = ", ".join(row.future_ref)
def get_return_entries(self):
doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
- filters={
- 'is_return': 1,
- 'docstatus': 1
- }
+ filters = {"is_return": 1, "docstatus": 1}
party_field = scrub(self.filters.party_type)
if self.filters.get(party_field):
filters.update({party_field: self.filters.get(party_field)})
self.return_entries = frappe._dict(
- frappe.get_all(doctype, filters, ['name', 'return_against'], as_list=1)
+ frappe.get_all(doctype, filters, ["name", "return_against"], as_list=1)
)
def set_ageing(self, row):
@@ -571,16 +647,26 @@ class ReceivablePayableReport(object):
row.age = (getdate(self.age_as_on) - getdate(entry_date)).days or 0
index = None
- if not (self.filters.range1 and self.filters.range2 and self.filters.range3 and self.filters.range4):
- self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = 30, 60, 90, 120
+ if not (
+ self.filters.range1 and self.filters.range2 and self.filters.range3 and self.filters.range4
+ ):
+ self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = (
+ 30,
+ 60,
+ 90,
+ 120,
+ )
- for i, days in enumerate([self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]):
+ for i, days in enumerate(
+ [self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]
+ ):
if cint(row.age) <= cint(days):
index = i
break
- if index is None: index = 4
- row['range' + str(index+1)] = row.outstanding
+ if index is None:
+ index = 4
+ row["range" + str(index + 1)] = row.outstanding
def get_gl_entries(self):
# get all the GL entries filtered by the given filters
@@ -605,7 +691,8 @@ class ReceivablePayableReport(object):
remarks = ", remarks" if self.filters.get("show_remarks") else ""
- self.gl_entries = frappe.db.sql("""
+ self.gl_entries = frappe.db.sql(
+ """
select
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
against_voucher_type, against_voucher, account_currency, {0}, {1} {remarks}
@@ -616,20 +703,27 @@ class ReceivablePayableReport(object):
and is_cancelled = 0
and party_type=%s
and (party is not null and party != '')
- {2} {3} {4}"""
- .format(select_fields, doc_currency_fields, date_condition, conditions, order_by, remarks=remarks), values, as_dict=True)
+ {2} {3} {4}""".format(
+ select_fields, doc_currency_fields, date_condition, conditions, order_by, remarks=remarks
+ ),
+ values,
+ as_dict=True,
+ )
def get_sales_invoices_or_customers_based_on_sales_person(self):
if self.filters.get("sales_person"):
- lft, rgt = frappe.db.get_value("Sales Person",
- self.filters.get("sales_person"), ["lft", "rgt"])
+ lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"])
- records = frappe.db.sql("""
+ records = frappe.db.sql(
+ """
select distinct parent, parenttype
from `tabSales Team` steam
where parenttype in ('Customer', 'Sales Invoice')
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
- """, (lft, rgt), as_dict=1)
+ """,
+ (lft, rgt),
+ as_dict=1,
+ )
self.sales_person_records = frappe._dict()
for d in records:
@@ -642,10 +736,10 @@ class ReceivablePayableReport(object):
self.add_common_filters(conditions, values, party_type_field)
- if party_type_field=="customer":
+ if party_type_field == "customer":
self.add_customer_filters(conditions, values)
- elif party_type_field=="supplier":
+ elif party_type_field == "supplier":
self.add_supplier_filters(conditions, values)
if self.filters.cost_center:
@@ -656,13 +750,16 @@ class ReceivablePayableReport(object):
def get_cost_center_conditions(self, conditions):
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
- cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
+ cost_center_list = [
+ center.name
+ for center in frappe.get_list("Cost Center", filters={"lft": (">=", lft), "rgt": ("<=", rgt)})
+ ]
cost_center_string = '", "'.join(cost_center_list)
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
def get_order_by_condition(self):
- if self.filters.get('group_by_party'):
+ if self.filters.get("group_by_party"):
return "order by party, posting_date"
else:
return "order by posting_date, party"
@@ -682,19 +779,23 @@ class ReceivablePayableReport(object):
# get GL with "receivable" or "payable" account_type
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
- accounts = [d.name for d in frappe.get_all("Account",
- filters={"account_type": account_type, "company": self.filters.company})]
+ accounts = [
+ d.name
+ for d in frappe.get_all(
+ "Account", filters={"account_type": account_type, "company": self.filters.company}
+ )
+ ]
if accounts:
- conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts)))
+ conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
values += accounts
def add_customer_filters(self, conditions, values):
if self.filters.get("customer_group"):
- conditions.append(self.get_hierarchical_filters('Customer Group', 'customer_group'))
+ conditions.append(self.get_hierarchical_filters("Customer Group", "customer_group"))
if self.filters.get("territory"):
- conditions.append(self.get_hierarchical_filters('Territory', 'territory'))
+ conditions.append(self.get_hierarchical_filters("Territory", "territory"))
if self.filters.get("payment_terms_template"):
conditions.append("party in (select name from tabCustomer where payment_terms=%s)")
@@ -706,8 +807,10 @@ class ReceivablePayableReport(object):
def add_supplier_filters(self, conditions, values):
if self.filters.get("supplier_group"):
- conditions.append("""party in (select name from tabSupplier
- where supplier_group=%s)""")
+ conditions.append(
+ """party in (select name from tabSupplier
+ where supplier_group=%s)"""
+ )
values.append(self.filters.get("supplier_group"))
if self.filters.get("payment_terms_template"):
@@ -720,7 +823,8 @@ class ReceivablePayableReport(object):
return """party in (select name from tabCustomer
where exists(select name from `tab{doctype}` where lft >= {lft} and rgt <= {rgt}
and name=tabCustomer.{key}))""".format(
- doctype=doctype, lft=lft, rgt=rgt, key=key)
+ doctype=doctype, lft=lft, rgt=rgt, key=key
+ )
def add_accounting_dimensions_filters(self, conditions, values):
accounting_dimensions = get_accounting_dimensions(as_list=False)
@@ -728,9 +832,10 @@ class ReceivablePayableReport(object):
if accounting_dimensions:
for dimension in accounting_dimensions:
if self.filters.get(dimension.fieldname):
- if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
- self.filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
- self.filters.get(dimension.fieldname))
+ if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
+ self.filters[dimension.fieldname] = get_dimension_with_children(
+ dimension.document_type, self.filters.get(dimension.fieldname)
+ )
conditions.append("{0} in %s".format(dimension.fieldname))
values.append(tuple(self.filters.get(dimension.fieldname)))
@@ -740,123 +845,168 @@ class ReceivablePayableReport(object):
def get_gle_balance_in_account_currency(self, gle):
# get the balance of the GL (debit - credit) or reverse balance based on report type
- return gle.get(self.dr_or_cr + '_in_account_currency') - self.get_reverse_balance_in_account_currency(gle)
+ return gle.get(
+ self.dr_or_cr + "_in_account_currency"
+ ) - self.get_reverse_balance_in_account_currency(gle)
def get_reverse_balance_in_account_currency(self, gle):
- return gle.get('debit_in_account_currency' if self.dr_or_cr=='credit' else 'credit_in_account_currency')
+ return gle.get(
+ "debit_in_account_currency" if self.dr_or_cr == "credit" else "credit_in_account_currency"
+ )
def get_reverse_balance(self, gle):
# get "credit" balance if report type is "debit" and vice versa
- return gle.get('debit' if self.dr_or_cr=='credit' else 'credit')
+ return gle.get("debit" if self.dr_or_cr == "credit" else "credit")
def is_invoice(self, gle):
- if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
+ if gle.voucher_type in ("Sales Invoice", "Purchase Invoice"):
return True
def get_party_details(self, party):
if not party in self.party_details:
- if self.party_type == 'Customer':
- self.party_details[party] = frappe.db.get_value('Customer', party, ['customer_name',
- 'territory', 'customer_group', 'customer_primary_contact'], as_dict=True)
+ if self.party_type == "Customer":
+ self.party_details[party] = frappe.db.get_value(
+ "Customer",
+ party,
+ ["customer_name", "territory", "customer_group", "customer_primary_contact"],
+ as_dict=True,
+ )
else:
- self.party_details[party] = frappe.db.get_value('Supplier', party, ['supplier_name',
- 'supplier_group'], as_dict=True)
+ self.party_details[party] = frappe.db.get_value(
+ "Supplier", party, ["supplier_name", "supplier_group"], as_dict=True
+ )
return self.party_details[party]
-
def get_columns(self):
self.columns = []
- self.add_column('Posting Date', fieldtype='Date')
- self.add_column(label=_(self.party_type), fieldname='party',
- fieldtype='Link', options=self.party_type, width=180)
+ self.add_column("Posting Date", fieldtype="Date")
+ self.add_column(
+ label=_(self.party_type),
+ fieldname="party",
+ fieldtype="Link",
+ options=self.party_type,
+ width=180,
+ )
if self.party_naming_by == "Naming Series":
- self.add_column(_('{0} Name').format(self.party_type),
- fieldname = scrub(self.party_type) + '_name', fieldtype='Data')
+ self.add_column(
+ _("{0} Name").format(self.party_type),
+ fieldname=scrub(self.party_type) + "_name",
+ fieldtype="Data",
+ )
- if self.party_type == 'Customer':
- self.add_column(_("Customer Contact"), fieldname='customer_primary_contact',
- fieldtype='Link', options='Contact')
+ if self.party_type == "Customer":
+ self.add_column(
+ _("Customer Contact"),
+ fieldname="customer_primary_contact",
+ fieldtype="Link",
+ options="Contact",
+ )
- self.add_column(label=_('Cost Center'), fieldname='cost_center', fieldtype='Data')
- self.add_column(label=_('Voucher Type'), fieldname='voucher_type', fieldtype='Data')
- self.add_column(label=_('Voucher No'), fieldname='voucher_no', fieldtype='Dynamic Link',
- options='voucher_type', width=180)
+ self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data")
+ self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data")
+ self.add_column(
+ label=_("Voucher No"),
+ fieldname="voucher_no",
+ fieldtype="Dynamic Link",
+ options="voucher_type",
+ width=180,
+ )
if self.filters.show_remarks:
- self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200),
+ self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
- self.add_column(label='Due Date', fieldtype='Date')
+ self.add_column(label="Due Date", fieldtype="Date")
if self.party_type == "Supplier":
- self.add_column(label=_('Bill No'), fieldname='bill_no', fieldtype='Data')
- self.add_column(label=_('Bill Date'), fieldname='bill_date', fieldtype='Date')
+ self.add_column(label=_("Bill No"), fieldname="bill_no", fieldtype="Data")
+ self.add_column(label=_("Bill Date"), fieldname="bill_date", fieldtype="Date")
if self.filters.based_on_payment_terms:
- self.add_column(label=_('Payment Term'), fieldname='payment_term', fieldtype='Data')
- self.add_column(label=_('Invoice Grand Total'), fieldname='invoice_grand_total')
+ self.add_column(label=_("Payment Term"), fieldname="payment_term", fieldtype="Data")
+ self.add_column(label=_("Invoice Grand Total"), fieldname="invoice_grand_total")
- self.add_column(_('Invoiced Amount'), fieldname='invoiced')
- self.add_column(_('Paid Amount'), fieldname='paid')
+ self.add_column(_("Invoiced Amount"), fieldname="invoiced")
+ self.add_column(_("Paid Amount"), fieldname="paid")
if self.party_type == "Customer":
- self.add_column(_('Credit Note'), fieldname='credit_note')
+ self.add_column(_("Credit Note"), fieldname="credit_note")
else:
# note: fieldname is still `credit_note`
- self.add_column(_('Debit Note'), fieldname='credit_note')
- self.add_column(_('Outstanding Amount'), fieldname='outstanding')
+ self.add_column(_("Debit Note"), fieldname="credit_note")
+ self.add_column(_("Outstanding Amount"), fieldname="outstanding")
self.setup_ageing_columns()
- self.add_column(label=_('Currency'), fieldname='currency', fieldtype='Link', options='Currency', width=80)
+ self.add_column(
+ label=_("Currency"), fieldname="currency", fieldtype="Link", options="Currency", width=80
+ )
if self.filters.show_future_payments:
- self.add_column(label=_('Future Payment Ref'), fieldname='future_ref', fieldtype='Data')
- self.add_column(label=_('Future Payment Amount'), fieldname='future_amount')
- self.add_column(label=_('Remaining Balance'), fieldname='remaining_balance')
+ self.add_column(label=_("Future Payment Ref"), fieldname="future_ref", fieldtype="Data")
+ self.add_column(label=_("Future Payment Amount"), fieldname="future_amount")
+ self.add_column(label=_("Remaining Balance"), fieldname="remaining_balance")
- if self.filters.party_type == 'Customer':
- self.add_column(label=_('Customer LPO'), fieldname='po_no', fieldtype='Data')
+ if self.filters.party_type == "Customer":
+ self.add_column(label=_("Customer LPO"), fieldname="po_no", fieldtype="Data")
# comma separated list of linked delivery notes
if self.filters.show_delivery_notes:
- self.add_column(label=_('Delivery Notes'), fieldname='delivery_notes', fieldtype='Data')
- self.add_column(label=_('Territory'), fieldname='territory', fieldtype='Link',
- options='Territory')
- self.add_column(label=_('Customer Group'), fieldname='customer_group', fieldtype='Link',
- options='Customer Group')
+ self.add_column(label=_("Delivery Notes"), fieldname="delivery_notes", fieldtype="Data")
+ self.add_column(
+ label=_("Territory"), fieldname="territory", fieldtype="Link", options="Territory"
+ )
+ self.add_column(
+ label=_("Customer Group"),
+ fieldname="customer_group",
+ fieldtype="Link",
+ options="Customer Group",
+ )
if self.filters.show_sales_person:
- self.add_column(label=_('Sales Person'), fieldname='sales_person', fieldtype='Data')
+ self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
if self.filters.party_type == "Supplier":
- self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
- options='Supplier Group')
+ self.add_column(
+ label=_("Supplier Group"),
+ fieldname="supplier_group",
+ fieldtype="Link",
+ options="Supplier Group",
+ )
- def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
- if not fieldname: fieldname = scrub(label)
- if fieldtype=='Currency': options='currency'
- if fieldtype=='Date': width = 90
+ def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
+ if not fieldname:
+ fieldname = scrub(label)
+ if fieldtype == "Currency":
+ options = "currency"
+ if fieldtype == "Date":
+ width = 90
- self.columns.append(dict(
- label=label,
- fieldname=fieldname,
- fieldtype=fieldtype,
- options=options,
- width=width
- ))
+ self.columns.append(
+ dict(label=label, fieldname=fieldname, fieldtype=fieldtype, options=options, width=width)
+ )
def setup_ageing_columns(self):
# for charts
self.ageing_column_labels = []
- self.add_column(label=_('Age (Days)'), fieldname='age', fieldtype='Int', width=80)
+ self.add_column(label=_("Age (Days)"), fieldname="age", fieldtype="Int", width=80)
- for i, label in enumerate(["0-{range1}".format(range1=self.filters["range1"]),
- "{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
- "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
- "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
- "{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
- self.add_column(label=label, fieldname='range' + str(i+1))
- self.ageing_column_labels.append(label)
+ for i, label in enumerate(
+ [
+ "0-{range1}".format(range1=self.filters["range1"]),
+ "{range1}-{range2}".format(
+ range1=cint(self.filters["range1"]) + 1, range2=self.filters["range2"]
+ ),
+ "{range2}-{range3}".format(
+ range2=cint(self.filters["range2"]) + 1, range3=self.filters["range3"]
+ ),
+ "{range3}-{range4}".format(
+ range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"]
+ ),
+ "{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")),
+ ]
+ ):
+ self.add_column(label=label, fieldname="range" + str(i + 1))
+ self.ageing_column_labels.append(label)
def get_chart_data(self):
rows = []
@@ -865,14 +1015,9 @@ class ReceivablePayableReport(object):
if not cint(row.bold):
values = [row.range1, row.range2, row.range3, row.range4, row.range5]
precision = cint(frappe.db.get_default("float_precision")) or 2
- rows.append({
- 'values': [flt(val, precision) for val in values]
- })
+ rows.append({"values": [flt(val, precision) for val in values]})
self.chart = {
- "data": {
- 'labels': self.ageing_column_labels,
- 'datasets': rows
- },
- "type": 'percentage'
+ "data": {"labels": self.ageing_column_labels, "datasets": rows},
+ "type": "percentage",
}
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index ab95c93e36..7a6989f9e5 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -14,13 +14,13 @@ class TestAccountsReceivable(unittest.TestCase):
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
filters = {
- 'company': '_Test Company 2',
- 'based_on_payment_terms': 1,
- 'report_date': today(),
- 'range1': 30,
- 'range2': 60,
- 'range3': 90,
- 'range4': 120
+ "company": "_Test Company 2",
+ "based_on_payment_terms": 1,
+ "report_date": today(),
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120,
}
# check invoice grand total and invoiced column's value for 3 payment terms
@@ -30,8 +30,8 @@ class TestAccountsReceivable(unittest.TestCase):
expected_data = [[100, 30], [100, 50], [100, 20]]
for i in range(3):
- row = report[1][i-1]
- self.assertEqual(expected_data[i-1], [row.invoice_grand_total, row.invoiced])
+ row = report[1][i - 1]
+ self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced])
# check invoice grand total, invoiced, paid and outstanding column's value after payment
make_payment(name)
@@ -40,9 +40,11 @@ class TestAccountsReceivable(unittest.TestCase):
expected_data_after_payment = [[100, 50, 10, 40], [100, 20, 0, 20]]
for i in range(2):
- row = report[1][i-1]
- self.assertEqual(expected_data_after_payment[i-1],
- [row.invoice_grand_total, row.invoiced, row.paid, row.outstanding])
+ row = report[1][i - 1]
+ self.assertEqual(
+ expected_data_after_payment[i - 1],
+ [row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
+ )
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
make_credit_note(name)
@@ -51,30 +53,45 @@ class TestAccountsReceivable(unittest.TestCase):
expected_data_after_credit_note = [100, 0, 0, 40, -40]
row = report[1][0]
- self.assertEqual(expected_data_after_credit_note,
- [row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding])
+ self.assertEqual(
+ expected_data_after_credit_note,
+ [row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding],
+ )
+
def make_sales_invoice():
frappe.set_user("Administrator")
- si = create_sales_invoice(company="_Test Company 2",
- customer = '_Test Customer 2',
- currency = 'EUR',
- warehouse = 'Finished Goods - _TC2',
- debit_to = 'Debtors - _TC2',
- income_account = 'Sales - _TC2',
- expense_account = 'Cost of Goods Sold - _TC2',
- cost_center = 'Main - _TC2',
- do_not_save=1)
+ si = create_sales_invoice(
+ company="_Test Company 2",
+ customer="_Test Customer 2",
+ currency="EUR",
+ warehouse="Finished Goods - _TC2",
+ debit_to="Debtors - _TC2",
+ income_account="Sales - _TC2",
+ expense_account="Cost of Goods Sold - _TC2",
+ cost_center="Main - _TC2",
+ do_not_save=1,
+ )
- si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30))
- si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50))
- si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20))
+ si.append(
+ "payment_schedule",
+ dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30),
+ )
+ si.append(
+ "payment_schedule",
+ dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50),
+ )
+ si.append(
+ "payment_schedule",
+ dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20),
+ )
si.submit()
return si.name
+
def make_payment(docname):
pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=40)
pe.paid_from = "Debtors - _TC2"
@@ -83,14 +100,16 @@ def make_payment(docname):
def make_credit_note(docname):
- create_sales_invoice(company="_Test Company 2",
- customer = '_Test Customer 2',
- currency = 'EUR',
- qty = -1,
- warehouse = 'Finished Goods - _TC2',
- debit_to = 'Debtors - _TC2',
- income_account = 'Sales - _TC2',
- expense_account = 'Cost of Goods Sold - _TC2',
- cost_center = 'Main - _TC2',
- is_return = 1,
- return_against = docname)
+ create_sales_invoice(
+ company="_Test Company 2",
+ customer="_Test Customer 2",
+ currency="EUR",
+ qty=-1,
+ warehouse="Finished Goods - _TC2",
+ debit_to="Debtors - _TC2",
+ income_account="Sales - _TC2",
+ expense_account="Cost of Goods Sold - _TC2",
+ cost_center="Main - _TC2",
+ is_return=1,
+ return_against=docname,
+ )
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 8e3bd8be1b..889f5a22a8 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -18,10 +18,13 @@ def execute(filters=None):
return AccountsReceivableSummary(filters).run(args)
+
class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args):
- self.party_type = args.get('party_type')
- self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
+ self.party_type = args.get("party_type")
+ self.party_naming_by = frappe.db.get_value(
+ args.get("naming_by")[0], None, args.get("naming_by")[1]
+ )
self.get_columns()
self.get_data(args)
return self.columns, self.data
@@ -33,8 +36,15 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.get_party_total(args)
- party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
- self.filters.report_date, self.filters.show_future_payments, self.filters.company) or {}
+ party_advance_amount = (
+ get_partywise_advanced_payment_amount(
+ self.party_type,
+ self.filters.report_date,
+ self.filters.show_future_payments,
+ self.filters.company,
+ )
+ or {}
+ )
if self.filters.show_gl_balance:
gl_balance_map = get_gl_balance(self.filters.report_date)
@@ -47,7 +57,9 @@ class AccountsReceivableSummary(ReceivablePayableReport):
row.party = party
if self.party_naming_by == "Naming Series":
- row.party_name = frappe.get_cached_value(self.party_type, party, scrub(self.party_type) + "_name")
+ row.party_name = frappe.get_cached_value(
+ self.party_type, party, scrub(self.party_type) + "_name"
+ )
row.update(party_dict)
@@ -80,24 +92,29 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.set_party_details(d)
def init_party_total(self, row):
- self.party_total.setdefault(row.party, frappe._dict({
- "invoiced": 0.0,
- "paid": 0.0,
- "credit_note": 0.0,
- "outstanding": 0.0,
- "range1": 0.0,
- "range2": 0.0,
- "range3": 0.0,
- "range4": 0.0,
- "range5": 0.0,
- "total_due": 0.0,
- "sales_person": []
- }))
+ self.party_total.setdefault(
+ row.party,
+ frappe._dict(
+ {
+ "invoiced": 0.0,
+ "paid": 0.0,
+ "credit_note": 0.0,
+ "outstanding": 0.0,
+ "range1": 0.0,
+ "range2": 0.0,
+ "range3": 0.0,
+ "range4": 0.0,
+ "range5": 0.0,
+ "total_due": 0.0,
+ "sales_person": [],
+ }
+ ),
+ )
def set_party_details(self, row):
self.party_total[row.party].currency = row.currency
- for key in ('territory', 'customer_group', 'supplier_group'):
+ for key in ("territory", "customer_group", "supplier_group"):
if row.get(key):
self.party_total[row.party][key] = row.get(key)
@@ -106,52 +123,84 @@ class AccountsReceivableSummary(ReceivablePayableReport):
def get_columns(self):
self.columns = []
- self.add_column(label=_(self.party_type), fieldname='party',
- fieldtype='Link', options=self.party_type, width=180)
+ self.add_column(
+ label=_(self.party_type),
+ fieldname="party",
+ fieldtype="Link",
+ options=self.party_type,
+ width=180,
+ )
if self.party_naming_by == "Naming Series":
- self.add_column(_('{0} Name').format(self.party_type),
- fieldname = 'party_name', fieldtype='Data')
+ self.add_column(_("{0} Name").format(self.party_type), fieldname="party_name", fieldtype="Data")
- credit_debit_label = "Credit Note" if self.party_type == 'Customer' else "Debit Note"
+ credit_debit_label = "Credit Note" if self.party_type == "Customer" else "Debit Note"
- self.add_column(_('Advance Amount'), fieldname='advance')
- self.add_column(_('Invoiced Amount'), fieldname='invoiced')
- self.add_column(_('Paid Amount'), fieldname='paid')
- self.add_column(_(credit_debit_label), fieldname='credit_note')
- self.add_column(_('Outstanding Amount'), fieldname='outstanding')
+ self.add_column(_("Advance Amount"), fieldname="advance")
+ self.add_column(_("Invoiced Amount"), fieldname="invoiced")
+ self.add_column(_("Paid Amount"), fieldname="paid")
+ self.add_column(_(credit_debit_label), fieldname="credit_note")
+ self.add_column(_("Outstanding Amount"), fieldname="outstanding")
if self.filters.show_gl_balance:
- self.add_column(_('GL Balance'), fieldname='gl_balance')
- self.add_column(_('Difference'), fieldname='diff')
+ self.add_column(_("GL Balance"), fieldname="gl_balance")
+ self.add_column(_("Difference"), fieldname="diff")
self.setup_ageing_columns()
if self.party_type == "Customer":
- self.add_column(label=_('Territory'), fieldname='territory', fieldtype='Link',
- options='Territory')
- self.add_column(label=_('Customer Group'), fieldname='customer_group', fieldtype='Link',
- options='Customer Group')
+ self.add_column(
+ label=_("Territory"), fieldname="territory", fieldtype="Link", options="Territory"
+ )
+ self.add_column(
+ label=_("Customer Group"),
+ fieldname="customer_group",
+ fieldtype="Link",
+ options="Customer Group",
+ )
if self.filters.show_sales_person:
- self.add_column(label=_('Sales Person'), fieldname='sales_person', fieldtype='Data')
+ self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
else:
- self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
- options='Supplier Group')
+ self.add_column(
+ label=_("Supplier Group"),
+ fieldname="supplier_group",
+ fieldtype="Link",
+ options="Supplier Group",
+ )
- self.add_column(label=_('Currency'), fieldname='currency', fieldtype='Link',
- options='Currency', width=80)
+ self.add_column(
+ label=_("Currency"), fieldname="currency", fieldtype="Link", options="Currency", width=80
+ )
def setup_ageing_columns(self):
- for i, label in enumerate(["0-{range1}".format(range1=self.filters["range1"]),
- "{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
- "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
- "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
- "{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
- self.add_column(label=label, fieldname='range' + str(i+1))
+ for i, label in enumerate(
+ [
+ "0-{range1}".format(range1=self.filters["range1"]),
+ "{range1}-{range2}".format(
+ range1=cint(self.filters["range1"]) + 1, range2=self.filters["range2"]
+ ),
+ "{range2}-{range3}".format(
+ range2=cint(self.filters["range2"]) + 1, range3=self.filters["range3"]
+ ),
+ "{range3}-{range4}".format(
+ range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"]
+ ),
+ "{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")),
+ ]
+ ):
+ self.add_column(label=label, fieldname="range" + str(i + 1))
# Add column for total due amount
- self.add_column(label="Total Amount Due", fieldname='total_due')
+ self.add_column(label="Total Amount Due", fieldname="total_due")
+
def get_gl_balance(report_date):
- return frappe._dict(frappe.db.get_all("GL Entry", fields=['party', 'sum(debit - credit)'],
- filters={'posting_date': ("<=", report_date), 'is_cancelled': 0}, group_by='party', as_list=1))
+ return frappe._dict(
+ frappe.db.get_all(
+ "GL Entry",
+ fields=["party", "sum(debit - credit)"],
+ filters={"posting_date": ("<=", report_date), "is_cancelled": 0},
+ group_by="party",
+ as_list=1,
+ )
+ )
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index 98f5b74eaa..57d80492ae 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -11,34 +11,44 @@ def execute(filters=None):
columns, data = get_columns(), get_data(filters)
return columns, data
+
def get_data(filters):
data = []
- depreciation_accounts = frappe.db.sql_list(""" select name from tabAccount
- where ifnull(account_type, '') = 'Depreciation' """)
+ depreciation_accounts = frappe.db.sql_list(
+ """ select name from tabAccount
+ where ifnull(account_type, '') = 'Depreciation' """
+ )
- filters_data = [["company", "=", filters.get('company')],
- ["posting_date", ">=", filters.get('from_date')],
- ["posting_date", "<=", filters.get('to_date')],
+ filters_data = [
+ ["company", "=", filters.get("company")],
+ ["posting_date", ">=", filters.get("from_date")],
+ ["posting_date", "<=", filters.get("to_date")],
["against_voucher_type", "=", "Asset"],
- ["account", "in", depreciation_accounts]]
+ ["account", "in", depreciation_accounts],
+ ]
if filters.get("asset"):
filters_data.append(["against_voucher", "=", filters.get("asset")])
if filters.get("asset_category"):
- assets = frappe.db.sql_list("""select name from tabAsset
- where asset_category = %s and docstatus=1""", filters.get("asset_category"))
+ assets = frappe.db.sql_list(
+ """select name from tabAsset
+ where asset_category = %s and docstatus=1""",
+ filters.get("asset_category"),
+ )
filters_data.append(["against_voucher", "in", assets])
if filters.get("finance_book"):
- filters_data.append(["finance_book", "in", ['', filters.get('finance_book')]])
+ filters_data.append(["finance_book", "in", ["", filters.get("finance_book")]])
- gl_entries = frappe.get_all('GL Entry',
- filters= filters_data,
- fields = ["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"],
- order_by= "against_voucher, posting_date")
+ gl_entries = frappe.get_all(
+ "GL Entry",
+ filters=filters_data,
+ fields=["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"],
+ order_by="against_voucher, posting_date",
+ )
if not gl_entries:
return data
@@ -55,29 +65,40 @@ def get_data(filters):
asset_data.accumulated_depreciation_amount += d.debit
row = frappe._dict(asset_data)
- row.update({
- "depreciation_amount": d.debit,
- "depreciation_date": d.posting_date,
- "amount_after_depreciation": (flt(row.gross_purchase_amount) -
- flt(row.accumulated_depreciation_amount)),
- "depreciation_entry": d.voucher_no
- })
+ row.update(
+ {
+ "depreciation_amount": d.debit,
+ "depreciation_date": d.posting_date,
+ "amount_after_depreciation": (
+ flt(row.gross_purchase_amount) - flt(row.accumulated_depreciation_amount)
+ ),
+ "depreciation_entry": d.voucher_no,
+ }
+ )
data.append(row)
return data
+
def get_assets_details(assets):
assets_details = {}
- fields = ["name as asset", "gross_purchase_amount",
- "asset_category", "status", "depreciation_method", "purchase_date"]
+ fields = [
+ "name as asset",
+ "gross_purchase_amount",
+ "asset_category",
+ "status",
+ "depreciation_method",
+ "purchase_date",
+ ]
- for d in frappe.get_all("Asset", fields = fields, filters = {'name': ('in', assets)}):
+ for d in frappe.get_all("Asset", fields=fields, filters={"name": ("in", assets)}):
assets_details.setdefault(d.asset, d)
return assets_details
+
def get_columns():
return [
{
@@ -85,68 +106,58 @@ def get_columns():
"fieldname": "asset",
"fieldtype": "Link",
"options": "Asset",
- "width": 120
+ "width": 120,
},
{
"label": _("Depreciation Date"),
"fieldname": "depreciation_date",
"fieldtype": "Date",
- "width": 120
+ "width": 120,
},
{
"label": _("Purchase Amount"),
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
- "width": 120
+ "width": 120,
},
{
"label": _("Depreciation Amount"),
"fieldname": "depreciation_amount",
"fieldtype": "Currency",
- "width": 140
+ "width": 140,
},
{
"label": _("Accumulated Depreciation Amount"),
"fieldname": "accumulated_depreciation_amount",
"fieldtype": "Currency",
- "width": 210
+ "width": 210,
},
{
"label": _("Amount After Depreciation"),
"fieldname": "amount_after_depreciation",
"fieldtype": "Currency",
- "width": 180
+ "width": 180,
},
{
"label": _("Depreciation Entry"),
"fieldname": "depreciation_entry",
"fieldtype": "Link",
"options": "Journal Entry",
- "width": 140
+ "width": 140,
},
{
"label": _("Asset Category"),
"fieldname": "asset_category",
"fieldtype": "Link",
"options": "Asset Category",
- "width": 120
- },
- {
- "label": _("Current Status"),
- "fieldname": "status",
- "fieldtype": "Data",
- "width": 120
+ "width": 120,
},
+ {"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
{
"label": _("Depreciation Method"),
"fieldname": "depreciation_method",
"fieldtype": "Data",
- "width": 130
+ "width": 130,
},
- {
- "label": _("Purchase Date"),
- "fieldname": "purchase_date",
- "fieldtype": "Date",
- "width": 120
- }
+ {"label": _("Purchase Date"), "fieldname": "purchase_date", "fieldtype": "Date", "width": 120},
]
diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
index 0f9435f4a5..ad9b1ba58e 100644
--- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
+++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
@@ -24,18 +24,33 @@ def get_data(filters):
# row.asset_category = asset_category
row.update(asset_category)
- row.cost_as_on_to_date = (flt(row.cost_as_on_from_date) + flt(row.cost_of_new_purchase) -
- flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset))
+ row.cost_as_on_to_date = (
+ flt(row.cost_as_on_from_date)
+ + flt(row.cost_of_new_purchase)
+ - flt(row.cost_of_sold_asset)
+ - flt(row.cost_of_scrapped_asset)
+ )
- row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", "")))
- row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) +
- flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated_during_the_period))
+ row.update(
+ next(
+ asset
+ for asset in assets
+ if asset["asset_category"] == asset_category.get("asset_category", "")
+ )
+ )
+ row.accumulated_depreciation_as_on_to_date = (
+ flt(row.accumulated_depreciation_as_on_from_date)
+ + flt(row.depreciation_amount_during_the_period)
+ - flt(row.depreciation_eliminated_during_the_period)
+ )
- row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) -
- flt(row.accumulated_depreciation_as_on_from_date))
+ row.net_asset_value_as_on_from_date = flt(row.cost_as_on_from_date) - flt(
+ row.accumulated_depreciation_as_on_from_date
+ )
- row.net_asset_value_as_on_to_date = (flt(row.cost_as_on_to_date) -
- flt(row.accumulated_depreciation_as_on_to_date))
+ row.net_asset_value_as_on_to_date = flt(row.cost_as_on_to_date) - flt(
+ row.accumulated_depreciation_as_on_to_date
+ )
data.append(row)
@@ -43,7 +58,8 @@ def get_data(filters):
def get_asset_categories(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT asset_category,
ifnull(sum(case when purchase_date < %(from_date)s then
case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then
@@ -84,10 +100,15 @@ def get_asset_categories(filters):
from `tabAsset`
where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s
group by asset_category
- """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1)
+ """,
+ {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company},
+ as_dict=1,
+ )
+
def get_assets(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT results.asset_category,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
@@ -130,7 +151,10 @@ def get_assets(filters):
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s
group by a.asset_category) as results
group by results.asset_category
- """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1)
+ """,
+ {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company},
+ as_dict=1,
+ )
def get_columns(filters):
@@ -140,72 +164,72 @@ def get_columns(filters):
"fieldname": "asset_category",
"fieldtype": "Link",
"options": "Asset Category",
- "width": 120
+ "width": 120,
},
{
"label": _("Cost as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "cost_as_on_from_date",
"fieldtype": "Currency",
- "width": 140
+ "width": 140,
},
{
"label": _("Cost of New Purchase"),
"fieldname": "cost_of_new_purchase",
"fieldtype": "Currency",
- "width": 140
+ "width": 140,
},
{
"label": _("Cost of Sold Asset"),
"fieldname": "cost_of_sold_asset",
"fieldtype": "Currency",
- "width": 140
+ "width": 140,
},
{
"label": _("Cost of Scrapped Asset"),
"fieldname": "cost_of_scrapped_asset",
"fieldtype": "Currency",
- "width": 140
+ "width": 140,
},
{
"label": _("Cost as on") + " " + formatdate(filters.to_date),
"fieldname": "cost_as_on_to_date",
"fieldtype": "Currency",
- "width": 140
+ "width": 140,
},
{
"label": _("Accumulated Depreciation as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "accumulated_depreciation_as_on_from_date",
"fieldtype": "Currency",
- "width": 270
+ "width": 270,
},
{
"label": _("Depreciation Amount during the period"),
"fieldname": "depreciation_amount_during_the_period",
"fieldtype": "Currency",
- "width": 240
+ "width": 240,
},
{
"label": _("Depreciation Eliminated due to disposal of assets"),
"fieldname": "depreciation_eliminated_during_the_period",
"fieldtype": "Currency",
- "width": 300
+ "width": 300,
},
{
"label": _("Accumulated Depreciation as on") + " " + formatdate(filters.to_date),
"fieldname": "accumulated_depreciation_as_on_to_date",
"fieldtype": "Currency",
- "width": 270
+ "width": 270,
},
{
"label": _("Net Asset value as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "net_asset_value_as_on_from_date",
"fieldtype": "Currency",
- "width": 200
+ "width": 200,
},
{
"label": _("Net Asset value as on") + " " + formatdate(filters.to_date),
"fieldname": "net_asset_value_as_on_to_date",
"fieldtype": "Currency",
- "width": 200
- }
+ "width": 200,
+ },
]
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py
index f10a5eab10..7b1e979326 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.py
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py
@@ -15,26 +15,53 @@ from erpnext.accounts.report.financial_statements import (
def execute(filters=None):
- period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
- filters.period_start_date, filters.period_end_date, filters.filter_based_on,
- filters.periodicity, company=filters.company)
+ period_list = get_period_list(
+ filters.from_fiscal_year,
+ filters.to_fiscal_year,
+ filters.period_start_date,
+ filters.period_end_date,
+ filters.filter_based_on,
+ filters.periodicity,
+ company=filters.company,
+ )
- currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
+ currency = filters.presentation_currency or frappe.get_cached_value(
+ "Company", filters.company, "default_currency"
+ )
- asset = get_data(filters.company, "Asset", "Debit", period_list,
- only_current_fiscal_year=False, filters=filters,
- accumulated_values=filters.accumulated_values)
+ asset = get_data(
+ filters.company,
+ "Asset",
+ "Debit",
+ period_list,
+ only_current_fiscal_year=False,
+ filters=filters,
+ accumulated_values=filters.accumulated_values,
+ )
- liability = get_data(filters.company, "Liability", "Credit", period_list,
- only_current_fiscal_year=False, filters=filters,
- accumulated_values=filters.accumulated_values)
+ liability = get_data(
+ filters.company,
+ "Liability",
+ "Credit",
+ period_list,
+ only_current_fiscal_year=False,
+ filters=filters,
+ accumulated_values=filters.accumulated_values,
+ )
- equity = get_data(filters.company, "Equity", "Credit", period_list,
- only_current_fiscal_year=False, filters=filters,
- accumulated_values=filters.accumulated_values)
+ equity = get_data(
+ filters.company,
+ "Equity",
+ "Credit",
+ period_list,
+ only_current_fiscal_year=False,
+ filters=filters,
+ accumulated_values=filters.accumulated_values,
+ )
- provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity,
- period_list, filters.company, currency)
+ provisional_profit_loss, total_credit = get_provisional_profit_loss(
+ asset, liability, equity, period_list, filters.company, currency
+ )
message, opening_balance = check_opening_balance(asset, liability, equity)
@@ -42,19 +69,19 @@ def execute(filters=None):
data.extend(asset or [])
data.extend(liability or [])
data.extend(equity or [])
- if opening_balance and round(opening_balance,2) !=0:
- unclosed ={
+ if opening_balance and round(opening_balance, 2) != 0:
+ unclosed = {
"account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"warn_if_negative": True,
- "currency": currency
+ "currency": currency,
}
for period in period_list:
unclosed[period.key] = opening_balance
if provisional_profit_loss:
provisional_profit_loss[period.key] = provisional_profit_loss[period.key] - opening_balance
- unclosed["total"]=opening_balance
+ unclosed["total"] = opening_balance
data.append(unclosed)
if provisional_profit_loss:
@@ -62,26 +89,32 @@ def execute(filters=None):
if total_credit:
data.append(total_credit)
- columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, company=filters.company)
+ columns = get_columns(
+ filters.periodicity, period_list, filters.accumulated_values, company=filters.company
+ )
chart = get_chart_data(filters, columns, asset, liability, equity)
- report_summary = get_report_summary(period_list, asset, liability, equity, provisional_profit_loss,
- total_credit, currency, filters)
+ report_summary = get_report_summary(
+ period_list, asset, liability, equity, provisional_profit_loss, total_credit, currency, filters
+ )
return columns, data, message, chart, report_summary
-def get_provisional_profit_loss(asset, liability, equity, period_list, company, currency=None, consolidated=False):
+
+def get_provisional_profit_loss(
+ asset, liability, equity, period_list, company, currency=None, consolidated=False
+):
provisional_profit_loss = {}
total_row = {}
if asset and (liability or equity):
- total = total_row_total=0
- currency = currency or frappe.get_cached_value('Company', company, "default_currency")
+ total = total_row_total = 0
+ currency = currency or frappe.get_cached_value("Company", company, "default_currency")
total_row = {
"account_name": "'" + _("Total (Credit)") + "'",
"account": "'" + _("Total (Credit)") + "'",
"warn_if_negative": True,
- "currency": currency
+ "currency": currency,
}
has_value = False
@@ -106,15 +139,18 @@ def get_provisional_profit_loss(asset, liability, equity, period_list, company,
total_row["total"] = total_row_total
if has_value:
- provisional_profit_loss.update({
- "account_name": "'" + _("Provisional Profit / Loss (Credit)") + "'",
- "account": "'" + _("Provisional Profit / Loss (Credit)") + "'",
- "warn_if_negative": True,
- "currency": currency
- })
+ provisional_profit_loss.update(
+ {
+ "account_name": "'" + _("Provisional Profit / Loss (Credit)") + "'",
+ "account": "'" + _("Provisional Profit / Loss (Credit)") + "'",
+ "warn_if_negative": True,
+ "currency": currency,
+ }
+ )
return provisional_profit_loss, total_row
+
def check_opening_balance(asset, liability, equity):
# Check if previous year balance sheet closed
opening_balance = 0
@@ -128,19 +164,29 @@ def check_opening_balance(asset, liability, equity):
opening_balance = flt(opening_balance, float_precision)
if opening_balance:
- return _("Previous Financial Year is not closed"),opening_balance
- return None,None
+ return _("Previous Financial Year is not closed"), opening_balance
+ return None, None
-def get_report_summary(period_list, asset, liability, equity, provisional_profit_loss, total_credit, currency,
- filters, consolidated=False):
+
+def get_report_summary(
+ period_list,
+ asset,
+ liability,
+ equity,
+ provisional_profit_loss,
+ total_credit,
+ currency,
+ filters,
+ consolidated=False,
+):
net_asset, net_liability, net_equity, net_provisional_profit_loss = 0.0, 0.0, 0.0, 0.0
- if filters.get('accumulated_values'):
+ if filters.get("accumulated_values"):
period_list = [period_list[-1]]
# from consolidated financial statement
- if filters.get('accumulated_in_group_company'):
+ if filters.get("accumulated_in_group_company"):
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
for period in period_list:
@@ -155,33 +201,24 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit
net_provisional_profit_loss += provisional_profit_loss.get(key)
return [
- {
- "value": net_asset,
- "label": "Total Asset",
- "datatype": "Currency",
- "currency": currency
- },
+ {"value": net_asset, "label": "Total Asset", "datatype": "Currency", "currency": currency},
{
"value": net_liability,
"label": "Total Liability",
"datatype": "Currency",
- "currency": currency
- },
- {
- "value": net_equity,
- "label": "Total Equity",
- "datatype": "Currency",
- "currency": currency
+ "currency": currency,
},
+ {"value": net_equity, "label": "Total Equity", "datatype": "Currency", "currency": currency},
{
"value": net_provisional_profit_loss,
"label": "Provisional Profit / Loss (Credit)",
"indicator": "Green" if net_provisional_profit_loss > 0 else "Red",
"datatype": "Currency",
- "currency": currency
- }
+ "currency": currency,
+ },
]
+
def get_chart_data(filters, columns, asset, liability, equity):
labels = [d.get("label") for d in columns[2:]]
@@ -197,18 +234,13 @@ def get_chart_data(filters, columns, asset, liability, equity):
datasets = []
if asset_data:
- datasets.append({'name': _('Assets'), 'values': asset_data})
+ datasets.append({"name": _("Assets"), "values": asset_data})
if liability_data:
- datasets.append({'name': _('Liabilities'), 'values': liability_data})
+ datasets.append({"name": _("Liabilities"), "values": liability_data})
if equity_data:
- datasets.append({'name': _('Equity'), 'values': equity_data})
+ datasets.append({"name": _("Equity"), "values": equity_data})
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- }
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}}
if not filters.accumulated_values:
chart["type"] = "bar"
diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
index b456e89f34..20f7643a1c 100644
--- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
+++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
@@ -8,86 +8,88 @@ from frappe.utils import getdate, nowdate
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns()
data = get_entries(filters)
return columns, data
+
def get_columns():
- columns = [{
+ columns = [
+ {
"label": _("Payment Document Type"),
"fieldname": "payment_document_type",
"fieldtype": "Link",
"options": "Doctype",
- "width": 130
+ "width": 130,
},
{
"label": _("Payment Entry"),
"fieldname": "payment_entry",
"fieldtype": "Dynamic Link",
"options": "payment_document_type",
- "width": 140
- },
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 100
- },
- {
- "label": _("Cheque/Reference No"),
- "fieldname": "cheque_no",
- "width": 120
- },
- {
- "label": _("Clearance Date"),
- "fieldname": "clearance_date",
- "fieldtype": "Date",
- "width": 100
+ "width": 140,
},
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
+ {"label": _("Cheque/Reference No"), "fieldname": "cheque_no", "width": 120},
+ {"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 100},
{
"label": _("Against Account"),
"fieldname": "against",
"fieldtype": "Link",
"options": "Account",
- "width": 170
+ "width": 170,
},
- {
- "label": _("Amount"),
- "fieldname": "amount",
- "width": 120
- }]
+ {"label": _("Amount"), "fieldname": "amount", "width": 120},
+ ]
return columns
+
def get_conditions(filters):
conditions = ""
- if filters.get("from_date"): conditions += " and posting_date>=%(from_date)s"
- if filters.get("to_date"): conditions += " and posting_date<=%(to_date)s"
+ if filters.get("from_date"):
+ conditions += " and posting_date>=%(from_date)s"
+ if filters.get("to_date"):
+ conditions += " and posting_date<=%(to_date)s"
return conditions
+
def get_entries(filters):
conditions = get_conditions(filters)
- journal_entries = frappe.db.sql("""SELECT
+ journal_entries = frappe.db.sql(
+ """SELECT
"Journal Entry", jv.name, jv.posting_date, jv.cheque_no,
jv.clearance_date, jvd.against_account, jvd.debit - jvd.credit
FROM
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
WHERE
jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {0}
- order by posting_date DESC, jv.name DESC""".format(conditions), filters, as_list=1)
+ order by posting_date DESC, jv.name DESC""".format(
+ conditions
+ ),
+ filters,
+ as_list=1,
+ )
- payment_entries = frappe.db.sql("""SELECT
+ payment_entries = frappe.db.sql(
+ """SELECT
"Payment Entry", name, posting_date, reference_no, clearance_date, party,
if(paid_from=%(account)s, paid_amount * -1, received_amount)
FROM
`tabPayment Entry`
WHERE
docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {0}
- order by posting_date DESC, name DESC""".format(conditions), filters, as_list=1)
+ order by posting_date DESC, name DESC""".format(
+ conditions
+ ),
+ filters,
+ as_list=1,
+ )
return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate()))
diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
index fe644ed356..2ac1fea5af 100644
--- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
+++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
@@ -13,11 +13,13 @@ from erpnext.accounts.utils import get_balance_on
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns()
- if not filters.get("account"): return columns, []
+ if not filters.get("account"):
+ return columns, []
account_currency = frappe.db.get_value("Account", filters.account, "account_currency")
@@ -25,102 +27,91 @@ def execute(filters=None):
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
- total_debit, total_credit = 0,0
+ total_debit, total_credit = 0, 0
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
- bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \
+ bank_bal = (
+ flt(balance_as_per_system)
+ - flt(total_debit)
+ + flt(total_credit)
+ amounts_not_reflected_in_system
+ )
data += [
- get_balance_row(_("Bank Statement balance as per General Ledger"), balance_as_per_system, account_currency),
+ get_balance_row(
+ _("Bank Statement balance as per General Ledger"), balance_as_per_system, account_currency
+ ),
{},
{
"payment_entry": _("Outstanding Cheques and Deposits to clear"),
"debit": total_debit,
"credit": total_credit,
- "account_currency": account_currency
+ "account_currency": account_currency,
},
- get_balance_row(_("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system,
- account_currency),
+ get_balance_row(
+ _("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system, account_currency
+ ),
{},
- get_balance_row(_("Calculated Bank Statement balance"), bank_bal, account_currency)
+ get_balance_row(_("Calculated Bank Statement balance"), bank_bal, account_currency),
]
return columns, data
+
def get_columns():
return [
- {
- "fieldname": "posting_date",
- "label": _("Posting Date"),
- "fieldtype": "Date",
- "width": 90
- },
+ {"fieldname": "posting_date", "label": _("Posting Date"), "fieldtype": "Date", "width": 90},
{
"fieldname": "payment_document",
"label": _("Payment Document Type"),
"fieldtype": "Data",
- "width": 220
+ "width": 220,
},
{
"fieldname": "payment_entry",
"label": _("Payment Document"),
"fieldtype": "Dynamic Link",
"options": "payment_document",
- "width": 220
+ "width": 220,
},
{
"fieldname": "debit",
"label": _("Debit"),
"fieldtype": "Currency",
"options": "account_currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "credit",
"label": _("Credit"),
"fieldtype": "Currency",
"options": "account_currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "against_account",
"label": _("Against Account"),
"fieldtype": "Link",
"options": "Account",
- "width": 200
- },
- {
- "fieldname": "reference_no",
- "label": _("Reference"),
- "fieldtype": "Data",
- "width": 100
- },
- {
- "fieldname": "ref_date",
- "label": _("Ref Date"),
- "fieldtype": "Date",
- "width": 110
- },
- {
- "fieldname": "clearance_date",
- "label": _("Clearance Date"),
- "fieldtype": "Date",
- "width": 110
+ "width": 200,
},
+ {"fieldname": "reference_no", "label": _("Reference"), "fieldtype": "Data", "width": 100},
+ {"fieldname": "ref_date", "label": _("Ref Date"), "fieldtype": "Date", "width": 110},
+ {"fieldname": "clearance_date", "label": _("Clearance Date"), "fieldtype": "Date", "width": 110},
{
"fieldname": "account_currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
- "width": 100
- }
+ "width": 100,
+ },
]
+
def get_entries(filters):
journal_entries = get_journal_entries(filters)
@@ -132,11 +123,15 @@ def get_entries(filters):
if filters.include_pos_transactions:
pos_entries = get_pos_entries(filters)
- return sorted(list(payment_entries)+list(journal_entries+list(pos_entries) + list(loan_entries)),
- key=lambda k: getdate(k['posting_date']))
+ return sorted(
+ list(payment_entries) + list(journal_entries + list(pos_entries) + list(loan_entries)),
+ key=lambda k: getdate(k["posting_date"]),
+ )
+
def get_journal_entries(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select "Journal Entry" as payment_document, jv.posting_date,
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
jvd.credit_in_account_currency as credit, jvd.against_account,
@@ -146,10 +141,15 @@ def get_journal_entries(filters):
where jvd.parent = jv.name and jv.docstatus=1
and jvd.account = %(account)s and jv.posting_date <= %(report_date)s
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
- and ifnull(jv.is_opening, 'No') = 'No'""", filters, as_dict=1)
+ and ifnull(jv.is_opening, 'No') = 'No'""",
+ filters,
+ as_dict=1,
+ )
+
def get_payment_entries(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date,
@@ -162,10 +162,15 @@ def get_payment_entries(filters):
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date <= %(report_date)s
and ifnull(clearance_date, '4000-01-01') > %(report_date)s
- """, filters, as_dict=1)
+ """,
+ filters,
+ as_dict=1,
+ )
+
def get_pos_entries(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.debit_to as against_account, sip.clearance_date,
@@ -177,13 +182,17 @@ def get_pos_entries(filters):
ifnull(sip.clearance_date, '4000-01-01') > %(report_date)s
order by
si.posting_date ASC, si.name DESC
- """, filters, as_dict=1)
+ """,
+ filters,
+ as_dict=1,
+ )
+
def get_loan_entries(filters):
loan_docs = []
for doctype in ["Loan Disbursement", "Loan Repayment"]:
loan_doc = frappe.qb.DocType(doctype)
- ifnull = CustomFunction('IFNULL', ['value', 'default'])
+ ifnull = CustomFunction("IFNULL", ["value", "default"])
if doctype == "Loan Disbursement":
amount_field = (loan_doc.disbursed_amount).as_("credit")
@@ -194,22 +203,22 @@ def get_loan_entries(filters):
posting_date = (loan_doc.posting_date).as_("posting_date")
account = loan_doc.payment_account
- entries = frappe.qb.from_(loan_doc).select(
- ConstantColumn(doctype).as_("payment_document"),
- (loan_doc.name).as_("payment_entry"),
- (loan_doc.reference_number).as_("reference_no"),
- (loan_doc.reference_date).as_("ref_date"),
- amount_field,
- posting_date,
- ).where(
- loan_doc.docstatus == 1
- ).where(
- account == filters.get('account')
- ).where(
- posting_date <= getdate(filters.get('report_date'))
- ).where(
- ifnull(loan_doc.clearance_date, '4000-01-01') > getdate(filters.get('report_date'))
- ).run(as_dict=1)
+ entries = (
+ frappe.qb.from_(loan_doc)
+ .select(
+ ConstantColumn(doctype).as_("payment_document"),
+ (loan_doc.name).as_("payment_entry"),
+ (loan_doc.reference_number).as_("reference_no"),
+ (loan_doc.reference_date).as_("ref_date"),
+ amount_field,
+ posting_date,
+ )
+ .where(loan_doc.docstatus == 1)
+ .where(account == filters.get("account"))
+ .where(posting_date <= getdate(filters.get("report_date")))
+ .where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date")))
+ .run(as_dict=1)
+ )
loan_docs.extend(entries)
@@ -217,20 +226,26 @@ def get_loan_entries(filters):
def get_amounts_not_reflected_in_system(filters):
- je_amount = frappe.db.sql("""
+ je_amount = frappe.db.sql(
+ """
select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency)
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
- and ifnull(jv.is_opening, 'No') = 'No' """, filters)
+ and ifnull(jv.is_opening, 'No') = 'No' """,
+ filters,
+ )
je_amount = flt(je_amount[0][0]) if je_amount else 0.0
- pe_amount = frappe.db.sql("""
+ pe_amount = frappe.db.sql(
+ """
select sum(if(paid_from=%(account)s, paid_amount, received_amount))
from `tabPayment Entry`
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
- and posting_date > %(report_date)s and clearance_date <= %(report_date)s""", filters)
+ and posting_date > %(report_date)s and clearance_date <= %(report_date)s""",
+ filters,
+ )
pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
@@ -238,11 +253,12 @@ def get_amounts_not_reflected_in_system(filters):
return je_amount + pe_amount + loan_amount
+
def get_loan_amount(filters):
total_amount = 0
for doctype in ["Loan Disbursement", "Loan Repayment"]:
loan_doc = frappe.qb.DocType(doctype)
- ifnull = CustomFunction('IFNULL', ['value', 'default'])
+ ifnull = CustomFunction("IFNULL", ["value", "default"])
if doctype == "Loan Disbursement":
amount_field = Sum(loan_doc.disbursed_amount)
@@ -253,34 +269,33 @@ def get_loan_amount(filters):
posting_date = (loan_doc.posting_date).as_("posting_date")
account = loan_doc.payment_account
- amount = frappe.qb.from_(loan_doc).select(
- amount_field
- ).where(
- loan_doc.docstatus == 1
- ).where(
- account == filters.get('account')
- ).where(
- posting_date > getdate(filters.get('report_date'))
- ).where(
- ifnull(loan_doc.clearance_date, '4000-01-01') <= getdate(filters.get('report_date'))
- ).run()[0][0]
+ amount = (
+ frappe.qb.from_(loan_doc)
+ .select(amount_field)
+ .where(loan_doc.docstatus == 1)
+ .where(account == filters.get("account"))
+ .where(posting_date > getdate(filters.get("report_date")))
+ .where(ifnull(loan_doc.clearance_date, "4000-01-01") <= getdate(filters.get("report_date")))
+ .run()[0][0]
+ )
total_amount += flt(amount)
return total_amount
+
def get_balance_row(label, amount, account_currency):
if amount > 0:
return {
"payment_entry": label,
"debit": amount,
"credit": 0,
- "account_currency": account_currency
+ "account_currency": account_currency,
}
else:
return {
"payment_entry": label,
"debit": 0,
"credit": abs(amount),
- "account_currency": account_currency
+ "account_currency": account_currency,
}
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
index 1d7463c892..62bee82590 100644
--- a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
@@ -12,97 +12,70 @@ def execute(filters=None):
return columns, data
+
def get_data(report_filters):
filters = get_report_filters(report_filters)
fields = get_report_fields()
- return frappe.get_all('Purchase Invoice',
- fields= fields, filters=filters)
+ return frappe.get_all("Purchase Invoice", fields=fields, filters=filters)
+
def get_report_filters(report_filters):
- filters = [['Purchase Invoice','company','=',report_filters.get('company')],
- ['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
- ['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
+ filters = [
+ ["Purchase Invoice", "company", "=", report_filters.get("company")],
+ ["Purchase Invoice", "posting_date", "<=", report_filters.get("posting_date")],
+ ["Purchase Invoice", "docstatus", "=", 1],
+ ["Purchase Invoice", "per_received", "<", 100],
+ ["Purchase Invoice", "update_stock", "=", 0],
+ ]
- if report_filters.get('purchase_invoice'):
- filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
+ if report_filters.get("purchase_invoice"):
+ filters.append(
+ ["Purchase Invoice", "per_received", "in", [report_filters.get("purchase_invoice")]]
+ )
return filters
+
def get_report_fields():
fields = []
- for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
- fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
+ for p_field in ["name", "supplier", "company", "posting_date", "currency"]:
+ fields.append("`tabPurchase Invoice`.`{}`".format(p_field))
- for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
- fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
+ for c_field in ["item_code", "item_name", "uom", "qty", "received_qty", "rate", "amount"]:
+ fields.append("`tabPurchase Invoice Item`.`{}`".format(c_field))
return fields
+
def get_columns():
return [
{
- 'label': _('Purchase Invoice'),
- 'fieldname': 'name',
- 'fieldtype': 'Link',
- 'options': 'Purchase Invoice',
- 'width': 170
+ "label": _("Purchase Invoice"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Purchase Invoice",
+ "width": 170,
},
{
- 'label': _('Supplier'),
- 'fieldname': 'supplier',
- 'fieldtype': 'Link',
- 'options': 'Supplier',
- 'width': 120
+ "label": _("Supplier"),
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "width": 120,
},
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
{
- 'label': _('Posting Date'),
- 'fieldname': 'posting_date',
- 'fieldtype': 'Date',
- 'width': 100
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
},
- {
- 'label': _('Item Code'),
- 'fieldname': 'item_code',
- 'fieldtype': 'Link',
- 'options': 'Item',
- 'width': 100
- },
- {
- 'label': _('Item Name'),
- 'fieldname': 'item_name',
- 'fieldtype': 'Data',
- 'width': 100
- },
- {
- 'label': _('UOM'),
- 'fieldname': 'uom',
- 'fieldtype': 'Link',
- 'options': 'UOM',
- 'width': 100
- },
- {
- 'label': _('Invoiced Qty'),
- 'fieldname': 'qty',
- 'fieldtype': 'Float',
- 'width': 100
- },
- {
- 'label': _('Received Qty'),
- 'fieldname': 'received_qty',
- 'fieldtype': 'Float',
- 'width': 100
- },
- {
- 'label': _('Rate'),
- 'fieldname': 'rate',
- 'fieldtype': 'Currency',
- 'width': 100
- },
- {
- 'label': _('Amount'),
- 'fieldname': 'amount',
- 'fieldtype': 'Currency',
- 'width': 100
- }
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+ {"label": _("UOM"), "fieldname": "uom", "fieldtype": "Link", "options": "UOM", "width": 100},
+ {"label": _("Invoiced Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 100},
+ {"label": _("Received Qty"), "fieldname": "received_qty", "fieldtype": "Float", "width": 100},
+ {"label": _("Rate"), "fieldname": "rate", "fieldtype": "Currency", "width": 100},
+ {"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 100},
]
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 56ee5008cf..ca341f4993 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -34,6 +34,7 @@ def execute(filters=None):
return columns, data, None, chart
+
def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation):
for account, monthwise_data in dimension_items.items():
row = [dimension, account]
@@ -53,16 +54,16 @@ def get_final_data(dimension, dimension_items, filters, period_month_ranges, dat
period_data[0] += last_total
if DCC_allocation:
- period_data[0] = period_data[0]*(DCC_allocation/100)
- period_data[1] = period_data[1]*(DCC_allocation/100)
+ period_data[0] = period_data[0] * (DCC_allocation / 100)
+ period_data[1] = period_data[1] * (DCC_allocation / 100)
- if(filters.get("show_cumulative")):
+ if filters.get("show_cumulative"):
last_total = period_data[0] - period_data[1]
period_data[2] = period_data[0] - period_data[1]
row += period_data
totals[2] = totals[0] - totals[1]
- if filters["period"] != "Yearly" :
+ if filters["period"] != "Yearly":
row += totals
data.append(row)
@@ -72,19 +73,19 @@ def get_final_data(dimension, dimension_items, filters, period_month_ranges, dat
def get_columns(filters):
columns = [
{
- 'label': _(filters.get("budget_against")),
- 'fieldtype': 'Link',
- 'fieldname': 'budget_against',
- 'options': filters.get('budget_against'),
- 'width': 150
+ "label": _(filters.get("budget_against")),
+ "fieldtype": "Link",
+ "fieldname": "budget_against",
+ "options": filters.get("budget_against"),
+ "width": 150,
},
{
- 'label': _('Account'),
- 'fieldname': 'Account',
- 'fieldtype': 'Link',
- 'options': 'Account',
- 'width': 150
- }
+ "label": _("Account"),
+ "fieldname": "Account",
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 150,
+ },
]
group_months = False if filters["period"] == "Monthly" else True
@@ -97,45 +98,34 @@ def get_columns(filters):
labels = [
_("Budget") + " " + str(year[0]),
_("Actual ") + " " + str(year[0]),
- _("Variance ") + " " + str(year[0])
+ _("Variance ") + " " + str(year[0]),
]
for label in labels:
- columns.append({
- 'label': label,
- 'fieldtype': 'Float',
- 'fieldname': frappe.scrub(label),
- 'width': 150
- })
+ columns.append(
+ {"label": label, "fieldtype": "Float", "fieldname": frappe.scrub(label), "width": 150}
+ )
else:
for label in [
_("Budget") + " (%s)" + " " + str(year[0]),
_("Actual") + " (%s)" + " " + str(year[0]),
- _("Variance") + " (%s)" + " " + str(year[0])
+ _("Variance") + " (%s)" + " " + str(year[0]),
]:
if group_months:
label = label % (
- formatdate(from_date, format_string="MMM")
- + "-"
- + formatdate(to_date, format_string="MMM")
+ formatdate(from_date, format_string="MMM") + "-" + formatdate(to_date, format_string="MMM")
)
else:
label = label % formatdate(from_date, format_string="MMM")
- columns.append({
- 'label': label,
- 'fieldtype': 'Float',
- 'fieldname': frappe.scrub(label),
- 'width': 150
- })
+ columns.append(
+ {"label": label, "fieldtype": "Float", "fieldname": frappe.scrub(label), "width": 150}
+ )
if filters["period"] != "Yearly":
for label in [_("Total Budget"), _("Total Actual"), _("Total Variance")]:
- columns.append({
- 'label': label,
- 'fieldtype': 'Float',
- 'fieldname': frappe.scrub(label),
- 'width': 150
- })
+ columns.append(
+ {"label": label, "fieldtype": "Float", "fieldname": frappe.scrub(label), "width": 150}
+ )
return columns
else:
@@ -157,8 +147,11 @@ def get_cost_centers(filters):
where
company = %s
{order_by}
- """.format(tab=filters.get("budget_against"), order_by=order_by),
- filters.get("company"))
+ """.format(
+ tab=filters.get("budget_against"), order_by=order_by
+ ),
+ filters.get("company"),
+ )
else:
return frappe.db.sql_list(
"""
@@ -166,7 +159,10 @@ def get_cost_centers(filters):
name
from
`tab{tab}`
- """.format(tab=filters.get("budget_against"))) # nosec
+ """.format(
+ tab=filters.get("budget_against")
+ )
+ ) # nosec
# Get dimension & target details
@@ -174,8 +170,9 @@ def get_dimension_target_details(filters):
budget_against = frappe.scrub(filters.get("budget_against"))
cond = ""
if filters.get("budget_against_filter"):
- cond += """ and b.{budget_against} in (%s)""".format(
- budget_against=budget_against) % ", ".join(["%s"] * len(filters.get("budget_against_filter")))
+ cond += """ and b.{budget_against} in (%s)""".format(budget_against=budget_against) % ", ".join(
+ ["%s"] * len(filters.get("budget_against_filter"))
+ )
return frappe.db.sql(
"""
@@ -209,7 +206,9 @@ def get_dimension_target_details(filters):
filters.company,
]
+ (filters.get("budget_against_filter") or [])
- ), as_dict=True)
+ ),
+ as_dict=True,
+ )
# Get target distribution details of accounts of cost center
@@ -230,13 +229,14 @@ def get_target_distribution_details(filters):
order by
md.fiscal_year
""",
- (filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1):
- target_details.setdefault(d.name, {}).setdefault(
- d.month, flt(d.percentage_allocation)
- )
+ (filters.from_fiscal_year, filters.to_fiscal_year),
+ as_dict=1,
+ ):
+ target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation))
return target_details
+
# Get actual details from gl entry
def get_actual_details(name, filters):
budget_against = frappe.scrub(filters.get("budget_against"))
@@ -247,7 +247,9 @@ def get_actual_details(name, filters):
cond = """
and lft >= "{lft}"
and rgt <= "{rgt}"
- """.format(lft=cc_lft, rgt=cc_rgt)
+ """.format(
+ lft=cc_lft, rgt=cc_rgt
+ )
ac_details = frappe.db.sql(
"""
@@ -281,8 +283,12 @@ def get_actual_details(name, filters):
group by
gl.name
order by gl.fiscal_year
- """.format(tab=filters.budget_against, budget_against=budget_against, cond=cond),
- (filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1)
+ """.format(
+ tab=filters.budget_against, budget_against=budget_against, cond=cond
+ ),
+ (filters.from_fiscal_year, filters.to_fiscal_year, name),
+ as_dict=1,
+ )
cc_actual_details = {}
for d in ac_details:
@@ -290,6 +296,7 @@ def get_actual_details(name, filters):
return cc_actual_details
+
def get_dimension_account_month_map(filters):
dimension_target_details = get_dimension_target_details(filters)
tdd = get_target_distribution_details(filters)
@@ -301,17 +308,13 @@ def get_dimension_account_month_map(filters):
for month_id in range(1, 13):
month = datetime.date(2013, month_id, 1).strftime("%B")
- cam_map.setdefault(ccd.budget_against, {}).setdefault(
- ccd.account, {}
- ).setdefault(ccd.fiscal_year, {}).setdefault(
- month, frappe._dict({"target": 0.0, "actual": 0.0})
- )
+ cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(
+ ccd.fiscal_year, {}
+ ).setdefault(month, frappe._dict({"target": 0.0, "actual": 0.0}))
tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month]
month_percentage = (
- tdd.get(ccd.monthly_distribution, {}).get(month, 0)
- if ccd.monthly_distribution
- else 100.0 / 12
+ tdd.get(ccd.monthly_distribution, {}).get(month, 0) if ccd.monthly_distribution else 100.0 / 12
)
tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100
@@ -334,13 +337,12 @@ def get_fiscal_years(filters):
where
name between %(from_fiscal_year)s and %(to_fiscal_year)s
""",
- {
- "from_fiscal_year": filters["from_fiscal_year"],
- "to_fiscal_year": filters["to_fiscal_year"]
- })
+ {"from_fiscal_year": filters["from_fiscal_year"], "to_fiscal_year": filters["to_fiscal_year"]},
+ )
return fiscal_year
+
def get_chart_data(filters, columns, data):
if not data:
@@ -353,12 +355,13 @@ def get_chart_data(filters, columns, data):
for year in fiscal_year:
for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
- if filters['period'] == 'Yearly':
+ if filters["period"] == "Yearly":
labels.append(year[0])
else:
if group_months:
- label = formatdate(from_date, format_string="MMM") + "-" \
- + formatdate(to_date, format_string="MMM")
+ label = (
+ formatdate(from_date, format_string="MMM") + "-" + formatdate(to_date, format_string="MMM")
+ )
labels.append(label)
else:
label = formatdate(from_date, format_string="MMM")
@@ -373,16 +376,16 @@ def get_chart_data(filters, columns, data):
for i in range(no_of_columns):
budget_values[i] += values[index]
- actual_values[i] += values[index+1]
+ actual_values[i] += values[index + 1]
index += 3
return {
- 'data': {
- 'labels': labels,
- 'datasets': [
- {'name': 'Budget', 'chartType': 'bar', 'values': budget_values},
- {'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values}
- ]
+ "data": {
+ "labels": labels,
+ "datasets": [
+ {"name": "Budget", "chartType": "bar", "values": budget_values},
+ {"name": "Actual Expense", "chartType": "bar", "values": actual_values},
+ ],
},
- 'type' : 'bar'
+ "type": "bar",
}
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py
index 15041f2516..74926b90ff 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/cash_flow.py
@@ -19,65 +19,103 @@ from erpnext.accounts.utils import get_fiscal_year
def execute(filters=None):
- if cint(frappe.db.get_single_value('Accounts Settings', 'use_custom_cash_flow')):
+ if cint(frappe.db.get_single_value("Accounts Settings", "use_custom_cash_flow")):
from erpnext.accounts.report.cash_flow.custom_cash_flow import execute as execute_custom
+
return execute_custom(filters=filters)
- period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
- filters.period_start_date, filters.period_end_date, filters.filter_based_on,
- filters.periodicity, company=filters.company)
+ period_list = get_period_list(
+ filters.from_fiscal_year,
+ filters.to_fiscal_year,
+ filters.period_start_date,
+ filters.period_end_date,
+ filters.filter_based_on,
+ filters.periodicity,
+ company=filters.company,
+ )
cash_flow_accounts = get_cash_flow_accounts()
# compute net profit / loss
- income = get_data(filters.company, "Income", "Credit", period_list, filters=filters,
- accumulated_values=filters.accumulated_values, ignore_closing_entries=True, ignore_accumulated_values_for_fy= True)
- expense = get_data(filters.company, "Expense", "Debit", period_list, filters=filters,
- accumulated_values=filters.accumulated_values, ignore_closing_entries=True, ignore_accumulated_values_for_fy= True)
+ income = get_data(
+ filters.company,
+ "Income",
+ "Credit",
+ period_list,
+ filters=filters,
+ accumulated_values=filters.accumulated_values,
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
+ )
+ expense = get_data(
+ filters.company,
+ "Expense",
+ "Debit",
+ period_list,
+ filters=filters,
+ accumulated_values=filters.accumulated_values,
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
+ )
net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company)
data = []
summary_data = {}
- company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
+ company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
for cash_flow_account in cash_flow_accounts:
section_data = []
- data.append({
- "account_name": cash_flow_account['section_header'],
- "parent_account": None,
- "indent": 0.0,
- "account": cash_flow_account['section_header']
- })
+ data.append(
+ {
+ "account_name": cash_flow_account["section_header"],
+ "parent_account": None,
+ "indent": 0.0,
+ "account": cash_flow_account["section_header"],
+ }
+ )
if len(data) == 1:
# add first net income in operations section
if net_profit_loss:
- net_profit_loss.update({
- "indent": 1,
- "parent_account": cash_flow_accounts[0]['section_header']
- })
+ net_profit_loss.update(
+ {"indent": 1, "parent_account": cash_flow_accounts[0]["section_header"]}
+ )
data.append(net_profit_loss)
section_data.append(net_profit_loss)
- for account in cash_flow_account['account_types']:
- account_data = get_account_type_based_data(filters.company,
- account['account_type'], period_list, filters.accumulated_values, filters)
- account_data.update({
- "account_name": account['label'],
- "account": account['label'],
- "indent": 1,
- "parent_account": cash_flow_account['section_header'],
- "currency": company_currency
- })
+ for account in cash_flow_account["account_types"]:
+ account_data = get_account_type_based_data(
+ filters.company, account["account_type"], period_list, filters.accumulated_values, filters
+ )
+ account_data.update(
+ {
+ "account_name": account["label"],
+ "account": account["label"],
+ "indent": 1,
+ "parent_account": cash_flow_account["section_header"],
+ "currency": company_currency,
+ }
+ )
data.append(account_data)
section_data.append(account_data)
- add_total_row_account(data, section_data, cash_flow_account['section_footer'],
- period_list, company_currency, summary_data, filters)
+ add_total_row_account(
+ data,
+ section_data,
+ cash_flow_account["section_footer"],
+ period_list,
+ company_currency,
+ summary_data,
+ filters,
+ )
- add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters)
- columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
+ add_total_row_account(
+ data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters
+ )
+ columns = get_columns(
+ filters.periodicity, period_list, filters.accumulated_values, filters.company
+ )
chart = get_chart_data(columns, data)
@@ -85,6 +123,7 @@ def execute(filters=None):
return columns, data, None, chart, report_summary
+
def get_cash_flow_accounts():
operation_accounts = {
"section_name": "Operations",
@@ -94,39 +133,37 @@ def get_cash_flow_accounts():
{"account_type": "Depreciation", "label": _("Depreciation")},
{"account_type": "Receivable", "label": _("Net Change in Accounts Receivable")},
{"account_type": "Payable", "label": _("Net Change in Accounts Payable")},
- {"account_type": "Stock", "label": _("Net Change in Inventory")}
- ]
+ {"account_type": "Stock", "label": _("Net Change in Inventory")},
+ ],
}
investing_accounts = {
"section_name": "Investing",
"section_footer": _("Net Cash from Investing"),
"section_header": _("Cash Flow from Investing"),
- "account_types": [
- {"account_type": "Fixed Asset", "label": _("Net Change in Fixed Asset")}
- ]
+ "account_types": [{"account_type": "Fixed Asset", "label": _("Net Change in Fixed Asset")}],
}
financing_accounts = {
"section_name": "Financing",
"section_footer": _("Net Cash from Financing"),
"section_header": _("Cash Flow from Financing"),
- "account_types": [
- {"account_type": "Equity", "label": _("Net Change in Equity")}
- ]
+ "account_types": [{"account_type": "Equity", "label": _("Net Change in Equity")}],
}
# combine all cash flow accounts for iteration
return [operation_accounts, investing_accounts, financing_accounts]
+
def get_account_type_based_data(company, account_type, period_list, accumulated_values, filters):
data = {}
total = 0
for period in period_list:
start_date = get_start_date(period, accumulated_values, company)
- amount = get_account_type_based_gl_data(company, start_date,
- period['to_date'], account_type, filters)
+ amount = get_account_type_based_gl_data(
+ company, start_date, period["to_date"], account_type, filters
+ )
if amount and account_type == "Depreciation":
amount *= -1
@@ -137,31 +174,42 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
data["total"] = total
return data
+
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None):
cond = ""
filters = frappe._dict(filters or {})
if filters.include_default_book_entries:
- company_fb = frappe.db.get_value("Company", company, 'default_finance_book')
+ company_fb = frappe.db.get_value("Company", company, "default_finance_book")
cond = """ AND (finance_book in (%s, %s, '') OR finance_book IS NULL)
- """ %(frappe.db.escape(filters.finance_book), frappe.db.escape(company_fb))
+ """ % (
+ frappe.db.escape(filters.finance_book),
+ frappe.db.escape(company_fb),
+ )
else:
- cond = " AND (finance_book in (%s, '') OR finance_book IS NULL)" %(frappe.db.escape(cstr(filters.finance_book)))
+ cond = " AND (finance_book in (%s, '') OR finance_book IS NULL)" % (
+ frappe.db.escape(cstr(filters.finance_book))
+ )
-
- gl_sum = frappe.db.sql_list("""
+ gl_sum = frappe.db.sql_list(
+ """
select sum(credit) - sum(debit)
from `tabGL Entry`
where company=%s and posting_date >= %s and posting_date <= %s
and voucher_type != 'Period Closing Voucher'
and account in ( SELECT name FROM tabAccount WHERE account_type = %s) {cond}
- """.format(cond=cond), (company, start_date, end_date, account_type))
+ """.format(
+ cond=cond
+ ),
+ (company, start_date, end_date, account_type),
+ )
return gl_sum[0] if gl_sum and gl_sum[0] else 0
+
def get_start_date(period, accumulated_values, company):
- if not accumulated_values and period.get('from_date'):
- return period['from_date']
+ if not accumulated_values and period.get("from_date"):
+ return period["from_date"]
start_date = period["year_start_date"]
if accumulated_values:
@@ -169,23 +217,26 @@ def get_start_date(period, accumulated_values, company):
return start_date
-def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False):
+
+def add_total_row_account(
+ out, data, label, period_list, currency, summary_data, filters, consolidated=False
+):
total_row = {
"account_name": "'" + _("{0}").format(label) + "'",
"account": "'" + _("{0}").format(label) + "'",
- "currency": currency
+ "currency": currency,
}
summary_data[label] = 0
# from consolidated financial statement
- if filters.get('accumulated_in_group_company'):
+ if filters.get("accumulated_in_group_company"):
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
for row in data:
if row.get("parent_account"):
for period in period_list:
- key = period if consolidated else period['key']
+ key = period if consolidated else period["key"]
total_row.setdefault(key, 0.0)
total_row[key] += row.get(key, 0.0)
summary_data[label] += row.get(key)
@@ -202,12 +253,7 @@ def get_report_summary(summary_data, currency):
for label, value in summary_data.items():
report_summary.append(
- {
- "value": value,
- "label": label,
- "datatype": "Currency",
- "currency": currency
- }
+ {"value": value, "label": label, "datatype": "Currency", "currency": currency}
)
return report_summary
@@ -215,16 +261,14 @@ def get_report_summary(summary_data, currency):
def get_chart_data(columns, data):
labels = [d.get("label") for d in columns[2:]]
- datasets = [{'name':account.get('account').replace("'", ""), 'values': [account.get('total')]} for account in data if account.get('parent_account') == None and account.get('currency')]
+ datasets = [
+ {"name": account.get("account").replace("'", ""), "values": [account.get("total")]}
+ for account in data
+ if account.get("parent_account") == None and account.get("currency")
+ ]
datasets = datasets[:-1]
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- },
- "type": "bar"
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"}
chart["fieldtype"] = "Currency"
diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
index e81e0d73cd..b165c88c06 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -14,48 +14,59 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement
def get_mapper_for(mappers, position):
- mapper_list = list(filter(lambda x: x['position'] == position, mappers))
+ mapper_list = list(filter(lambda x: x["position"] == position, mappers))
return mapper_list[0] if mapper_list else []
def get_mappers_from_db():
return frappe.get_all(
- 'Cash Flow Mapper',
+ "Cash Flow Mapper",
fields=[
- 'section_name', 'section_header', 'section_leader', 'section_subtotal',
- 'section_footer', 'name', 'position'],
- order_by='position'
+ "section_name",
+ "section_header",
+ "section_leader",
+ "section_subtotal",
+ "section_footer",
+ "name",
+ "position",
+ ],
+ order_by="position",
)
def get_accounts_in_mappers(mapping_names):
- cfm = frappe.qb.DocType('Cash Flow Mapping')
- cfma = frappe.qb.DocType('Cash Flow Mapping Accounts')
+ cfm = frappe.qb.DocType("Cash Flow Mapping")
+ cfma = frappe.qb.DocType("Cash Flow Mapping Accounts")
result = (
- frappe.qb
- .select(
- cfma.name, cfm.label, cfm.is_working_capital,
- cfm.is_income_tax_liability, cfm.is_income_tax_expense,
- cfm.is_finance_cost, cfm.is_finance_cost_adjustment, cfma.account
- )
- .from_(cfm)
- .join(cfma)
- .on(cfm.name == cfma.parent)
- .where(cfma.parent.isin(mapping_names))
- ).run()
+ frappe.qb.select(
+ cfma.name,
+ cfm.label,
+ cfm.is_working_capital,
+ cfm.is_income_tax_liability,
+ cfm.is_income_tax_expense,
+ cfm.is_finance_cost,
+ cfm.is_finance_cost_adjustment,
+ cfma.account,
+ )
+ .from_(cfm)
+ .join(cfma)
+ .on(cfm.name == cfma.parent)
+ .where(cfma.parent.isin(mapping_names))
+ ).run()
return result
+
def setup_mappers(mappers):
cash_flow_accounts = []
for mapping in mappers:
- mapping['account_types'] = []
- mapping['tax_liabilities'] = []
- mapping['tax_expenses'] = []
- mapping['finance_costs'] = []
- mapping['finance_costs_adjustments'] = []
- doc = frappe.get_doc('Cash Flow Mapper', mapping['name'])
+ mapping["account_types"] = []
+ mapping["tax_liabilities"] = []
+ mapping["tax_expenses"] = []
+ mapping["finance_costs"] = []
+ mapping["finance_costs_adjustments"] = []
+ doc = frappe.get_doc("Cash Flow Mapper", mapping["name"])
mapping_names = [item.name for item in doc.accounts]
if not mapping_names:
@@ -65,96 +76,123 @@ def setup_mappers(mappers):
account_types = [
dict(
- name=account[0], account_name=account[7], label=account[1], is_working_capital=account[2],
- is_income_tax_liability=account[3], is_income_tax_expense=account[4]
- ) for account in accounts if not account[3]]
+ name=account[0],
+ account_name=account[7],
+ label=account[1],
+ is_working_capital=account[2],
+ is_income_tax_liability=account[3],
+ is_income_tax_expense=account[4],
+ )
+ for account in accounts
+ if not account[3]
+ ]
finance_costs_adjustments = [
dict(
- name=account[0], account_name=account[7], label=account[1], is_finance_cost=account[5],
- is_finance_cost_adjustment=account[6]
- ) for account in accounts if account[6]]
+ name=account[0],
+ account_name=account[7],
+ label=account[1],
+ is_finance_cost=account[5],
+ is_finance_cost_adjustment=account[6],
+ )
+ for account in accounts
+ if account[6]
+ ]
tax_liabilities = [
dict(
- name=account[0], account_name=account[7], label=account[1], is_income_tax_liability=account[3],
- is_income_tax_expense=account[4]
- ) for account in accounts if account[3]]
+ name=account[0],
+ account_name=account[7],
+ label=account[1],
+ is_income_tax_liability=account[3],
+ is_income_tax_expense=account[4],
+ )
+ for account in accounts
+ if account[3]
+ ]
tax_expenses = [
dict(
- name=account[0], account_name=account[7], label=account[1], is_income_tax_liability=account[3],
- is_income_tax_expense=account[4]
- ) for account in accounts if account[4]]
+ name=account[0],
+ account_name=account[7],
+ label=account[1],
+ is_income_tax_liability=account[3],
+ is_income_tax_expense=account[4],
+ )
+ for account in accounts
+ if account[4]
+ ]
finance_costs = [
- dict(
- name=account[0], account_name=account[7], label=account[1], is_finance_cost=account[5])
- for account in accounts if account[5]]
+ dict(name=account[0], account_name=account[7], label=account[1], is_finance_cost=account[5])
+ for account in accounts
+ if account[5]
+ ]
account_types_labels = sorted(
set(
- (d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense'])
- for d in account_types
+ (d["label"], d["is_working_capital"], d["is_income_tax_liability"], d["is_income_tax_expense"])
+ for d in account_types
),
- key=lambda x: x[1]
+ key=lambda x: x[1],
)
fc_adjustment_labels = sorted(
set(
- [(d['label'], d['is_finance_cost'], d['is_finance_cost_adjustment'])
- for d in finance_costs_adjustments if d['is_finance_cost_adjustment']]
+ [
+ (d["label"], d["is_finance_cost"], d["is_finance_cost_adjustment"])
+ for d in finance_costs_adjustments
+ if d["is_finance_cost_adjustment"]
+ ]
),
- key=lambda x: x[2]
+ key=lambda x: x[2],
)
unique_liability_labels = sorted(
set(
- [(d['label'], d['is_income_tax_liability'], d['is_income_tax_expense'])
- for d in tax_liabilities]
+ [
+ (d["label"], d["is_income_tax_liability"], d["is_income_tax_expense"])
+ for d in tax_liabilities
+ ]
),
- key=lambda x: x[0]
+ key=lambda x: x[0],
)
unique_expense_labels = sorted(
set(
- [(d['label'], d['is_income_tax_liability'], d['is_income_tax_expense'])
- for d in tax_expenses]
+ [(d["label"], d["is_income_tax_liability"], d["is_income_tax_expense"]) for d in tax_expenses]
),
- key=lambda x: x[0]
+ key=lambda x: x[0],
)
unique_finance_costs_labels = sorted(
- set(
- [(d['label'], d['is_finance_cost']) for d in finance_costs]
- ),
- key=lambda x: x[0]
+ set([(d["label"], d["is_finance_cost"]) for d in finance_costs]), key=lambda x: x[0]
)
for label in account_types_labels:
- names = [d['account_name'] for d in account_types if d['label'] == label[0]]
+ names = [d["account_name"] for d in account_types if d["label"] == label[0]]
m = dict(label=label[0], names=names, is_working_capital=label[1])
- mapping['account_types'].append(m)
+ mapping["account_types"].append(m)
for label in fc_adjustment_labels:
- names = [d['account_name'] for d in finance_costs_adjustments if d['label'] == label[0]]
+ names = [d["account_name"] for d in finance_costs_adjustments if d["label"] == label[0]]
m = dict(label=label[0], names=names)
- mapping['finance_costs_adjustments'].append(m)
+ mapping["finance_costs_adjustments"].append(m)
for label in unique_liability_labels:
- names = [d['account_name'] for d in tax_liabilities if d['label'] == label[0]]
+ names = [d["account_name"] for d in tax_liabilities if d["label"] == label[0]]
m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
- mapping['tax_liabilities'].append(m)
+ mapping["tax_liabilities"].append(m)
for label in unique_expense_labels:
- names = [d['account_name'] for d in tax_expenses if d['label'] == label[0]]
+ names = [d["account_name"] for d in tax_expenses if d["label"] == label[0]]
m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
- mapping['tax_expenses'].append(m)
+ mapping["tax_expenses"].append(m)
for label in unique_finance_costs_labels:
- names = [d['account_name'] for d in finance_costs if d['label'] == label[0]]
+ names = [d["account_name"] for d in finance_costs if d["label"] == label[0]]
m = dict(label=label[0], names=names, is_finance_cost=label[1])
- mapping['finance_costs'].append(m)
+ mapping["finance_costs"].append(m)
cash_flow_accounts.append(mapping)
@@ -162,119 +200,145 @@ def setup_mappers(mappers):
def add_data_for_operating_activities(
- filters, company_currency, profit_data, period_list, light_mappers, mapper, data):
+ filters, company_currency, profit_data, period_list, light_mappers, mapper, data
+):
has_added_working_capital_header = False
section_data = []
- data.append({
- "account_name": mapper['section_header'],
- "parent_account": None,
- "indent": 0.0,
- "account": mapper['section_header']
- })
+ data.append(
+ {
+ "account_name": mapper["section_header"],
+ "parent_account": None,
+ "indent": 0.0,
+ "account": mapper["section_header"],
+ }
+ )
if profit_data:
- profit_data.update({
- "indent": 1,
- "parent_account": get_mapper_for(light_mappers, position=1)['section_header']
- })
+ profit_data.update(
+ {"indent": 1, "parent_account": get_mapper_for(light_mappers, position=1)["section_header"]}
+ )
data.append(profit_data)
section_data.append(profit_data)
- data.append({
- "account_name": mapper["section_leader"],
- "parent_account": None,
- "indent": 1.0,
- "account": mapper["section_leader"]
- })
-
- for account in mapper['account_types']:
- if account['is_working_capital'] and not has_added_working_capital_header:
- data.append({
- "account_name": 'Movement in working capital',
+ data.append(
+ {
+ "account_name": mapper["section_leader"],
"parent_account": None,
"indent": 1.0,
- "account": ""
- })
+ "account": mapper["section_leader"],
+ }
+ )
+
+ for account in mapper["account_types"]:
+ if account["is_working_capital"] and not has_added_working_capital_header:
+ data.append(
+ {
+ "account_name": "Movement in working capital",
+ "parent_account": None,
+ "indent": 1.0,
+ "account": "",
+ }
+ )
has_added_working_capital_header = True
account_data = _get_account_type_based_data(
- filters, account['names'], period_list, filters.accumulated_values)
+ filters, account["names"], period_list, filters.accumulated_values
+ )
- if not account['is_working_capital']:
+ if not account["is_working_capital"]:
for key in account_data:
- if key != 'total':
+ if key != "total":
account_data[key] *= -1
- if account_data['total'] != 0:
- account_data.update({
- "account_name": account['label'],
- "account": account['names'],
- "indent": 1.0,
- "parent_account": mapper['section_header'],
- "currency": company_currency
- })
+ if account_data["total"] != 0:
+ account_data.update(
+ {
+ "account_name": account["label"],
+ "account": account["names"],
+ "indent": 1.0,
+ "parent_account": mapper["section_header"],
+ "currency": company_currency,
+ }
+ )
data.append(account_data)
section_data.append(account_data)
_add_total_row_account(
- data, section_data, mapper['section_subtotal'], period_list, company_currency, indent=1)
+ data, section_data, mapper["section_subtotal"], period_list, company_currency, indent=1
+ )
# calculate adjustment for tax paid and add to data
- if not mapper['tax_liabilities']:
- mapper['tax_liabilities'] = [
- dict(label='Income tax paid', names=[''], tax_liability=1, tax_expense=0)]
+ if not mapper["tax_liabilities"]:
+ mapper["tax_liabilities"] = [
+ dict(label="Income tax paid", names=[""], tax_liability=1, tax_expense=0)
+ ]
- for account in mapper['tax_liabilities']:
+ for account in mapper["tax_liabilities"]:
tax_paid = calculate_adjustment(
- filters, mapper['tax_liabilities'], mapper['tax_expenses'],
- filters.accumulated_values, period_list)
+ filters,
+ mapper["tax_liabilities"],
+ mapper["tax_expenses"],
+ filters.accumulated_values,
+ period_list,
+ )
if tax_paid:
- tax_paid.update({
- 'parent_account': mapper['section_header'],
- 'currency': company_currency,
- 'account_name': account['label'],
- 'indent': 1.0
- })
+ tax_paid.update(
+ {
+ "parent_account": mapper["section_header"],
+ "currency": company_currency,
+ "account_name": account["label"],
+ "indent": 1.0,
+ }
+ )
data.append(tax_paid)
section_data.append(tax_paid)
- if not mapper['finance_costs_adjustments']:
- mapper['finance_costs_adjustments'] = [dict(label='Interest Paid', names=[''])]
+ if not mapper["finance_costs_adjustments"]:
+ mapper["finance_costs_adjustments"] = [dict(label="Interest Paid", names=[""])]
- for account in mapper['finance_costs_adjustments']:
+ for account in mapper["finance_costs_adjustments"]:
interest_paid = calculate_adjustment(
- filters, mapper['finance_costs_adjustments'], mapper['finance_costs'],
- filters.accumulated_values, period_list
+ filters,
+ mapper["finance_costs_adjustments"],
+ mapper["finance_costs"],
+ filters.accumulated_values,
+ period_list,
)
if interest_paid:
- interest_paid.update({
- 'parent_account': mapper['section_header'],
- 'currency': company_currency,
- 'account_name': account['label'],
- 'indent': 1.0
- })
+ interest_paid.update(
+ {
+ "parent_account": mapper["section_header"],
+ "currency": company_currency,
+ "account_name": account["label"],
+ "indent": 1.0,
+ }
+ )
data.append(interest_paid)
section_data.append(interest_paid)
_add_total_row_account(
- data, section_data, mapper['section_footer'], period_list, company_currency)
+ data, section_data, mapper["section_footer"], period_list, company_currency
+ )
-def calculate_adjustment(filters, non_expense_mapper, expense_mapper, use_accumulated_values, period_list):
- liability_accounts = [d['names'] for d in non_expense_mapper]
- expense_accounts = [d['names'] for d in expense_mapper]
+def calculate_adjustment(
+ filters, non_expense_mapper, expense_mapper, use_accumulated_values, period_list
+):
+ liability_accounts = [d["names"] for d in non_expense_mapper]
+ expense_accounts = [d["names"] for d in expense_mapper]
- non_expense_closing = _get_account_type_based_data(
- filters, liability_accounts, period_list, 0)
+ non_expense_closing = _get_account_type_based_data(filters, liability_accounts, period_list, 0)
non_expense_opening = _get_account_type_based_data(
- filters, liability_accounts, period_list, use_accumulated_values, opening_balances=1)
+ filters, liability_accounts, period_list, use_accumulated_values, opening_balances=1
+ )
expense_data = _get_account_type_based_data(
- filters, expense_accounts, period_list, use_accumulated_values)
+ filters, expense_accounts, period_list, use_accumulated_values
+ )
data = _calculate_adjustment(non_expense_closing, non_expense_opening, expense_data)
return data
@@ -284,7 +348,9 @@ def _calculate_adjustment(non_expense_closing, non_expense_opening, expense_data
account_data = {}
for month in non_expense_opening.keys():
if non_expense_opening[month] and non_expense_closing[month]:
- account_data[month] = non_expense_opening[month] - expense_data[month] + non_expense_closing[month]
+ account_data[month] = (
+ non_expense_opening[month] - expense_data[month] + non_expense_closing[month]
+ )
elif expense_data[month]:
account_data[month] = expense_data[month]
@@ -292,32 +358,39 @@ def _calculate_adjustment(non_expense_closing, non_expense_opening, expense_data
def add_data_for_other_activities(
- filters, company_currency, profit_data, period_list, light_mappers, mapper_list, data):
+ filters, company_currency, profit_data, period_list, light_mappers, mapper_list, data
+):
for mapper in mapper_list:
section_data = []
- data.append({
- "account_name": mapper['section_header'],
- "parent_account": None,
- "indent": 0.0,
- "account": mapper['section_header']
- })
+ data.append(
+ {
+ "account_name": mapper["section_header"],
+ "parent_account": None,
+ "indent": 0.0,
+ "account": mapper["section_header"],
+ }
+ )
- for account in mapper['account_types']:
- account_data = _get_account_type_based_data(filters,
- account['names'], period_list, filters.accumulated_values)
- if account_data['total'] != 0:
- account_data.update({
- "account_name": account['label'],
- "account": account['names'],
- "indent": 1,
- "parent_account": mapper['section_header'],
- "currency": company_currency
- })
+ for account in mapper["account_types"]:
+ account_data = _get_account_type_based_data(
+ filters, account["names"], period_list, filters.accumulated_values
+ )
+ if account_data["total"] != 0:
+ account_data.update(
+ {
+ "account_name": account["label"],
+ "account": account["names"],
+ "indent": 1,
+ "parent_account": mapper["section_header"],
+ "currency": company_currency,
+ }
+ )
data.append(account_data)
section_data.append(account_data)
- _add_total_row_account(data, section_data, mapper['section_footer'],
- period_list, company_currency)
+ _add_total_row_account(
+ data, section_data, mapper["section_footer"], period_list, company_currency
+ )
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
@@ -326,13 +399,18 @@ def compute_data(filters, company_currency, profit_data, period_list, light_mapp
operating_activities_mapper = get_mapper_for(light_mappers, position=1)
other_mappers = [
get_mapper_for(light_mappers, position=2),
- get_mapper_for(light_mappers, position=3)
+ get_mapper_for(light_mappers, position=3),
]
if operating_activities_mapper:
add_data_for_operating_activities(
- filters, company_currency, profit_data, period_list, light_mappers,
- operating_activities_mapper, data
+ filters,
+ company_currency,
+ profit_data,
+ period_list,
+ light_mappers,
+ operating_activities_mapper,
+ data,
)
if all(other_mappers):
@@ -344,10 +422,17 @@ def compute_data(filters, company_currency, profit_data, period_list, light_mapp
def execute(filters=None):
- if not filters.periodicity: filters.periodicity = "Monthly"
- period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
- filters.period_start_date, filters.period_end_date, filters.filter_based_on,
- filters.periodicity, company=filters.company)
+ if not filters.periodicity:
+ filters.periodicity = "Monthly"
+ period_list = get_period_list(
+ filters.from_fiscal_year,
+ filters.to_fiscal_year,
+ filters.period_start_date,
+ filters.period_end_date,
+ filters.filter_based_on,
+ filters.periodicity,
+ company=filters.company,
+ )
mappers = get_mappers_from_db()
@@ -355,30 +440,46 @@ def execute(filters=None):
# compute net profit / loss
income = get_data(
- filters.company, "Income", "Credit", period_list, filters=filters,
- accumulated_values=filters.accumulated_values, ignore_closing_entries=True,
- ignore_accumulated_values_for_fy=True
+ filters.company,
+ "Income",
+ "Credit",
+ period_list,
+ filters=filters,
+ accumulated_values=filters.accumulated_values,
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
)
expense = get_data(
- filters.company, "Expense", "Debit", period_list, filters=filters,
- accumulated_values=filters.accumulated_values, ignore_closing_entries=True,
- ignore_accumulated_values_for_fy=True
+ filters.company,
+ "Expense",
+ "Debit",
+ period_list,
+ filters=filters,
+ accumulated_values=filters.accumulated_values,
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
)
net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company)
- company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
+ company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
- data = compute_data(filters, company_currency, net_profit_loss, period_list, mappers, cash_flow_accounts)
+ data = compute_data(
+ filters, company_currency, net_profit_loss, period_list, mappers, cash_flow_accounts
+ )
_add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency)
- columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
+ columns = get_columns(
+ filters.periodicity, period_list, filters.accumulated_values, filters.company
+ )
return columns, data
-def _get_account_type_based_data(filters, account_names, period_list, accumulated_values, opening_balances=0):
+def _get_account_type_based_data(
+ filters, account_names, period_list, accumulated_values, opening_balances=0
+):
if not account_names or not account_names[0] or not type(account_names[0]) == str:
# only proceed if account_names is a list of account names
return {}
@@ -388,26 +489,23 @@ def _get_account_type_based_data(filters, account_names, period_list, accumulate
company = filters.company
data = {}
total = 0
- GLEntry = frappe.qb.DocType('GL Entry')
- Account = frappe.qb.DocType('Account')
+ GLEntry = frappe.qb.DocType("GL Entry")
+ Account = frappe.qb.DocType("Account")
for period in period_list:
start_date = get_start_date(period, accumulated_values, company)
account_subquery = (
frappe.qb.from_(Account)
- .where(
- (Account.name.isin(account_names)) |
- (Account.parent_account.isin(account_names))
- )
+ .where((Account.name.isin(account_names)) | (Account.parent_account.isin(account_names)))
.select(Account.name)
.as_("account_subquery")
)
if opening_balances:
date_info = dict(date=start_date)
- months_map = {'Monthly': -1, 'Quarterly': -3, 'Half-Yearly': -6}
- years_map = {'Yearly': -1}
+ months_map = {"Monthly": -1, "Quarterly": -3, "Half-Yearly": -6}
+ years_map = {"Yearly": -1}
if months_map.get(filters.periodicity):
date_info.update(months=months_map[filters.periodicity])
@@ -415,25 +513,25 @@ def _get_account_type_based_data(filters, account_names, period_list, accumulate
date_info.update(years=years_map[filters.periodicity])
if accumulated_values:
- start, end = add_to_date(start_date, years=-1), add_to_date(period['to_date'], years=-1)
+ start, end = add_to_date(start_date, years=-1), add_to_date(period["to_date"], years=-1)
else:
start, end = add_to_date(**date_info), add_to_date(**date_info)
start, end = get_date_str(start), get_date_str(end)
else:
- start, end = start_date if accumulated_values else period['from_date'], period['to_date']
+ start, end = start_date if accumulated_values else period["from_date"], period["to_date"]
start, end = get_date_str(start), get_date_str(end)
result = (
frappe.qb.from_(GLEntry)
.select(Sum(GLEntry.credit) - Sum(GLEntry.debit))
.where(
- (GLEntry.company == company) &
- (GLEntry.posting_date >= start) &
- (GLEntry.posting_date <= end) &
- (GLEntry.voucher_type != 'Period Closing Voucher') &
- (GLEntry.account.isin(account_subquery))
+ (GLEntry.company == company)
+ & (GLEntry.posting_date >= start)
+ & (GLEntry.posting_date <= end)
+ & (GLEntry.voucher_type != "Period Closing Voucher")
+ & (GLEntry.account.isin(account_subquery))
)
).run()
@@ -454,7 +552,7 @@ def _add_total_row_account(out, data, label, period_list, currency, indent=0.0):
"indent": indent,
"account_name": "'" + _("{0}").format(label) + "'",
"account": "'" + _("{0}").format(label) + "'",
- "currency": currency
+ "currency": currency,
}
for row in data:
if row.get("parent_account"):
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 1e20f7be3e..98dbbf6c44 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -42,26 +42,32 @@ from erpnext.accounts.report.utils import convert, convert_to_presentation_curre
def execute(filters=None):
columns, data, message, chart = [], [], [], []
- if not filters.get('company'):
+ if not filters.get("company"):
return columns, data, message, chart
- fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year'))
+ fiscal_year = get_fiscal_year_data(filters.get("from_fiscal_year"), filters.get("to_fiscal_year"))
companies_column, companies = get_companies(filters)
columns = get_columns(companies_column, filters)
- if filters.get('report') == "Balance Sheet":
- data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters)
- elif filters.get('report') == "Profit and Loss Statement":
- data, message, chart, report_summary = get_profit_loss_data(fiscal_year, companies, columns, filters)
+ if filters.get("report") == "Balance Sheet":
+ data, message, chart, report_summary = get_balance_sheet_data(
+ fiscal_year, companies, columns, filters
+ )
+ elif filters.get("report") == "Profit and Loss Statement":
+ data, message, chart, report_summary = get_profit_loss_data(
+ fiscal_year, companies, columns, filters
+ )
else:
- if cint(frappe.db.get_single_value('Accounts Settings', 'use_custom_cash_flow')):
+ if cint(frappe.db.get_single_value("Accounts Settings", "use_custom_cash_flow")):
from erpnext.accounts.report.cash_flow.custom_cash_flow import execute as execute_custom
+
return execute_custom(filters=filters)
data, report_summary = get_cash_flow_data(fiscal_year, companies, filters)
return columns, data, message, chart, report_summary
+
def get_balance_sheet_data(fiscal_year, companies, columns, filters):
asset = get_data(companies, "Asset", "Debit", fiscal_year, filters=filters)
@@ -75,24 +81,27 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
data.extend(equity or [])
company_currency = get_company_currency(filters)
- provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity,
- companies, filters.get('company'), company_currency, True)
+ provisional_profit_loss, total_credit = get_provisional_profit_loss(
+ asset, liability, equity, companies, filters.get("company"), company_currency, True
+ )
- message, opening_balance = prepare_companywise_opening_balance(asset, liability, equity, companies)
+ message, opening_balance = prepare_companywise_opening_balance(
+ asset, liability, equity, companies
+ )
if opening_balance:
unclosed = {
"account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"warn_if_negative": True,
- "currency": company_currency
+ "currency": company_currency,
}
for company in companies:
unclosed[company] = opening_balance.get(company)
if provisional_profit_loss and provisional_profit_loss.get(company):
- provisional_profit_loss[company] = (
- flt(provisional_profit_loss[company]) - flt(opening_balance.get(company))
+ provisional_profit_loss[company] = flt(provisional_profit_loss[company]) - flt(
+ opening_balance.get(company)
)
unclosed["total"] = opening_balance.get(company)
@@ -103,13 +112,23 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
if total_credit:
data.append(total_credit)
- report_summary = get_bs_summary(companies, asset, liability, equity, provisional_profit_loss, total_credit,
- company_currency, filters, True)
+ report_summary = get_bs_summary(
+ companies,
+ asset,
+ liability,
+ equity,
+ provisional_profit_loss,
+ total_credit,
+ company_currency,
+ filters,
+ True,
+ )
chart = get_chart_data(filters, columns, asset, liability, equity)
return data, message, chart, report_summary
+
def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, companies):
opening_balance = {}
for company in companies:
@@ -119,29 +138,36 @@ def prepare_companywise_opening_balance(asset_data, liability_data, equity_data,
for data in [asset_data, liability_data, equity_data]:
if data:
account_name = get_root_account_name(data[0].root_type, company)
- opening_value += (get_opening_balance(account_name, data, company) or 0.0)
+ opening_value += get_opening_balance(account_name, data, company) or 0.0
opening_balance[company] = opening_value
if opening_balance:
return _("Previous Financial Year is not closed"), opening_balance
- return '', {}
+ return "", {}
+
def get_opening_balance(account_name, data, company):
for row in data:
- if row.get('account_name') == account_name:
- return row.get('company_wise_opening_bal', {}).get(company, 0.0)
+ if row.get("account_name") == account_name:
+ return row.get("company_wise_opening_bal", {}).get(company, 0.0)
+
def get_root_account_name(root_type, company):
return frappe.get_all(
- 'Account',
- fields=['account_name'],
- filters = {'root_type': root_type, 'is_group': 1,
- 'company': company, 'parent_account': ('is', 'not set')},
- as_list=1
+ "Account",
+ fields=["account_name"],
+ filters={
+ "root_type": root_type,
+ "is_group": 1,
+ "company": company,
+ "parent_account": ("is", "not set"),
+ },
+ as_list=1,
)[0][0]
+
def get_profit_loss_data(fiscal_year, companies, columns, filters):
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
company_currency = get_company_currency(filters)
@@ -154,20 +180,26 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
- report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True)
+ report_summary = get_pl_summary(
+ companies, "", income, expense, net_profit_loss, company_currency, filters, True
+ )
return data, None, chart, report_summary
+
def get_income_expense_data(companies, fiscal_year, filters):
company_currency = get_company_currency(filters)
income = get_data(companies, "Income", "Credit", fiscal_year, filters, True)
expense = get_data(companies, "Expense", "Debit", fiscal_year, filters, True)
- net_profit_loss = get_net_profit_loss(income, expense, companies, filters.company, company_currency, True)
+ net_profit_loss = get_net_profit_loss(
+ income, expense, companies, filters.company, company_currency, True
+ )
return income, expense, net_profit_loss
+
def get_cash_flow_data(fiscal_year, companies, filters):
cash_flow_accounts = get_cash_flow_accounts()
@@ -179,50 +211,67 @@ def get_cash_flow_data(fiscal_year, companies, filters):
for cash_flow_account in cash_flow_accounts:
section_data = []
- data.append({
- "account_name": cash_flow_account['section_header'],
- "parent_account": None,
- "indent": 0.0,
- "account": cash_flow_account['section_header']
- })
+ data.append(
+ {
+ "account_name": cash_flow_account["section_header"],
+ "parent_account": None,
+ "indent": 0.0,
+ "account": cash_flow_account["section_header"],
+ }
+ )
if len(data) == 1:
# add first net income in operations section
if net_profit_loss:
- net_profit_loss.update({
- "indent": 1,
- "parent_account": cash_flow_accounts[0]['section_header']
- })
+ net_profit_loss.update(
+ {"indent": 1, "parent_account": cash_flow_accounts[0]["section_header"]}
+ )
data.append(net_profit_loss)
section_data.append(net_profit_loss)
- for account in cash_flow_account['account_types']:
- account_data = get_account_type_based_data(account['account_type'], companies, fiscal_year, filters)
- account_data.update({
- "account_name": account['label'],
- "account": account['label'],
- "indent": 1,
- "parent_account": cash_flow_account['section_header'],
- "currency": company_currency
- })
+ for account in cash_flow_account["account_types"]:
+ account_data = get_account_type_based_data(
+ account["account_type"], companies, fiscal_year, filters
+ )
+ account_data.update(
+ {
+ "account_name": account["label"],
+ "account": account["label"],
+ "indent": 1,
+ "parent_account": cash_flow_account["section_header"],
+ "currency": company_currency,
+ }
+ )
data.append(account_data)
section_data.append(account_data)
- add_total_row_account(data, section_data, cash_flow_account['section_footer'],
- companies, company_currency, summary_data, filters, True)
+ add_total_row_account(
+ data,
+ section_data,
+ cash_flow_account["section_footer"],
+ companies,
+ company_currency,
+ summary_data,
+ filters,
+ True,
+ )
- add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True)
+ add_total_row_account(
+ data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True
+ )
report_summary = get_cash_flow_summary(summary_data, company_currency)
return data, report_summary
+
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
data = {}
total = 0
for company in companies:
- amount = get_account_type_based_gl_data(company,
- fiscal_year.year_start_date, fiscal_year.year_end_date, account_type, filters)
+ amount = get_account_type_based_gl_data(
+ company, fiscal_year.year_start_date, fiscal_year.year_end_date, account_type, filters
+ )
if amount and account_type == "Depreciation":
amount *= -1
@@ -233,6 +282,7 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters):
data["total"] = total
return data
+
def get_columns(companies, filters):
columns = [
{
@@ -240,14 +290,15 @@ def get_columns(companies, filters):
"label": _("Account"),
"fieldtype": "Link",
"options": "Account",
- "width": 300
- }, {
+ "width": 300,
+ },
+ {
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
- "hidden": 1
- }
+ "hidden": 1,
+ },
]
for company in companies:
@@ -256,69 +307,96 @@ def get_columns(companies, filters):
if not currency:
currency = erpnext.get_company_currency(company)
- columns.append({
- "fieldname": company,
- "label": f'{company} ({currency})',
- "fieldtype": "Currency",
- "options": "currency",
- "width": 150,
- "apply_currency_formatter": apply_currency_formatter,
- "company_name": company
- })
+ columns.append(
+ {
+ "fieldname": company,
+ "label": f"{company} ({currency})",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 150,
+ "apply_currency_formatter": apply_currency_formatter,
+ "company_name": company,
+ }
+ )
return columns
-def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False):
- accounts, accounts_by_name, parent_children_map = get_account_heads(root_type,
- companies, filters)
- if not accounts: return []
+def get_data(
+ companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False
+):
+ accounts, accounts_by_name, parent_children_map = get_account_heads(root_type, companies, filters)
+
+ if not accounts:
+ return []
company_currency = get_company_currency(filters)
- if filters.filter_based_on == 'Fiscal Year':
- start_date = fiscal_year.year_start_date if filters.report != 'Balance Sheet' else None
+ if filters.filter_based_on == "Fiscal Year":
+ start_date = fiscal_year.year_start_date if filters.report != "Balance Sheet" else None
end_date = fiscal_year.year_end_date
else:
- start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
+ start_date = filters.period_start_date if filters.report != "Balance Sheet" else None
end_date = filters.period_end_date
filters.end_date = end_date
gl_entries_by_account = {}
- for root in frappe.db.sql("""select lft, rgt from tabAccount
- where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1):
+ for root in frappe.db.sql(
+ """select lft, rgt from tabAccount
+ where root_type=%s and ifnull(parent_account, '') = ''""",
+ root_type,
+ as_dict=1,
+ ):
- set_gl_entries_by_account(start_date,
- end_date, root.lft, root.rgt, filters,
- gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False)
+ set_gl_entries_by_account(
+ start_date,
+ end_date,
+ root.lft,
+ root.rgt,
+ filters,
+ gl_entries_by_account,
+ accounts_by_name,
+ accounts,
+ ignore_closing_entries=False,
+ )
calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year)
accumulate_values_into_parents(accounts, accounts_by_name, companies)
- out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
+ out = prepare_data(
+ accounts, start_date, end_date, balance_must_be, companies, company_currency, filters
+ )
- out = filter_out_zero_value_rows(out, parent_children_map, show_zero_values=filters.get("show_zero_values"))
+ out = filter_out_zero_value_rows(
+ out, parent_children_map, show_zero_values=filters.get("show_zero_values")
+ )
if out:
add_total_row(out, root_type, balance_must_be, companies, company_currency)
return out
+
def get_company_currency(filters=None):
- return (filters.get('presentation_currency')
- or frappe.get_cached_value('Company', filters.company, "default_currency"))
+ return filters.get("presentation_currency") or frappe.get_cached_value(
+ "Company", filters.company, "default_currency"
+ )
+
def calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year):
- start_date = (fiscal_year.year_start_date
- if filters.filter_based_on == 'Fiscal Year' else filters.period_start_date)
+ start_date = (
+ fiscal_year.year_start_date
+ if filters.filter_based_on == "Fiscal Year"
+ else filters.period_start_date
+ )
for entries in gl_entries_by_account.values():
for entry in entries:
if entry.account_number:
- account_name = entry.account_number + ' - ' + entry.account_name
+ account_name = entry.account_number + " - " + entry.account_name
else:
- account_name = entry.account_name
+ account_name = entry.account_name
d = accounts_by_name.get(account_name)
@@ -326,28 +404,34 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, filters
debit, credit = 0, 0
for company in companies:
# check if posting date is within the period
- if (entry.company == company or (filters.get('accumulated_in_group_company'))
- and entry.company in companies.get(company)):
+ if (
+ entry.company == company
+ or (filters.get("accumulated_in_group_company"))
+ and entry.company in companies.get(company)
+ ):
parent_company_currency = erpnext.get_company_currency(d.company)
child_company_currency = erpnext.get_company_currency(entry.company)
debit, credit = flt(entry.debit), flt(entry.credit)
- if (not filters.get('presentation_currency')
+ if (
+ not filters.get("presentation_currency")
and entry.company != company
and parent_company_currency != child_company_currency
- and filters.get('accumulated_in_group_company')):
+ and filters.get("accumulated_in_group_company")
+ ):
debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date)
credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date)
d[company] = d.get(company, 0.0) + flt(debit) - flt(credit)
if entry.posting_date < getdate(start_date):
- d['company_wise_opening_bal'][company] += (flt(debit) - flt(credit))
+ d["company_wise_opening_bal"][company] += flt(debit) - flt(credit)
if entry.posting_date < getdate(start_date):
d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit)
+
def accumulate_values_into_parents(accounts, accounts_by_name, companies):
"""accumulate children's values in parent accounts"""
for d in reversed(accounts):
@@ -355,13 +439,18 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
account = d.parent_account_name
for company in companies:
- accounts_by_name[account][company] = \
- accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0)
+ accounts_by_name[account][company] = accounts_by_name[account].get(company, 0.0) + d.get(
+ company, 0.0
+ )
- accounts_by_name[account]['company_wise_opening_bal'][company] += d.get('company_wise_opening_bal', {}).get(company, 0.0)
+ accounts_by_name[account]["company_wise_opening_bal"][company] += d.get(
+ "company_wise_opening_bal", {}
+ ).get(company, 0.0)
+
+ accounts_by_name[account]["opening_balance"] = accounts_by_name[account].get(
+ "opening_balance", 0.0
+ ) + d.get("opening_balance", 0.0)
- accounts_by_name[account]["opening_balance"] = \
- accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
def get_account_heads(root_type, companies, filters):
accounts = get_accounts(root_type, companies)
@@ -375,20 +464,21 @@ def get_account_heads(root_type, companies, filters):
return accounts, accounts_by_name, parent_children_map
+
def update_parent_account_names(accounts):
"""Update parent_account_name in accounts list.
- parent_name is `name` of parent account which could have other prefix
- of account_number and suffix of company abbr. This function adds key called
- `parent_account_name` which does not have such prefix/suffix.
+ parent_name is `name` of parent account which could have other prefix
+ of account_number and suffix of company abbr. This function adds key called
+ `parent_account_name` which does not have such prefix/suffix.
"""
name_to_account_map = {}
for d in accounts:
if d.account_number:
- account_name = d.account_number + ' - ' + d.account_name
+ account_name = d.account_number + " - " + d.account_name
else:
- account_name = d.account_name
+ account_name = d.account_name
name_to_account_map[d.name] = account_name
for account in accounts:
@@ -397,10 +487,11 @@ def update_parent_account_names(accounts):
return accounts
+
def get_companies(filters):
companies = {}
- all_companies = get_subsidiary_companies(filters.get('company'))
- companies.setdefault(filters.get('company'), all_companies)
+ all_companies = get_subsidiary_companies(filters.get("company"))
+ companies.setdefault(filters.get("company"), all_companies)
for d in all_companies:
if d not in companies:
@@ -409,47 +500,73 @@ def get_companies(filters):
return all_companies, companies
-def get_subsidiary_companies(company):
- lft, rgt = frappe.get_cached_value('Company',
- company, ["lft", "rgt"])
- return frappe.db.sql_list("""select name from `tabCompany`
- where lft >= {0} and rgt <= {1} order by lft, rgt""".format(lft, rgt))
+def get_subsidiary_companies(company):
+ lft, rgt = frappe.get_cached_value("Company", company, ["lft", "rgt"])
+
+ return frappe.db.sql_list(
+ """select name from `tabCompany`
+ where lft >= {0} and rgt <= {1} order by lft, rgt""".format(
+ lft, rgt
+ )
+ )
+
def get_accounts(root_type, companies):
accounts = []
added_accounts = []
for company in companies:
- for account in frappe.get_all("Account", fields=["name", "is_group", "company",
- "parent_account", "lft", "rgt", "root_type", "report_type", "account_name", "account_number"],
- filters={"company": company, "root_type": root_type}):
+ for account in frappe.get_all(
+ "Account",
+ fields=[
+ "name",
+ "is_group",
+ "company",
+ "parent_account",
+ "lft",
+ "rgt",
+ "root_type",
+ "report_type",
+ "account_name",
+ "account_number",
+ ],
+ filters={"company": company, "root_type": root_type},
+ ):
if account.account_name not in added_accounts:
accounts.append(account)
added_accounts.append(account.account_name)
return accounts
-def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters):
+
+def prepare_data(
+ accounts, start_date, end_date, balance_must_be, companies, company_currency, filters
+):
data = []
for d in accounts:
# add to output
has_value = False
total = 0
- row = frappe._dict({
- "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
- if d.account_number else _(d.account_name)),
- "account": _(d.name),
- "parent_account": _(d.parent_account),
- "indent": flt(d.indent),
- "year_start_date": start_date,
- "root_type": d.root_type,
- "year_end_date": end_date,
- "currency": filters.presentation_currency,
- "company_wise_opening_bal": d.company_wise_opening_bal,
- "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1)
- })
+ row = frappe._dict(
+ {
+ "account_name": (
+ "%s - %s" % (_(d.account_number), _(d.account_name))
+ if d.account_number
+ else _(d.account_name)
+ ),
+ "account": _(d.name),
+ "parent_account": _(d.parent_account),
+ "indent": flt(d.indent),
+ "year_start_date": start_date,
+ "root_type": d.root_type,
+ "year_end_date": end_date,
+ "currency": filters.presentation_currency,
+ "company_wise_opening_bal": d.company_wise_opening_bal,
+ "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1),
+ }
+ )
for company in companies:
if d.get(company) and balance_must_be == "Credit":
@@ -470,32 +587,49 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
return data
-def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account,
- accounts_by_name, accounts, ignore_closing_entries=False):
+
+def set_gl_entries_by_account(
+ from_date,
+ to_date,
+ root_lft,
+ root_rgt,
+ filters,
+ gl_entries_by_account,
+ accounts_by_name,
+ accounts,
+ ignore_closing_entries=False,
+):
"""Returns a dict like { "account": [gl entries], ... }"""
- company_lft, company_rgt = frappe.get_cached_value('Company',
- filters.get('company'), ["lft", "rgt"])
+ company_lft, company_rgt = frappe.get_cached_value(
+ "Company", filters.get("company"), ["lft", "rgt"]
+ )
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
- companies = frappe.db.sql(""" select name, default_currency from `tabCompany`
- where lft >= %(company_lft)s and rgt <= %(company_rgt)s""", {
+ companies = frappe.db.sql(
+ """ select name, default_currency from `tabCompany`
+ where lft >= %(company_lft)s and rgt <= %(company_rgt)s""",
+ {
"company_lft": company_lft,
"company_rgt": company_rgt,
- }, as_dict=1)
+ },
+ as_dict=1,
+ )
- currency_info = frappe._dict({
- 'report_date': to_date,
- 'presentation_currency': filters.get('presentation_currency')
- })
+ currency_info = frappe._dict(
+ {"report_date": to_date, "presentation_currency": filters.get("presentation_currency")}
+ )
for d in companies:
- gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
+ gl_entries = frappe.db.sql(
+ """select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency,
acc.account_name, acc.account_number
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0
{additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s
- order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions),
+ order by gl.account, gl.posting_date""".format(
+ additional_conditions=additional_conditions
+ ),
{
"from_date": from_date,
"to_date": to_date,
@@ -503,29 +637,47 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
"rgt": root_rgt,
"company": d.name,
"finance_book": filters.get("finance_book"),
- "company_fb": frappe.db.get_value("Company", d.name, 'default_finance_book')
+ "company_fb": frappe.db.get_value("Company", d.name, "default_finance_book"),
},
- as_dict=True)
+ as_dict=True,
+ )
- if filters and filters.get('presentation_currency') != d.default_currency:
- currency_info['company'] = d.name
- currency_info['company_currency'] = d.default_currency
- convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
+ if filters and filters.get("presentation_currency") != d.default_currency:
+ currency_info["company"] = d.name
+ currency_info["company_currency"] = d.default_currency
+ convert_to_presentation_currency(gl_entries, currency_info, filters.get("company"))
for entry in gl_entries:
if entry.account_number:
- account_name = entry.account_number + ' - ' + entry.account_name
+ account_name = entry.account_number + " - " + entry.account_name
else:
- account_name = entry.account_name
+ account_name = entry.account_name
validate_entries(account_name, entry, accounts_by_name, accounts)
gl_entries_by_account.setdefault(account_name, []).append(entry)
return gl_entries_by_account
+
def get_account_details(account):
- return frappe.get_cached_value('Account', account, ['name', 'report_type', 'root_type', 'company',
- 'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1)
+ return frappe.get_cached_value(
+ "Account",
+ account,
+ [
+ "name",
+ "report_type",
+ "root_type",
+ "company",
+ "is_group",
+ "account_name",
+ "account_number",
+ "parent_account",
+ "lft",
+ "rgt",
+ ],
+ as_dict=1,
+ )
+
def validate_entries(key, entry, accounts_by_name, accounts):
# If an account present in the child company and not in the parent company
@@ -535,15 +687,17 @@ def validate_entries(key, entry, accounts_by_name, accounts):
if args.parent_account:
parent_args = get_account_details(args.parent_account)
- args.update({
- 'lft': parent_args.lft + 1,
- 'rgt': parent_args.rgt - 1,
- 'indent': 3,
- 'root_type': parent_args.root_type,
- 'report_type': parent_args.report_type,
- 'parent_account_name': parent_args.account_name,
- 'company_wise_opening_bal': defaultdict(float)
- })
+ args.update(
+ {
+ "lft": parent_args.lft + 1,
+ "rgt": parent_args.rgt - 1,
+ "indent": 3,
+ "root_type": parent_args.root_type,
+ "report_type": parent_args.report_type,
+ "parent_account_name": parent_args.account_name,
+ "company_wise_opening_bal": defaultdict(float),
+ }
+ )
accounts_by_name.setdefault(key, args)
@@ -554,7 +708,8 @@ def validate_entries(key, entry, accounts_by_name, accounts):
idx = index
break
- accounts.insert(idx+1, args)
+ accounts.insert(idx + 1, args)
+
def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions = []
@@ -566,17 +721,20 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions.append("gl.posting_date >= %(from_date)s")
if filters.get("include_default_book_entries"):
- additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)")
+ additional_conditions.append(
+ "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
+ )
else:
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
+
def add_total_row(out, root_type, balance_must_be, companies, company_currency):
total_row = {
"account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
"account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
- "currency": company_currency
+ "currency": company_currency,
}
for row in out:
@@ -595,15 +753,16 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency):
# blank row after Total
out.append({})
+
def filter_accounts(accounts, depth=10):
parent_children_map = {}
accounts_by_name = {}
for d in accounts:
if d.account_number:
- account_name = d.account_number + ' - ' + d.account_name
+ account_name = d.account_number + " - " + d.account_name
else:
- account_name = d.account_name
- d['company_wise_opening_bal'] = defaultdict(float)
+ account_name = d.account_name
+ d["company_wise_opening_bal"] = defaultdict(float)
accounts_by_name[account_name] = d
parent_children_map.setdefault(d.parent_account or None, []).append(d)
@@ -613,7 +772,7 @@ def filter_accounts(accounts, depth=10):
def add_to_list(parent, level):
if level < depth:
children = parent_children_map.get(parent) or []
- sort_accounts(children, is_root=True if parent==None else False)
+ sort_accounts(children, is_root=True if parent == None else False)
for child in children:
child.indent = level
diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
index 56db841e94..cafe95b360 100644
--- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
+++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
@@ -14,14 +14,16 @@ class PartyLedgerSummaryReport(object):
self.filters.to_date = getdate(self.filters.to_date or nowdate())
if not self.filters.get("company"):
- self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company')
+ self.filters["company"] = frappe.db.get_single_value("Global Defaults", "default_company")
def run(self, args):
if self.filters.from_date > self.filters.to_date:
frappe.throw(_("From Date must be before To Date"))
self.filters.party_type = args.get("party_type")
- self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
+ self.party_naming_by = frappe.db.get_value(
+ args.get("naming_by")[0], None, args.get("naming_by")[1]
+ )
self.get_gl_entries()
self.get_return_invoices()
@@ -32,21 +34,25 @@ class PartyLedgerSummaryReport(object):
return columns, data
def get_columns(self):
- columns = [{
- "label": _(self.filters.party_type),
- "fieldtype": "Link",
- "fieldname": "party",
- "options": self.filters.party_type,
- "width": 200
- }]
+ columns = [
+ {
+ "label": _(self.filters.party_type),
+ "fieldtype": "Link",
+ "fieldname": "party",
+ "options": self.filters.party_type,
+ "width": 200,
+ }
+ ]
if self.party_naming_by == "Naming Series":
- columns.append({
- "label": _(self.filters.party_type + "Name"),
- "fieldtype": "Data",
- "fieldname": "party_name",
- "width": 110
- })
+ columns.append(
+ {
+ "label": _(self.filters.party_type + "Name"),
+ "fieldtype": "Data",
+ "fieldname": "party_name",
+ "width": 110,
+ }
+ )
credit_or_debit_note = "Credit Note" if self.filters.party_type == "Customer" else "Debit Note"
@@ -56,40 +62,42 @@ class PartyLedgerSummaryReport(object):
"fieldname": "opening_balance",
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"label": _("Invoiced Amount"),
"fieldname": "invoiced_amount",
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"label": _("Paid Amount"),
"fieldname": "paid_amount",
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"label": _(credit_or_debit_note),
"fieldname": "return_amount",
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
]
for account in self.party_adjustment_accounts:
- columns.append({
- "label": account,
- "fieldname": "adj_" + scrub(account),
- "fieldtype": "Currency",
- "options": "currency",
- "width": 120,
- "is_adjustment": 1
- })
+ columns.append(
+ {
+ "label": account,
+ "fieldname": "adj_" + scrub(account),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ "is_adjustment": 1,
+ }
+ )
columns += [
{
@@ -97,36 +105,43 @@ class PartyLedgerSummaryReport(object):
"fieldname": "closing_balance",
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"label": _("Currency"),
"fieldname": "currency",
"fieldtype": "Link",
"options": "Currency",
- "width": 50
- }
+ "width": 50,
+ },
]
return columns
def get_data(self):
- company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency")
+ company_currency = frappe.get_cached_value(
+ "Company", self.filters.get("company"), "default_currency"
+ )
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
self.party_data = frappe._dict({})
for gle in self.gl_entries:
- self.party_data.setdefault(gle.party, frappe._dict({
- "party": gle.party,
- "party_name": gle.party_name,
- "opening_balance": 0,
- "invoiced_amount": 0,
- "paid_amount": 0,
- "return_amount": 0,
- "closing_balance": 0,
- "currency": company_currency
- }))
+ self.party_data.setdefault(
+ gle.party,
+ frappe._dict(
+ {
+ "party": gle.party,
+ "party_name": gle.party_name,
+ "opening_balance": 0,
+ "invoiced_amount": 0,
+ "paid_amount": 0,
+ "return_amount": 0,
+ "closing_balance": 0,
+ "currency": company_currency,
+ }
+ ),
+ )
amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
self.party_data[gle.party].closing_balance += amount
@@ -143,8 +158,16 @@ class PartyLedgerSummaryReport(object):
out = []
for party, row in self.party_data.items():
- if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount:
- total_party_adjustment = sum(amount for amount in self.party_adjustment_details.get(party, {}).values())
+ if (
+ row.opening_balance
+ or row.invoiced_amount
+ or row.paid_amount
+ or row.return_amount
+ or row.closing_amount
+ ):
+ total_party_adjustment = sum(
+ amount for amount in self.party_adjustment_details.get(party, {}).values()
+ )
row.paid_amount -= total_party_adjustment
adjustments = self.party_adjustment_details.get(party, {})
@@ -165,7 +188,8 @@ class PartyLedgerSummaryReport(object):
join_field = ", p.supplier_name as party_name"
join = "left join `tabSupplier` p on gle.party = p.name"
- self.gl_entries = frappe.db.sql("""
+ self.gl_entries = frappe.db.sql(
+ """
select
gle.posting_date, gle.party, gle.voucher_type, gle.voucher_no, gle.against_voucher_type,
gle.against_voucher, gle.debit, gle.credit, gle.is_opening {join_field}
@@ -175,7 +199,12 @@ class PartyLedgerSummaryReport(object):
gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
and gle.posting_date <= %(to_date)s {conditions}
order by gle.posting_date
- """.format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True)
+ """.format(
+ join=join, join_field=join_field, conditions=conditions
+ ),
+ self.filters,
+ as_dict=True,
+ )
def prepare_conditions(self):
conditions = [""]
@@ -191,57 +220,88 @@ class PartyLedgerSummaryReport(object):
if self.filters.party_type == "Customer":
if self.filters.get("customer_group"):
- lft, rgt = frappe.db.get_value("Customer Group",
- self.filters.get("customer_group"), ["lft", "rgt"])
+ lft, rgt = frappe.db.get_value(
+ "Customer Group", self.filters.get("customer_group"), ["lft", "rgt"]
+ )
- conditions.append("""party in (select name from tabCustomer
+ conditions.append(
+ """party in (select name from tabCustomer
where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1}
- and name=tabCustomer.customer_group))""".format(lft, rgt))
+ and name=tabCustomer.customer_group))""".format(
+ lft, rgt
+ )
+ )
if self.filters.get("territory"):
- lft, rgt = frappe.db.get_value("Territory",
- self.filters.get("territory"), ["lft", "rgt"])
+ lft, rgt = frappe.db.get_value("Territory", self.filters.get("territory"), ["lft", "rgt"])
- conditions.append("""party in (select name from tabCustomer
+ conditions.append(
+ """party in (select name from tabCustomer
where exists(select name from `tabTerritory` where lft >= {0} and rgt <= {1}
- and name=tabCustomer.territory))""".format(lft, rgt))
+ and name=tabCustomer.territory))""".format(
+ lft, rgt
+ )
+ )
if self.filters.get("payment_terms_template"):
- conditions.append("party in (select name from tabCustomer where payment_terms=%(payment_terms_template)s)")
+ conditions.append(
+ "party in (select name from tabCustomer where payment_terms=%(payment_terms_template)s)"
+ )
if self.filters.get("sales_partner"):
- conditions.append("party in (select name from tabCustomer where default_sales_partner=%(sales_partner)s)")
+ conditions.append(
+ "party in (select name from tabCustomer where default_sales_partner=%(sales_partner)s)"
+ )
if self.filters.get("sales_person"):
- lft, rgt = frappe.db.get_value("Sales Person",
- self.filters.get("sales_person"), ["lft", "rgt"])
+ lft, rgt = frappe.db.get_value(
+ "Sales Person", self.filters.get("sales_person"), ["lft", "rgt"]
+ )
- conditions.append("""exists(select name from `tabSales Team` steam where
+ conditions.append(
+ """exists(select name from `tabSales Team` steam where
steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1})
and ((steam.parent = voucher_no and steam.parenttype = voucher_type)
or (steam.parent = against_voucher and steam.parenttype = against_voucher_type)
- or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt))
+ or (steam.parent = party and steam.parenttype = 'Customer')))""".format(
+ lft, rgt
+ )
+ )
if self.filters.party_type == "Supplier":
if self.filters.get("supplier_group"):
- conditions.append("""party in (select name from tabSupplier
- where supplier_group=%(supplier_group)s)""")
+ conditions.append(
+ """party in (select name from tabSupplier
+ where supplier_group=%(supplier_group)s)"""
+ )
return " and ".join(conditions)
def get_return_invoices(self):
doctype = "Sales Invoice" if self.filters.party_type == "Customer" else "Purchase Invoice"
- self.return_invoices = [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1,
- "posting_date": ["between", [self.filters.from_date, self.filters.to_date]]})]
+ self.return_invoices = [
+ d.name
+ for d in frappe.get_all(
+ doctype,
+ filters={
+ "is_return": 1,
+ "docstatus": 1,
+ "posting_date": ["between", [self.filters.from_date, self.filters.to_date]],
+ },
+ )
+ ]
def get_party_adjustment_amounts(self):
conditions = self.prepare_conditions()
- income_or_expense = "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
+ income_or_expense = (
+ "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
+ )
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
- round_off_account = frappe.get_cached_value('Company', self.filters.company, "round_off_account")
+ round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select
posting_date, account, party, voucher_type, voucher_no, debit, credit
from
@@ -257,7 +317,12 @@ class PartyLedgerSummaryReport(object):
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
)
- """.format(conditions=conditions, income_or_expense=income_or_expense), self.filters, as_dict=True)
+ """.format(
+ conditions=conditions, income_or_expense=income_or_expense
+ ),
+ self.filters,
+ as_dict=True,
+ )
self.party_adjustment_details = {}
self.party_adjustment_accounts = set()
@@ -299,6 +364,7 @@ class PartyLedgerSummaryReport(object):
self.party_adjustment_details[party].setdefault(account, 0)
self.party_adjustment_details[party][account] += amount
+
def execute(filters=None):
args = {
"party_type": "Customer",
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
index 3a51db8a97..1eb257ac85 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
@@ -361,7 +361,8 @@ class Deferred_Revenue_and_Expense_Report(object):
"fieldname": period.key,
"fieldtype": "Currency",
"read_only": 1,
- })
+ }
+ )
return columns
def generate_report_data(self):
@@ -408,11 +409,9 @@ class Deferred_Revenue_and_Expense_Report(object):
}
if self.filters.with_upcoming_postings:
- chart["data"]["datasets"].append({
- "name": "Expected",
- "chartType": "line",
- "values": [x.total for x in self.period_total]
- })
+ chart["data"]["datasets"].append(
+ {"name": "Expected", "chartType": "line", "values": [x.total for x in self.period_total]}
+ )
return chart
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
index 17475a77db..023ff225ee 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
@@ -322,6 +322,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
]
self.assertEqual(report.period_total, expected)
+
def create_company():
company = frappe.db.exists("Company", "_Test Company DR")
if not company:
diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
index 004d09250a..59914dc29a 100644
--- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
+++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
@@ -13,6 +13,7 @@ def execute(filters=None):
data = get_ordered_to_be_billed_data(args)
return columns, data
+
def get_column():
return [
{
@@ -20,90 +21,76 @@ def get_column():
"fieldname": "name",
"fieldtype": "Link",
"options": "Delivery Note",
- "width": 160
- },
- {
- "label": _("Date"),
- "fieldname": "date",
- "fieldtype": "Date",
- "width": 100
+ "width": 160,
},
+ {"label": _("Date"), "fieldname": "date", "fieldtype": "Date", "width": 100},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
- "width": 120
- },
- {
- "label": _("Customer Name"),
- "fieldname": "customer_name",
- "fieldtype": "Data",
- "width": 120
+ "width": 120,
},
+ {"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 120
+ "width": 120,
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 100,
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
},
{
"label": _("Billed Amount"),
"fieldname": "billed_amount",
"fieldtype": "Currency",
"width": 100,
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
},
{
"label": _("Returned Amount"),
"fieldname": "returned_amount",
"fieldtype": "Currency",
"width": 120,
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
},
{
"label": _("Pending Amount"),
"fieldname": "pending_amount",
"fieldtype": "Currency",
"width": 120,
- "options": "Company:company:default_currency"
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 120
+ "options": "Company:company:default_currency",
},
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 120},
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 120},
{
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
- "width": 120
+ "width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
- "width": 120
- }
+ "width": 120,
+ },
]
+
def get_args():
- return {'doctype': 'Delivery Note', 'party': 'customer',
- 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}
+ return {
+ "doctype": "Delivery Note",
+ "party": "customer",
+ "date": "posting_date",
+ "order": "name",
+ "order_by": "desc",
+ }
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
index 0c2b6cb4cb..8e8465cee9 100644
--- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
@@ -27,10 +27,12 @@ def execute(filters=None):
return columns, data
+
def get_data(filters, dimension_list):
company_currency = erpnext.get_company_currency(filters.company)
- acc = frappe.db.sql("""
+ acc = frappe.db.sql(
+ """
select
name, account_number, parent_account, lft, rgt, root_type,
report_type, account_name, include_in_gross, account_type, is_group
@@ -38,50 +40,62 @@ def get_data(filters, dimension_list):
`tabAccount`
where
company=%s
- order by lft""", (filters.company), as_dict=True)
+ order by lft""",
+ (filters.company),
+ as_dict=True,
+ )
if not acc:
return None
accounts, accounts_by_name, parent_children_map = filter_accounts(acc)
- min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount`
- where company=%s""", (filters.company))[0]
+ min_lft, max_rgt = frappe.db.sql(
+ """select min(lft), max(rgt) from `tabAccount`
+ where company=%s""",
+ (filters.company),
+ )[0]
- account = frappe.db.sql_list("""select name from `tabAccount`
- where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company))
+ account = frappe.db.sql_list(
+ """select name from `tabAccount`
+ where lft >= %s and rgt <= %s and company = %s""",
+ (min_lft, max_rgt, filters.company),
+ )
gl_entries_by_account = {}
set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_account)
- format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_list,
- frappe.scrub(filters.get('dimension')))
+ format_gl_entries(
+ gl_entries_by_account, accounts_by_name, dimension_list, frappe.scrub(filters.get("dimension"))
+ )
accumulate_values_into_parents(accounts, accounts_by_name, dimension_list)
out = prepare_data(accounts, filters, company_currency, dimension_list)
out = filter_out_zero_value_rows(out, parent_children_map)
return out
+
def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_account):
- condition = get_condition(filters.get('dimension'))
+ condition = get_condition(filters.get("dimension"))
if account:
- condition += " and account in ({})"\
- .format(", ".join([frappe.db.escape(d) for d in account]))
+ condition += " and account in ({})".format(", ".join([frappe.db.escape(d) for d in account]))
gl_filters = {
"company": filters.get("company"),
"from_date": filters.get("from_date"),
"to_date": filters.get("to_date"),
- "finance_book": cstr(filters.get("finance_book"))
+ "finance_book": cstr(filters.get("finance_book")),
}
- gl_filters['dimensions'] = set(dimension_list)
+ gl_filters["dimensions"] = set(dimension_list)
if filters.get("include_default_book_entries"):
- gl_filters["company_fb"] = frappe.db.get_value("Company",
- filters.company, 'default_finance_book')
+ gl_filters["company_fb"] = frappe.db.get_value(
+ "Company", filters.company, "default_finance_book"
+ )
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select
posting_date, account, {dimension}, debit, credit, is_opening, fiscal_year,
debit_in_account_currency, credit_in_account_currency, account_currency
@@ -94,11 +108,16 @@ def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_ac
and posting_date <= %(to_date)s
and is_cancelled = 0
order by account, posting_date""".format(
- dimension = frappe.scrub(filters.get('dimension')), condition=condition), gl_filters, as_dict=True) #nosec
+ dimension=frappe.scrub(filters.get("dimension")), condition=condition
+ ),
+ gl_filters,
+ as_dict=True,
+ ) # nosec
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry)
+
def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_list, dimension_type):
for entries in gl_entries_by_account.values():
@@ -106,13 +125,17 @@ def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_list, d
d = accounts_by_name.get(entry.account)
if not d:
frappe.msgprint(
- _("Could not retrieve information for {0}.").format(entry.account), title="Error",
- raise_exception=1
+ _("Could not retrieve information for {0}.").format(entry.account),
+ title="Error",
+ raise_exception=1,
)
for dimension in dimension_list:
if dimension == entry.get(dimension_type):
- d[frappe.scrub(dimension)] = d.get(frappe.scrub(dimension), 0.0) + flt(entry.debit) - flt(entry.credit)
+ d[frappe.scrub(dimension)] = (
+ d.get(frappe.scrub(dimension), 0.0) + flt(entry.debit) - flt(entry.credit)
+ )
+
def prepare_data(accounts, filters, company_currency, dimension_list):
data = []
@@ -127,8 +150,9 @@ def prepare_data(accounts, filters, company_currency, dimension_list):
"from_date": filters.from_date,
"to_date": filters.to_date,
"currency": company_currency,
- "account_name": ('{} - {}'.format(d.account_number, d.account_name)
- if d.account_number else d.account_name)
+ "account_name": (
+ "{} - {}".format(d.account_number, d.account_name) if d.account_number else d.account_name
+ ),
}
for dimension in dimension_list:
@@ -145,13 +169,16 @@ def prepare_data(accounts, filters, company_currency, dimension_list):
return data
+
def accumulate_values_into_parents(accounts, accounts_by_name, dimension_list):
"""accumulate children's values in parent accounts"""
for d in reversed(accounts):
if d.parent_account:
for dimension in dimension_list:
- accounts_by_name[d.parent_account][frappe.scrub(dimension)] = \
- accounts_by_name[d.parent_account].get(frappe.scrub(dimension), 0.0) + d.get(frappe.scrub(dimension), 0.0)
+ accounts_by_name[d.parent_account][frappe.scrub(dimension)] = accounts_by_name[
+ d.parent_account
+ ].get(frappe.scrub(dimension), 0.0) + d.get(frappe.scrub(dimension), 0.0)
+
def get_condition(dimension):
conditions = []
@@ -160,46 +187,54 @@ def get_condition(dimension):
return " and {}".format(" and ".join(conditions)) if conditions else ""
+
def get_dimensions(filters):
- meta = frappe.get_meta(filters.get('dimension'), cached=False)
+ meta = frappe.get_meta(filters.get("dimension"), cached=False)
query_filters = {}
- if meta.has_field('company'):
- query_filters = {'company': filters.get('company')}
+ if meta.has_field("company"):
+ query_filters = {"company": filters.get("company")}
+
+ return frappe.get_all(filters.get("dimension"), filters=query_filters, pluck="name")
- return frappe.get_all(filters.get('dimension'), filters=query_filters, pluck='name')
def get_columns(dimension_list):
- columns = [{
- "fieldname": "account",
- "label": _("Account"),
- "fieldtype": "Link",
- "options": "Account",
- "width": 300
- },
- {
- "fieldname": "currency",
- "label": _("Currency"),
- "fieldtype": "Link",
- "options": "Currency",
- "hidden": 1
- }]
+ columns = [
+ {
+ "fieldname": "account",
+ "label": _("Account"),
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 300,
+ },
+ {
+ "fieldname": "currency",
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "options": "Currency",
+ "hidden": 1,
+ },
+ ]
for dimension in dimension_list:
- columns.append({
- "fieldname": frappe.scrub(dimension),
- "label": dimension,
- "fieldtype": "Currency",
- "options": "currency",
- "width": 150
- })
+ columns.append(
+ {
+ "fieldname": frappe.scrub(dimension),
+ "label": dimension,
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 150,
+ }
+ )
- columns.append({
+ columns.append(
+ {
"fieldname": "total",
"label": "Total",
"fieldtype": "Currency",
"options": "currency",
- "width": 150
- })
+ "width": 150,
+ }
+ )
return columns
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index db28cdfdd3..56d68e1fa9 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -18,12 +18,22 @@ from erpnext.accounts.report.utils import convert_to_presentation_currency, get_
from erpnext.accounts.utils import get_fiscal_year
-def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_end_date, filter_based_on, periodicity, accumulated_values=False,
- company=None, reset_period_on_fy_change=True, ignore_fiscal_year=False):
+def get_period_list(
+ from_fiscal_year,
+ to_fiscal_year,
+ period_start_date,
+ period_end_date,
+ filter_based_on,
+ periodicity,
+ accumulated_values=False,
+ company=None,
+ reset_period_on_fy_change=True,
+ ignore_fiscal_year=False,
+):
"""Get a list of dict {"from_date": from_date, "to_date": to_date, "key": key, "label": label}
- Periodicity can be (Yearly, Quarterly, Monthly)"""
+ Periodicity can be (Yearly, Quarterly, Monthly)"""
- if filter_based_on == 'Fiscal Year':
+ if filter_based_on == "Fiscal Year":
fiscal_year = get_fiscal_year_data(from_fiscal_year, to_fiscal_year)
validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year)
year_start_date = getdate(fiscal_year.year_start_date)
@@ -33,12 +43,7 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
year_start_date = getdate(period_start_date)
year_end_date = getdate(period_end_date)
- months_to_add = {
- "Yearly": 12,
- "Half-Yearly": 6,
- "Quarterly": 3,
- "Monthly": 1
- }[periodicity]
+ months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_list = []
@@ -46,11 +51,9 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
months = get_months(year_start_date, year_end_date)
for i in range(cint(math.ceil(months / months_to_add))):
- period = frappe._dict({
- "from_date": start_date
- })
+ period = frappe._dict({"from_date": start_date})
- if i==0 and filter_based_on == 'Date Range':
+ if i == 0 and filter_based_on == "Date Range":
to_date = add_months(get_first_day(start_date), months_to_add)
else:
to_date = add_months(start_date, months_to_add)
@@ -90,32 +93,38 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
else:
label = get_label(periodicity, period_list[0].from_date, opts["to_date"])
- opts.update({
- "key": key.replace(" ", "_").replace("-", "_"),
- "label": label,
- "year_start_date": year_start_date,
- "year_end_date": year_end_date
- })
+ opts.update(
+ {
+ "key": key.replace(" ", "_").replace("-", "_"),
+ "label": label,
+ "year_start_date": year_start_date,
+ "year_end_date": year_end_date,
+ }
+ )
return period_list
def get_fiscal_year_data(from_fiscal_year, to_fiscal_year):
- fiscal_year = frappe.db.sql("""select min(year_start_date) as year_start_date,
+ fiscal_year = frappe.db.sql(
+ """select min(year_start_date) as year_start_date,
max(year_end_date) as year_end_date from `tabFiscal Year` where
name between %(from_fiscal_year)s and %(to_fiscal_year)s""",
- {'from_fiscal_year': from_fiscal_year, 'to_fiscal_year': to_fiscal_year}, as_dict=1)
+ {"from_fiscal_year": from_fiscal_year, "to_fiscal_year": to_fiscal_year},
+ as_dict=1,
+ )
return fiscal_year[0] if fiscal_year else {}
def validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year):
- if not fiscal_year.get('year_start_date') or not fiscal_year.get('year_end_date'):
+ if not fiscal_year.get("year_start_date") or not fiscal_year.get("year_end_date"):
frappe.throw(_("Start Year and End Year are mandatory"))
- if getdate(fiscal_year.get('year_end_date')) < getdate(fiscal_year.get('year_start_date')):
+ if getdate(fiscal_year.get("year_end_date")) < getdate(fiscal_year.get("year_start_date")):
frappe.throw(_("End Year cannot be before Start Year"))
+
def validate_dates(from_date, to_date):
if not from_date or not to_date:
frappe.throw(_("From Date and To Date are mandatory"))
@@ -123,6 +132,7 @@ def validate_dates(from_date, to_date):
if to_date < from_date:
frappe.throw(_("To Date cannot be less than From Date"))
+
def get_months(start_date, end_date):
diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month)
return diff + 1
@@ -141,9 +151,17 @@ def get_label(periodicity, from_date, to_date):
def get_data(
- company, root_type, balance_must_be, period_list, filters=None,
- accumulated_values=1, only_current_fiscal_year=True, ignore_closing_entries=False,
- ignore_accumulated_values_for_fy=False , total = True):
+ company,
+ root_type,
+ balance_must_be,
+ period_list,
+ filters=None,
+ accumulated_values=1,
+ only_current_fiscal_year=True,
+ ignore_closing_entries=False,
+ ignore_accumulated_values_for_fy=False,
+ total=True,
+):
accounts = get_accounts(company, root_type)
if not accounts:
@@ -154,19 +172,31 @@ def get_data(
company_currency = get_appropriate_currency(company, filters)
gl_entries_by_account = {}
- for root in frappe.db.sql("""select lft, rgt from tabAccount
- where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1):
+ for root in frappe.db.sql(
+ """select lft, rgt from tabAccount
+ where root_type=%s and ifnull(parent_account, '') = ''""",
+ root_type,
+ as_dict=1,
+ ):
set_gl_entries_by_account(
company,
period_list[0]["year_start_date"] if only_current_fiscal_year else None,
period_list[-1]["to_date"],
- root.lft, root.rgt, filters,
- gl_entries_by_account, ignore_closing_entries=ignore_closing_entries
+ root.lft,
+ root.rgt,
+ filters,
+ gl_entries_by_account,
+ ignore_closing_entries=ignore_closing_entries,
)
calculate_values(
- accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy)
+ accounts_by_name,
+ gl_entries_by_account,
+ period_list,
+ accumulated_values,
+ ignore_accumulated_values_for_fy,
+ )
accumulate_values_into_parents(accounts, accounts_by_name, period_list)
out = prepare_data(accounts, balance_must_be, period_list, company_currency)
out = filter_out_zero_value_rows(out, parent_children_map)
@@ -181,26 +211,32 @@ def get_appropriate_currency(company, filters=None):
if filters and filters.get("presentation_currency"):
return filters["presentation_currency"]
else:
- return frappe.get_cached_value('Company', company, "default_currency")
+ return frappe.get_cached_value("Company", company, "default_currency")
def calculate_values(
- accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy):
+ accounts_by_name,
+ gl_entries_by_account,
+ period_list,
+ accumulated_values,
+ ignore_accumulated_values_for_fy,
+):
for entries in gl_entries_by_account.values():
for entry in entries:
d = accounts_by_name.get(entry.account)
if not d:
frappe.msgprint(
- _("Could not retrieve information for {0}.").format(entry.account), title="Error",
- raise_exception=1
+ _("Could not retrieve information for {0}.").format(entry.account),
+ title="Error",
+ raise_exception=1,
)
for period in period_list:
# check if posting date is within the period
if entry.posting_date <= period.to_date:
- if (accumulated_values or entry.posting_date >= period.from_date) and \
- (not ignore_accumulated_values_for_fy or
- entry.fiscal_year == period.to_date_fiscal_year):
+ if (accumulated_values or entry.posting_date >= period.from_date) and (
+ not ignore_accumulated_values_for_fy or entry.fiscal_year == period.to_date_fiscal_year
+ ):
d[period.key] = d.get(period.key, 0.0) + flt(entry.debit) - flt(entry.credit)
if entry.posting_date < period_list[0].year_start_date:
@@ -212,11 +248,13 @@ def accumulate_values_into_parents(accounts, accounts_by_name, period_list):
for d in reversed(accounts):
if d.parent_account:
for period in period_list:
- accounts_by_name[d.parent_account][period.key] = \
- accounts_by_name[d.parent_account].get(period.key, 0.0) + d.get(period.key, 0.0)
+ accounts_by_name[d.parent_account][period.key] = accounts_by_name[d.parent_account].get(
+ period.key, 0.0
+ ) + d.get(period.key, 0.0)
- accounts_by_name[d.parent_account]["opening_balance"] = \
- accounts_by_name[d.parent_account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
+ accounts_by_name[d.parent_account]["opening_balance"] = accounts_by_name[d.parent_account].get(
+ "opening_balance", 0.0
+ ) + d.get("opening_balance", 0.0)
def prepare_data(accounts, balance_must_be, period_list, company_currency):
@@ -228,20 +266,25 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency):
# add to output
has_value = False
total = 0
- row = frappe._dict({
- "account": _(d.name),
- "parent_account": _(d.parent_account) if d.parent_account else '',
- "indent": flt(d.indent),
- "year_start_date": year_start_date,
- "year_end_date": year_end_date,
- "currency": company_currency,
- "include_in_gross": d.include_in_gross,
- "account_type": d.account_type,
- "is_group": d.is_group,
- "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be=="Debit" else -1),
- "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
- if d.account_number else _(d.account_name))
- })
+ row = frappe._dict(
+ {
+ "account": _(d.name),
+ "parent_account": _(d.parent_account) if d.parent_account else "",
+ "indent": flt(d.indent),
+ "year_start_date": year_start_date,
+ "year_end_date": year_end_date,
+ "currency": company_currency,
+ "include_in_gross": d.include_in_gross,
+ "account_type": d.account_type,
+ "is_group": d.is_group,
+ "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1),
+ "account_name": (
+ "%s - %s" % (_(d.account_number), _(d.account_name))
+ if d.account_number
+ else _(d.account_name)
+ ),
+ }
+ )
for period in period_list:
if d.get(period.key) and balance_must_be == "Credit":
# change sign based on Debit or Credit, since calculation is done using (debit - credit)
@@ -283,7 +326,7 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency
"account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"currency": company_currency,
- "opening_balance": 0.0
+ "opening_balance": 0.0,
}
for row in out:
@@ -306,10 +349,14 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency
def get_accounts(company, root_type):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select name, account_number, parent_account, lft, rgt, root_type, report_type, account_name, include_in_gross, account_type, is_group, lft, rgt
from `tabAccount`
- where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
+ where company=%s and root_type=%s order by lft""",
+ (company, root_type),
+ as_dict=True,
+ )
def filter_accounts(accounts, depth=20):
@@ -324,7 +371,7 @@ def filter_accounts(accounts, depth=20):
def add_to_list(parent, level):
if level < depth:
children = parent_children_map.get(parent) or []
- sort_accounts(children, is_root=True if parent==None else False)
+ sort_accounts(children, is_root=True if parent == None else False)
for child in children:
child.indent = level
@@ -340,7 +387,7 @@ def sort_accounts(accounts, is_root=False, key="name"):
"""Sort root types as Asset, Liability, Equity, Income, Expense"""
def compare_accounts(a, b):
- if re.split(r'\W+', a[key])[0].isdigit():
+ if re.split(r"\W+", a[key])[0].isdigit():
# if chart of accounts is numbered, then sort by number
return int(a[key] > b[key]) - int(a[key] < b[key])
elif is_root:
@@ -357,50 +404,64 @@ def sort_accounts(accounts, is_root=False, key="name"):
return int(a[key] > b[key]) - int(a[key] < b[key])
return 1
- accounts.sort(key = functools.cmp_to_key(compare_accounts))
+ accounts.sort(key=functools.cmp_to_key(compare_accounts))
+
def set_gl_entries_by_account(
- company, from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account, ignore_closing_entries=False):
+ company,
+ from_date,
+ to_date,
+ root_lft,
+ root_rgt,
+ filters,
+ gl_entries_by_account,
+ ignore_closing_entries=False,
+):
"""Returns a dict like { "account": [gl entries], ... }"""
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
- accounts = frappe.db.sql_list("""select name from `tabAccount`
- where lft >= %s and rgt <= %s and company = %s""", (root_lft, root_rgt, company))
+ accounts = frappe.db.sql_list(
+ """select name from `tabAccount`
+ where lft >= %s and rgt <= %s and company = %s""",
+ (root_lft, root_rgt, company),
+ )
if accounts:
- additional_conditions += " and account in ({})"\
- .format(", ".join(frappe.db.escape(d) for d in accounts))
+ additional_conditions += " and account in ({})".format(
+ ", ".join(frappe.db.escape(d) for d in accounts)
+ )
gl_filters = {
"company": company,
"from_date": from_date,
"to_date": to_date,
- "finance_book": cstr(filters.get("finance_book"))
+ "finance_book": cstr(filters.get("finance_book")),
}
if filters.get("include_default_book_entries"):
- gl_filters["company_fb"] = frappe.db.get_value("Company",
- company, 'default_finance_book')
+ gl_filters["company_fb"] = frappe.db.get_value("Company", company, "default_finance_book")
for key, value in filters.items():
if value:
- gl_filters.update({
- key: value
- })
+ gl_filters.update({key: value})
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select posting_date, account, debit, credit, is_opening, fiscal_year,
debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
and is_cancelled = 0""".format(
- additional_conditions=additional_conditions), gl_filters, as_dict=True
+ additional_conditions=additional_conditions
+ ),
+ gl_filters,
+ as_dict=True,
)
- if filters and filters.get('presentation_currency'):
- convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company'))
+ if filters and filters.get("presentation_currency"):
+ convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get("company"))
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry)
@@ -431,25 +492,29 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions.append("cost_center in %(cost_center)s")
if filters.get("include_default_book_entries"):
- additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)")
+ additional_conditions.append(
+ "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
+ )
else:
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
if accounting_dimensions:
for dimension in accounting_dimensions:
if filters.get(dimension.fieldname):
- if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
- filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
- filters.get(dimension.fieldname))
+ if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
+ filters[dimension.fieldname] = get_dimension_with_children(
+ dimension.document_type, filters.get(dimension.fieldname)
+ )
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
else:
additional_conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
+
def get_cost_centers_with_children(cost_centers):
if not isinstance(cost_centers, list):
- cost_centers = [d.strip() for d in cost_centers.strip().split(',') if d]
+ cost_centers = [d.strip() for d in cost_centers.strip().split(",") if d]
all_cost_centers = []
for d in cost_centers:
@@ -462,45 +527,50 @@ def get_cost_centers_with_children(cost_centers):
return list(set(all_cost_centers))
+
def get_columns(periodicity, period_list, accumulated_values=1, company=None):
- columns = [{
- "fieldname": "account",
- "label": _("Account"),
- "fieldtype": "Link",
- "options": "Account",
- "width": 300
- }]
- if company:
- columns.append({
- "fieldname": "currency",
- "label": _("Currency"),
+ columns = [
+ {
+ "fieldname": "account",
+ "label": _("Account"),
"fieldtype": "Link",
- "options": "Currency",
- "hidden": 1
- })
+ "options": "Account",
+ "width": 300,
+ }
+ ]
+ if company:
+ columns.append(
+ {
+ "fieldname": "currency",
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "options": "Currency",
+ "hidden": 1,
+ }
+ )
for period in period_list:
- columns.append({
- "fieldname": period.key,
- "label": period.label,
- "fieldtype": "Currency",
- "options": "currency",
- "width": 150
- })
- if periodicity!="Yearly":
- if not accumulated_values:
- columns.append({
- "fieldname": "total",
- "label": _("Total"),
+ columns.append(
+ {
+ "fieldname": period.key,
+ "label": period.label,
"fieldtype": "Currency",
- "width": 150
- })
+ "options": "currency",
+ "width": 150,
+ }
+ )
+ if periodicity != "Yearly":
+ if not accumulated_values:
+ columns.append(
+ {"fieldname": "total", "label": _("Total"), "fieldtype": "Currency", "width": 150}
+ )
return columns
+
def get_filtered_list_for_consolidated_report(filters, period_list):
filtered_summary_list = []
for period in period_list:
- if period == filters.get('company'):
+ if period == filters.get("company"):
filtered_summary_list.append(period)
return filtered_summary_list
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 4ff0297dba..636d624506 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -20,20 +20,20 @@ from erpnext.accounts.utils import get_account_currency
# to cache translations
TRANSLATIONS = frappe._dict()
+
def execute(filters=None):
if not filters:
return [], []
account_details = {}
- if filters and filters.get('print_in_account_currency') and \
- not filters.get('account'):
+ if filters and filters.get("print_in_account_currency") and not filters.get("account"):
frappe.throw(_("Select an account to print in account currency"))
for acc in frappe.db.sql("""select name, is_group from tabAccount""", as_dict=1):
account_details.setdefault(acc.name, acc)
- if filters.get('party'):
+ if filters.get("party"):
filters.party = frappe.parse_json(filters.get("party"))
validate_filters(filters, account_details)
@@ -50,46 +50,45 @@ def execute(filters=None):
return columns, res
+
def update_translations():
TRANSLATIONS.update(
- dict(
- OPENING = _('Opening'),
- TOTAL = _('Total'),
- CLOSING_TOTAL = _('Closing (Opening + Total)')
- )
+ dict(OPENING=_("Opening"), TOTAL=_("Total"), CLOSING_TOTAL=_("Closing (Opening + Total)"))
)
+
def validate_filters(filters, account_details):
if not filters.get("company"):
frappe.throw(_("{0} is mandatory").format(_("Company")))
if not filters.get("from_date") and not filters.get("to_date"):
- frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
+ frappe.throw(
+ _("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))
+ )
- if filters.get('account'):
- filters.account = frappe.parse_json(filters.get('account'))
+ if filters.get("account"):
+ filters.account = frappe.parse_json(filters.get("account"))
for account in filters.account:
if not account_details.get(account):
frappe.throw(_("Account {0} does not exists").format(account))
- if (filters.get("account") and filters.get("group_by") == 'Group by Account'):
- filters.account = frappe.parse_json(filters.get('account'))
+ if filters.get("account") and filters.get("group_by") == "Group by Account":
+ filters.account = frappe.parse_json(filters.get("account"))
for account in filters.account:
if account_details[account].is_group == 0:
frappe.throw(_("Can not filter based on Child Account, if grouped by Account"))
- if (filters.get("voucher_no")
- and filters.get("group_by") in ['Group by Voucher']):
+ if filters.get("voucher_no") and filters.get("group_by") in ["Group by Voucher"]:
frappe.throw(_("Can not filter based on Voucher No, if grouped by Voucher"))
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
- if filters.get('project'):
- filters.project = frappe.parse_json(filters.get('project'))
+ if filters.get("project"):
+ filters.project = frappe.parse_json(filters.get("project"))
- if filters.get('cost_center'):
- filters.cost_center = frappe.parse_json(filters.get('cost_center'))
+ if filters.get("cost_center"):
+ filters.cost_center = frappe.parse_json(filters.get("cost_center"))
def validate_party(filters):
@@ -100,9 +99,12 @@ def validate_party(filters):
if not frappe.db.exists(party_type, d):
frappe.throw(_("Invalid {0}: {1}").format(party_type, d))
+
def set_account_currency(filters):
- if filters.get("account") or (filters.get('party') and len(filters.party) == 1):
- filters["company_currency"] = frappe.get_cached_value('Company', filters.company, "default_currency")
+ if filters.get("account") or (filters.get("party") and len(filters.party) == 1):
+ filters["company_currency"] = frappe.get_cached_value(
+ "Company", filters.company, "default_currency"
+ )
account_currency = None
if filters.get("account"):
@@ -121,17 +123,19 @@ def set_account_currency(filters):
elif filters.get("party"):
gle_currency = frappe.db.get_value(
- "GL Entry", {
- "party_type": filters.party_type, "party": filters.party[0], "company": filters.company
- },
- "account_currency"
+ "GL Entry",
+ {"party_type": filters.party_type, "party": filters.party[0], "company": filters.company},
+ "account_currency",
)
if gle_currency:
account_currency = gle_currency
else:
- account_currency = (None if filters.party_type in ["Employee", "Student", "Shareholder", "Member"] else
- frappe.db.get_value(filters.party_type, filters.party[0], "default_currency"))
+ account_currency = (
+ None
+ if filters.party_type in ["Employee", "Student", "Shareholder", "Member"]
+ else frappe.db.get_value(filters.party_type, filters.party[0], "default_currency")
+ )
filters["account_currency"] = account_currency or filters.company_currency
if filters.account_currency != filters.company_currency and not filters.presentation_currency:
@@ -139,6 +143,7 @@ def set_account_currency(filters):
return filters
+
def get_result(filters, account_details):
accounting_dimensions = []
if filters.get("include_dimensions"):
@@ -146,13 +151,13 @@ def get_result(filters, account_details):
gl_entries = get_gl_entries(filters, accounting_dimensions)
- data = get_data_with_opening_closing(filters, account_details,
- accounting_dimensions, gl_entries)
+ data = get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries)
result = get_result_as_list(data, filters)
return result
+
def get_gl_entries(filters, accounting_dimensions):
currency_map = get_currency(filters)
select_fields = """, debit, credit, debit_in_account_currency,
@@ -169,14 +174,16 @@ def get_gl_entries(filters, accounting_dimensions):
order_by_statement = "order by account, posting_date, creation"
if filters.get("include_default_book_entries"):
- filters['company_fb'] = frappe.db.get_value("Company",
- filters.get("company"), 'default_finance_book')
+ filters["company_fb"] = frappe.db.get_value(
+ "Company", filters.get("company"), "default_finance_book"
+ )
dimension_fields = ""
if accounting_dimensions:
- dimension_fields = ', '.join(accounting_dimensions) + ','
+ dimension_fields = ", ".join(accounting_dimensions) + ","
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select
name as gl_entry, posting_date, account, party_type, party,
voucher_type, voucher_no, {dimension_fields}
@@ -187,12 +194,17 @@ def get_gl_entries(filters, accounting_dimensions):
where company=%(company)s {conditions}
{order_by_statement}
""".format(
- dimension_fields=dimension_fields, select_fields=select_fields,
- conditions=get_conditions(filters), order_by_statement=order_by_statement
- ), filters, as_dict=1)
+ dimension_fields=dimension_fields,
+ select_fields=select_fields,
+ conditions=get_conditions(filters),
+ order_by_statement=order_by_statement,
+ ),
+ filters,
+ as_dict=1,
+ )
- if filters.get('presentation_currency'):
- return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company'))
+ if filters.get("presentation_currency"):
+ return convert_to_presentation_currency(gl_entries, currency_map, filters.get("company"))
else:
return gl_entries
@@ -220,8 +232,11 @@ def get_conditions(filters):
if filters.get("party"):
conditions.append("party in %(party)s")
- if not (filters.get("account") or filters.get("party") or
- filters.get("group_by") in ["Group by Account", "Group by Party"]):
+ if not (
+ filters.get("account")
+ or filters.get("party")
+ or filters.get("group_by") in ["Group by Account", "Group by Party"]
+ ):
conditions.append("posting_date >=%(from_date)s")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
@@ -231,7 +246,9 @@ def get_conditions(filters):
if filters.get("finance_book"):
if filters.get("include_default_book_entries"):
- conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)")
+ conditions.append(
+ "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
+ )
else:
conditions.append("finance_book in (%(finance_book)s)")
@@ -239,6 +256,7 @@ def get_conditions(filters):
conditions.append("is_cancelled = 0")
from frappe.desk.reportview import build_match_conditions
+
match_conditions = build_match_conditions("GL Entry")
if match_conditions:
@@ -251,18 +269,20 @@ def get_conditions(filters):
for dimension in accounting_dimensions:
if not dimension.disabled:
if filters.get(dimension.fieldname):
- if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
- filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
- filters.get(dimension.fieldname))
+ if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
+ filters[dimension.fieldname] = get_dimension_with_children(
+ dimension.document_type, filters.get(dimension.fieldname)
+ )
conditions.append("{0} in %({0})s".format(dimension.fieldname))
else:
conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
return "and {}".format(" and ".join(conditions)) if conditions else ""
+
def get_accounts_with_children(accounts):
if not isinstance(accounts, list):
- accounts = [d.strip() for d in accounts.strip().split(',') if d]
+ accounts = [d.strip() for d in accounts.strip().split(",") if d]
all_accounts = []
for d in accounts:
@@ -275,6 +295,7 @@ def get_accounts_with_children(accounts):
return list(set(all_accounts))
+
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
data = []
@@ -285,7 +306,7 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
# Opening for filtered account
data.append(totals.opening)
- if filters.get("group_by") != 'Group by Voucher (Consolidated)':
+ if filters.get("group_by") != "Group by Voucher (Consolidated)":
for acc, acc_dict in gle_map.items():
# acc
if acc_dict.entries:
@@ -314,6 +335,7 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
return data
+
def get_totals_dict():
def _get_debit_credit_dict(label):
return _dict(
@@ -321,25 +343,28 @@ def get_totals_dict():
debit=0.0,
credit=0.0,
debit_in_account_currency=0.0,
- credit_in_account_currency=0.0
+ credit_in_account_currency=0.0,
)
+
return _dict(
- opening = _get_debit_credit_dict(TRANSLATIONS.OPENING),
- total = _get_debit_credit_dict(TRANSLATIONS.TOTAL),
- closing = _get_debit_credit_dict(TRANSLATIONS.CLOSING_TOTAL)
+ opening=_get_debit_credit_dict(TRANSLATIONS.OPENING),
+ total=_get_debit_credit_dict(TRANSLATIONS.TOTAL),
+ closing=_get_debit_credit_dict(TRANSLATIONS.CLOSING_TOTAL),
)
+
def group_by_field(group_by):
- if group_by == 'Group by Party':
- return 'party'
- elif group_by in ['Group by Voucher (Consolidated)', 'Group by Account']:
- return 'account'
+ if group_by == "Group by Party":
+ return "party"
+ elif group_by in ["Group by Voucher (Consolidated)", "Group by Account"]:
+ return "account"
else:
- return 'voucher_no'
+ return "voucher_no"
+
def initialize_gle_map(gl_entries, filters):
gle_map = OrderedDict()
- group_by = group_by_field(filters.get('group_by'))
+ group_by = group_by_field(filters.get("group_by"))
for gle in gl_entries:
gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[]))
@@ -350,11 +375,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
totals = get_totals_dict()
entries = []
consolidated_gle = OrderedDict()
- group_by = group_by_field(filters.get('group_by'))
- group_by_voucher_consolidated = filters.get("group_by") == 'Group by Voucher (Consolidated)'
+ group_by = group_by_field(filters.get("group_by"))
+ group_by_voucher_consolidated = filters.get("group_by") == "Group by Voucher (Consolidated)"
- if filters.get('show_net_values_in_party_account'):
- account_type_map = get_account_type_map(filters.get('company'))
+ if filters.get("show_net_values_in_party_account"):
+ account_type_map = get_account_type_map(filters.get("company"))
def update_value_in_dict(data, key, gle):
data[key].debit += gle.debit
@@ -363,26 +388,28 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
data[key].debit_in_account_currency += gle.debit_in_account_currency
data[key].credit_in_account_currency += gle.credit_in_account_currency
- if filters.get('show_net_values_in_party_account') and \
- account_type_map.get(data[key].account) in ('Receivable', 'Payable'):
+ if filters.get("show_net_values_in_party_account") and account_type_map.get(
+ data[key].account
+ ) in ("Receivable", "Payable"):
net_value = data[key].debit - data[key].credit
- net_value_in_account_currency = data[key].debit_in_account_currency \
- - data[key].credit_in_account_currency
+ net_value_in_account_currency = (
+ data[key].debit_in_account_currency - data[key].credit_in_account_currency
+ )
if net_value < 0:
- dr_or_cr = 'credit'
- rev_dr_or_cr = 'debit'
+ dr_or_cr = "credit"
+ rev_dr_or_cr = "debit"
else:
- dr_or_cr = 'debit'
- rev_dr_or_cr = 'credit'
+ dr_or_cr = "debit"
+ rev_dr_or_cr = "credit"
data[key][dr_or_cr] = abs(net_value)
- data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency)
+ data[key][dr_or_cr + "_in_account_currency"] = abs(net_value_in_account_currency)
data[key][rev_dr_or_cr] = 0
- data[key][rev_dr_or_cr+'_in_account_currency'] = 0
+ data[key][rev_dr_or_cr + "_in_account_currency"] = 0
if data[key].against_voucher and gle.against_voucher:
- data[key].against_voucher += ', ' + gle.against_voucher
+ data[key].against_voucher += ", " + gle.against_voucher
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
show_opening_entries = filters.get("show_opening_entries")
@@ -390,20 +417,20 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
for gle in gl_entries:
group_by_value = gle.get(group_by)
- if (gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries)):
+ if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
if not group_by_voucher_consolidated:
- update_value_in_dict(gle_map[group_by_value].totals, 'opening', gle)
- update_value_in_dict(gle_map[group_by_value].totals, 'closing', gle)
+ update_value_in_dict(gle_map[group_by_value].totals, "opening", gle)
+ update_value_in_dict(gle_map[group_by_value].totals, "closing", gle)
- update_value_in_dict(totals, 'opening', gle)
- update_value_in_dict(totals, 'closing', gle)
+ update_value_in_dict(totals, "opening", gle)
+ update_value_in_dict(totals, "closing", gle)
elif gle.posting_date <= to_date:
if not group_by_voucher_consolidated:
- update_value_in_dict(gle_map[group_by_value].totals, 'total', gle)
- update_value_in_dict(gle_map[group_by_value].totals, 'closing', gle)
- update_value_in_dict(totals, 'total', gle)
- update_value_in_dict(totals, 'closing', gle)
+ update_value_in_dict(gle_map[group_by_value].totals, "total", gle)
+ update_value_in_dict(gle_map[group_by_value].totals, "closing", gle)
+ update_value_in_dict(totals, "total", gle)
+ update_value_in_dict(totals, "closing", gle)
gle_map[group_by_value].entries.append(gle)
@@ -421,47 +448,58 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
update_value_in_dict(consolidated_gle, key, gle)
for key, value in consolidated_gle.items():
- update_value_in_dict(totals, 'total', value)
- update_value_in_dict(totals, 'closing', value)
+ update_value_in_dict(totals, "total", value)
+ update_value_in_dict(totals, "closing", value)
entries.append(value)
return totals, entries
+
def get_account_type_map(company):
- account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'],
- filters={'company': company}, as_list=1))
+ account_type_map = frappe._dict(
+ frappe.get_all(
+ "Account", fields=["name", "account_type"], filters={"company": company}, as_list=1
+ )
+ )
return account_type_map
+
def get_result_as_list(data, filters):
balance, balance_in_account_currency = 0, 0
inv_details = get_supplier_invoice_details()
for d in data:
- if not d.get('posting_date'):
+ if not d.get("posting_date"):
balance, balance_in_account_currency = 0, 0
- balance = get_balance(d, balance, 'debit', 'credit')
- d['balance'] = balance
+ balance = get_balance(d, balance, "debit", "credit")
+ d["balance"] = balance
- d['account_currency'] = filters.account_currency
- d['bill_no'] = inv_details.get(d.get('against_voucher'), '')
+ d["account_currency"] = filters.account_currency
+ d["bill_no"] = inv_details.get(d.get("against_voucher"), "")
return data
+
def get_supplier_invoice_details():
inv_details = {}
- for d in frappe.db.sql(""" select name, bill_no from `tabPurchase Invoice`
- where docstatus = 1 and bill_no is not null and bill_no != '' """, as_dict=1):
+ for d in frappe.db.sql(
+ """ select name, bill_no from `tabPurchase Invoice`
+ where docstatus = 1 and bill_no is not null and bill_no != '' """,
+ as_dict=1,
+ ):
inv_details[d.name] = d.bill_no
return inv_details
+
def get_balance(row, balance, debit_field, credit_field):
- balance += (row.get(debit_field, 0) - row.get(credit_field, 0))
+ balance += row.get(debit_field, 0) - row.get(credit_field, 0)
return balance
+
def get_columns(filters):
if filters.get("presentation_currency"):
currency = filters["presentation_currency"]
@@ -478,113 +516,70 @@ def get_columns(filters):
"fieldname": "gl_entry",
"fieldtype": "Link",
"options": "GL Entry",
- "hidden": 1
- },
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 90
+ "hidden": 1,
},
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 90},
{
"label": _("Account"),
"fieldname": "account",
"fieldtype": "Link",
"options": "Account",
- "width": 180
+ "width": 180,
},
{
"label": _("Debit ({0})").format(currency),
"fieldname": "debit",
"fieldtype": "Float",
- "width": 100
+ "width": 100,
},
{
"label": _("Credit ({0})").format(currency),
"fieldname": "credit",
"fieldtype": "Float",
- "width": 100
+ "width": 100,
},
{
"label": _("Balance ({0})").format(currency),
"fieldname": "balance",
"fieldtype": "Float",
- "width": 130
- },
- {
- "label": _("Voucher Type"),
- "fieldname": "voucher_type",
- "width": 120
+ "width": 130,
},
+ {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
{
"label": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
- "width": 180
+ "width": 180,
},
- {
- "label": _("Against Account"),
- "fieldname": "against",
- "width": 120
- },
- {
- "label": _("Party Type"),
- "fieldname": "party_type",
- "width": 100
- },
- {
- "label": _("Party"),
- "fieldname": "party",
- "width": 100
- },
- {
- "label": _("Project"),
- "options": "Project",
- "fieldname": "project",
- "width": 100
- }
+ {"label": _("Against Account"), "fieldname": "against", "width": 120},
+ {"label": _("Party Type"), "fieldname": "party_type", "width": 100},
+ {"label": _("Party"), "fieldname": "party", "width": 100},
+ {"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100},
]
if filters.get("include_dimensions"):
- for dim in get_accounting_dimensions(as_list = False):
- columns.append({
- "label": _(dim.label),
- "options": dim.label,
- "fieldname": dim.fieldname,
- "width": 100
- })
- columns.append({
- "label": _("Cost Center"),
- "options": "Cost Center",
- "fieldname": "cost_center",
- "width": 100
- })
+ for dim in get_accounting_dimensions(as_list=False):
+ columns.append(
+ {"label": _(dim.label), "options": dim.label, "fieldname": dim.fieldname, "width": 100}
+ )
+ columns.append(
+ {"label": _("Cost Center"), "options": "Cost Center", "fieldname": "cost_center", "width": 100}
+ )
- columns.extend([
- {
- "label": _("Against Voucher Type"),
- "fieldname": "against_voucher_type",
- "width": 100
- },
- {
- "label": _("Against Voucher"),
- "fieldname": "against_voucher",
- "fieldtype": "Dynamic Link",
- "options": "against_voucher_type",
- "width": 100
- },
- {
- "label": _("Supplier Invoice No"),
- "fieldname": "bill_no",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Remarks"),
- "fieldname": "remarks",
- "width": 400
- }
- ])
+ columns.extend(
+ [
+ {"label": _("Against Voucher Type"), "fieldname": "against_voucher_type", "width": 100},
+ {
+ "label": _("Against Voucher"),
+ "fieldname": "against_voucher",
+ "fieldtype": "Dynamic Link",
+ "options": "against_voucher_type",
+ "width": 100,
+ },
+ {"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100},
+ {"label": _("Remarks"), "fieldname": "remarks", "width": 400},
+ ]
+ )
return columns
diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py
index 2373c8c2a6..b10e769618 100644
--- a/erpnext/accounts/report/general_ledger/test_general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py
@@ -9,7 +9,6 @@ from erpnext.accounts.report.general_ledger.general_ledger import execute
class TestGeneralLedger(FrappeTestCase):
-
def test_foreign_account_balance_after_exchange_rate_revaluation(self):
"""
Checks the correctness of balance after exchange rate revaluation
@@ -17,18 +16,20 @@ class TestGeneralLedger(FrappeTestCase):
# create a new account with USD currency
account_name = "Test USD Account for Revalutation"
company = "_Test Company"
- account = frappe.get_doc({
- "account_name": account_name,
- "is_group": 0,
- "company": company,
- "root_type": "Asset",
- "report_type": "Balance Sheet",
- "account_currency": "USD",
- "inter_company_account": 0,
- "parent_account": "Bank Accounts - _TC",
- "account_type": "Bank",
- "doctype": "Account"
- })
+ account = frappe.get_doc(
+ {
+ "account_name": account_name,
+ "is_group": 0,
+ "company": company,
+ "root_type": "Asset",
+ "report_type": "Balance Sheet",
+ "account_currency": "USD",
+ "inter_company_account": 0,
+ "parent_account": "Bank Accounts - _TC",
+ "account_type": "Bank",
+ "doctype": "Account",
+ }
+ )
account.insert(ignore_if_duplicate=True)
# create a JV to debit 1000 USD at 75 exchange rate
jv = frappe.new_doc("Journal Entry")
@@ -36,21 +37,24 @@ class TestGeneralLedger(FrappeTestCase):
jv.company = company
jv.multi_currency = 1
jv.cost_center = "_Test Cost Center - _TC"
- jv.set("accounts", [
- {
- "account": account.name,
- "debit_in_account_currency": 1000,
- "credit_in_account_currency": 0,
- "exchange_rate": 75,
- "cost_center": "_Test Cost Center - _TC",
- },
- {
- "account": "Cash - _TC",
- "debit_in_account_currency": 0,
- "credit_in_account_currency": 75000,
- "cost_center": "_Test Cost Center - _TC",
- },
- ])
+ jv.set(
+ "accounts",
+ [
+ {
+ "account": account.name,
+ "debit_in_account_currency": 1000,
+ "credit_in_account_currency": 0,
+ "exchange_rate": 75,
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ {
+ "account": "Cash - _TC",
+ "debit_in_account_currency": 0,
+ "credit_in_account_currency": 75000,
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ ],
+ )
jv.save()
jv.submit()
# create a JV to credit 900 USD at 100 exchange rate
@@ -59,21 +63,24 @@ class TestGeneralLedger(FrappeTestCase):
jv.company = company
jv.multi_currency = 1
jv.cost_center = "_Test Cost Center - _TC"
- jv.set("accounts", [
- {
- "account": account.name,
- "debit_in_account_currency": 0,
- "credit_in_account_currency": 900,
- "exchange_rate": 100,
- "cost_center": "_Test Cost Center - _TC",
- },
- {
- "account": "Cash - _TC",
- "debit_in_account_currency": 90000,
- "credit_in_account_currency": 0,
- "cost_center": "_Test Cost Center - _TC",
- },
- ])
+ jv.set(
+ "accounts",
+ [
+ {
+ "account": account.name,
+ "debit_in_account_currency": 0,
+ "credit_in_account_currency": 900,
+ "exchange_rate": 100,
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ {
+ "account": "Cash - _TC",
+ "debit_in_account_currency": 90000,
+ "credit_in_account_currency": 0,
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ ],
+ )
jv.save()
jv.submit()
@@ -81,22 +88,27 @@ class TestGeneralLedger(FrappeTestCase):
revaluation = frappe.new_doc("Exchange Rate Revaluation")
revaluation.posting_date = today()
revaluation.company = company
- revaluation.set("accounts", [
- {
- "account": account.name,
- "account_currency": "USD",
- "new_exchange_rate": 77,
- "new_balance_in_base_currency": 7700,
- "balance_in_base_currency": -15000,
- "balance_in_account_currency": 100,
- "current_exchange_rate": -150
- }
- ])
+ revaluation.set(
+ "accounts",
+ [
+ {
+ "account": account.name,
+ "account_currency": "USD",
+ "new_exchange_rate": 77,
+ "new_balance_in_base_currency": 7700,
+ "balance_in_base_currency": -15000,
+ "balance_in_account_currency": 100,
+ "current_exchange_rate": -150,
+ }
+ ],
+ )
revaluation.save()
revaluation.submit()
# post journal entry to revaluate
- frappe.db.set_value('Company', company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC")
+ frappe.db.set_value(
+ "Company", company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
+ )
revaluation_jv = revaluation.make_jv_entry()
revaluation_jv = frappe.get_doc(revaluation_jv)
revaluation_jv.cost_center = "_Test Cost Center - _TC"
@@ -112,18 +124,24 @@ class TestGeneralLedger(FrappeTestCase):
from `tabGL Entry`
where account = %s
group by account
- """, account.name)
+ """,
+ account.name,
+ )
self.assertEqual(balance[0][0], 100)
# check if general ledger shows correct balance
- columns, data = execute(frappe._dict({
- "company": company,
- "from_date": today(),
- "to_date": today(),
- "account": [account.name],
- "group_by": "Group by Voucher (Consolidated)",
- }))
+ columns, data = execute(
+ frappe._dict(
+ {
+ "company": company,
+ "from_date": today(),
+ "to_date": today(),
+ "account": [account.name],
+ "group_by": "Group by Voucher (Consolidated)",
+ }
+ )
+ )
self.assertEqual(data[1]["account"], account.name)
self.assertEqual(data[1]["debit"], 1000)
@@ -131,4 +149,4 @@ class TestGeneralLedger(FrappeTestCase):
self.assertEqual(data[2]["debit"], 0)
self.assertEqual(data[2]["credit"], 900)
self.assertEqual(data[3]["debit"], 100)
- self.assertEqual(data[3]["credit"], 100)
\ No newline at end of file
+ self.assertEqual(data[3]["credit"], 100)
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
index b18b940fd2..9d56678541 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
@@ -12,30 +12,57 @@ from erpnext.accounts.report.financial_statements import get_columns, get_data,
def execute(filters=None):
- period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.period_start_date,
- filters.period_end_date, filters.filter_based_on, filters.periodicity, filters.accumulated_values, filters.company)
+ period_list = get_period_list(
+ filters.from_fiscal_year,
+ filters.to_fiscal_year,
+ filters.period_start_date,
+ filters.period_end_date,
+ filters.filter_based_on,
+ filters.periodicity,
+ filters.accumulated_values,
+ filters.company,
+ )
columns, data = [], []
- income = get_data(filters.company, "Income", "Credit", period_list, filters = filters,
+ income = get_data(
+ filters.company,
+ "Income",
+ "Credit",
+ period_list,
+ filters=filters,
accumulated_values=filters.accumulated_values,
- ignore_closing_entries=True, ignore_accumulated_values_for_fy= True, total= False)
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
+ total=False,
+ )
- expense = get_data(filters.company, "Expense", "Debit", period_list, filters=filters,
+ expense = get_data(
+ filters.company,
+ "Expense",
+ "Debit",
+ period_list,
+ filters=filters,
accumulated_values=filters.accumulated_values,
- ignore_closing_entries=True, ignore_accumulated_values_for_fy= True, total= False)
-
- columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
+ total=False,
+ )
+ columns = get_columns(
+ filters.periodicity, period_list, filters.accumulated_values, filters.company
+ )
gross_income = get_revenue(income, period_list)
gross_expense = get_revenue(expense, period_list)
- if(len(gross_income)==0 and len(gross_expense)== 0):
- data.append({
- "account_name": "'" + _("Nothing is included in gross") + "'",
- "account": "'" + _("Nothing is included in gross") + "'"
- })
+ if len(gross_income) == 0 and len(gross_expense) == 0:
+ data.append(
+ {
+ "account_name": "'" + _("Nothing is included in gross") + "'",
+ "account": "'" + _("Nothing is included in gross") + "'",
+ }
+ )
return columns, data
# to avoid error eg: gross_income[0] : list index out of range
@@ -44,10 +71,12 @@ def execute(filters=None):
if not gross_expense:
gross_expense = [{}]
- data.append({
- "account_name": "'" + _("Included in Gross Profit") + "'",
- "account": "'" + _("Included in Gross Profit") + "'"
- })
+ data.append(
+ {
+ "account_name": "'" + _("Included in Gross Profit") + "'",
+ "account": "'" + _("Included in Gross Profit") + "'",
+ }
+ )
data.append({})
data.extend(gross_income or [])
@@ -56,7 +85,14 @@ def execute(filters=None):
data.extend(gross_expense or [])
data.append({})
- gross_profit = get_profit(gross_income, gross_expense, period_list, filters.company, 'Gross Profit',filters.presentation_currency)
+ gross_profit = get_profit(
+ gross_income,
+ gross_expense,
+ period_list,
+ filters.company,
+ "Gross Profit",
+ filters.presentation_currency,
+ )
data.append(gross_profit)
non_gross_income = get_revenue(income, period_list, 0)
@@ -67,28 +103,40 @@ def execute(filters=None):
data.append({})
data.extend(non_gross_expense or [])
- net_profit = get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expense, period_list, filters.company,filters.presentation_currency)
+ net_profit = get_net_profit(
+ non_gross_income,
+ gross_income,
+ gross_expense,
+ non_gross_expense,
+ period_list,
+ filters.company,
+ filters.presentation_currency,
+ )
data.append({})
data.append(net_profit)
return columns, data
-def get_revenue(data, period_list, include_in_gross=1):
- revenue = [item for item in data if item['include_in_gross']==include_in_gross or item['is_group']==1]
- data_to_be_removed =True
+def get_revenue(data, period_list, include_in_gross=1):
+ revenue = [
+ item for item in data if item["include_in_gross"] == include_in_gross or item["is_group"] == 1
+ ]
+
+ data_to_be_removed = True
while data_to_be_removed:
revenue, data_to_be_removed = remove_parent_with_no_child(revenue, period_list)
revenue = adjust_account(revenue, period_list)
return copy.deepcopy(revenue)
+
def remove_parent_with_no_child(data, period_list):
data_to_be_removed = False
for parent in data:
- if 'is_group' in parent and parent.get("is_group") == 1:
+ if "is_group" in parent and parent.get("is_group") == 1:
have_child = False
for child in data:
- if 'parent_account' in child and child.get("parent_account") == parent.get("account"):
+ if "parent_account" in child and child.get("parent_account") == parent.get("account"):
have_child = True
break
@@ -98,8 +146,9 @@ def remove_parent_with_no_child(data, period_list):
return data, data_to_be_removed
-def adjust_account(data, period_list, consolidated= False):
- leaf_nodes = [item for item in data if item['is_group'] == 0]
+
+def adjust_account(data, period_list, consolidated=False):
+ leaf_nodes = [item for item in data if item["is_group"] == 0]
totals = {}
for node in leaf_nodes:
set_total(node, node["total"], data, totals)
@@ -107,25 +156,30 @@ def adjust_account(data, period_list, consolidated= False):
for period in period_list:
key = period if consolidated else period.key
d[key] = totals[d["account"]]
- d['total'] = totals[d["account"]]
+ d["total"] = totals[d["account"]]
return data
+
def set_total(node, value, complete_list, totals):
- if not totals.get(node['account']):
+ if not totals.get(node["account"]):
totals[node["account"]] = 0
totals[node["account"]] += value
- parent = node['parent_account']
- if not parent == '':
- return set_total(next(item for item in complete_list if item['account'] == parent), value, complete_list, totals)
+ parent = node["parent_account"]
+ if not parent == "":
+ return set_total(
+ next(item for item in complete_list if item["account"] == parent), value, complete_list, totals
+ )
-def get_profit(gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False):
+def get_profit(
+ gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False
+):
profit_loss = {
"account_name": "'" + _(profit_type) + "'",
"account": "'" + _(profit_type) + "'",
"warn_if_negative": True,
- "currency": currency or frappe.get_cached_value('Company', company, "default_currency")
+ "currency": currency or frappe.get_cached_value("Company", company, "default_currency"),
}
has_value = False
@@ -137,17 +191,27 @@ def get_profit(gross_income, gross_expense, period_list, company, profit_type, c
profit_loss[key] = gross_income_for_period - gross_expense_for_period
if profit_loss[key]:
- has_value=True
+ has_value = True
if has_value:
return profit_loss
-def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expense, period_list, company, currency=None, consolidated=False):
+
+def get_net_profit(
+ non_gross_income,
+ gross_income,
+ gross_expense,
+ non_gross_expense,
+ period_list,
+ company,
+ currency=None,
+ consolidated=False,
+):
profit_loss = {
"account_name": "'" + _("Net Profit") + "'",
"account": "'" + _("Net Profit") + "'",
"warn_if_negative": True,
- "currency": currency or frappe.get_cached_value('Company', company, "default_currency")
+ "currency": currency or frappe.get_cached_value("Company", company, "default_currency"),
}
has_value = False
@@ -165,7 +229,7 @@ def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expe
profit_loss[key] = flt(total_income) - flt(total_expense)
if profit_loss[key]:
- has_value=True
+ has_value = True
if has_value:
return profit_loss
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index b03bb9bb13..9668992e02 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -11,38 +11,125 @@ from erpnext.stock.utils import get_incoming_rate
def execute(filters=None):
- if not filters: filters = frappe._dict()
- filters.currency = frappe.get_cached_value('Company', filters.company, "default_currency")
+ if not filters:
+ filters = frappe._dict()
+ filters.currency = frappe.get_cached_value("Company", filters.company, "default_currency")
gross_profit_data = GrossProfitGenerator(filters)
data = []
- group_wise_columns = frappe._dict({
- "invoice": ["invoice_or_item", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description",
- "warehouse", "qty", "base_rate", "buying_rate", "base_amount",
- "buying_amount", "gross_profit", "gross_profit_percent", "project"],
- "item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate",
- "buying_rate", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"],
- "warehouse": ["warehouse", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
- "gross_profit", "gross_profit_percent"],
- "brand": ["brand", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
- "gross_profit", "gross_profit_percent"],
- "item_group": ["item_group", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
- "gross_profit", "gross_profit_percent"],
- "customer": ["customer", "customer_group", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
- "gross_profit", "gross_profit_percent"],
- "customer_group": ["customer_group", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
- "gross_profit", "gross_profit_percent"],
- "sales_person": ["sales_person", "allocated_amount", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
- "gross_profit", "gross_profit_percent"],
- "project": ["project", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"],
- "territory": ["territory", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"]
- })
+ group_wise_columns = frappe._dict(
+ {
+ "invoice": [
+ "invoice_or_item",
+ "customer",
+ "customer_group",
+ "posting_date",
+ "item_code",
+ "item_name",
+ "item_group",
+ "brand",
+ "description",
+ "warehouse",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ "project",
+ ],
+ "item_code": [
+ "item_code",
+ "item_name",
+ "brand",
+ "description",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ "warehouse": [
+ "warehouse",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ "brand": [
+ "brand",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ "item_group": [
+ "item_group",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ "customer": [
+ "customer",
+ "customer_group",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ "customer_group": [
+ "customer_group",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ "sales_person": [
+ "sales_person",
+ "allocated_amount",
+ "qty",
+ "base_rate",
+ "buying_rate",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ "project": ["project", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"],
+ "territory": [
+ "territory",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
+ }
+ )
columns = get_columns(group_wise_columns, filters)
- if filters.group_by == 'Invoice':
+ if filters.group_by == "Invoice":
get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data)
else:
@@ -50,11 +137,14 @@ def execute(filters=None):
return columns, data
-def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data):
+
+def get_data_when_grouped_by_invoice(
+ columns, gross_profit_data, filters, group_wise_columns, data
+):
column_names = get_column_names()
# to display item as Item Code: Item Name
- columns[0] = 'Sales Invoice:Link/Item:300'
+ columns[0] = "Sales Invoice:Link/Item:300"
# removing Item Code and Item Name columns
del columns[4:6]
@@ -69,6 +159,7 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
data.append(row)
+
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
for src in gross_profit_data.grouped_data:
row = []
@@ -79,69 +170,196 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
data.append(row)
+
def get_columns(group_wise_columns, filters):
columns = []
- column_map = frappe._dict({
- "parent": {"label": _('Sales Invoice'), "fieldname": "parent_invoice", "fieldtype": "Link", "options": "Sales Invoice", "width": 120},
- "invoice_or_item": {"label": _('Sales Invoice'), "fieldtype": "Link", "options": "Sales Invoice", "width": 120},
- "posting_date": {"label": _('Posting Date'), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
- "posting_time": {"label": _('Posting Time'), "fieldname": "posting_time", "fieldtype": "Data", "width": 100},
- "item_code": {"label": _('Item Code'), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
- "item_name": {"label": _('Item Name'), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
- "item_group": {"label": _('Item Group'), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
- "brand": {"label": _('Brand'), "fieldtype": "Link", "options": "Brand", "width": 100},
- "description": {"label": _('Description'), "fieldname": "description", "fieldtype": "Data", "width": 100},
- "warehouse": {"label": _('Warehouse'), "fieldname": "warehouse", "fieldtype": "Link", "options": "warehouse", "width": 100},
- "qty": {"label": _('Qty'), "fieldname": "qty", "fieldtype": "Float", "width": 80},
- "base_rate": {"label": _('Avg. Selling Rate'), "fieldname": "avg._selling_rate", "fieldtype": "Currency", "options": "currency", "width": 100},
- "buying_rate": {"label": _('Valuation Rate'), "fieldname": "valuation_rate", "fieldtype": "Currency", "options": "currency", "width": 100},
- "base_amount": {"label": _('Selling Amount'), "fieldname": "selling_amount", "fieldtype": "Currency", "options": "currency", "width": 100},
- "buying_amount": {"label": _('Buying Amount'), "fieldname": "buying_amount", "fieldtype": "Currency", "options": "currency", "width": 100},
- "gross_profit": {"label": _('Gross Profit'), "fieldname": "gross_profit", "fieldtype": "Currency", "options": "currency", "width": 100},
- "gross_profit_percent": {"label": _('Gross Profit Percent'), "fieldname": "gross_profit_%",
- "fieldtype": "Percent", "width": 100},
- "project": {"label": _('Project'), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
- "sales_person": {"label": _('Sales Person'), "fieldname": "sales_person", "fieldtype": "Data","width": 100},
- "allocated_amount": {"label": _('Allocated Amount'), "fieldname": "allocated_amount", "fieldtype": "Currency", "options": "currency", "width": 100},
- "customer": {"label": _('Customer'), "fieldname": "customer", "fieldtype": "Link", "options": "Customer", "width": 100},
- "customer_group": {"label": _('Customer Group'), "fieldname": "customer_group", "fieldtype": "Link", "options": "customer", "width": 100},
- "territory": {"label": _('Territory'), "fieldname": "territory", "fieldtype": "Link", "options": "territory", "width": 100},
- })
+ column_map = frappe._dict(
+ {
+ "parent": {
+ "label": _("Sales Invoice"),
+ "fieldname": "parent_invoice",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120,
+ },
+ "invoice_or_item": {
+ "label": _("Sales Invoice"),
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120,
+ },
+ "posting_date": {
+ "label": _("Posting Date"),
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ "posting_time": {
+ "label": _("Posting Time"),
+ "fieldname": "posting_time",
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ "item_code": {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
+ },
+ "item_name": {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ "item_group": {
+ "label": _("Item Group"),
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "options": "Item Group",
+ "width": 100,
+ },
+ "brand": {"label": _("Brand"), "fieldtype": "Link", "options": "Brand", "width": 100},
+ "description": {
+ "label": _("Description"),
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ "warehouse": {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "warehouse",
+ "width": 100,
+ },
+ "qty": {"label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 80},
+ "base_rate": {
+ "label": _("Avg. Selling Rate"),
+ "fieldname": "avg._selling_rate",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ "buying_rate": {
+ "label": _("Valuation Rate"),
+ "fieldname": "valuation_rate",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ "base_amount": {
+ "label": _("Selling Amount"),
+ "fieldname": "selling_amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ "buying_amount": {
+ "label": _("Buying Amount"),
+ "fieldname": "buying_amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ "gross_profit": {
+ "label": _("Gross Profit"),
+ "fieldname": "gross_profit",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ "gross_profit_percent": {
+ "label": _("Gross Profit Percent"),
+ "fieldname": "gross_profit_%",
+ "fieldtype": "Percent",
+ "width": 100,
+ },
+ "project": {
+ "label": _("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 100,
+ },
+ "sales_person": {
+ "label": _("Sales Person"),
+ "fieldname": "sales_person",
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ "allocated_amount": {
+ "label": _("Allocated Amount"),
+ "fieldname": "allocated_amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ "customer": {
+ "label": _("Customer"),
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "options": "Customer",
+ "width": 100,
+ },
+ "customer_group": {
+ "label": _("Customer Group"),
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "options": "customer",
+ "width": 100,
+ },
+ "territory": {
+ "label": _("Territory"),
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "options": "territory",
+ "width": 100,
+ },
+ }
+ )
for col in group_wise_columns.get(scrub(filters.group_by)):
columns.append(column_map.get(col))
- columns.append({
- "fieldname": "currency",
- "label" : _("Currency"),
- "fieldtype": "Link",
- "options": "Currency",
- "hidden": 1
- })
+ columns.append(
+ {
+ "fieldname": "currency",
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "options": "Currency",
+ "hidden": 1,
+ }
+ )
return columns
+
def get_column_names():
- return frappe._dict({
- 'invoice_or_item': 'sales_invoice',
- 'customer': 'customer',
- 'customer_group': 'customer_group',
- 'posting_date': 'posting_date',
- 'item_code': 'item_code',
- 'item_name': 'item_name',
- 'item_group': 'item_group',
- 'brand': 'brand',
- 'description': 'description',
- 'warehouse': 'warehouse',
- 'qty': 'qty',
- 'base_rate': 'avg._selling_rate',
- 'buying_rate': 'valuation_rate',
- 'base_amount': 'selling_amount',
- 'buying_amount': 'buying_amount',
- 'gross_profit': 'gross_profit',
- 'gross_profit_percent': 'gross_profit_%',
- 'project': 'project'
- })
+ return frappe._dict(
+ {
+ "invoice_or_item": "sales_invoice",
+ "customer": "customer",
+ "customer_group": "customer_group",
+ "posting_date": "posting_date",
+ "item_code": "item_code",
+ "item_name": "item_name",
+ "item_group": "item_group",
+ "brand": "brand",
+ "description": "description",
+ "warehouse": "warehouse",
+ "qty": "qty",
+ "base_rate": "avg._selling_rate",
+ "buying_rate": "valuation_rate",
+ "base_amount": "selling_amount",
+ "buying_amount": "buying_amount",
+ "gross_profit": "gross_profit",
+ "gross_profit_percent": "gross_profit_%",
+ "project": "project",
+ }
+ )
+
class GrossProfitGenerator(object):
def __init__(self, filters=None):
@@ -150,7 +368,7 @@ class GrossProfitGenerator(object):
self.filters = frappe._dict(filters)
self.load_invoice_items()
- if filters.group_by == 'Invoice':
+ if filters.group_by == "Invoice":
self.group_items_by_invoice()
self.load_stock_ledger_entries()
@@ -181,17 +399,19 @@ class GrossProfitGenerator(object):
if row.update_stock:
product_bundles = self.product_bundles.get(row.parenttype, {}).get(row.parent, frappe._dict())
elif row.dn_detail:
- product_bundles = self.product_bundles.get("Delivery Note", {})\
- .get(row.delivery_note, frappe._dict())
+ product_bundles = self.product_bundles.get("Delivery Note", {}).get(
+ row.delivery_note, frappe._dict()
+ )
row.item_row = row.dn_detail
# get buying amount
if row.item_code in product_bundles:
- row.buying_amount = flt(self.get_buying_amount_from_product_bundle(row,
- product_bundles[row.item_code]), self.currency_precision)
+ row.buying_amount = flt(
+ self.get_buying_amount_from_product_bundle(row, product_bundles[row.item_code]),
+ self.currency_precision,
+ )
else:
- row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
- self.currency_precision)
+ row.buying_amount = flt(self.get_buying_amount(row, row.item_code), self.currency_precision)
if grouped_by_invoice:
if row.indent == 1.0:
@@ -211,7 +431,9 @@ class GrossProfitGenerator(object):
# calculate gross profit
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
if row.base_amount:
- row.gross_profit_percent = flt((row.gross_profit / row.base_amount) * 100.0, self.currency_precision)
+ row.gross_profit_percent = flt(
+ (row.gross_profit / row.base_amount) * 100.0, self.currency_precision
+ )
else:
row.gross_profit_percent = 0.0
@@ -225,7 +447,7 @@ class GrossProfitGenerator(object):
for key in list(self.grouped):
if self.filters.get("group_by") != "Invoice":
for i, row in enumerate(self.grouped[key]):
- if i==0:
+ if i == 0:
new_row = row
else:
new_row.qty += flt(row.qty)
@@ -236,33 +458,44 @@ class GrossProfitGenerator(object):
else:
for i, row in enumerate(self.grouped[key]):
if row.indent == 1.0:
- if row.parent in self.returned_invoices \
- and row.item_code in self.returned_invoices[row.parent]:
+ if (
+ row.parent in self.returned_invoices and row.item_code in self.returned_invoices[row.parent]
+ ):
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
for returned_item_row in returned_item_rows:
row.qty += flt(returned_item_row.qty)
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
- if (flt(row.qty) or row.base_amount):
+ if flt(row.qty) or row.base_amount:
row = self.set_average_rate(row)
self.grouped_data.append(row)
def is_not_invoice_row(self, row):
- return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
+ return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get(
+ "group_by"
+ ) != "Invoice"
def set_average_rate(self, new_row):
self.set_average_gross_profit(new_row)
- new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
- new_row.base_rate = flt(new_row.base_amount / new_row.qty, self.float_precision) if new_row.qty else 0
+ new_row.buying_rate = (
+ flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
+ )
+ new_row.base_rate = (
+ flt(new_row.base_amount / new_row.qty, self.float_precision) if new_row.qty else 0
+ )
return new_row
def set_average_gross_profit(self, new_row):
new_row.gross_profit = flt(new_row.base_amount - new_row.buying_amount, self.currency_precision)
- new_row.gross_profit_percent = flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision) \
- if new_row.base_amount else 0
+ new_row.gross_profit_percent = (
+ flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision)
+ if new_row.base_amount
+ else 0
+ )
def get_returned_invoice_items(self):
- returned_invoices = frappe.db.sql("""
+ returned_invoices = frappe.db.sql(
+ """
select
si.name, si_item.item_code, si_item.stock_qty as qty, si_item.base_net_amount as base_amount, si.return_against
from
@@ -271,12 +504,15 @@ class GrossProfitGenerator(object):
si.name = si_item.parent
and si.docstatus = 1
and si.is_return = 1
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
self.returned_invoices = frappe._dict()
for inv in returned_invoices:
- self.returned_invoices.setdefault(inv.return_against, frappe._dict())\
- .setdefault(inv.item_code, []).append(inv)
+ self.returned_invoices.setdefault(inv.return_against, frappe._dict()).setdefault(
+ inv.item_code, []
+ ).append(inv)
def skip_row(self, row):
if self.filters.get("group_by") != "Invoice":
@@ -288,7 +524,7 @@ class GrossProfitGenerator(object):
def get_buying_amount_from_product_bundle(self, row, product_bundle):
buying_amount = 0.0
for packed_item in product_bundle:
- if packed_item.get("parent_detail_docname")==row.item_row:
+ if packed_item.get("parent_detail_docname") == row.item_row:
buying_amount += self.get_buying_amount(row, packed_item.item_code)
return flt(buying_amount, self.currency_precision)
@@ -298,7 +534,7 @@ class GrossProfitGenerator(object):
# stock_ledger_entries should already be filtered by item_code and warehouse and
# sorted by posting_date desc, posting_time desc
if item_code in self.non_stock_items and (row.project or row.cost_center):
- #Issue 6089-Get last purchasing rate for non-stock item
+ # Issue 6089-Get last purchasing rate for non-stock item
item_rate = self.get_last_purchase_rate(item_code, row)
return flt(row.qty) * item_rate
@@ -311,15 +547,17 @@ class GrossProfitGenerator(object):
for i, sle in enumerate(my_sle):
# find the stock valution rate from stock ledger entry
- if sle.voucher_type == parenttype and parent == sle.voucher_no and \
- sle.voucher_detail_no == row.item_row:
- previous_stock_value = len(my_sle) > i+1 and \
- flt(my_sle[i+1].stock_value) or 0.0
+ if (
+ sle.voucher_type == parenttype
+ and parent == sle.voucher_no
+ and sle.voucher_detail_no == row.item_row
+ ):
+ previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
- if previous_stock_value:
- return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
- else:
- return flt(row.qty) * self.get_average_buying_rate(row, item_code)
+ if previous_stock_value:
+ return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
+ else:
+ return flt(row.qty) * self.get_average_buying_rate(row, item_code)
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
@@ -328,15 +566,17 @@ class GrossProfitGenerator(object):
def get_average_buying_rate(self, row, item_code):
args = row
if not item_code in self.average_buying_rate:
- args.update({
- 'voucher_type': row.parenttype,
- 'voucher_no': row.parent,
- 'allow_zero_valuation': True,
- 'company': self.filters.company
- })
+ args.update(
+ {
+ "voucher_type": row.parenttype,
+ "voucher_no": row.parent,
+ "allow_zero_valuation": True,
+ "company": self.filters.company,
+ }
+ )
average_buying_rate = get_incoming_rate(args)
- self.average_buying_rate[item_code] = flt(average_buying_rate)
+ self.average_buying_rate[item_code] = flt(average_buying_rate)
return self.average_buying_rate[item_code]
@@ -344,30 +584,21 @@ class GrossProfitGenerator(object):
purchase_invoice = frappe.qb.DocType("Purchase Invoice")
purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item")
- query = (frappe.qb.from_(purchase_invoice_item)
- .inner_join(
- purchase_invoice
- ).on(
- purchase_invoice.name == purchase_invoice_item.parent
- ).select(
- purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor
- ).where(
- purchase_invoice.docstatus == 1
- ).where(
- purchase_invoice.posting_date <= self.filters.to_date
- ).where(
- purchase_invoice_item.item_code == item_code
- ))
+ query = (
+ frappe.qb.from_(purchase_invoice_item)
+ .inner_join(purchase_invoice)
+ .on(purchase_invoice.name == purchase_invoice_item.parent)
+ .select(purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor)
+ .where(purchase_invoice.docstatus == 1)
+ .where(purchase_invoice.posting_date <= self.filters.to_date)
+ .where(purchase_invoice_item.item_code == item_code)
+ )
if row.project:
- query.where(
- purchase_invoice_item.project == row.project
- )
+ query.where(purchase_invoice_item.project == row.project)
if row.cost_center:
- query.where(
- purchase_invoice_item.cost_center == row.cost_center
- )
+ query.where(purchase_invoice_item.cost_center == row.cost_center)
query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc)
query.limit(1)
@@ -384,7 +615,7 @@ class GrossProfitGenerator(object):
if self.filters.to_date:
conditions += " and posting_date <= %(to_date)s"
- if self.filters.group_by=="Sales Person":
+ if self.filters.group_by == "Sales Person":
sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives"
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
else:
@@ -397,7 +628,8 @@ class GrossProfitGenerator(object):
if self.filters.get("item_code"):
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
- self.si_list = frappe.db.sql("""
+ self.si_list = frappe.db.sql(
+ """
select
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
@@ -419,13 +651,19 @@ class GrossProfitGenerator(object):
where
`tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond}
order by
- `tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc"""
- .format(conditions=conditions, sales_person_cols=sales_person_cols,
- sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1)
+ `tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc""".format(
+ conditions=conditions,
+ sales_person_cols=sales_person_cols,
+ sales_team_table=sales_team_table,
+ match_cond=get_match_cond("Sales Invoice"),
+ ),
+ self.filters,
+ as_dict=1,
+ )
def group_items_by_invoice(self):
"""
- Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
+ Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
"""
parents = []
@@ -448,94 +686,96 @@ class GrossProfitGenerator(object):
row.parent_invoice = row.parent
row.invoice_or_item = row.item_code
- if frappe.db.exists('Product Bundle', row.item_code):
+ if frappe.db.exists("Product Bundle", row.item_code):
self.add_bundle_items(row, index)
def get_invoice_row(self, row):
- return frappe._dict({
- 'parent_invoice': "",
- 'indent': 0.0,
- 'invoice_or_item': row.parent,
- 'parent': None,
- 'posting_date': row.posting_date,
- 'posting_time': row.posting_time,
- 'project': row.project,
- 'update_stock': row.update_stock,
- 'customer': row.customer,
- 'customer_group': row.customer_group,
- 'item_code': None,
- 'item_name': None,
- 'description': None,
- 'warehouse': None,
- 'item_group': None,
- 'brand': None,
- 'dn_detail': None,
- 'delivery_note': None,
- 'qty': None,
- 'item_row': None,
- 'is_return': row.is_return,
- 'cost_center': row.cost_center,
- 'base_net_amount': frappe.db.get_value('Sales Invoice', row.parent, 'base_net_total')
- })
+ return frappe._dict(
+ {
+ "parent_invoice": "",
+ "indent": 0.0,
+ "invoice_or_item": row.parent,
+ "parent": None,
+ "posting_date": row.posting_date,
+ "posting_time": row.posting_time,
+ "project": row.project,
+ "update_stock": row.update_stock,
+ "customer": row.customer,
+ "customer_group": row.customer_group,
+ "item_code": None,
+ "item_name": None,
+ "description": None,
+ "warehouse": None,
+ "item_group": None,
+ "brand": None,
+ "dn_detail": None,
+ "delivery_note": None,
+ "qty": None,
+ "item_row": None,
+ "is_return": row.is_return,
+ "cost_center": row.cost_center,
+ "base_net_amount": frappe.db.get_value("Sales Invoice", row.parent, "base_net_total"),
+ }
+ )
def add_bundle_items(self, product_bundle, index):
bundle_items = self.get_bundle_items(product_bundle)
for i, item in enumerate(bundle_items):
bundle_item = self.get_bundle_item_row(product_bundle, item)
- self.si_list.insert((index+i+1), bundle_item)
+ self.si_list.insert((index + i + 1), bundle_item)
def get_bundle_items(self, product_bundle):
return frappe.get_all(
- 'Product Bundle Item',
- filters = {
- 'parent': product_bundle.item_code
- },
- fields = ['item_code', 'qty']
+ "Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]
)
def get_bundle_item_row(self, product_bundle, item):
item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code)
- return frappe._dict({
- 'parent_invoice': product_bundle.item_code,
- 'indent': product_bundle.indent + 1,
- 'parent': None,
- 'invoice_or_item': item.item_code,
- 'posting_date': product_bundle.posting_date,
- 'posting_time': product_bundle.posting_time,
- 'project': product_bundle.project,
- 'customer': product_bundle.customer,
- 'customer_group': product_bundle.customer_group,
- 'item_code': item.item_code,
- 'item_name': item_name,
- 'description': description,
- 'warehouse': product_bundle.warehouse,
- 'item_group': item_group,
- 'brand': brand,
- 'dn_detail': product_bundle.dn_detail,
- 'delivery_note': product_bundle.delivery_note,
- 'qty': (flt(product_bundle.qty) * flt(item.qty)),
- 'item_row': None,
- 'is_return': product_bundle.is_return,
- 'cost_center': product_bundle.cost_center
- })
+ return frappe._dict(
+ {
+ "parent_invoice": product_bundle.item_code,
+ "indent": product_bundle.indent + 1,
+ "parent": None,
+ "invoice_or_item": item.item_code,
+ "posting_date": product_bundle.posting_date,
+ "posting_time": product_bundle.posting_time,
+ "project": product_bundle.project,
+ "customer": product_bundle.customer,
+ "customer_group": product_bundle.customer_group,
+ "item_code": item.item_code,
+ "item_name": item_name,
+ "description": description,
+ "warehouse": product_bundle.warehouse,
+ "item_group": item_group,
+ "brand": brand,
+ "dn_detail": product_bundle.dn_detail,
+ "delivery_note": product_bundle.delivery_note,
+ "qty": (flt(product_bundle.qty) * flt(item.qty)),
+ "item_row": None,
+ "is_return": product_bundle.is_return,
+ "cost_center": product_bundle.cost_center,
+ }
+ )
def get_bundle_item_details(self, item_code):
return frappe.db.get_value(
- 'Item',
- item_code,
- ['item_name', 'description', 'item_group', 'brand']
+ "Item", item_code, ["item_name", "description", "item_group", "brand"]
)
def load_stock_ledger_entries(self):
- res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
+ res = frappe.db.sql(
+ """select item_code, voucher_type, voucher_no,
voucher_detail_no, stock_value, warehouse, actual_qty as qty
from `tabStock Ledger Entry`
where company=%(company)s and is_cancelled = 0
order by
item_code desc, warehouse desc, posting_date desc,
- posting_time desc, creation desc""", self.filters, as_dict=True)
+ posting_time desc, creation desc""",
+ self.filters,
+ as_dict=True,
+ )
self.sle = {}
for r in res:
if (r.item_code, r.warehouse) not in self.sle:
@@ -546,12 +786,18 @@ class GrossProfitGenerator(object):
def load_product_bundle(self):
self.product_bundles = {}
- for d in frappe.db.sql("""select parenttype, parent, parent_item,
+ for d in frappe.db.sql(
+ """select parenttype, parent, parent_item,
item_code, warehouse, -1*qty as total_qty, parent_detail_docname
- from `tabPacked Item` where docstatus=1""", as_dict=True):
- self.product_bundles.setdefault(d.parenttype, frappe._dict()).setdefault(d.parent,
- frappe._dict()).setdefault(d.parent_item, []).append(d)
+ from `tabPacked Item` where docstatus=1""",
+ as_dict=True,
+ ):
+ self.product_bundles.setdefault(d.parenttype, frappe._dict()).setdefault(
+ d.parent, frappe._dict()
+ ).setdefault(d.parent_item, []).append(d)
def load_non_stock_items(self):
- self.non_stock_items = frappe.db.sql_list("""select name from tabItem
- where is_stock_item=0""")
+ self.non_stock_items = frappe.db.sql_list(
+ """select name from tabItem
+ where is_stock_item=0"""
+ )
diff --git a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py
index 2f23c8ed1d..8db72de22f 100644
--- a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py
+++ b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py
@@ -12,6 +12,7 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_columns():
columns = [
{
@@ -19,53 +20,36 @@ def get_columns():
"fieldtype": "Link",
"label": _("Territory"),
"options": "Territory",
- "width": 100
+ "width": 100,
},
{
"fieldname": "item_group",
"fieldtype": "Link",
"label": _("Item Group"),
"options": "Item Group",
- "width": 150
+ "width": 150,
},
- {
- "fieldname": "item",
- "fieldtype": "Link",
- "options": "Item",
- "label": "Item",
- "width": 150
- },
- {
- "fieldname": "item_name",
- "fieldtype": "Data",
- "label": _("Item Name"),
- "width": 150
- },
-
+ {"fieldname": "item", "fieldtype": "Link", "options": "Item", "label": "Item", "width": 150},
+ {"fieldname": "item_name", "fieldtype": "Data", "label": _("Item Name"), "width": 150},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": _("Customer"),
"options": "Customer",
- "width": 100
+ "width": 100,
},
{
"fieldname": "last_order_date",
"fieldtype": "Date",
"label": _("Last Order Date"),
- "width": 100
- },
- {
- "fieldname": "qty",
- "fieldtype": "Float",
- "label": _("Quantity"),
- "width": 100
+ "width": 100,
},
+ {"fieldname": "qty", "fieldtype": "Float", "label": _("Quantity"), "width": 100},
{
"fieldname": "days_since_last_order",
"fieldtype": "Int",
"label": _("Days Since Last Order"),
- "width": 100
+ "width": 100,
},
]
@@ -84,19 +68,21 @@ def get_data(filters):
"territory": territory.name,
"item_group": item.item_group,
"item": item.item_code,
- "item_name": item.item_name
+ "item_name": item.item_name,
}
- if sales_invoice_data.get((territory.name,item.item_code)):
- item_obj = sales_invoice_data[(territory.name,item.item_code)]
- if item_obj.days_since_last_order > cint(filters['days']):
- row.update({
- "territory": item_obj.territory,
- "customer": item_obj.customer,
- "last_order_date": item_obj.last_order_date,
- "qty": item_obj.qty,
- "days_since_last_order": item_obj.days_since_last_order
- })
+ if sales_invoice_data.get((territory.name, item.item_code)):
+ item_obj = sales_invoice_data[(territory.name, item.item_code)]
+ if item_obj.days_since_last_order > cint(filters["days"]):
+ row.update(
+ {
+ "territory": item_obj.territory,
+ "customer": item_obj.customer,
+ "last_order_date": item_obj.last_order_date,
+ "qty": item_obj.qty,
+ "days_since_last_order": item_obj.days_since_last_order,
+ }
+ )
else:
continue
@@ -111,45 +97,49 @@ def get_sales_details(filters):
date_field = "s.transaction_date" if filters["based_on"] == "Sales Order" else "s.posting_date"
- sales_data = frappe.db.sql("""
+ sales_data = frappe.db.sql(
+ """
select s.territory, s.customer, si.item_group, si.item_code, si.qty, {date_field} as last_order_date,
DATEDIFF(CURDATE(), {date_field}) as days_since_last_order
from `tab{doctype}` s, `tab{doctype} Item` si
where s.name = si.parent and s.docstatus = 1
- order by days_since_last_order """ #nosec
- .format(date_field = date_field, doctype = filters['based_on']), as_dict=1)
+ order by days_since_last_order """.format( # nosec
+ date_field=date_field, doctype=filters["based_on"]
+ ),
+ as_dict=1,
+ )
for d in sales_data:
- item_details_map.setdefault((d.territory,d.item_code), d)
+ item_details_map.setdefault((d.territory, d.item_code), d)
return item_details_map
+
def get_territories(filters):
filter_dict = {}
if filters.get("territory"):
- filter_dict.update({'name': filters['territory']})
+ filter_dict.update({"name": filters["territory"]})
territories = frappe.get_all("Territory", fields=["name"], filters=filter_dict)
return territories
+
def get_items(filters):
- filters_dict = {
- "disabled": 0,
- "is_stock_item": 1
- }
+ filters_dict = {"disabled": 0, "is_stock_item": 1}
if filters.get("item_group"):
- filters_dict.update({
- "item_group": filters["item_group"]
- })
+ filters_dict.update({"item_group": filters["item_group"]})
if filters.get("item"):
- filters_dict.update({
- "name": filters["item"]
- })
+ filters_dict.update({"name": filters["item"]})
- items = frappe.get_all("Item", fields=["name", "item_group", "item_name", "item_code"], filters=filters_dict, order_by="name")
+ items = frappe.get_all(
+ "Item",
+ fields=["name", "item_group", "item_name", "item_code"],
+ filters=filters_dict,
+ order_by="name",
+ )
return items
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index aaed58d070..c04b9c7125 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -21,8 +21,10 @@ from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history impo
def execute(filters=None):
return _execute(filters)
+
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = erpnext.get_company_currency(filters.company)
@@ -30,18 +32,23 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
item_list = get_items(filters, additional_query_columns)
aii_account_map = get_aii_accounts()
if item_list:
- itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
- doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
+ itemised_tax, tax_columns = get_tax_accounts(
+ item_list,
+ columns,
+ company_currency,
+ doctype="Purchase Invoice",
+ tax_doctype="Purchase Taxes and Charges",
+ )
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
data = []
total_row_map = {}
skip_total_row = 0
- prev_group_by_value = ''
+ prev_group_by_value = ""
- if filters.get('group_by'):
- grand_total = get_grand_total(filters, 'Purchase Invoice')
+ if filters.get("group_by"):
+ grand_total = get_grand_total(filters, "Purchase Invoice")
item_details = get_item_details()
@@ -57,71 +64,81 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
elif d.po_detail:
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
- expense_account = d.unrealized_profit_loss_account or d.expense_account \
- or aii_account_map.get(d.company)
+ expense_account = (
+ d.unrealized_profit_loss_account or d.expense_account or aii_account_map.get(d.company)
+ )
row = {
- 'item_code': d.item_code,
- 'item_name': item_record.item_name if item_record else d.item_name,
- 'item_group': item_record.item_group if item_record else d.item_group,
- 'description': d.description,
- 'invoice': d.parent,
- 'posting_date': d.posting_date,
- 'supplier': d.supplier,
- 'supplier_name': d.supplier_name
+ "item_code": d.item_code,
+ "item_name": item_record.item_name if item_record else d.item_name,
+ "item_group": item_record.item_group if item_record else d.item_group,
+ "description": d.description,
+ "invoice": d.parent,
+ "posting_date": d.posting_date,
+ "supplier": d.supplier,
+ "supplier_name": d.supplier_name,
}
if additional_query_columns:
for col in additional_query_columns:
- row.update({
- col: d.get(col)
- })
+ row.update({col: d.get(col)})
- row.update({
- 'credit_to': d.credit_to,
- 'mode_of_payment': d.mode_of_payment,
- 'project': d.project,
- 'company': d.company,
- 'purchase_order': d.purchase_order,
- 'purchase_receipt': d.purchase_receipt,
- 'expense_account': expense_account,
- 'stock_qty': d.stock_qty,
- 'stock_uom': d.stock_uom,
- 'rate': d.base_net_amount / d.stock_qty,
- 'amount': d.base_net_amount
- })
+ row.update(
+ {
+ "credit_to": d.credit_to,
+ "mode_of_payment": d.mode_of_payment,
+ "project": d.project,
+ "company": d.company,
+ "purchase_order": d.purchase_order,
+ "purchase_receipt": d.purchase_receipt,
+ "expense_account": expense_account,
+ "stock_qty": d.stock_qty,
+ "stock_uom": d.stock_uom,
+ "rate": d.base_net_amount / d.stock_qty,
+ "amount": d.base_net_amount,
+ }
+ )
total_tax = 0
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
- row.update({
- frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
- frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
- })
- total_tax += flt(item_tax.get('tax_amount'))
+ row.update(
+ {
+ frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
+ frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
+ }
+ )
+ total_tax += flt(item_tax.get("tax_amount"))
- row.update({
- 'total_tax': total_tax,
- 'total': d.base_net_amount + total_tax,
- 'currency': company_currency
- })
+ row.update(
+ {"total_tax": total_tax, "total": d.base_net_amount + total_tax, "currency": company_currency}
+ )
- if filters.get('group_by'):
- row.update({'percent_gt': flt(row['total']/grand_total) * 100})
+ if filters.get("group_by"):
+ row.update({"percent_gt": flt(row["total"] / grand_total) * 100})
group_by_field, subtotal_display_field = get_group_by_and_display_fields(filters)
- data, prev_group_by_value = add_total_row(data, filters, prev_group_by_value, d, total_row_map,
- group_by_field, subtotal_display_field, grand_total, tax_columns)
- add_sub_total_row(row, total_row_map, d.get(group_by_field, ''), tax_columns)
+ data, prev_group_by_value = add_total_row(
+ data,
+ filters,
+ prev_group_by_value,
+ d,
+ total_row_map,
+ group_by_field,
+ subtotal_display_field,
+ grand_total,
+ tax_columns,
+ )
+ add_sub_total_row(row, total_row_map, d.get(group_by_field, ""), tax_columns)
data.append(row)
- if filters.get('group_by') and item_list:
- total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
- total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
+ if filters.get("group_by") and item_list:
+ total_row = total_row_map.get(prev_group_by_value or d.get("item_name"))
+ total_row["percent_gt"] = flt(total_row["total"] / grand_total * 100)
data.append(total_row)
data.append({})
- add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
- data.append(total_row_map.get('total_row'))
+ add_sub_total_row(total_row, total_row_map, "total_row", tax_columns)
+ data.append(total_row_map.get("total_row"))
skip_total_row = 1
return columns, data, None, None, None, skip_total_row
@@ -131,195 +148,180 @@ def get_columns(additional_table_columns, filters):
columns = []
- if filters.get('group_by') != ('Item'):
+ if filters.get("group_by") != ("Item"):
columns.extend(
[
{
- 'label': _('Item Code'),
- 'fieldname': 'item_code',
- 'fieldtype': 'Link',
- 'options': 'Item',
- 'width': 120
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
},
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 120},
+ ]
+ )
+
+ if filters.get("group_by") not in ("Item", "Item Group"):
+ columns.extend(
+ [
{
- 'label': _('Item Name'),
- 'fieldname': 'item_name',
- 'fieldtype': 'Data',
- 'width': 120
+ "label": _("Item Group"),
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "options": "Item Group",
+ "width": 120,
}
]
)
- if filters.get('group_by') not in ('Item', 'Item Group'):
- columns.extend([
+ columns.extend(
+ [
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 150},
{
- 'label': _('Item Group'),
- 'fieldname': 'item_group',
- 'fieldtype': 'Link',
- 'options': 'Item Group',
- 'width': 120
- }
- ])
-
- columns.extend([
- {
- 'label': _('Description'),
- 'fieldname': 'description',
- 'fieldtype': 'Data',
- 'width': 150
- },
- {
- 'label': _('Invoice'),
- 'fieldname': 'invoice',
- 'fieldtype': 'Link',
- 'options': 'Purchase Invoice',
- 'width': 120
- },
- {
- 'label': _('Posting Date'),
- 'fieldname': 'posting_date',
- 'fieldtype': 'Date',
- 'width': 120
- }
- ])
-
- if filters.get('group_by') != 'Supplier':
- columns.extend([
- {
- 'label': _('Supplier'),
- 'fieldname': 'supplier',
- 'fieldtype': 'Link',
- 'options': 'Supplier',
- 'width': 120
+ "label": _("Invoice"),
+ "fieldname": "invoice",
+ "fieldtype": "Link",
+ "options": "Purchase Invoice",
+ "width": 120,
},
- {
- 'label': _('Supplier Name'),
- 'fieldname': 'supplier_name',
- 'fieldtype': 'Data',
- 'width': 120
- }
- ])
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
+ ]
+ )
+
+ if filters.get("group_by") != "Supplier":
+ columns.extend(
+ [
+ {
+ "label": _("Supplier"),
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "width": 120,
+ },
+ {"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 120},
+ ]
+ )
if additional_table_columns:
columns += additional_table_columns
columns += [
{
- 'label': _('Payable Account'),
- 'fieldname': 'credit_to',
- 'fieldtype': 'Link',
- 'options': 'Account',
- 'width': 80
+ "label": _("Payable Account"),
+ "fieldname": "credit_to",
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 80,
},
{
- 'label': _('Mode Of Payment'),
- 'fieldname': 'mode_of_payment',
- 'fieldtype': 'Link',
- 'options': 'Mode of Payment',
- 'width': 120
+ "label": _("Mode Of Payment"),
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "options": "Mode of Payment",
+ "width": 120,
},
{
- 'label': _('Project'),
- 'fieldname': 'project',
- 'fieldtype': 'Link',
- 'options': 'Project',
- 'width': 80
+ "label": _("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 80,
},
{
- 'label': _('Company'),
- 'fieldname': 'company',
- 'fieldtype': 'Link',
- 'options': 'Company',
- 'width': 80
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 80,
},
{
- 'label': _('Purchase Order'),
- 'fieldname': 'purchase_order',
- 'fieldtype': 'Link',
- 'options': 'Purchase Order',
- 'width': 100
+ "label": _("Purchase Order"),
+ "fieldname": "purchase_order",
+ "fieldtype": "Link",
+ "options": "Purchase Order",
+ "width": 100,
},
{
- 'label': _("Purchase Receipt"),
- 'fieldname': 'Purchase Receipt',
- 'fieldtype': 'Link',
- 'options': 'Purchase Receipt',
- 'width': 100
+ "label": _("Purchase Receipt"),
+ "fieldname": "Purchase Receipt",
+ "fieldtype": "Link",
+ "options": "Purchase Receipt",
+ "width": 100,
},
{
- 'label': _('Expense Account'),
- 'fieldname': 'expense_account',
- 'fieldtype': 'Link',
- 'options': 'Account',
- 'width': 100
+ "label": _("Expense Account"),
+ "fieldname": "expense_account",
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 100,
+ },
+ {"label": _("Stock Qty"), "fieldname": "stock_qty", "fieldtype": "Float", "width": 100},
+ {
+ "label": _("Stock UOM"),
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 100,
},
{
- 'label': _('Stock Qty'),
- 'fieldname': 'stock_qty',
- 'fieldtype': 'Float',
- 'width': 100
+ "label": _("Rate"),
+ "fieldname": "rate",
+ "fieldtype": "Float",
+ "options": "currency",
+ "width": 100,
},
{
- 'label': _('Stock UOM'),
- 'fieldname': 'stock_uom',
- 'fieldtype': 'Link',
- 'options': 'UOM',
- 'width': 100
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
},
- {
- 'label': _('Rate'),
- 'fieldname': 'rate',
- 'fieldtype': 'Float',
- 'options': 'currency',
- 'width': 100
- },
- {
- 'label': _('Amount'),
- 'fieldname': 'amount',
- 'fieldtype': 'Currency',
- 'options': 'currency',
- 'width': 100
- }
]
- if filters.get('group_by'):
- columns.append({
- 'label': _('% Of Grand Total'),
- 'fieldname': 'percent_gt',
- 'fieldtype': 'Float',
- 'width': 80
- })
+ if filters.get("group_by"):
+ columns.append(
+ {"label": _("% Of Grand Total"), "fieldname": "percent_gt", "fieldtype": "Float", "width": 80}
+ )
return columns
+
def get_conditions(filters):
conditions = ""
- for opts in (("company", " and company=%(company)s"),
+ for opts in (
+ ("company", " and company=%(company)s"),
("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"),
("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"),
("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"),
("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"),
- ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s")):
- if filters.get(opts[0]):
- conditions += opts[1]
+ ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"),
+ ):
+ if filters.get(opts[0]):
+ conditions += opts[1]
if not filters.get("group_by"):
- conditions += "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc"
+ conditions += (
+ "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc"
+ )
else:
- conditions += get_group_by_conditions(filters, 'Purchase Invoice')
+ conditions += get_group_by_conditions(filters, "Purchase Invoice")
return conditions
+
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
if additional_query_columns:
- additional_query_columns = ', ' + ', '.join(additional_query_columns)
+ additional_query_columns = ", " + ", ".join(additional_query_columns)
else:
- additional_query_columns = ''
+ additional_query_columns = ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
@@ -335,22 +337,35 @@ def get_items(filters, additional_query_columns):
from `tabPurchase Invoice`, `tabPurchase Invoice Item`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabPurchase Invoice`.docstatus = 1 %s
- """.format(additional_query_columns) % (conditions), filters, as_dict=1)
+ """.format(
+ additional_query_columns
+ )
+ % (conditions),
+ filters,
+ as_dict=1,
+ )
+
def get_aii_accounts():
return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany"))
+
def get_purchase_receipts_against_purchase_order(item_list):
po_pr_map = frappe._dict()
po_item_rows = list(set(d.po_detail for d in item_list))
if po_item_rows:
- purchase_receipts = frappe.db.sql("""
+ purchase_receipts = frappe.db.sql(
+ """
select parent, purchase_order_item
from `tabPurchase Receipt Item`
where docstatus=1 and purchase_order_item in (%s)
group by purchase_order_item, parent
- """ % (', '.join(['%s']*len(po_item_rows))), tuple(po_item_rows), as_dict=1)
+ """
+ % (", ".join(["%s"] * len(po_item_rows))),
+ tuple(po_item_rows),
+ as_dict=1,
+ )
for pr in purchase_receipts:
po_pr_map.setdefault(pr.po_detail, []).append(pr.parent)
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 9b35538bb6..2e7213f42b 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -18,11 +18,13 @@ from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history impo
def execute(filters=None):
return _execute(filters)
+
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns(additional_table_columns, filters)
- company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
+ company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
item_list = get_items(filters, additional_query_columns)
if item_list:
@@ -34,10 +36,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
data = []
total_row_map = {}
skip_total_row = 0
- prev_group_by_value = ''
+ prev_group_by_value = ""
- if filters.get('group_by'):
- grand_total = get_grand_total(filters, 'Sales Invoice')
+ if filters.get("group_by"):
+ grand_total = get_grand_total(filters, "Sales Invoice")
customer_details = get_customer_details()
item_details = get_item_details()
@@ -56,289 +58,279 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
delivery_note = d.parent
row = {
- 'item_code': d.item_code,
- 'item_name': item_record.item_name if item_record else d.item_name,
- 'item_group': item_record.item_group if item_record else d.item_group,
- 'description': d.description,
- 'invoice': d.parent,
- 'posting_date': d.posting_date,
- 'customer': d.customer,
- 'customer_name': customer_record.customer_name,
- 'customer_group': customer_record.customer_group,
+ "item_code": d.item_code,
+ "item_name": item_record.item_name if item_record else d.item_name,
+ "item_group": item_record.item_group if item_record else d.item_group,
+ "description": d.description,
+ "invoice": d.parent,
+ "posting_date": d.posting_date,
+ "customer": d.customer,
+ "customer_name": customer_record.customer_name,
+ "customer_group": customer_record.customer_group,
}
if additional_query_columns:
for col in additional_query_columns:
- row.update({
- col: d.get(col)
- })
+ row.update({col: d.get(col)})
- row.update({
- 'debit_to': d.debit_to,
- 'mode_of_payment': ", ".join(mode_of_payments.get(d.parent, [])),
- 'territory': d.territory,
- 'project': d.project,
- 'company': d.company,
- 'sales_order': d.sales_order,
- 'delivery_note': d.delivery_note,
- 'income_account': d.unrealized_profit_loss_account if d.is_internal_customer == 1 else d.income_account,
- 'cost_center': d.cost_center,
- 'stock_qty': d.stock_qty,
- 'stock_uom': d.stock_uom
- })
+ row.update(
+ {
+ "debit_to": d.debit_to,
+ "mode_of_payment": ", ".join(mode_of_payments.get(d.parent, [])),
+ "territory": d.territory,
+ "project": d.project,
+ "company": d.company,
+ "sales_order": d.sales_order,
+ "delivery_note": d.delivery_note,
+ "income_account": d.unrealized_profit_loss_account
+ if d.is_internal_customer == 1
+ else d.income_account,
+ "cost_center": d.cost_center,
+ "stock_qty": d.stock_qty,
+ "stock_uom": d.stock_uom,
+ }
+ )
if d.stock_uom != d.uom and d.stock_qty:
- row.update({
- 'rate': (d.base_net_rate * d.qty)/d.stock_qty,
- 'amount': d.base_net_amount
- })
+ row.update({"rate": (d.base_net_rate * d.qty) / d.stock_qty, "amount": d.base_net_amount})
else:
- row.update({
- 'rate': d.base_net_rate,
- 'amount': d.base_net_amount
- })
+ row.update({"rate": d.base_net_rate, "amount": d.base_net_amount})
total_tax = 0
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
- row.update({
- frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
- frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
- })
- total_tax += flt(item_tax.get('tax_amount'))
+ row.update(
+ {
+ frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
+ frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
+ }
+ )
+ total_tax += flt(item_tax.get("tax_amount"))
- row.update({
- 'total_tax': total_tax,
- 'total': d.base_net_amount + total_tax,
- 'currency': company_currency
- })
+ row.update(
+ {"total_tax": total_tax, "total": d.base_net_amount + total_tax, "currency": company_currency}
+ )
- if filters.get('group_by'):
- row.update({'percent_gt': flt(row['total']/grand_total) * 100})
+ if filters.get("group_by"):
+ row.update({"percent_gt": flt(row["total"] / grand_total) * 100})
group_by_field, subtotal_display_field = get_group_by_and_display_fields(filters)
- data, prev_group_by_value = add_total_row(data, filters, prev_group_by_value, d, total_row_map,
- group_by_field, subtotal_display_field, grand_total, tax_columns)
- add_sub_total_row(row, total_row_map, d.get(group_by_field, ''), tax_columns)
+ data, prev_group_by_value = add_total_row(
+ data,
+ filters,
+ prev_group_by_value,
+ d,
+ total_row_map,
+ group_by_field,
+ subtotal_display_field,
+ grand_total,
+ tax_columns,
+ )
+ add_sub_total_row(row, total_row_map, d.get(group_by_field, ""), tax_columns)
data.append(row)
- if filters.get('group_by') and item_list:
- total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
- total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
+ if filters.get("group_by") and item_list:
+ total_row = total_row_map.get(prev_group_by_value or d.get("item_name"))
+ total_row["percent_gt"] = flt(total_row["total"] / grand_total * 100)
data.append(total_row)
data.append({})
- add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
- data.append(total_row_map.get('total_row'))
+ add_sub_total_row(total_row, total_row_map, "total_row", tax_columns)
+ data.append(total_row_map.get("total_row"))
skip_total_row = 1
return columns, data, None, None, None, skip_total_row
+
def get_columns(additional_table_columns, filters):
columns = []
- if filters.get('group_by') != ('Item'):
+ if filters.get("group_by") != ("Item"):
columns.extend(
[
{
- 'label': _('Item Code'),
- 'fieldname': 'item_code',
- 'fieldtype': 'Link',
- 'options': 'Item',
- 'width': 120
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
},
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 120},
+ ]
+ )
+
+ if filters.get("group_by") not in ("Item", "Item Group"):
+ columns.extend(
+ [
{
- 'label': _('Item Name'),
- 'fieldname': 'item_name',
- 'fieldtype': 'Data',
- 'width': 120
+ "label": _("Item Group"),
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "options": "Item Group",
+ "width": 120,
}
]
)
- if filters.get('group_by') not in ('Item', 'Item Group'):
- columns.extend([
+ columns.extend(
+ [
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 150},
{
- 'label': _('Item Group'),
- 'fieldname': 'item_group',
- 'fieldtype': 'Link',
- 'options': 'Item Group',
- 'width': 120
- }
- ])
-
- columns.extend([
- {
- 'label': _('Description'),
- 'fieldname': 'description',
- 'fieldtype': 'Data',
- 'width': 150
- },
- {
- 'label': _('Invoice'),
- 'fieldname': 'invoice',
- 'fieldtype': 'Link',
- 'options': 'Sales Invoice',
- 'width': 120
- },
- {
- 'label': _('Posting Date'),
- 'fieldname': 'posting_date',
- 'fieldtype': 'Date',
- 'width': 120
- }
- ])
-
- if filters.get('group_by') != 'Customer':
- columns.extend([
- {
- 'label': _('Customer Group'),
- 'fieldname': 'customer_group',
- 'fieldtype': 'Link',
- 'options': 'Customer Group',
- 'width': 120
- }
- ])
-
- if filters.get('group_by') not in ('Customer', 'Customer Group'):
- columns.extend([
- {
- 'label': _('Customer'),
- 'fieldname': 'customer',
- 'fieldtype': 'Link',
- 'options': 'Customer',
- 'width': 120
+ "label": _("Invoice"),
+ "fieldname": "invoice",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120,
},
- {
- 'label': _('Customer Name'),
- 'fieldname': 'customer_name',
- 'fieldtype': 'Data',
- 'width': 120
- }
- ])
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
+ ]
+ )
+
+ if filters.get("group_by") != "Customer":
+ columns.extend(
+ [
+ {
+ "label": _("Customer Group"),
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "options": "Customer Group",
+ "width": 120,
+ }
+ ]
+ )
+
+ if filters.get("group_by") not in ("Customer", "Customer Group"):
+ columns.extend(
+ [
+ {
+ "label": _("Customer"),
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "options": "Customer",
+ "width": 120,
+ },
+ {"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
+ ]
+ )
if additional_table_columns:
columns += additional_table_columns
columns += [
{
- 'label': _('Receivable Account'),
- 'fieldname': 'debit_to',
- 'fieldtype': 'Link',
- 'options': 'Account',
- 'width': 80
+ "label": _("Receivable Account"),
+ "fieldname": "debit_to",
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 80,
},
{
- 'label': _('Mode Of Payment'),
- 'fieldname': 'mode_of_payment',
- 'fieldtype': 'Data',
- 'width': 120
- }
+ "label": _("Mode Of Payment"),
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Data",
+ "width": 120,
+ },
]
- if filters.get('group_by') != 'Territory':
- columns.extend([
- {
- 'label': _('Territory'),
- 'fieldname': 'territory',
- 'fieldtype': 'Link',
- 'options': 'Territory',
- 'width': 80
- }
- ])
-
+ if filters.get("group_by") != "Territory":
+ columns.extend(
+ [
+ {
+ "label": _("Territory"),
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "options": "Territory",
+ "width": 80,
+ }
+ ]
+ )
columns += [
{
- 'label': _('Project'),
- 'fieldname': 'project',
- 'fieldtype': 'Link',
- 'options': 'Project',
- 'width': 80
+ "label": _("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 80,
},
{
- 'label': _('Company'),
- 'fieldname': 'company',
- 'fieldtype': 'Link',
- 'options': 'Company',
- 'width': 80
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 80,
},
{
- 'label': _('Sales Order'),
- 'fieldname': 'sales_order',
- 'fieldtype': 'Link',
- 'options': 'Sales Order',
- 'width': 100
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 100,
},
{
- 'label': _("Delivery Note"),
- 'fieldname': 'delivery_note',
- 'fieldtype': 'Link',
- 'options': 'Delivery Note',
- 'width': 100
+ "label": _("Delivery Note"),
+ "fieldname": "delivery_note",
+ "fieldtype": "Link",
+ "options": "Delivery Note",
+ "width": 100,
},
{
- 'label': _('Income Account'),
- 'fieldname': 'income_account',
- 'fieldtype': 'Link',
- 'options': 'Account',
- 'width': 100
+ "label": _("Income Account"),
+ "fieldname": "income_account",
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 100,
},
{
- 'label': _("Cost Center"),
- 'fieldname': 'cost_center',
- 'fieldtype': 'Link',
- 'options': 'Cost Center',
- 'width': 100
+ "label": _("Cost Center"),
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "options": "Cost Center",
+ "width": 100,
+ },
+ {"label": _("Stock Qty"), "fieldname": "stock_qty", "fieldtype": "Float", "width": 100},
+ {
+ "label": _("Stock UOM"),
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 100,
},
{
- 'label': _('Stock Qty'),
- 'fieldname': 'stock_qty',
- 'fieldtype': 'Float',
- 'width': 100
+ "label": _("Rate"),
+ "fieldname": "rate",
+ "fieldtype": "Float",
+ "options": "currency",
+ "width": 100,
},
{
- 'label': _('Stock UOM'),
- 'fieldname': 'stock_uom',
- 'fieldtype': 'Link',
- 'options': 'UOM',
- 'width': 100
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
},
- {
- 'label': _('Rate'),
- 'fieldname': 'rate',
- 'fieldtype': 'Float',
- 'options': 'currency',
- 'width': 100
- },
- {
- 'label': _('Amount'),
- 'fieldname': 'amount',
- 'fieldtype': 'Currency',
- 'options': 'currency',
- 'width': 100
- }
]
- if filters.get('group_by'):
- columns.append({
- 'label': _('% Of Grand Total'),
- 'fieldname': 'percent_gt',
- 'fieldtype': 'Float',
- 'width': 80
- })
+ if filters.get("group_by"):
+ columns.append(
+ {"label": _("% Of Grand Total"), "fieldname": "percent_gt", "fieldtype": "Float", "width": 80}
+ )
return columns
+
def get_conditions(filters):
conditions = ""
- for opts in (("company", " and company=%(company)s"),
+ for opts in (
+ ("company", " and company=%(company)s"),
("customer", " and `tabSales Invoice`.customer = %(customer)s"),
("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"),
("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
- ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s")):
- if filters.get(opts[0]):
- conditions += opts[1]
+ ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s"),
+ ):
+ if filters.get(opts[0]):
+ conditions += opts[1]
if filters.get("mode_of_payment"):
conditions += """ and exists(select name from `tabSales Invoice Payment`
@@ -346,41 +338,45 @@ def get_conditions(filters):
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"):
- conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
-
+ conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
if filters.get("brand"):
- conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
+ conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
if filters.get("item_group"):
- conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
+ conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
if not filters.get("group_by"):
- conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
+ conditions += (
+ "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
+ )
else:
- conditions += get_group_by_conditions(filters, 'Sales Invoice')
+ conditions += get_group_by_conditions(filters, "Sales Invoice")
return conditions
+
def get_group_by_conditions(filters, doctype):
- if filters.get("group_by") == 'Invoice':
+ if filters.get("group_by") == "Invoice":
return "ORDER BY `tab{0} Item`.parent desc".format(doctype)
- elif filters.get("group_by") == 'Item':
+ elif filters.get("group_by") == "Item":
return "ORDER BY `tab{0} Item`.`item_code`".format(doctype)
- elif filters.get("group_by") == 'Item Group':
- return "ORDER BY `tab{0} Item`.{1}".format(doctype, frappe.scrub(filters.get('group_by')))
- elif filters.get("group_by") in ('Customer', 'Customer Group', 'Territory', 'Supplier'):
- return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get('group_by')))
+ elif filters.get("group_by") == "Item Group":
+ return "ORDER BY `tab{0} Item`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
+ elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
+ return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
+
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
if additional_query_columns:
- additional_query_columns = ', ' + ', '.join(additional_query_columns)
+ additional_query_columns = ", " + ", ".join(additional_query_columns)
else:
- additional_query_columns = ''
+ additional_query_columns = ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
@@ -399,47 +395,80 @@ def get_items(filters, additional_query_columns):
from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Invoice`.docstatus = 1 {1}
- """.format(additional_query_columns or '', conditions), filters, as_dict=1) #nosec
+ """.format(
+ additional_query_columns or "", conditions
+ ),
+ filters,
+ as_dict=1,
+ ) # nosec
+
def get_delivery_notes_against_sales_order(item_list):
so_dn_map = frappe._dict()
so_item_rows = list(set([d.so_detail for d in item_list]))
if so_item_rows:
- delivery_notes = frappe.db.sql("""
+ delivery_notes = frappe.db.sql(
+ """
select parent, so_detail
from `tabDelivery Note Item`
where docstatus=1 and so_detail in (%s)
group by so_detail, parent
- """ % (', '.join(['%s']*len(so_item_rows))), tuple(so_item_rows), as_dict=1)
+ """
+ % (", ".join(["%s"] * len(so_item_rows))),
+ tuple(so_item_rows),
+ as_dict=1,
+ )
for dn in delivery_notes:
so_dn_map.setdefault(dn.so_detail, []).append(dn.parent)
return so_dn_map
+
def get_grand_total(filters, doctype):
- return frappe.db.sql(""" SELECT
+ return frappe.db.sql(
+ """ SELECT
SUM(`tab{0}`.base_grand_total)
FROM `tab{0}`
WHERE `tab{0}`.docstatus = 1
and posting_date between %s and %s
- """.format(doctype), (filters.get('from_date'), filters.get('to_date')))[0][0] #nosec
+ """.format(
+ doctype
+ ),
+ (filters.get("from_date"), filters.get("to_date")),
+ )[0][
+ 0
+ ] # nosec
+
def get_deducted_taxes():
- return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
+ return frappe.db.sql_list(
+ "select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'"
+ )
-def get_tax_accounts(item_list, columns, company_currency,
- doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
+
+def get_tax_accounts(
+ item_list,
+ columns,
+ company_currency,
+ doctype="Sales Invoice",
+ tax_doctype="Sales Taxes and Charges",
+):
import json
+
item_row_map = {}
tax_columns = []
invoice_item_row = {}
itemised_tax = {}
- tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
- currency=company_currency) or 2
+ tax_amount_precision = (
+ get_field_precision(
+ frappe.get_meta(tax_doctype).get_field("tax_amount"), currency=company_currency
+ )
+ or 2
+ )
for d in item_list:
invoice_item_row.setdefault(d.parent, []).append(d)
@@ -450,7 +479,8 @@ def get_tax_accounts(item_list, columns, company_currency,
conditions = " and category in ('Total', 'Valuation and Total') and base_tax_amount_after_discount_amount != 0"
deducted_tax = get_deducted_taxes()
- tax_details = frappe.db.sql("""
+ tax_details = frappe.db.sql(
+ """
select
name, parent, description, item_wise_tax_detail,
charge_type, base_tax_amount_after_discount_amount
@@ -461,8 +491,10 @@ def get_tax_accounts(item_list, columns, company_currency,
and parent in (%s)
%s
order by description
- """ % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions),
- tuple([doctype] + list(invoice_item_row)))
+ """
+ % (tax_doctype, "%s", ", ".join(["%s"] * len(invoice_item_row)), conditions),
+ tuple([doctype] + list(invoice_item_row)),
+ )
for name, parent, description, item_wise_tax_detail, charge_type, tax_amount in tax_details:
description = handle_html(description)
@@ -483,151 +515,187 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_rate = tax_data
tax_amount = 0
- if charge_type == 'Actual' and not tax_rate:
- tax_rate = 'NA'
+ if charge_type == "Actual" and not tax_rate:
+ tax_rate = "NA"
- item_net_amount = sum([flt(d.base_net_amount)
- for d in item_row_map.get(parent, {}).get(item_code, [])])
+ item_net_amount = sum(
+ [flt(d.base_net_amount) for d in item_row_map.get(parent, {}).get(item_code, [])]
+ )
for d in item_row_map.get(parent, {}).get(item_code, []):
- item_tax_amount = flt((tax_amount * d.base_net_amount) / item_net_amount) \
- if item_net_amount else 0
+ item_tax_amount = (
+ flt((tax_amount * d.base_net_amount) / item_net_amount) if item_net_amount else 0
+ )
if item_tax_amount:
tax_value = flt(item_tax_amount, tax_amount_precision)
- tax_value = (tax_value * -1
- if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
+ tax_value = (
+ tax_value * -1 if (doctype == "Purchase Invoice" and name in deducted_tax) else tax_value
+ )
- itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
- 'tax_rate': tax_rate,
- 'tax_amount': tax_value
- })
+ itemised_tax.setdefault(d.name, {})[description] = frappe._dict(
+ {"tax_rate": tax_rate, "tax_amount": tax_value}
+ )
except ValueError:
continue
- elif charge_type == 'Actual' and tax_amount:
+ elif charge_type == "Actual" and tax_amount:
for d in invoice_item_row.get(parent, []):
- itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
- 'tax_rate': 'NA',
- 'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
- tax_amount_precision)
- })
+ itemised_tax.setdefault(d.name, {})[description] = frappe._dict(
+ {
+ "tax_rate": "NA",
+ "tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total, tax_amount_precision),
+ }
+ )
tax_columns.sort()
for desc in tax_columns:
- columns.append({
- 'label': _(desc + ' Rate'),
- 'fieldname': frappe.scrub(desc + ' Rate'),
- 'fieldtype': 'Float',
- 'width': 100
- })
+ columns.append(
+ {
+ "label": _(desc + " Rate"),
+ "fieldname": frappe.scrub(desc + " Rate"),
+ "fieldtype": "Float",
+ "width": 100,
+ }
+ )
- columns.append({
- 'label': _(desc + ' Amount'),
- 'fieldname': frappe.scrub(desc + ' Amount'),
- 'fieldtype': 'Currency',
- 'options': 'currency',
- 'width': 100
- })
+ columns.append(
+ {
+ "label": _(desc + " Amount"),
+ "fieldname": frappe.scrub(desc + " Amount"),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ }
+ )
columns += [
{
- 'label': _('Total Tax'),
- 'fieldname': 'total_tax',
- 'fieldtype': 'Currency',
- 'options': 'currency',
- 'width': 100
+ "label": _("Total Tax"),
+ "fieldname": "total_tax",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
},
{
- 'label': _('Total'),
- 'fieldname': 'total',
- 'fieldtype': 'Currency',
- 'options': 'currency',
- 'width': 100
+ "label": _("Total"),
+ "fieldname": "total",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
},
{
- 'fieldname': 'currency',
- 'label': _('Currency'),
- 'fieldtype': 'Currency',
- 'width': 80,
- 'hidden': 1
- }
+ "fieldname": "currency",
+ "label": _("Currency"),
+ "fieldtype": "Currency",
+ "width": 80,
+ "hidden": 1,
+ },
]
return itemised_tax, tax_columns
-def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
- group_by_field, subtotal_display_field, grand_total, tax_columns):
- if prev_group_by_value != item.get(group_by_field, ''):
+
+def add_total_row(
+ data,
+ filters,
+ prev_group_by_value,
+ item,
+ total_row_map,
+ group_by_field,
+ subtotal_display_field,
+ grand_total,
+ tax_columns,
+):
+ if prev_group_by_value != item.get(group_by_field, ""):
if prev_group_by_value:
total_row = total_row_map.get(prev_group_by_value)
data.append(total_row)
data.append({})
- add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
+ add_sub_total_row(total_row, total_row_map, "total_row", tax_columns)
- prev_group_by_value = item.get(group_by_field, '')
+ prev_group_by_value = item.get(group_by_field, "")
- total_row_map.setdefault(item.get(group_by_field, ''), {
- subtotal_display_field: get_display_value(filters, group_by_field, item),
- 'stock_qty': 0.0,
- 'amount': 0.0,
- 'bold': 1,
- 'total_tax': 0.0,
- 'total': 0.0,
- 'percent_gt': 0.0
- })
+ total_row_map.setdefault(
+ item.get(group_by_field, ""),
+ {
+ subtotal_display_field: get_display_value(filters, group_by_field, item),
+ "stock_qty": 0.0,
+ "amount": 0.0,
+ "bold": 1,
+ "total_tax": 0.0,
+ "total": 0.0,
+ "percent_gt": 0.0,
+ },
+ )
- total_row_map.setdefault('total_row', {
- subtotal_display_field: 'Total',
- 'stock_qty': 0.0,
- 'amount': 0.0,
- 'bold': 1,
- 'total_tax': 0.0,
- 'total': 0.0,
- 'percent_gt': 0.0
- })
+ total_row_map.setdefault(
+ "total_row",
+ {
+ subtotal_display_field: "Total",
+ "stock_qty": 0.0,
+ "amount": 0.0,
+ "bold": 1,
+ "total_tax": 0.0,
+ "total": 0.0,
+ "percent_gt": 0.0,
+ },
+ )
return data, prev_group_by_value
+
def get_display_value(filters, group_by_field, item):
- if filters.get('group_by') == 'Item':
- if item.get('item_code') != item.get('item_name'):
- value = cstr(item.get('item_code')) + "
" + \
- "" + cstr(item.get('item_name')) + ""
+ if filters.get("group_by") == "Item":
+ if item.get("item_code") != item.get("item_name"):
+ value = (
+ cstr(item.get("item_code"))
+ + "
"
+ + ""
+ + cstr(item.get("item_name"))
+ + ""
+ )
else:
- value = item.get('item_code', '')
- elif filters.get('group_by') in ('Customer', 'Supplier'):
- party = frappe.scrub(filters.get('group_by'))
- if item.get(party) != item.get(party+'_name'):
- value = item.get(party) + "
" + \
- "" + item.get(party+'_name') + ""
+ value = item.get("item_code", "")
+ elif filters.get("group_by") in ("Customer", "Supplier"):
+ party = frappe.scrub(filters.get("group_by"))
+ if item.get(party) != item.get(party + "_name"):
+ value = (
+ item.get(party)
+ + "
"
+ + ""
+ + item.get(party + "_name")
+ + ""
+ )
else:
- value = item.get(party)
+ value = item.get(party)
else:
value = item.get(group_by_field)
return value
+
def get_group_by_and_display_fields(filters):
- if filters.get('group_by') == 'Item':
- group_by_field = 'item_code'
- subtotal_display_field = 'invoice'
- elif filters.get('group_by') == 'Invoice':
- group_by_field = 'parent'
- subtotal_display_field = 'item_code'
+ if filters.get("group_by") == "Item":
+ group_by_field = "item_code"
+ subtotal_display_field = "invoice"
+ elif filters.get("group_by") == "Invoice":
+ group_by_field = "parent"
+ subtotal_display_field = "item_code"
else:
- group_by_field = frappe.scrub(filters.get('group_by'))
- subtotal_display_field = 'item_code'
+ group_by_field = frappe.scrub(filters.get("group_by"))
+ subtotal_display_field = "item_code"
return group_by_field, subtotal_display_field
+
def add_sub_total_row(item, total_row_map, group_by_value, tax_columns):
total_row = total_row_map.get(group_by_value)
- total_row['stock_qty'] += item['stock_qty']
- total_row['amount'] += item['amount']
- total_row['total_tax'] += item['total_tax']
- total_row['total'] += item['total']
- total_row['percent_gt'] += item['percent_gt']
+ total_row["stock_qty"] += item["stock_qty"]
+ total_row["amount"] += item["amount"]
+ total_row["total_tax"] += item["total_tax"]
+ total_row["total"] += item["total"]
+ total_row["percent_gt"] += item["percent_gt"]
for tax in tax_columns:
- total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0)
- total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')])
+ total_row.setdefault(frappe.scrub(tax + " Amount"), 0.0)
+ total_row[frappe.scrub(tax + " Amount")] += flt(item[frappe.scrub(tax + " Amount")])
diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py
index a421bc5c20..39c5311cd9 100644
--- a/erpnext/accounts/report/non_billed_report.py
+++ b/erpnext/accounts/report/non_billed_report.py
@@ -9,14 +9,19 @@ from erpnext import get_default_currency
def get_ordered_to_be_billed_data(args):
- doctype, party = args.get('doctype'), args.get('party')
+ doctype, party = args.get("doctype"), args.get("party")
child_tab = doctype + " Item"
- precision = get_field_precision(frappe.get_meta(child_tab).get_field("billed_amt"),
- currency=get_default_currency()) or 2
+ precision = (
+ get_field_precision(
+ frappe.get_meta(child_tab).get_field("billed_amt"), currency=get_default_currency()
+ )
+ or 2
+ )
project_field = get_project_field(doctype, party)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
Select
`{parent_tab}`.name, `{parent_tab}`.{date_field},
`{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
@@ -40,9 +45,20 @@ def get_ordered_to_be_billed_data(args):
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
order by
`{parent_tab}`.{order} {order_by}
- """.format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,
- date_field = args.get('date'), project_field = project_field, order= args.get('order'), order_by = args.get('order_by')))
+ """.format(
+ parent_tab="tab" + doctype,
+ child_tab="tab" + child_tab,
+ precision=precision,
+ party=party,
+ date_field=args.get("date"),
+ project_field=project_field,
+ order=args.get("order"),
+ order_by=args.get("order_by"),
+ )
+ )
+
def get_project_field(doctype, party):
- if party == "supplier": doctype = doctype + ' Item'
- return "`tab%s`.project"%(doctype)
+ if party == "supplier":
+ doctype = doctype + " Item"
+ return "`tab%s`.project" % (doctype)
diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
index 6c12093763..00f5948a1b 100644
--- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
+++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
@@ -28,21 +28,28 @@ def execute(filters=None):
else:
payment_amount = flt(d.credit) or -1 * flt(d.debit)
- d.update({
- "range1": 0,
- "range2": 0,
- "range3": 0,
- "range4": 0,
- "outstanding": payment_amount
- })
+ d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount})
if d.against_voucher:
ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d)
row = [
- d.voucher_type, d.voucher_no, d.party_type, d.party, d.posting_date, d.against_voucher,
- invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks,
- d.age, d.range1, d.range2, d.range3, d.range4
+ d.voucher_type,
+ d.voucher_no,
+ d.party_type,
+ d.party,
+ d.posting_date,
+ d.against_voucher,
+ invoice.posting_date,
+ invoice.due_date,
+ d.debit,
+ d.credit,
+ d.remarks,
+ d.age,
+ d.range1,
+ d.range2,
+ d.range3,
+ d.range4,
]
if invoice.due_date:
@@ -52,11 +59,17 @@ def execute(filters=None):
return columns, data
+
def validate_filters(filters):
- if (filters.get("payment_type") == _("Incoming") and filters.get("party_type") == "Supplier") or \
- (filters.get("payment_type") == _("Outgoing") and filters.get("party_type") == "Customer"):
- frappe.throw(_("{0} payment entries can not be filtered by {1}")\
- .format(filters.payment_type, filters.party_type))
+ if (filters.get("payment_type") == _("Incoming") and filters.get("party_type") == "Supplier") or (
+ filters.get("payment_type") == _("Outgoing") and filters.get("party_type") == "Customer"
+ ):
+ frappe.throw(
+ _("{0} payment entries can not be filtered by {1}").format(
+ filters.payment_type, filters.party_type
+ )
+ )
+
def get_columns(filters):
return [
@@ -64,109 +77,57 @@ def get_columns(filters):
"fieldname": "payment_document",
"label": _("Payment Document Type"),
"fieldtype": "Data",
- "width": 100
+ "width": 100,
},
{
"fieldname": "payment_entry",
"label": _("Payment Document"),
"fieldtype": "Dynamic Link",
"options": "payment_document",
- "width": 160
- },
- {
- "fieldname": "party_type",
- "label": _("Party Type"),
- "fieldtype": "Data",
- "width": 100
+ "width": 160,
},
+ {"fieldname": "party_type", "label": _("Party Type"), "fieldtype": "Data", "width": 100},
{
"fieldname": "party",
"label": _("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
- "width": 160
- },
- {
- "fieldname": "posting_date",
- "label": _("Posting Date"),
- "fieldtype": "Date",
- "width": 100
+ "width": 160,
},
+ {"fieldname": "posting_date", "label": _("Posting Date"), "fieldtype": "Date", "width": 100},
{
"fieldname": "invoice",
"label": _("Invoice"),
"fieldtype": "Link",
- "options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
- "width": 160
+ "options": "Purchase Invoice"
+ if filters.get("payment_type") == _("Outgoing")
+ else "Sales Invoice",
+ "width": 160,
},
{
"fieldname": "invoice_posting_date",
"label": _("Invoice Posting Date"),
"fieldtype": "Date",
- "width": 100
+ "width": 100,
},
+ {"fieldname": "due_date", "label": _("Payment Due Date"), "fieldtype": "Date", "width": 100},
+ {"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 140},
+ {"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140},
+ {"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200},
+ {"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50},
+ {"fieldname": "range1", "label": "0-30", "fieldtype": "Currency", "width": 140},
+ {"fieldname": "range2", "label": "30-60", "fieldtype": "Currency", "width": 140},
+ {"fieldname": "range3", "label": "60-90", "fieldtype": "Currency", "width": 140},
+ {"fieldname": "range4", "label": _("90 Above"), "fieldtype": "Currency", "width": 140},
{
- "fieldname": "due_date",
- "label": _("Payment Due Date"),
- "fieldtype": "Date",
- "width": 100
- },
- {
- "fieldname": "debit",
- "label": _("Debit"),
- "fieldtype": "Currency",
- "width": 140
- },
- {
- "fieldname": "credit",
- "label": _("Credit"),
- "fieldtype": "Currency",
- "width": 140
- },
- {
- "fieldname": "remarks",
- "label": _("Remarks"),
- "fieldtype": "Data",
- "width": 200
- },
- {
- "fieldname": "age",
- "label": _("Age"),
- "fieldtype": "Int",
- "width": 50
- },
- {
- "fieldname": "range1",
- "label": "0-30",
- "fieldtype": "Currency",
- "width": 140
- },
- {
- "fieldname": "range2",
- "label": "30-60",
- "fieldtype": "Currency",
- "width": 140
- },
- {
- "fieldname": "range3",
- "label": "60-90",
- "fieldtype": "Currency",
- "width": 140
- },
- {
- "fieldname": "range4",
- "label": _("90 Above"),
- "fieldtype": "Currency",
- "width": 140
- },
- {
"fieldname": "delay_in_payment",
"label": _("Delay in payment (Days)"),
"fieldtype": "Int",
- "width": 100
- }
+ "width": 100,
+ },
]
+
def get_conditions(filters):
conditions = []
@@ -184,7 +145,9 @@ def get_conditions(filters):
if filters.party_type:
conditions.append("against_voucher_type=%(reference_type)s")
- filters["reference_type"] = "Sales Invoice" if filters.party_type=="Customer" else "Purchase Invoice"
+ filters["reference_type"] = (
+ "Sales Invoice" if filters.party_type == "Customer" else "Purchase Invoice"
+ )
if filters.get("from_date"):
conditions.append("posting_date >= %(from_date)s")
@@ -194,12 +157,20 @@ def get_conditions(filters):
return "and " + " and ".join(conditions) if conditions else ""
+
def get_entries(filters):
- return frappe.db.sql("""select
+ return frappe.db.sql(
+ """select
voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher
from `tabGL Entry`
where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') {0}
- """.format(get_conditions(filters)), filters, as_dict=1)
+ """.format(
+ get_conditions(filters)
+ ),
+ filters,
+ as_dict=1,
+ )
+
def get_invoice_posting_date_map(filters):
invoice_details = {}
diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py
index 77e7568533..1bda0d8ae3 100644
--- a/erpnext/accounts/report/pos_register/pos_register.py
+++ b/erpnext/accounts/report/pos_register/pos_register.py
@@ -37,11 +37,14 @@ def execute(filters=None):
add_subtotal_row(grouped_data, invoices, group_by_field, key)
# move group by column to first position
- column_index = next((index for (index, d) in enumerate(columns) if d["fieldname"] == group_by_field), None)
+ column_index = next(
+ (index for (index, d) in enumerate(columns) if d["fieldname"] == group_by_field), None
+ )
columns.insert(0, columns.pop(column_index))
return columns, grouped_data
+
def get_pos_entries(filters, group_by_field):
conditions = get_conditions(filters)
order_by = "p.posting_date"
@@ -74,8 +77,12 @@ def get_pos_entries(filters, group_by_field):
from_sales_invoice_payment=from_sales_invoice_payment,
group_by_mop_condition=group_by_mop_condition,
conditions=conditions,
- order_by=order_by
- ), filters, as_dict=1)
+ order_by=order_by,
+ ),
+ filters,
+ as_dict=1,
+ )
+
def concat_mode_of_payments(pos_entries):
mode_of_payments = get_mode_of_payments(set(d.pos_invoice for d in pos_entries))
@@ -83,41 +90,50 @@ def concat_mode_of_payments(pos_entries):
if mode_of_payments.get(entry.pos_invoice):
entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, []))
+
def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
grand_total = sum(d.grand_total for d in group_invoices)
paid_amount = sum(d.paid_amount for d in group_invoices)
- data.append({
- group_by_field: group_by_value,
- "grand_total": grand_total,
- "paid_amount": paid_amount,
- "bold": 1
- })
+ data.append(
+ {
+ group_by_field: group_by_value,
+ "grand_total": grand_total,
+ "paid_amount": paid_amount,
+ "bold": 1,
+ }
+ )
data.append({})
+
def validate_filters(filters):
if not filters.get("company"):
frappe.throw(_("{0} is mandatory").format(_("Company")))
if not filters.get("from_date") and not filters.get("to_date"):
- frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
+ frappe.throw(
+ _("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))
+ )
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
- if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')):
+ if filters.get("pos_profile") and filters.get("group_by") == _("POS Profile"):
frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile"))
- if (filters.get("customer") and filters.get("group_by") == _('Customer')):
+ if filters.get("customer") and filters.get("group_by") == _("Customer"):
frappe.throw(_("Can not filter based on Customer, if grouped by Customer"))
- if (filters.get("owner") and filters.get("group_by") == _('Cashier')):
+ if filters.get("owner") and filters.get("group_by") == _("Cashier"):
frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier"))
- if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')):
+ if filters.get("mode_of_payment") and filters.get("group_by") == _("Payment Method"):
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
+
def get_conditions(filters):
- conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s"
+ conditions = (
+ "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s"
+ )
if filters.get("pos_profile"):
conditions += " AND pos_profile = %(pos_profile)s"
@@ -140,6 +156,7 @@ def get_conditions(filters):
return conditions
+
def get_group_by_field(group_by):
group_by_field = ""
@@ -154,68 +171,59 @@ def get_group_by_field(group_by):
return group_by_field
+
def get_columns(filters):
columns = [
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 90
- },
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 90},
{
"label": _("POS Invoice"),
"fieldname": "pos_invoice",
"fieldtype": "Link",
"options": "POS Invoice",
- "width": 120
+ "width": 120,
},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
- "width": 120
+ "width": 120,
},
{
"label": _("POS Profile"),
"fieldname": "pos_profile",
"fieldtype": "Link",
"options": "POS Profile",
- "width": 160
+ "width": 160,
},
{
"label": _("Cashier"),
"fieldname": "owner",
"fieldtype": "Link",
"options": "User",
- "width": 140
+ "width": 140,
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 120
+ "width": 120,
},
{
"label": _("Paid Amount"),
"fieldname": "paid_amount",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 120
+ "width": 120,
},
{
"label": _("Payment Method"),
"fieldname": "mode_of_payment",
"fieldtype": "Data",
- "width": 150
- },
- {
- "label": _("Is Return"),
- "fieldname": "is_return",
- "fieldtype": "Data",
- "width": 80
+ "width": 150,
},
+ {"label": _("Is Return"), "fieldname": "is_return", "fieldtype": "Data", "width": 80},
]
return columns
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
index 882e411246..66353358a0 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
@@ -15,19 +15,41 @@ from erpnext.accounts.report.financial_statements import (
def execute(filters=None):
- period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
- filters.period_start_date, filters.period_end_date, filters.filter_based_on, filters.periodicity,
- company=filters.company)
+ period_list = get_period_list(
+ filters.from_fiscal_year,
+ filters.to_fiscal_year,
+ filters.period_start_date,
+ filters.period_end_date,
+ filters.filter_based_on,
+ filters.periodicity,
+ company=filters.company,
+ )
- income = get_data(filters.company, "Income", "Credit", period_list, filters = filters,
+ income = get_data(
+ filters.company,
+ "Income",
+ "Credit",
+ period_list,
+ filters=filters,
accumulated_values=filters.accumulated_values,
- ignore_closing_entries=True, ignore_accumulated_values_for_fy= True)
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
+ )
- expense = get_data(filters.company, "Expense", "Debit", period_list, filters=filters,
+ expense = get_data(
+ filters.company,
+ "Expense",
+ "Debit",
+ period_list,
+ filters=filters,
accumulated_values=filters.accumulated_values,
- ignore_closing_entries=True, ignore_accumulated_values_for_fy= True)
+ ignore_closing_entries=True,
+ ignore_accumulated_values_for_fy=True,
+ )
- net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company, filters.presentation_currency)
+ net_profit_loss = get_net_profit_loss(
+ income, expense, period_list, filters.company, filters.presentation_currency
+ )
data = []
data.extend(income or [])
@@ -35,20 +57,29 @@ def execute(filters=None):
if net_profit_loss:
data.append(net_profit_loss)
- columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
+ columns = get_columns(
+ filters.periodicity, period_list, filters.accumulated_values, filters.company
+ )
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
- currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
- report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters)
+ currency = filters.presentation_currency or frappe.get_cached_value(
+ "Company", filters.company, "default_currency"
+ )
+ report_summary = get_report_summary(
+ period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
+ )
return columns, data, None, chart, report_summary
-def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False):
+
+def get_report_summary(
+ period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False
+):
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
# from consolidated financial statement
- if filters.get('accumulated_in_group_company'):
+ if filters.get("accumulated_in_group_company"):
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
for period in period_list:
@@ -60,37 +91,27 @@ def get_report_summary(period_list, periodicity, income, expense, net_profit_los
if net_profit_loss:
net_profit += net_profit_loss.get(key)
- if (len(period_list) == 1 and periodicity== 'Yearly'):
- profit_label = _("Profit This Year")
- income_label = _("Total Income This Year")
- expense_label = _("Total Expense This Year")
+ if len(period_list) == 1 and periodicity == "Yearly":
+ profit_label = _("Profit This Year")
+ income_label = _("Total Income This Year")
+ expense_label = _("Total Expense This Year")
else:
profit_label = _("Net Profit")
income_label = _("Total Income")
expense_label = _("Total Expense")
return [
- {
- "value": net_income,
- "label": income_label,
- "datatype": "Currency",
- "currency": currency
- },
- { "type": "separator", "value": "-"},
- {
- "value": net_expense,
- "label": expense_label,
- "datatype": "Currency",
- "currency": currency
- },
- { "type": "separator", "value": "=", "color": "blue"},
+ {"value": net_income, "label": income_label, "datatype": "Currency", "currency": currency},
+ {"type": "separator", "value": "-"},
+ {"value": net_expense, "label": expense_label, "datatype": "Currency", "currency": currency},
+ {"type": "separator", "value": "=", "color": "blue"},
{
"value": net_profit,
"indicator": "Green" if net_profit > 0 else "Red",
"label": profit_label,
"datatype": "Currency",
- "currency": currency
- }
+ "currency": currency,
+ },
]
@@ -100,7 +121,7 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co
"account_name": "'" + _("Profit for the year") + "'",
"account": "'" + _("Profit for the year") + "'",
"warn_if_negative": True,
- "currency": currency or frappe.get_cached_value('Company', company, "default_currency")
+ "currency": currency or frappe.get_cached_value("Company", company, "default_currency"),
}
has_value = False
@@ -113,7 +134,7 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co
net_profit_loss[key] = total_income - total_expense
if net_profit_loss[key]:
- has_value=True
+ has_value = True
total += flt(net_profit_loss[key])
net_profit_loss["total"] = total
@@ -121,6 +142,7 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co
if has_value:
return net_profit_loss
+
def get_chart_data(filters, columns, income, expense, net_profit_loss):
labels = [d.get("label") for d in columns[2:]]
@@ -136,18 +158,13 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss):
datasets = []
if income_data:
- datasets.append({'name': _('Income'), 'values': income_data})
+ datasets.append({"name": _("Income"), "values": income_data})
if expense_data:
- datasets.append({'name': _('Expense'), 'values': expense_data})
+ datasets.append({"name": _("Expense"), "values": expense_data})
if net_profit:
- datasets.append({'name': _('Net Profit/Loss'), 'values': net_profit})
+ datasets.append({"name": _("Net Profit/Loss"), "values": net_profit})
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- }
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}}
if not filters.accumulated_values:
chart["type"] = "bar"
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index f4b8731ba8..3e7aa1e368 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -14,31 +14,39 @@ from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
value_fields = ("income", "expense", "gross_profit_loss")
-def execute(filters=None):
- if not filters.get('based_on'): filters["based_on"] = 'Cost Center'
- based_on = filters.based_on.replace(' ', '_').lower()
+def execute(filters=None):
+ if not filters.get("based_on"):
+ filters["based_on"] = "Cost Center"
+
+ based_on = filters.based_on.replace(" ", "_").lower()
validate_filters(filters)
accounts = get_accounts_data(based_on, filters.get("company"))
data = get_data(accounts, filters, based_on)
columns = get_columns(filters)
return columns, data
+
def get_accounts_data(based_on, company):
- if based_on == 'cost_center':
- return frappe.db.sql("""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
- from `tabCost Center` where company=%s order by name""", company, as_dict=True)
- elif based_on == 'project':
- return frappe.get_all('Project', fields = ["name"], filters = {'company': company}, order_by = 'name')
+ if based_on == "cost_center":
+ return frappe.db.sql(
+ """select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
+ from `tabCost Center` where company=%s order by name""",
+ company,
+ as_dict=True,
+ )
+ elif based_on == "project":
+ return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
else:
filters = {}
doctype = frappe.unscrub(based_on)
- has_company = frappe.db.has_column(doctype, 'company')
+ has_company = frappe.db.has_column(doctype, "company")
if has_company:
- filters.update({'company': company})
+ filters.update({"company": company})
+
+ return frappe.get_all(doctype, fields=["name"], filters=filters, order_by="name")
- return frappe.get_all(doctype, fields = ["name"], filters = filters, order_by = 'name')
def get_data(accounts, filters, based_on):
if not accounts:
@@ -48,24 +56,28 @@ def get_data(accounts, filters, based_on):
gl_entries_by_account = {}
- set_gl_entries_by_account(filters.get("company"), filters.get("from_date"),
- filters.get("to_date"), based_on, gl_entries_by_account, ignore_closing_entries=not flt(filters.get("with_period_closing_entry")))
+ set_gl_entries_by_account(
+ filters.get("company"),
+ filters.get("from_date"),
+ filters.get("to_date"),
+ based_on,
+ gl_entries_by_account,
+ ignore_closing_entries=not flt(filters.get("with_period_closing_entry")),
+ )
total_row = calculate_values(accounts, gl_entries_by_account, filters)
accumulate_values_into_parents(accounts, accounts_by_name)
data = prepare_data(accounts, filters, total_row, parent_children_map, based_on)
- data = filter_out_zero_value_rows(data, parent_children_map,
- show_zero_values=filters.get("show_zero_values"))
+ data = filter_out_zero_value_rows(
+ data, parent_children_map, show_zero_values=filters.get("show_zero_values")
+ )
return data
+
def calculate_values(accounts, gl_entries_by_account, filters):
- init = {
- "income": 0.0,
- "expense": 0.0,
- "gross_profit_loss": 0.0
- }
+ init = {"income": 0.0, "expense": 0.0, "gross_profit_loss": 0.0}
total_row = {
"cost_center": None,
@@ -77,7 +89,7 @@ def calculate_values(accounts, gl_entries_by_account, filters):
"account": "'" + _("Total") + "'",
"parent_account": None,
"indent": 0,
- "has_value": True
+ "has_value": True,
}
for d in accounts:
@@ -87,9 +99,9 @@ def calculate_values(accounts, gl_entries_by_account, filters):
for entry in gl_entries_by_account.get(d.name, []):
if cstr(entry.is_opening) != "Yes":
- if entry.type == 'Income':
+ if entry.type == "Income":
d["income"] += flt(entry.credit) - flt(entry.debit)
- if entry.type == 'Expense':
+ if entry.type == "Expense":
d["expense"] += flt(entry.debit) - flt(entry.credit)
d["gross_profit_loss"] = d.get("income") - d.get("expense")
@@ -101,15 +113,17 @@ def calculate_values(accounts, gl_entries_by_account, filters):
return total_row
+
def accumulate_values_into_parents(accounts, accounts_by_name):
for d in reversed(accounts):
if d.parent_account:
for key in value_fields:
accounts_by_name[d.parent_account][key] += d[key]
+
def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
data = []
- company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
+ company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
for d in accounts:
has_value = False
@@ -120,7 +134,7 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
"indent": d.indent,
"fiscal_year": filters.get("fiscal_year"),
"currency": company_currency,
- "based_on": based_on
+ "based_on": based_on,
}
for key in value_fields:
@@ -133,10 +147,11 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
row["has_value"] = has_value
data.append(row)
- data.extend([{},total_row])
+ data.extend([{}, total_row])
return data
+
def get_columns(filters):
return [
{
@@ -144,43 +159,42 @@ def get_columns(filters):
"label": _(filters.get("based_on")),
"fieldtype": "Link",
"options": filters.get("based_on"),
- "width": 300
+ "width": 300,
},
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
- "hidden": 1
+ "hidden": 1,
},
{
"fieldname": "income",
"label": _("Income"),
"fieldtype": "Currency",
"options": "currency",
- "width": 305
-
+ "width": 305,
},
{
"fieldname": "expense",
"label": _("Expense"),
"fieldtype": "Currency",
"options": "currency",
- "width": 305
-
+ "width": 305,
},
{
"fieldname": "gross_profit_loss",
"label": _("Gross Profit / Loss"),
"fieldtype": "Currency",
"options": "currency",
- "width": 307
-
- }
+ "width": 307,
+ },
]
-def set_gl_entries_by_account(company, from_date, to_date, based_on, gl_entries_by_account,
- ignore_closing_entries=False):
+
+def set_gl_entries_by_account(
+ company, from_date, to_date, based_on, gl_entries_by_account, ignore_closing_entries=False
+):
"""Returns a dict like { "account": [gl entries], ... }"""
additional_conditions = []
@@ -190,19 +204,19 @@ def set_gl_entries_by_account(company, from_date, to_date, based_on, gl_entries_
if from_date:
additional_conditions.append("and posting_date >= %(from_date)s")
- gl_entries = frappe.db.sql("""select posting_date, {based_on} as based_on, debit, credit,
+ gl_entries = frappe.db.sql(
+ """select posting_date, {based_on} as based_on, debit, credit,
is_opening, (select root_type from `tabAccount` where name = account) as type
from `tabGL Entry` where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
and {based_on} is not null
- order by {based_on}, posting_date""".format(additional_conditions="\n".join(additional_conditions), based_on= based_on),
- {
- "company": company,
- "from_date": from_date,
- "to_date": to_date
- },
- as_dict=True)
+ order by {based_on}, posting_date""".format(
+ additional_conditions="\n".join(additional_conditions), based_on=based_on
+ ),
+ {"company": company, "from_date": from_date, "to_date": to_date},
+ as_dict=True,
+ )
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.based_on, []).append(entry)
diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py
index 406f7a50e8..8af9bb3ac8 100644
--- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py
+++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py
@@ -6,7 +6,8 @@ from erpnext.controllers.trends import get_columns, get_data
def execute(filters=None):
- if not filters: filters ={}
+ if not filters:
+ filters = {}
data = []
conditions = get_columns(filters, "Purchase Invoice")
data = get_data(filters, conditions)
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py
index a9696bd104..c359959310 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.py
+++ b/erpnext/accounts/report/purchase_register/purchase_register.py
@@ -15,12 +15,15 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
def execute(filters=None):
return _execute(filters)
+
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
invoice_list = get_invoices(filters, additional_query_columns)
- columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts \
- = get_columns(invoice_list, additional_table_columns)
+ columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
+ invoice_list, additional_table_columns
+ )
if not invoice_list:
msgprint(_("No record found"))
@@ -28,13 +31,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
invoice_expense_map = get_invoice_expense_map(invoice_list)
internal_invoice_map = get_internal_invoice_map(invoice_list)
- invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
- invoice_expense_map, expense_accounts)
+ invoice_expense_map, invoice_tax_map = get_invoice_tax_map(
+ invoice_list, invoice_expense_map, expense_accounts
+ )
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
suppliers = list(set(d.supplier for d in invoice_list))
supplier_details = get_supplier_details(suppliers)
- company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
+ company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
data = []
for inv in invoice_list:
@@ -50,10 +54,17 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row.append(inv.get(col))
row += [
- supplier_details.get(inv.supplier), # supplier_group
- inv.tax_id, inv.credit_to, inv.mode_of_payment, ", ".join(project),
- inv.bill_no, inv.bill_date, inv.remarks,
- ", ".join(purchase_order), ", ".join(purchase_receipt), company_currency
+ supplier_details.get(inv.supplier), # supplier_group
+ inv.tax_id,
+ inv.credit_to,
+ inv.mode_of_payment,
+ ", ".join(project),
+ inv.bill_no,
+ inv.bill_date,
+ inv.remarks,
+ ", ".join(purchase_order),
+ ", ".join(purchase_receipt),
+ company_currency,
]
# map expense values
@@ -91,85 +102,117 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
def get_columns(invoice_list, additional_table_columns):
"""return columns based on filters"""
columns = [
- _("Invoice") + ":Link/Purchase Invoice:120", _("Posting Date") + ":Date:80",
- _("Supplier Id") + "::120", _("Supplier Name") + "::120"]
+ _("Invoice") + ":Link/Purchase Invoice:120",
+ _("Posting Date") + ":Date:80",
+ _("Supplier Id") + "::120",
+ _("Supplier Name") + "::120",
+ ]
if additional_table_columns:
columns += additional_table_columns
columns += [
- _("Supplier Group") + ":Link/Supplier Group:120", _("Tax Id") + "::80", _("Payable Account") + ":Link/Account:120",
- _("Mode of Payment") + ":Link/Mode of Payment:80", _("Project") + ":Link/Project:80",
- _("Bill No") + "::120", _("Bill Date") + ":Date:80", _("Remarks") + "::150",
+ _("Supplier Group") + ":Link/Supplier Group:120",
+ _("Tax Id") + "::80",
+ _("Payable Account") + ":Link/Account:120",
+ _("Mode of Payment") + ":Link/Mode of Payment:80",
+ _("Project") + ":Link/Project:80",
+ _("Bill No") + "::120",
+ _("Bill Date") + ":Date:80",
+ _("Remarks") + "::150",
_("Purchase Order") + ":Link/Purchase Order:100",
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
- {
- "fieldname": "currency",
- "label": _("Currency"),
- "fieldtype": "Data",
- "width": 80
- }
+ {"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
]
- expense_accounts = tax_accounts = expense_columns = tax_columns = unrealized_profit_loss_accounts = \
- unrealized_profit_loss_account_columns = []
+ expense_accounts = (
+ tax_accounts
+ ) = (
+ expense_columns
+ ) = tax_columns = unrealized_profit_loss_accounts = unrealized_profit_loss_account_columns = []
if invoice_list:
- expense_accounts = frappe.db.sql_list("""select distinct expense_account
+ expense_accounts = frappe.db.sql_list(
+ """select distinct expense_account
from `tabPurchase Invoice Item` where docstatus = 1
and (expense_account is not null and expense_account != '')
- and parent in (%s) order by expense_account""" %
- ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
+ and parent in (%s) order by expense_account"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple([inv.name for inv in invoice_list]),
+ )
- tax_accounts = frappe.db.sql_list("""select distinct account_head
+ tax_accounts = frappe.db.sql_list(
+ """select distinct account_head
from `tabPurchase Taxes and Charges` where parenttype = 'Purchase Invoice'
and docstatus = 1 and (account_head is not null and account_head != '')
and category in ('Total', 'Valuation and Total')
- and parent in (%s) order by account_head""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
+ and parent in (%s) order by account_head"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ )
- unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
+ unrealized_profit_loss_accounts = frappe.db.sql_list(
+ """SELECT distinct unrealized_profit_loss_account
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
and ifnull(unrealized_profit_loss_account, '') != ''
- order by unrealized_profit_loss_account""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
+ order by unrealized_profit_loss_account"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ )
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
- unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
+ unrealized_profit_loss_account_columns = [
+ (account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
+ ]
for account in tax_accounts:
if account not in expense_accounts:
tax_columns.append(account + ":Currency/currency:120")
- columns = columns + expense_columns + unrealized_profit_loss_account_columns + \
- [_("Net Total") + ":Currency/currency:120"] + tax_columns + \
- [_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
- _("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
+ columns = (
+ columns
+ + expense_columns
+ + unrealized_profit_loss_account_columns
+ + [_("Net Total") + ":Currency/currency:120"]
+ + tax_columns
+ + [
+ _("Total Tax") + ":Currency/currency:120",
+ _("Grand Total") + ":Currency/currency:120",
+ _("Rounded Total") + ":Currency/currency:120",
+ _("Outstanding Amount") + ":Currency/currency:120",
+ ]
+ )
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
+
def get_conditions(filters):
conditions = ""
- if filters.get("company"): conditions += " and company=%(company)s"
- if filters.get("supplier"): conditions += " and supplier = %(supplier)s"
+ if filters.get("company"):
+ conditions += " and company=%(company)s"
+ if filters.get("supplier"):
+ conditions += " and supplier = %(supplier)s"
- if filters.get("from_date"): conditions += " and posting_date>=%(from_date)s"
- if filters.get("to_date"): conditions += " and posting_date<=%(to_date)s"
+ if filters.get("from_date"):
+ conditions += " and posting_date>=%(from_date)s"
+ if filters.get("to_date"):
+ conditions += " and posting_date<=%(to_date)s"
- if filters.get("mode_of_payment"): conditions += " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"
+ if filters.get("mode_of_payment"):
+ conditions += " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"
if filters.get("cost_center"):
- conditions += """ and exists(select name from `tabPurchase Invoice Item`
+ conditions += """ and exists(select name from `tabPurchase Invoice Item`
where parent=`tabPurchase Invoice`.name
and ifnull(`tabPurchase Invoice Item`.cost_center, '') = %(cost_center)s)"""
if filters.get("warehouse"):
- conditions += """ and exists(select name from `tabPurchase Invoice Item`
+ conditions += """ and exists(select name from `tabPurchase Invoice Item`
where parent=`tabPurchase Invoice`.name
and ifnull(`tabPurchase Invoice Item`.warehouse, '') = %(warehouse)s)"""
if filters.get("item_group"):
- conditions += """ and exists(select name from `tabPurchase Invoice Item`
+ conditions += """ and exists(select name from `tabPurchase Invoice Item`
where parent=`tabPurchase Invoice`.name
and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s)"""
@@ -182,38 +225,58 @@ def get_conditions(filters):
"""
for dimension in accounting_dimensions:
if filters.get(dimension.fieldname):
- if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
- filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
- filters.get(dimension.fieldname))
+ if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
+ filters[dimension.fieldname] = get_dimension_with_children(
+ dimension.document_type, filters.get(dimension.fieldname)
+ )
- conditions += common_condition + "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ conditions += (
+ common_condition
+ + "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ )
else:
- conditions += common_condition + "and ifnull(`tabPurchase Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
+ conditions += (
+ common_condition
+ + "and ifnull(`tabPurchase Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
+ )
return conditions
+
def get_invoices(filters, additional_query_columns):
if additional_query_columns:
- additional_query_columns = ', ' + ', '.join(additional_query_columns)
+ additional_query_columns = ", " + ", ".join(additional_query_columns)
conditions = get_conditions(filters)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
name, posting_date, credit_to, supplier, supplier_name, tax_id, bill_no, bill_date,
remarks, base_net_total, base_grand_total, outstanding_amount,
mode_of_payment {0}
from `tabPurchase Invoice`
where docstatus = 1 %s
- order by posting_date desc, name desc""".format(additional_query_columns or '') % conditions, filters, as_dict=1)
+ order by posting_date desc, name desc""".format(
+ additional_query_columns or ""
+ )
+ % conditions,
+ filters,
+ as_dict=1,
+ )
def get_invoice_expense_map(invoice_list):
- expense_details = frappe.db.sql("""
+ expense_details = frappe.db.sql(
+ """
select parent, expense_account, sum(base_net_amount) as amount
from `tabPurchase Invoice Item`
where parent in (%s)
group by parent, expense_account
- """ % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ """
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
invoice_expense_map = {}
for d in expense_details:
@@ -222,11 +285,16 @@ def get_invoice_expense_map(invoice_list):
return invoice_expense_map
+
def get_internal_invoice_map(invoice_list):
- unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
+ unrealized_amount_details = frappe.db.sql(
+ """SELECT name, unrealized_profit_loss_account,
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
- and is_internal_supplier = 1 and company = represents_company""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ and is_internal_supplier = 1 and company = represents_company"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
internal_invoice_map = {}
for d in unrealized_amount_details:
@@ -235,15 +303,21 @@ def get_internal_invoice_map(invoice_list):
return internal_invoice_map
+
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
- tax_details = frappe.db.sql("""
+ tax_details = frappe.db.sql(
+ """
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
else sum(base_tax_amount_after_discount_amount) * -1 end as tax_amount
from `tabPurchase Taxes and Charges`
where parent in (%s) and category in ('Total', 'Valuation and Total')
and base_tax_amount_after_discount_amount != 0
group by parent, account_head, add_deduct_tax
- """ % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ """
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
invoice_tax_map = {}
for d in tax_details:
@@ -258,48 +332,71 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
return invoice_expense_map, invoice_tax_map
+
def get_invoice_po_pr_map(invoice_list):
- pi_items = frappe.db.sql("""
+ pi_items = frappe.db.sql(
+ """
select parent, purchase_order, purchase_receipt, po_detail, project
from `tabPurchase Invoice Item`
where parent in (%s)
- """ % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ """
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
invoice_po_pr_map = {}
for d in pi_items:
if d.purchase_order:
- invoice_po_pr_map.setdefault(d.parent, frappe._dict()).setdefault(
- "purchase_order", []).append(d.purchase_order)
+ invoice_po_pr_map.setdefault(d.parent, frappe._dict()).setdefault("purchase_order", []).append(
+ d.purchase_order
+ )
pr_list = None
if d.purchase_receipt:
pr_list = [d.purchase_receipt]
elif d.po_detail:
- pr_list = frappe.db.sql_list("""select distinct parent from `tabPurchase Receipt Item`
- where docstatus=1 and purchase_order_item=%s""", d.po_detail)
+ pr_list = frappe.db.sql_list(
+ """select distinct parent from `tabPurchase Receipt Item`
+ where docstatus=1 and purchase_order_item=%s""",
+ d.po_detail,
+ )
if pr_list:
invoice_po_pr_map.setdefault(d.parent, frappe._dict()).setdefault("purchase_receipt", pr_list)
if d.project:
- invoice_po_pr_map.setdefault(d.parent, frappe._dict()).setdefault(
- "project", []).append(d.project)
+ invoice_po_pr_map.setdefault(d.parent, frappe._dict()).setdefault("project", []).append(
+ d.project
+ )
return invoice_po_pr_map
+
def get_account_details(invoice_list):
account_map = {}
accounts = list(set([inv.credit_to for inv in invoice_list]))
- for acc in frappe.db.sql("""select name, parent_account from tabAccount
- where name in (%s)""" % ", ".join(["%s"]*len(accounts)), tuple(accounts), as_dict=1):
- account_map[acc.name] = acc.parent_account
+ for acc in frappe.db.sql(
+ """select name, parent_account from tabAccount
+ where name in (%s)"""
+ % ", ".join(["%s"] * len(accounts)),
+ tuple(accounts),
+ as_dict=1,
+ ):
+ account_map[acc.name] = acc.parent_account
return account_map
+
def get_supplier_details(suppliers):
supplier_details = {}
- for supp in frappe.db.sql("""select name, supplier_group from `tabSupplier`
- where name in (%s)""" % ", ".join(["%s"]*len(suppliers)), tuple(suppliers), as_dict=1):
- supplier_details.setdefault(supp.name, supp.supplier_group)
+ for supp in frappe.db.sql(
+ """select name, supplier_group from `tabSupplier`
+ where name in (%s)"""
+ % ", ".join(["%s"] * len(suppliers)),
+ tuple(suppliers),
+ as_dict=1,
+ ):
+ supplier_details.setdefault(supp.name, supp.supplier_group)
return supplier_details
diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
index e88675bb8d..1dcacb9742 100644
--- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
+++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
@@ -13,6 +13,7 @@ def execute(filters=None):
data = get_ordered_to_be_billed_data(args)
return columns, data
+
def get_column():
return [
{
@@ -20,90 +21,76 @@ def get_column():
"fieldname": "name",
"fieldtype": "Link",
"options": "Purchase Receipt",
- "width": 160
- },
- {
- "label": _("Date"),
- "fieldname": "date",
- "fieldtype": "Date",
- "width": 100
+ "width": 160,
},
+ {"label": _("Date"), "fieldname": "date", "fieldtype": "Date", "width": 100},
{
"label": _("Supplier"),
"fieldname": "supplier",
"fieldtype": "Link",
"options": "Supplier",
- "width": 120
- },
- {
- "label": _("Supplier Name"),
- "fieldname": "supplier_name",
- "fieldtype": "Data",
- "width": 120
+ "width": 120,
},
+ {"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 120},
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 120
+ "width": 120,
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 100,
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
},
{
"label": _("Billed Amount"),
"fieldname": "billed_amount",
"fieldtype": "Currency",
"width": 100,
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
},
{
"label": _("Returned Amount"),
"fieldname": "returned_amount",
"fieldtype": "Currency",
"width": 120,
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
},
{
"label": _("Pending Amount"),
"fieldname": "pending_amount",
"fieldtype": "Currency",
"width": 120,
- "options": "Company:company:default_currency"
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 120
+ "options": "Company:company:default_currency",
},
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 120},
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 120},
{
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
- "width": 120
+ "width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
- "width": 120
- }
+ "width": 120,
+ },
]
+
def get_args():
- return {'doctype': 'Purchase Receipt', 'party': 'supplier',
- 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}
+ return {
+ "doctype": "Purchase Receipt",
+ "party": "supplier",
+ "date": "posting_date",
+ "order": "name",
+ "order_by": "desc",
+ }
diff --git a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.py b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.py
index 966b1d4fd0..483debaf8b 100644
--- a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.py
+++ b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.py
@@ -6,7 +6,8 @@ from erpnext.controllers.trends import get_columns, get_data
def execute(filters=None):
- if not filters: filters ={}
+ if not filters:
+ filters = {}
data = []
conditions = get_columns(filters, "Sales Invoice")
data = get_data(filters, conditions)
diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
index 904513c39f..4eef307286 100644
--- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
+++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
@@ -9,7 +9,11 @@ from frappe.utils import cstr
def execute(filters=None):
columns, data = [], []
columns = get_columns(filters)
- data = get_pos_sales_payment_data(filters) if filters.get('is_pos') else get_sales_payment_data(filters, columns)
+ data = (
+ get_pos_sales_payment_data(filters)
+ if filters.get("is_pos")
+ else get_sales_payment_data(filters, columns)
+ )
return columns, data
@@ -22,12 +26,12 @@ def get_pos_columns():
_("Taxes") + ":Currency/currency:120",
_("Payments") + ":Currency/currency:120",
_("Warehouse") + ":Data:200",
- _("Cost Center") + ":Data:200"
+ _("Cost Center") + ":Data:200",
]
def get_columns(filters):
- if filters.get('is_pos'):
+ if filters.get("is_pos"):
return get_pos_columns()
else:
return [
@@ -44,15 +48,17 @@ def get_pos_sales_payment_data(filters):
sales_invoice_data = get_pos_invoice_data(filters)
data = [
[
- row['posting_date'],
- row['owner'],
- row['mode_of_payment'],
- row['net_total'],
- row['total_taxes'],
- row['paid_amount'],
- row['warehouse'],
- row['cost_center']
- ] for row in sales_invoice_data]
+ row["posting_date"],
+ row["owner"],
+ row["mode_of_payment"],
+ row["net_total"],
+ row["total_taxes"],
+ row["paid_amount"],
+ row["warehouse"],
+ row["cost_center"],
+ ]
+ for row in sales_invoice_data
+ ]
return data
@@ -71,19 +77,25 @@ def get_sales_payment_data(filters, columns):
show_payment_detail = False
for inv in sales_invoice_data:
- owner_posting_date = inv["owner"]+cstr(inv["posting_date"])
+ owner_posting_date = inv["owner"] + cstr(inv["posting_date"])
if show_payment_detail:
- row = [inv.posting_date, inv.owner," ",inv.net_total,inv.total_taxes, 0]
+ row = [inv.posting_date, inv.owner, " ", inv.net_total, inv.total_taxes, 0]
data.append(row)
- for mop_detail in mode_of_payment_details.get(owner_posting_date,[]):
- row = [inv.posting_date, inv.owner,mop_detail[0],0,0,mop_detail[1],0]
+ for mop_detail in mode_of_payment_details.get(owner_posting_date, []):
+ row = [inv.posting_date, inv.owner, mop_detail[0], 0, 0, mop_detail[1], 0]
data.append(row)
else:
total_payment = 0
- for mop_detail in mode_of_payment_details.get(owner_posting_date,[]):
+ for mop_detail in mode_of_payment_details.get(owner_posting_date, []):
total_payment = total_payment + mop_detail[1]
- row = [inv.posting_date, inv.owner,", ".join(mode_of_payments.get(owner_posting_date, [])),
- inv.net_total,inv.total_taxes,total_payment]
+ row = [
+ inv.posting_date,
+ inv.owner,
+ ", ".join(mode_of_payments.get(owner_posting_date, [])),
+ inv.net_total,
+ inv.total_taxes,
+ total_payment,
+ ]
data.append(row)
return data
@@ -107,40 +119,44 @@ def get_conditions(filters):
def get_pos_invoice_data(filters):
conditions = get_conditions(filters)
- result = frappe.db.sql(''
- 'SELECT '
- 'posting_date, owner, sum(net_total) as "net_total", sum(total_taxes) as "total_taxes", '
- 'sum(paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount", '
- 'mode_of_payment, warehouse, cost_center '
- 'FROM ('
- 'SELECT '
- 'parent, item_code, sum(amount) as "base_total", warehouse, cost_center '
- 'from `tabSales Invoice Item` group by parent'
- ') t1 '
- 'left join '
- '(select parent, mode_of_payment from `tabSales Invoice Payment` group by parent) t3 '
- 'on (t3.parent = t1.parent) '
- 'JOIN ('
- 'SELECT '
- 'docstatus, company, is_pos, name, posting_date, owner, sum(base_total) as "base_total", '
- 'sum(net_total) as "net_total", sum(total_taxes_and_charges) as "total_taxes", '
- 'sum(base_paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount" '
- 'FROM `tabSales Invoice` '
- 'GROUP BY name'
- ') a '
- 'ON ('
- 't1.parent = a.name and t1.base_total = a.base_total) '
- 'WHERE a.docstatus = 1'
- ' AND {conditions} '
- 'GROUP BY '
- 'owner, posting_date, warehouse'.format(conditions=conditions), filters, as_dict=1
- )
+ result = frappe.db.sql(
+ ""
+ "SELECT "
+ 'posting_date, owner, sum(net_total) as "net_total", sum(total_taxes) as "total_taxes", '
+ 'sum(paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount", '
+ "mode_of_payment, warehouse, cost_center "
+ "FROM ("
+ "SELECT "
+ 'parent, item_code, sum(amount) as "base_total", warehouse, cost_center '
+ "from `tabSales Invoice Item` group by parent"
+ ") t1 "
+ "left join "
+ "(select parent, mode_of_payment from `tabSales Invoice Payment` group by parent) t3 "
+ "on (t3.parent = t1.parent) "
+ "JOIN ("
+ "SELECT "
+ 'docstatus, company, is_pos, name, posting_date, owner, sum(base_total) as "base_total", '
+ 'sum(net_total) as "net_total", sum(total_taxes_and_charges) as "total_taxes", '
+ 'sum(base_paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount" '
+ "FROM `tabSales Invoice` "
+ "GROUP BY name"
+ ") a "
+ "ON ("
+ "t1.parent = a.name and t1.base_total = a.base_total) "
+ "WHERE a.docstatus = 1"
+ " AND {conditions} "
+ "GROUP BY "
+ "owner, posting_date, warehouse".format(conditions=conditions),
+ filters,
+ as_dict=1,
+ )
return result
def get_sales_invoice_data(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
a.posting_date, a.owner,
sum(a.net_total) as "net_total",
@@ -152,15 +168,21 @@ def get_sales_invoice_data(filters):
and {conditions}
group by
a.owner, a.posting_date
- """.format(conditions=conditions), filters, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
def get_mode_of_payments(filters):
mode_of_payments = {}
invoice_list = get_invoices(filters)
- invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list)
+ invoice_list_names = ",".join('"' + invoice["name"] + '"' for invoice in invoice_list)
if invoice_list:
- inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment
+ inv_mop = frappe.db.sql(
+ """select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment
from `tabSales Invoice` a, `tabSales Invoice Payment` b
where a.name = b.parent
and a.docstatus = 1
@@ -180,26 +202,36 @@ def get_mode_of_payments(filters):
and a.docstatus = 1
and b.reference_type = "Sales Invoice"
and b.reference_name in ({invoice_list_names})
- """.format(invoice_list_names=invoice_list_names), as_dict=1)
+ """.format(
+ invoice_list_names=invoice_list_names
+ ),
+ as_dict=1,
+ )
for d in inv_mop:
- mode_of_payments.setdefault(d["owner"]+cstr(d["posting_date"]), []).append(d.mode_of_payment)
+ mode_of_payments.setdefault(d["owner"] + cstr(d["posting_date"]), []).append(d.mode_of_payment)
return mode_of_payments
def get_invoices(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select a.name
+ return frappe.db.sql(
+ """select a.name
from `tabSales Invoice` a
- where a.docstatus = 1 and {conditions}""".format(conditions=conditions),
- filters, as_dict=1)
+ where a.docstatus = 1 and {conditions}""".format(
+ conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
def get_mode_of_payment_details(filters):
mode_of_payment_details = {}
invoice_list = get_invoices(filters)
- invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list)
+ invoice_list_names = ",".join('"' + invoice["name"] + '"' for invoice in invoice_list)
if invoice_list:
- inv_mop_detail = frappe.db.sql("""
+ inv_mop_detail = frappe.db.sql(
+ """
select t.owner,
t.posting_date,
t.mode_of_payment,
@@ -232,24 +264,39 @@ def get_mode_of_payment_details(filters):
group by a.owner, a.posting_date, mode_of_payment
) t
group by t.owner, t.posting_date, t.mode_of_payment
- """.format(invoice_list_names=invoice_list_names), as_dict=1)
+ """.format(
+ invoice_list_names=invoice_list_names
+ ),
+ as_dict=1,
+ )
- inv_change_amount = frappe.db.sql("""select a.owner, a.posting_date,
+ inv_change_amount = frappe.db.sql(
+ """select a.owner, a.posting_date,
ifnull(b.mode_of_payment, '') as mode_of_payment, sum(a.base_change_amount) as change_amount
from `tabSales Invoice` a, `tabSales Invoice Payment` b
where a.name = b.parent
and a.name in ({invoice_list_names})
and b.type = 'Cash'
and a.base_change_amount > 0
- group by a.owner, a.posting_date, mode_of_payment""".format(invoice_list_names=invoice_list_names), as_dict=1)
+ group by a.owner, a.posting_date, mode_of_payment""".format(
+ invoice_list_names=invoice_list_names
+ ),
+ as_dict=1,
+ )
for d in inv_change_amount:
for det in inv_mop_detail:
- if det["owner"] == d["owner"] and det["posting_date"] == d["posting_date"] and det["mode_of_payment"] == d["mode_of_payment"]:
+ if (
+ det["owner"] == d["owner"]
+ and det["posting_date"] == d["posting_date"]
+ and det["mode_of_payment"] == d["mode_of_payment"]
+ ):
paid_amount = det["paid_amount"] - d["change_amount"]
det["paid_amount"] = paid_amount
for d in inv_mop_detail:
- mode_of_payment_details.setdefault(d["owner"]+cstr(d["posting_date"]), []).append((d.mode_of_payment,d.paid_amount))
+ mode_of_payment_details.setdefault(d["owner"] + cstr(d["posting_date"]), []).append(
+ (d.mode_of_payment, d.paid_amount)
+ )
return mode_of_payment_details
diff --git a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py
index b3f6c72a3e..3ad0ff2ce2 100644
--- a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py
+++ b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py
@@ -15,6 +15,7 @@ from erpnext.accounts.report.sales_payment_summary.sales_payment_summary import
test_dependencies = ["Sales Invoice"]
+
class TestSalesPaymentSummary(unittest.TestCase):
@classmethod
def setUpClass(self):
@@ -37,7 +38,7 @@ class TestSalesPaymentSummary(unittest.TestCase):
si.insert()
si.submit()
- if int(si.name[-3:])%2 == 0:
+ if int(si.name[-3:]) % 2 == 0:
bank_account = "_Test Cash - _TC"
mode_of_payment = "Cash"
else:
@@ -52,18 +53,22 @@ class TestSalesPaymentSummary(unittest.TestCase):
pe.submit()
mop = get_mode_of_payments(filters)
- self.assertTrue('Credit Card' in list(mop.values())[0])
- self.assertTrue('Cash' in list(mop.values())[0])
+ self.assertTrue("Credit Card" in list(mop.values())[0])
+ self.assertTrue("Cash" in list(mop.values())[0])
# Cancel all Cash payment entry and check if this mode of payment is still fetched.
- payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Cash", "docstatus": 1}, fields=["name", "docstatus"])
+ payment_entries = frappe.get_all(
+ "Payment Entry",
+ filters={"mode_of_payment": "Cash", "docstatus": 1},
+ fields=["name", "docstatus"],
+ )
for payment_entry in payment_entries:
pe = frappe.get_doc("Payment Entry", payment_entry.name)
pe.cancel()
mop = get_mode_of_payments(filters)
- self.assertTrue('Credit Card' in list(mop.values())[0])
- self.assertTrue('Cash' not in list(mop.values())[0])
+ self.assertTrue("Credit Card" in list(mop.values())[0])
+ self.assertTrue("Cash" not in list(mop.values())[0])
def test_get_mode_of_payments_details(self):
filters = get_filters()
@@ -73,7 +78,7 @@ class TestSalesPaymentSummary(unittest.TestCase):
si.insert()
si.submit()
- if int(si.name[-3:])%2 == 0:
+ if int(si.name[-3:]) % 2 == 0:
bank_account = "_Test Cash - _TC"
mode_of_payment = "Cash"
else:
@@ -95,7 +100,11 @@ class TestSalesPaymentSummary(unittest.TestCase):
cc_init_amount = mopd_value[1]
# Cancel one Credit Card Payment Entry and check that it is not fetched in mode of payment details.
- payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Credit Card", "docstatus": 1}, fields=["name", "docstatus"])
+ payment_entries = frappe.get_all(
+ "Payment Entry",
+ filters={"mode_of_payment": "Credit Card", "docstatus": 1},
+ fields=["name", "docstatus"],
+ )
for payment_entry in payment_entries[:1]:
pe = frappe.get_doc("Payment Entry", payment_entry.name)
pe.cancel()
@@ -108,63 +117,72 @@ class TestSalesPaymentSummary(unittest.TestCase):
self.assertTrue(cc_init_amount > cc_final_amount)
+
def get_filters():
- return {
- "from_date": "1900-01-01",
- "to_date": today(),
- "company": "_Test Company"
- }
+ return {"from_date": "1900-01-01", "to_date": today(), "company": "_Test Company"}
+
def create_sales_invoice_record(qty=1):
# return sales invoice doc object
- return frappe.get_doc({
- "doctype": "Sales Invoice",
- "customer": frappe.get_doc('Customer', {"customer_name": "Prestiga-Biz"}).name,
- "company": '_Test Company',
- "due_date": today(),
- "posting_date": today(),
- "currency": "INR",
- "taxes_and_charges": "",
- "debit_to": "Debtors - _TC",
- "taxes": [],
- "items": [{
- 'doctype': 'Sales Invoice Item',
- 'item_code': frappe.get_doc('Item', {'item_name': 'Consulting'}).name,
- 'qty': qty,
- "rate": 10000,
- 'income_account': 'Sales - _TC',
- 'cost_center': 'Main - _TC',
- 'expense_account': 'Cost of Goods Sold - _TC'
- }]
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Sales Invoice",
+ "customer": frappe.get_doc("Customer", {"customer_name": "Prestiga-Biz"}).name,
+ "company": "_Test Company",
+ "due_date": today(),
+ "posting_date": today(),
+ "currency": "INR",
+ "taxes_and_charges": "",
+ "debit_to": "Debtors - _TC",
+ "taxes": [],
+ "items": [
+ {
+ "doctype": "Sales Invoice Item",
+ "item_code": frappe.get_doc("Item", {"item_name": "Consulting"}).name,
+ "qty": qty,
+ "rate": 10000,
+ "income_account": "Sales - _TC",
+ "cost_center": "Main - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ }
+ ],
+ }
+ )
+
def create_records():
if frappe.db.exists("Customer", "Prestiga-Biz"):
return
- #customer
- frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": "Prestiga-Biz",
- "customer_type": "Company",
- "doctype": "Customer",
- "territory": "_Test Territory"
- }).insert()
+ # customer
+ frappe.get_doc(
+ {
+ "customer_group": "_Test Customer Group",
+ "customer_name": "Prestiga-Biz",
+ "customer_type": "Company",
+ "doctype": "Customer",
+ "territory": "_Test Territory",
+ }
+ ).insert()
# item
- item = frappe.get_doc({
- "doctype": "Item",
- "item_code": "Consulting",
- "item_name": "Consulting",
- "item_group": "All Item Groups",
- "company": "_Test Company",
- "is_stock_item": 0
- }).insert()
+ item = frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": "Consulting",
+ "item_name": "Consulting",
+ "item_group": "All Item Groups",
+ "company": "_Test Company",
+ "is_stock_item": 0,
+ }
+ ).insert()
# item price
- frappe.get_doc({
- "doctype": "Item Price",
- "price_list": "Standard Selling",
- "item_code": item.item_code,
- "price_list_rate": 10000
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": "Standard Selling",
+ "item_code": item.item_code,
+ "price_list_rate": 10000,
+ }
+ ).insert()
diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py
index a9d0081bac..fc48dd2816 100644
--- a/erpnext/accounts/report/sales_register/sales_register.py
+++ b/erpnext/accounts/report/sales_register/sales_register.py
@@ -16,11 +16,15 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
def execute(filters=None):
return _execute(filters)
+
def _execute(filters, additional_table_columns=None, additional_query_columns=None):
- if not filters: filters = frappe._dict({})
+ if not filters:
+ filters = frappe._dict({})
invoice_list = get_invoices(filters, additional_query_columns)
- columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(invoice_list, additional_table_columns)
+ columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
+ invoice_list, additional_table_columns
+ )
if not invoice_list:
msgprint(_("No record found"))
@@ -28,12 +32,13 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
invoice_income_map = get_invoice_income_map(invoice_list)
internal_invoice_map = get_internal_invoice_map(invoice_list)
- invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
- invoice_income_map, income_accounts)
- #Cost Center & Warehouse Map
+ invoice_income_map, invoice_tax_map = get_invoice_tax_map(
+ invoice_list, invoice_income_map, income_accounts
+ )
+ # Cost Center & Warehouse Map
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
invoice_so_dn_map = get_invoice_so_dn_map(invoice_list)
- company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
+ company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
mode_of_payments = get_mode_of_payments([inv.name for inv in invoice_list])
data = []
@@ -45,33 +50,33 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
row = {
- 'invoice': inv.name,
- 'posting_date': inv.posting_date,
- 'customer': inv.customer,
- 'customer_name': inv.customer_name
+ "invoice": inv.name,
+ "posting_date": inv.posting_date,
+ "customer": inv.customer,
+ "customer_name": inv.customer_name,
}
if additional_query_columns:
for col in additional_query_columns:
- row.update({
- col: inv.get(col)
- })
+ row.update({col: inv.get(col)})
- row.update({
- 'customer_group': inv.get("customer_group"),
- 'territory': inv.get("territory"),
- 'tax_id': inv.get("tax_id"),
- 'receivable_account': inv.debit_to,
- 'mode_of_payment': ", ".join(mode_of_payments.get(inv.name, [])),
- 'project': inv.project,
- 'owner': inv.owner,
- 'remarks': inv.remarks,
- 'sales_order': ", ".join(sales_order),
- 'delivery_note': ", ".join(delivery_note),
- 'cost_center': ", ".join(cost_center),
- 'warehouse': ", ".join(warehouse),
- 'currency': company_currency
- })
+ row.update(
+ {
+ "customer_group": inv.get("customer_group"),
+ "territory": inv.get("territory"),
+ "tax_id": inv.get("tax_id"),
+ "receivable_account": inv.debit_to,
+ "mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
+ "project": inv.project,
+ "owner": inv.owner,
+ "remarks": inv.remarks,
+ "sales_order": ", ".join(sales_order),
+ "delivery_note": ", ".join(delivery_note),
+ "cost_center": ", ".join(cost_center),
+ "warehouse": ", ".join(warehouse),
+ "currency": company_currency,
+ }
+ )
# map income values
base_net_total = 0
@@ -82,164 +87,138 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
base_net_total += income_amount
- row.update({
- frappe.scrub(income_acc): income_amount
- })
+ row.update({frappe.scrub(income_acc): income_amount})
# Add amount in unrealized account
for account in unrealized_profit_loss_accounts:
- row.update({
- frappe.scrub(account+"_unrealized"): flt(internal_invoice_map.get((inv.name, account)))
- })
+ row.update(
+ {frappe.scrub(account + "_unrealized"): flt(internal_invoice_map.get((inv.name, account)))}
+ )
# net total
- row.update({'net_total': base_net_total or inv.base_net_total})
+ row.update({"net_total": base_net_total or inv.base_net_total})
# tax account
total_tax = 0
for tax_acc in tax_accounts:
if tax_acc not in income_accounts:
- tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2
+ tax_amount_precision = (
+ get_field_precision(
+ frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency
+ )
+ or 2
+ )
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision)
total_tax += tax_amount
- row.update({
- frappe.scrub(tax_acc): tax_amount
- })
+ row.update({frappe.scrub(tax_acc): tax_amount})
# total tax, grand total, outstanding amount & rounded total
- row.update({
- 'tax_total': total_tax,
- 'grand_total': inv.base_grand_total,
- 'rounded_total': inv.base_rounded_total,
- 'outstanding_amount': inv.outstanding_amount
- })
+ row.update(
+ {
+ "tax_total": total_tax,
+ "grand_total": inv.base_grand_total,
+ "rounded_total": inv.base_rounded_total,
+ "outstanding_amount": inv.outstanding_amount,
+ }
+ )
data.append(row)
return columns, data
+
def get_columns(invoice_list, additional_table_columns):
"""return columns based on filters"""
columns = [
{
- 'label': _("Invoice"),
- 'fieldname': 'invoice',
- 'fieldtype': 'Link',
- 'options': 'Sales Invoice',
- 'width': 120
+ "label": _("Invoice"),
+ "fieldname": "invoice",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120,
},
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
{
- 'label': _("Posting Date"),
- 'fieldname': 'posting_date',
- 'fieldtype': 'Date',
- 'width': 80
- },
- {
- 'label': _("Customer"),
- 'fieldname': 'customer',
- 'fieldtype': 'Link',
- 'options': 'Customer',
- 'width': 120
- },
- {
- 'label': _("Customer Name"),
- 'fieldname': 'customer_name',
- 'fieldtype': 'Data',
- 'width': 120
+ "label": _("Customer"),
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "options": "Customer",
+ "width": 120,
},
+ {"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
]
if additional_table_columns:
columns += additional_table_columns
- columns +=[
+ columns += [
{
- 'label': _("Customer Group"),
- 'fieldname': 'customer_group',
- 'fieldtype': 'Link',
- 'options': 'Customer Group',
- 'width': 120
+ "label": _("Customer Group"),
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "options": "Customer Group",
+ "width": 120,
},
{
- 'label': _("Territory"),
- 'fieldname': 'territory',
- 'fieldtype': 'Link',
- 'options': 'Territory',
- 'width': 80
+ "label": _("Territory"),
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "options": "Territory",
+ "width": 80,
+ },
+ {"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 120},
+ {
+ "label": _("Receivable Account"),
+ "fieldname": "receivable_account",
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 80,
},
{
- 'label': _("Tax Id"),
- 'fieldname': 'tax_id',
- 'fieldtype': 'Data',
- 'width': 120
- },
- {
- 'label': _("Receivable Account"),
- 'fieldname': 'receivable_account',
- 'fieldtype': 'Link',
- 'options': 'Account',
- 'width': 80
- },
- {
- 'label': _("Mode Of Payment"),
- 'fieldname': 'mode_of_payment',
- 'fieldtype': 'Data',
- 'width': 120
- },
- {
- 'label': _("Project"),
- 'fieldname': 'project',
- 'fieldtype': 'Link',
- 'options': 'Project',
- 'width': 80
- },
- {
- 'label': _("Owner"),
- 'fieldname': 'owner',
- 'fieldtype': 'Data',
- 'width': 150
- },
- {
- 'label': _("Remarks"),
- 'fieldname': 'remarks',
- 'fieldtype': 'Data',
- 'width': 150
- },
- {
- 'label': _("Sales Order"),
- 'fieldname': 'sales_order',
- 'fieldtype': 'Link',
- 'options': 'Sales Order',
- 'width': 100
- },
- {
- 'label': _("Delivery Note"),
- 'fieldname': 'delivery_note',
- 'fieldtype': 'Link',
- 'options': 'Delivery Note',
- 'width': 100
- },
- {
- 'label': _("Cost Center"),
- 'fieldname': 'cost_center',
- 'fieldtype': 'Link',
- 'options': 'Cost Center',
- 'width': 100
- },
- {
- 'label': _("Warehouse"),
- 'fieldname': 'warehouse',
- 'fieldtype': 'Link',
- 'options': 'Warehouse',
- 'width': 100
- },
- {
- "fieldname": "currency",
- "label": _("Currency"),
+ "label": _("Mode Of Payment"),
+ "fieldname": "mode_of_payment",
"fieldtype": "Data",
- "width": 80
- }
+ "width": 120,
+ },
+ {
+ "label": _("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 80,
+ },
+ {"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 150},
+ {"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150},
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 100,
+ },
+ {
+ "label": _("Delivery Note"),
+ "fieldname": "delivery_note",
+ "fieldtype": "Link",
+ "options": "Delivery Note",
+ "width": 100,
+ },
+ {
+ "label": _("Cost Center"),
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "options": "Cost Center",
+ "width": 100,
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 100,
+ },
+ {"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
]
income_accounts = []
@@ -250,106 +229,135 @@ def get_columns(invoice_list, additional_table_columns):
unrealized_profit_loss_account_columns = []
if invoice_list:
- income_accounts = frappe.db.sql_list("""select distinct income_account
+ income_accounts = frappe.db.sql_list(
+ """select distinct income_account
from `tabSales Invoice Item` where docstatus = 1 and parent in (%s)
- order by income_account""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
+ order by income_account"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ )
- tax_accounts = frappe.db.sql_list("""select distinct account_head
+ tax_accounts = frappe.db.sql_list(
+ """select distinct account_head
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
- and parent in (%s) order by account_head""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
+ and parent in (%s) order by account_head"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ )
- unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
+ unrealized_profit_loss_accounts = frappe.db.sql_list(
+ """SELECT distinct unrealized_profit_loss_account
from `tabSales Invoice` where docstatus = 1 and name in (%s)
and is_internal_customer = 1
and ifnull(unrealized_profit_loss_account, '') != ''
- order by unrealized_profit_loss_account""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
+ order by unrealized_profit_loss_account"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ )
for account in income_accounts:
- income_columns.append({
- "label": account,
- "fieldname": frappe.scrub(account),
- "fieldtype": "Currency",
- "options": "currency",
- "width": 120
- })
-
- for account in tax_accounts:
- if account not in income_accounts:
- tax_columns.append({
+ income_columns.append(
+ {
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
- })
+ "width": 120,
+ }
+ )
+
+ for account in tax_accounts:
+ if account not in income_accounts:
+ tax_columns.append(
+ {
+ "label": account,
+ "fieldname": frappe.scrub(account),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ }
+ )
for account in unrealized_profit_loss_accounts:
- unrealized_profit_loss_account_columns.append({
- "label": account,
- "fieldname": frappe.scrub(account+"_unrealized"),
+ unrealized_profit_loss_account_columns.append(
+ {
+ "label": account,
+ "fieldname": frappe.scrub(account + "_unrealized"),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ }
+ )
+
+ net_total_column = [
+ {
+ "label": _("Net Total"),
+ "fieldname": "net_total",
"fieldtype": "Currency",
"options": "currency",
- "width": 120
- })
-
- net_total_column = [{
- "label": _("Net Total"),
- "fieldname": "net_total",
- "fieldtype": "Currency",
- "options": "currency",
- "width": 120
- }]
+ "width": 120,
+ }
+ ]
total_columns = [
{
"label": _("Tax Total"),
"fieldname": "tax_total",
"fieldtype": "Currency",
- "options": 'currency',
- "width": 120
+ "options": "currency",
+ "width": 120,
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Currency",
- "options": 'currency',
- "width": 120
+ "options": "currency",
+ "width": 120,
},
{
"label": _("Rounded Total"),
"fieldname": "rounded_total",
"fieldtype": "Currency",
- "options": 'currency',
- "width": 120
+ "options": "currency",
+ "width": 120,
},
{
"label": _("Outstanding Amount"),
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
- "options": 'currency',
- "width": 120
- }
+ "options": "currency",
+ "width": 120,
+ },
]
- columns = columns + income_columns + unrealized_profit_loss_account_columns + \
- net_total_column + tax_columns + total_columns
+ columns = (
+ columns
+ + income_columns
+ + unrealized_profit_loss_account_columns
+ + net_total_column
+ + tax_columns
+ + total_columns
+ )
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
+
def get_conditions(filters):
conditions = ""
- if filters.get("company"): conditions += " and company=%(company)s"
- if filters.get("customer"): conditions += " and customer = %(customer)s"
+ if filters.get("company"):
+ conditions += " and company=%(company)s"
+ if filters.get("customer"):
+ conditions += " and customer = %(customer)s"
- if filters.get("from_date"): conditions += " and posting_date >= %(from_date)s"
- if filters.get("to_date"): conditions += " and posting_date <= %(to_date)s"
+ if filters.get("from_date"):
+ conditions += " and posting_date >= %(from_date)s"
+ if filters.get("to_date"):
+ conditions += " and posting_date <= %(to_date)s"
- if filters.get("owner"): conditions += " and owner = %(owner)s"
+ if filters.get("owner"):
+ conditions += " and owner = %(owner)s"
if filters.get("mode_of_payment"):
conditions += """ and exists(select name from `tabSales Invoice Payment`
@@ -357,22 +365,22 @@ def get_conditions(filters):
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("cost_center"):
- conditions += """ and exists(select name from `tabSales Invoice Item`
+ conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.cost_center, '') = %(cost_center)s)"""
if filters.get("warehouse"):
- conditions += """ and exists(select name from `tabSales Invoice Item`
+ conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
if filters.get("brand"):
- conditions += """ and exists(select name from `tabSales Invoice Item`
+ conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s)"""
if filters.get("item_group"):
- conditions += """ and exists(select name from `tabSales Invoice Item`
+ conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s)"""
@@ -385,34 +393,53 @@ def get_conditions(filters):
"""
for dimension in accounting_dimensions:
if filters.get(dimension.fieldname):
- if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
- filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
- filters.get(dimension.fieldname))
+ if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
+ filters[dimension.fieldname] = get_dimension_with_children(
+ dimension.document_type, filters.get(dimension.fieldname)
+ )
- conditions += common_condition + "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ conditions += (
+ common_condition
+ + "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
+ )
else:
- conditions += common_condition + "and ifnull(`tabSales Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
+ conditions += (
+ common_condition
+ + "and ifnull(`tabSales Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
+ )
return conditions
+
def get_invoices(filters, additional_query_columns):
if additional_query_columns:
- additional_query_columns = ', ' + ', '.join(additional_query_columns)
+ additional_query_columns = ", " + ", ".join(additional_query_columns)
conditions = get_conditions(filters)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select name, posting_date, debit_to, project, customer,
customer_name, owner, remarks, territory, tax_id, customer_group,
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
is_internal_customer, represents_company, company {0}
from `tabSales Invoice`
- where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
- conditions, filters, as_dict=1)
+ where docstatus = 1 %s order by posting_date desc, name desc""".format(
+ additional_query_columns or ""
+ )
+ % conditions,
+ filters,
+ as_dict=1,
+ )
+
def get_invoice_income_map(invoice_list):
- income_details = frappe.db.sql("""select parent, income_account, sum(base_net_amount) as amount
- from `tabSales Invoice Item` where parent in (%s) group by parent, income_account""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ income_details = frappe.db.sql(
+ """select parent, income_account, sum(base_net_amount) as amount
+ from `tabSales Invoice Item` where parent in (%s) group by parent, income_account"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
invoice_income_map = {}
for d in income_details:
@@ -421,11 +448,16 @@ def get_invoice_income_map(invoice_list):
return invoice_income_map
+
def get_internal_invoice_map(invoice_list):
- unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
+ unrealized_amount_details = frappe.db.sql(
+ """SELECT name, unrealized_profit_loss_account,
base_net_total as amount from `tabSales Invoice` where name in (%s)
- and is_internal_customer = 1 and company = represents_company""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ and is_internal_customer = 1 and company = represents_company"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
internal_invoice_map = {}
for d in unrealized_amount_details:
@@ -434,11 +466,16 @@ def get_internal_invoice_map(invoice_list):
return internal_invoice_map
+
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
- tax_details = frappe.db.sql("""select parent, account_head,
+ tax_details = frappe.db.sql(
+ """select parent, account_head,
sum(base_tax_amount_after_discount_amount) as tax_amount
- from `tabSales Taxes and Charges` where parent in (%s) group by parent, account_head""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ from `tabSales Taxes and Charges` where parent in (%s) group by parent, account_head"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
invoice_tax_map = {}
for d in tax_details:
@@ -453,54 +490,77 @@ def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
return invoice_income_map, invoice_tax_map
+
def get_invoice_so_dn_map(invoice_list):
- si_items = frappe.db.sql("""select parent, sales_order, delivery_note, so_detail
+ si_items = frappe.db.sql(
+ """select parent, sales_order, delivery_note, so_detail
from `tabSales Invoice Item` where parent in (%s)
- and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
invoice_so_dn_map = {}
for d in si_items:
if d.sales_order:
- invoice_so_dn_map.setdefault(d.parent, frappe._dict()).setdefault(
- "sales_order", []).append(d.sales_order)
+ invoice_so_dn_map.setdefault(d.parent, frappe._dict()).setdefault("sales_order", []).append(
+ d.sales_order
+ )
delivery_note_list = None
if d.delivery_note:
delivery_note_list = [d.delivery_note]
elif d.sales_order:
- delivery_note_list = frappe.db.sql_list("""select distinct parent from `tabDelivery Note Item`
- where docstatus=1 and so_detail=%s""", d.so_detail)
+ delivery_note_list = frappe.db.sql_list(
+ """select distinct parent from `tabDelivery Note Item`
+ where docstatus=1 and so_detail=%s""",
+ d.so_detail,
+ )
if delivery_note_list:
- invoice_so_dn_map.setdefault(d.parent, frappe._dict()).setdefault("delivery_note", delivery_note_list)
+ invoice_so_dn_map.setdefault(d.parent, frappe._dict()).setdefault(
+ "delivery_note", delivery_note_list
+ )
return invoice_so_dn_map
+
def get_invoice_cc_wh_map(invoice_list):
- si_items = frappe.db.sql("""select parent, cost_center, warehouse
+ si_items = frappe.db.sql(
+ """select parent, cost_center, warehouse
from `tabSales Invoice Item` where parent in (%s)
- and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')""" %
- ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
+ and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(inv.name for inv in invoice_list),
+ as_dict=1,
+ )
invoice_cc_wh_map = {}
for d in si_items:
if d.cost_center:
- invoice_cc_wh_map.setdefault(d.parent, frappe._dict()).setdefault(
- "cost_center", []).append(d.cost_center)
+ invoice_cc_wh_map.setdefault(d.parent, frappe._dict()).setdefault("cost_center", []).append(
+ d.cost_center
+ )
if d.warehouse:
- invoice_cc_wh_map.setdefault(d.parent, frappe._dict()).setdefault(
- "warehouse", []).append(d.warehouse)
+ invoice_cc_wh_map.setdefault(d.parent, frappe._dict()).setdefault("warehouse", []).append(
+ d.warehouse
+ )
return invoice_cc_wh_map
+
def get_mode_of_payments(invoice_list):
mode_of_payments = {}
if invoice_list:
- inv_mop = frappe.db.sql("""select parent, mode_of_payment
- from `tabSales Invoice Payment` where parent in (%s) group by parent, mode_of_payment""" %
- ', '.join(['%s']*len(invoice_list)), tuple(invoice_list), as_dict=1)
+ inv_mop = frappe.db.sql(
+ """select parent, mode_of_payment
+ from `tabSales Invoice Payment` where parent in (%s) group by parent, mode_of_payment"""
+ % ", ".join(["%s"] * len(invoice_list)),
+ tuple(invoice_list),
+ as_dict=1,
+ )
for d in inv_mop:
mode_of_payments.setdefault(d.parent, []).append(d.mode_of_payment)
diff --git a/erpnext/accounts/report/share_balance/share_balance.py b/erpnext/accounts/report/share_balance/share_balance.py
index 943c4e150f..d02f53b0d2 100644
--- a/erpnext/accounts/report/share_balance/share_balance.py
+++ b/erpnext/accounts/report/share_balance/share_balance.py
@@ -7,7 +7,8 @@ from frappe import _
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if not filters.get("date"):
frappe.throw(_("Please select date"))
@@ -38,22 +39,29 @@ def execute(filters=None):
break
# new entry
if not row:
- row = [filters.get("shareholder"),
- share_entry.share_type, share_entry.no_of_shares, share_entry.rate, share_entry.amount]
+ row = [
+ filters.get("shareholder"),
+ share_entry.share_type,
+ share_entry.no_of_shares,
+ share_entry.rate,
+ share_entry.amount,
+ ]
data.append(row)
return columns, data
+
def get_columns(filters):
columns = [
_("Shareholder") + ":Link/Shareholder:150",
_("Share Type") + "::90",
_("No of Shares") + "::90",
_("Average Rate") + ":Currency:90",
- _("Amount") + ":Currency:90"
+ _("Amount") + ":Currency:90",
]
return columns
+
def get_all_shares(shareholder):
- return frappe.get_doc('Shareholder', shareholder).share_balance
+ return frappe.get_doc("Shareholder", shareholder).share_balance
diff --git a/erpnext/accounts/report/share_ledger/share_ledger.py b/erpnext/accounts/report/share_ledger/share_ledger.py
index b3ff6e48a6..d6c3bd059f 100644
--- a/erpnext/accounts/report/share_ledger/share_ledger.py
+++ b/erpnext/accounts/report/share_ledger/share_ledger.py
@@ -7,7 +7,8 @@ from frappe import _
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if not filters.get("date"):
frappe.throw(_("Please select date"))
@@ -23,19 +24,28 @@ def execute(filters=None):
else:
transfers = get_all_transfers(date, filters.get("shareholder"))
for transfer in transfers:
- if transfer.transfer_type == 'Transfer':
+ if transfer.transfer_type == "Transfer":
if transfer.from_shareholder == filters.get("shareholder"):
- transfer.transfer_type += ' to {}'.format(transfer.to_shareholder)
+ transfer.transfer_type += " to {}".format(transfer.to_shareholder)
else:
- transfer.transfer_type += ' from {}'.format(transfer.from_shareholder)
- row = [filters.get("shareholder"), transfer.date, transfer.transfer_type,
- transfer.share_type, transfer.no_of_shares, transfer.rate, transfer.amount,
- transfer.company, transfer.name]
+ transfer.transfer_type += " from {}".format(transfer.from_shareholder)
+ row = [
+ filters.get("shareholder"),
+ transfer.date,
+ transfer.transfer_type,
+ transfer.share_type,
+ transfer.no_of_shares,
+ transfer.rate,
+ transfer.amount,
+ transfer.company,
+ transfer.name,
+ ]
data.append(row)
return columns, data
+
def get_columns(filters):
columns = [
_("Shareholder") + ":Link/Shareholder:150",
@@ -46,16 +56,22 @@ def get_columns(filters):
_("Rate") + ":Currency:90",
_("Amount") + ":Currency:90",
_("Company") + "::150",
- _("Share Transfer") + ":Link/Share Transfer:90"
+ _("Share Transfer") + ":Link/Share Transfer:90",
]
return columns
+
def get_all_transfers(date, shareholder):
- condition = ' '
+ condition = " "
# if company:
# condition = 'AND company = %(company)s '
- return frappe.db.sql("""SELECT * FROM `tabShare Transfer`
+ return frappe.db.sql(
+ """SELECT * FROM `tabShare Transfer`
WHERE (DATE(date) <= %(date)s AND from_shareholder = %(shareholder)s {condition})
OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition})
- ORDER BY date""".format(condition=condition),
- {'date': date, 'shareholder': shareholder}, as_dict=1)
+ ORDER BY date""".format(
+ condition=condition
+ ),
+ {"date": date, "shareholder": shareholder},
+ as_dict=1,
+ )
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.py b/erpnext/accounts/report/tax_detail/tax_detail.py
index eeb8483586..ba8d307228 100644
--- a/erpnext/accounts/report/tax_detail/tax_detail.py
+++ b/erpnext/accounts/report/tax_detail/tax_detail.py
@@ -15,7 +15,13 @@ required_sql_fields = {
("GL Entry", 1): ["posting_date"],
("Account",): ["root_type", "account_type"],
("GL Entry", 2): ["account", "voucher_type", "voucher_no", "debit", "credit"],
- ("Purchase Invoice Item", "Sales Invoice Item"): ["base_net_amount", "item_tax_rate", "item_tax_template", "item_group", "item_name"],
+ ("Purchase Invoice Item", "Sales Invoice Item"): [
+ "base_net_amount",
+ "item_tax_rate",
+ "item_tax_template",
+ "item_group",
+ "item_name",
+ ],
("Purchase Invoice", "Sales Invoice"): ["taxes_and_charges", "tax_category"],
}
@@ -27,7 +33,8 @@ def execute(filters=None):
fieldlist = required_sql_fields
fieldstr = get_fieldstr(fieldlist)
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select {fieldstr}
from `tabGL Entry` ge
inner join `tabAccount` a on
@@ -45,61 +52,73 @@ def execute(filters=None):
ge.posting_date>=%(from_date)s and
ge.posting_date<=%(to_date)s
order by ge.posting_date, ge.voucher_no
- """.format(fieldstr=fieldstr), filters, as_dict=1)
+ """.format(
+ fieldstr=fieldstr
+ ),
+ filters,
+ as_dict=1,
+ )
report_data = modify_report_data(gl_entries)
summary = None
- if filters['mode'] == 'run' and filters['report_name'] != 'Tax Detail':
- report_data, summary = run_report(filters['report_name'], report_data)
+ if filters["mode"] == "run" and filters["report_name"] != "Tax Detail":
+ report_data, summary = run_report(filters["report_name"], report_data)
# return columns, data, message, chart, report_summary
return get_columns(fieldlist), report_data, None, None, summary
+
def run_report(report_name, data):
"Applies the sections and filters saved in the custom report"
- report_config = json.loads(frappe.get_doc('Report', report_name).json)
+ report_config = json.loads(frappe.get_doc("Report", report_name).json)
# Columns indexed from 1 wrt colno
- columns = report_config.get('columns')
- sections = report_config.get('sections', {})
- show_detail = report_config.get('show_detail', 1)
+ columns = report_config.get("columns")
+ sections = report_config.get("sections", {})
+ show_detail = report_config.get("show_detail", 1)
report = {}
new_data = []
summary = []
for section_name, section in sections.items():
- report[section_name] = {'rows': [], 'subtotal': 0.0}
+ report[section_name] = {"rows": [], "subtotal": 0.0}
for component_name, component in section.items():
- if component['type'] == 'filter':
+ if component["type"] == "filter":
for row in data:
matched = True
- for colno, filter_string in component['filters'].items():
- filter_field = columns[int(colno) - 1]['fieldname']
+ for colno, filter_string in component["filters"].items():
+ filter_field = columns[int(colno) - 1]["fieldname"]
if not filter_match(row[filter_field], filter_string):
matched = False
break
if matched:
- report[section_name]['rows'] += [row]
- report[section_name]['subtotal'] += row['amount']
- if component['type'] == 'section':
+ report[section_name]["rows"] += [row]
+ report[section_name]["subtotal"] += row["amount"]
+ if component["type"] == "section":
if component_name == section_name:
frappe.throw(_("A report component cannot refer to its parent section") + ": " + section_name)
try:
- report[section_name]['rows'] += report[component_name]['rows']
- report[section_name]['subtotal'] += report[component_name]['subtotal']
+ report[section_name]["rows"] += report[component_name]["rows"]
+ report[section_name]["subtotal"] += report[component_name]["subtotal"]
except KeyError:
- frappe.throw(_("A report component can only refer to an earlier section") + ": " + section_name)
+ frappe.throw(
+ _("A report component can only refer to an earlier section") + ": " + section_name
+ )
if show_detail:
- new_data += report[section_name]['rows']
- new_data += [{'voucher_no': section_name, 'amount': report[section_name]['subtotal']}]
- summary += [{'label': section_name, 'datatype': 'Currency', 'value': report[section_name]['subtotal']}]
+ new_data += report[section_name]["rows"]
+ new_data += [{"voucher_no": section_name, "amount": report[section_name]["subtotal"]}]
+ summary += [
+ {"label": section_name, "datatype": "Currency", "value": report[section_name]["subtotal"]}
+ ]
if show_detail:
new_data += [{}]
return new_data or data, summary or None
+
def filter_match(value, string):
"Approximation to datatable filters"
import datetime
- if string == '':
+
+ if string == "":
return True
if value is None:
value = -999999999999999
@@ -109,64 +128,68 @@ def filter_match(value, string):
if isinstance(value, str):
value = value.lower()
string = string.lower()
- if string[0] == '<':
+ if string[0] == "<":
return True if string[1:].strip() else False
- elif string[0] == '>':
+ elif string[0] == ">":
return False if string[1:].strip() else True
- elif string[0] == '=':
+ elif string[0] == "=":
return string[1:] in value if string[1:] else False
- elif string[0:2] == '!=':
+ elif string[0:2] == "!=":
return string[2:] not in value
- elif len(string.split(':')) == 2:
- pre, post = string.split(':')
- return (True if not pre.strip() and post.strip() in value else False)
+ elif len(string.split(":")) == 2:
+ pre, post = string.split(":")
+ return True if not pre.strip() and post.strip() in value else False
else:
return string in value
else:
- if string[0] in ['<', '>', '=']:
+ if string[0] in ["<", ">", "="]:
operator = string[0]
- if operator == '=':
- operator = '=='
+ if operator == "=":
+ operator = "=="
string = string[1:].strip()
- elif string[0:2] == '!=':
- operator = '!='
+ elif string[0:2] == "!=":
+ operator = "!="
string = string[2:].strip()
- elif len(string.split(':')) == 2:
- pre, post = string.split(':')
+ elif len(string.split(":")) == 2:
+ pre, post = string.split(":")
try:
- return (True if float(pre) <= value and float(post) >= value else False)
+ return True if float(pre) <= value and float(post) >= value else False
except ValueError:
- return (False if pre.strip() else True)
+ return False if pre.strip() else True
else:
return string in str(value)
try:
num = float(string) if string.strip() else 0
- return frappe.safe_eval(f'{value} {operator} {num}')
+ return frappe.safe_eval(f"{value} {operator} {num}")
except ValueError:
- if operator == '<':
+ if operator == "<":
return True
return False
def abbrev(dt):
- return ''.join(l[0].lower() for l in dt.split(' ')) + '.'
+ return "".join(l[0].lower() for l in dt.split(" ")) + "."
+
def doclist(dt, dfs):
return [abbrev(dt) + f for f in dfs]
+
def as_split(fields):
for field in fields:
- split = field.split(' as ')
+ split = field.split(" as ")
yield (split[0], split[1] if len(split) > 1 else split[0])
+
def coalesce(doctypes, fields):
coalesce = []
for name, new_name in as_split(fields):
- sharedfields = ', '.join(abbrev(dt) + name for dt in doctypes)
- coalesce += [f'coalesce({sharedfields}) as {new_name}']
+ sharedfields = ", ".join(abbrev(dt) + name for dt in doctypes)
+ coalesce += [f"coalesce({sharedfields}) as {new_name}"]
return coalesce
+
def get_fieldstr(fieldlist):
fields = []
for doctypes, docfields in fieldlist.items():
@@ -174,7 +197,8 @@ def get_fieldstr(fieldlist):
fields += doclist(doctypes[0], docfields)
else:
fields += coalesce(doctypes, docfields)
- return ', '.join(fields)
+ return ", ".join(fields)
+
def get_columns(fieldlist):
columns = {}
@@ -186,14 +210,14 @@ def get_columns(fieldlist):
meta = frappe.get_meta(doctype)
# get column field metadata from the db
fieldmeta = {}
- for field in meta.get('fields'):
+ for field in meta.get("fields"):
if field.fieldname in fieldmap.keys():
new_name = fieldmap[field.fieldname]
fieldmeta[new_name] = {
"label": _(field.label),
"fieldname": new_name,
"fieldtype": field.fieldtype,
- "options": field.options
+ "options": field.options,
}
# edit the columns to match the modified data
for field in fieldmap.values():
@@ -203,6 +227,7 @@ def get_columns(fieldlist):
# use of a dict ensures duplicate columns are removed
return list(columns.values())
+
def modify_report_columns(doctype, field, column):
"Because data is rearranged into other columns"
if doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
@@ -216,8 +241,10 @@ def modify_report_columns(doctype, field, column):
column.update({"label": _("Taxes and Charges Template")})
return column
+
def modify_report_data(data):
import json
+
new_data = []
for line in data:
if line.debit:
@@ -249,39 +276,37 @@ def modify_report_data(data):
# JS client utilities
custom_report_dict = {
- 'ref_doctype': 'GL Entry',
- 'report_type': 'Custom Report',
- 'reference_report': 'Tax Detail'
+ "ref_doctype": "GL Entry",
+ "report_type": "Custom Report",
+ "reference_report": "Tax Detail",
}
+
@frappe.whitelist()
def get_custom_reports(name=None):
filters = custom_report_dict.copy()
if name:
- filters['name'] = name
- reports = frappe.get_list('Report',
- filters = filters,
- fields = ['name', 'json'],
- as_list=False
- )
- reports_dict = {rep.pop('name'): rep for rep in reports}
+ filters["name"] = name
+ reports = frappe.get_list("Report", filters=filters, fields=["name", "json"], as_list=False)
+ reports_dict = {rep.pop("name"): rep for rep in reports}
# Prevent custom reports with the same name
- reports_dict['Tax Detail'] = {'json': None}
+ reports_dict["Tax Detail"] = {"json": None}
return reports_dict
+
@frappe.whitelist()
def save_custom_report(reference_report, report_name, data):
- if reference_report != 'Tax Detail':
+ if reference_report != "Tax Detail":
frappe.throw(_("The wrong report is referenced."))
- if report_name == 'Tax Detail':
+ if report_name == "Tax Detail":
frappe.throw(_("The parent report cannot be overwritten."))
doc = {
- 'doctype': 'Report',
- 'report_name': report_name,
- 'is_standard': 'No',
- 'module': 'Accounts',
- 'json': data
+ "doctype": "Report",
+ "report_name": report_name,
+ "is_standard": "No",
+ "module": "Accounts",
+ "json": data,
}
doc.update(custom_report_dict)
@@ -290,7 +315,7 @@ def save_custom_report(reference_report, report_name, data):
newdoc.insert()
frappe.msgprint(_("Report created successfully"))
except frappe.exceptions.DuplicateEntryError:
- dbdoc = frappe.get_doc('Report', report_name)
+ dbdoc = frappe.get_doc("Report", report_name)
dbdoc.update(doc)
dbdoc.save()
frappe.msgprint(_("Report updated successfully"))
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.py b/erpnext/accounts/report/tax_detail/test_tax_detail.py
index 621de825ea..869f245021 100644
--- a/erpnext/accounts/report/tax_detail/test_tax_detail.py
+++ b/erpnext/accounts/report/tax_detail/test_tax_detail.py
@@ -19,8 +19,9 @@ from .tax_detail import filter_match, save_custom_report
class TestTaxDetail(unittest.TestCase):
def load_testdocs(self):
from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
+
datapath, _ = os.path.splitext(os.path.realpath(__file__))
- with open(datapath + '.json', 'r') as fp:
+ with open(datapath + ".json", "r") as fp:
docs = json.load(fp)
now = getdate()
@@ -30,32 +31,38 @@ class TestTaxDetail(unittest.TestCase):
try:
get_fiscal_year(now, company="_T")
except FiscalYearError:
- docs = [{
- "companies": [{
- "company": "_T",
- "parent": "_Test Fiscal",
- "parentfield": "companies",
- "parenttype": "Fiscal Year"
- }],
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal",
- "year_end_date": get_year_ending(now),
- "year_start_date": get_year_start(now)
- }] + docs
+ docs = [
+ {
+ "companies": [
+ {
+ "company": "_T",
+ "parent": "_Test Fiscal",
+ "parentfield": "companies",
+ "parenttype": "Fiscal Year",
+ }
+ ],
+ "doctype": "Fiscal Year",
+ "year": "_Test Fiscal",
+ "year_end_date": get_year_ending(now),
+ "year_start_date": get_year_start(now),
+ }
+ ] + docs
- docs = [{
- "abbr": "_T",
- "company_name": "_T",
- "country": "United Kingdom",
- "default_currency": "GBP",
- "doctype": "Company",
- "name": "_T"
- }] + docs
+ docs = [
+ {
+ "abbr": "_T",
+ "company_name": "_T",
+ "country": "United Kingdom",
+ "default_currency": "GBP",
+ "doctype": "Company",
+ "name": "_T",
+ }
+ ] + docs
for doc in docs:
try:
db_doc = frappe.get_doc(doc)
- if 'Invoice' in db_doc.doctype:
+ if "Invoice" in db_doc.doctype:
db_doc.due_date = add_to_date(now, days=1)
db_doc.insert()
# Create GL Entries:
@@ -66,119 +73,141 @@ class TestTaxDetail(unittest.TestCase):
pass
def load_defcols(self):
- self.company = frappe.get_doc('Company', '_T')
- custom_report = frappe.get_doc('Report', 'Tax Detail')
+ self.company = frappe.get_doc("Company", "_T")
+ custom_report = frappe.get_doc("Report", "Tax Detail")
self.default_columns, _ = custom_report.run_query_report(
filters={
- 'from_date': '2021-03-01',
- 'to_date': '2021-03-31',
- 'company': self.company.name,
- 'mode': 'run',
- 'report_name': 'Tax Detail'
- }, user=frappe.session.user)
+ "from_date": "2021-03-01",
+ "to_date": "2021-03-31",
+ "company": self.company.name,
+ "mode": "run",
+ "report_name": "Tax Detail",
+ },
+ user=frappe.session.user,
+ )
def rm_testdocs(self):
"Remove the Company and all data"
from erpnext.setup.doctype.company.company import create_transaction_deletion_request
+
create_transaction_deletion_request(self.company.name)
def test_report(self):
self.load_testdocs()
self.load_defcols()
report_name = save_custom_report(
- 'Tax Detail',
- '_Test Tax Detail',
- json.dumps({
- 'columns': self.default_columns,
- 'sections': {
- 'Box1':{'Filter0':{'type':'filter','filters':{'4':'VAT on Sales'}}},
- 'Box2':{'Filter0':{'type':'filter','filters':{'4':'Acquisition'}}},
- 'Box3':{'Box1':{'type':'section'},'Box2':{'type':'section'}},
- 'Box4':{'Filter0':{'type':'filter','filters':{'4':'VAT on Purchases'}}},
- 'Box5':{'Box3':{'type':'section'},'Box4':{'type':'section'}},
- 'Box6':{'Filter0':{'type':'filter','filters':{'3':'!=Tax','4':'Sales'}}},
- 'Box7':{'Filter0':{'type':'filter','filters':{'2':'Expense','3':'!=Tax'}}},
- 'Box8':{'Filter0':{'type':'filter','filters':{'3':'!=Tax','4':'Sales','12':'EU'}}},
- 'Box9':{'Filter0':{'type':'filter','filters':{'2':'Expense','3':'!=Tax','12':'EU'}}}
- },
- 'show_detail': 1
- }))
- data = frappe.desk.query_report.run(report_name,
+ "Tax Detail",
+ "_Test Tax Detail",
+ json.dumps(
+ {
+ "columns": self.default_columns,
+ "sections": {
+ "Box1": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Sales"}}},
+ "Box2": {"Filter0": {"type": "filter", "filters": {"4": "Acquisition"}}},
+ "Box3": {"Box1": {"type": "section"}, "Box2": {"type": "section"}},
+ "Box4": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Purchases"}}},
+ "Box5": {"Box3": {"type": "section"}, "Box4": {"type": "section"}},
+ "Box6": {"Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales"}}},
+ "Box7": {"Filter0": {"type": "filter", "filters": {"2": "Expense", "3": "!=Tax"}}},
+ "Box8": {"Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales", "12": "EU"}}},
+ "Box9": {
+ "Filter0": {"type": "filter", "filters": {"2": "Expense", "3": "!=Tax", "12": "EU"}}
+ },
+ },
+ "show_detail": 1,
+ }
+ ),
+ )
+ data = frappe.desk.query_report.run(
+ report_name,
filters={
- 'from_date': self.from_date,
- 'to_date': self.to_date,
- 'company': self.company.name,
- 'mode': 'run',
- 'report_name': report_name
- }, user=frappe.session.user)
+ "from_date": self.from_date,
+ "to_date": self.to_date,
+ "company": self.company.name,
+ "mode": "run",
+ "report_name": report_name,
+ },
+ user=frappe.session.user,
+ )
- self.assertListEqual(data.get('columns'), self.default_columns)
- expected = (('Box1', 43.25), ('Box2', 0.0), ('Box3', 43.25), ('Box4', -85.28), ('Box5', -42.03),
- ('Box6', 825.0), ('Box7', -426.40), ('Box8', 0.0), ('Box9', 0.0))
+ self.assertListEqual(data.get("columns"), self.default_columns)
+ expected = (
+ ("Box1", 43.25),
+ ("Box2", 0.0),
+ ("Box3", 43.25),
+ ("Box4", -85.28),
+ ("Box5", -42.03),
+ ("Box6", 825.0),
+ ("Box7", -426.40),
+ ("Box8", 0.0),
+ ("Box9", 0.0),
+ )
exrow = iter(expected)
- for row in data.get('result'):
- if row.get('voucher_no') and not row.get('posting_date'):
+ for row in data.get("result"):
+ if row.get("voucher_no") and not row.get("posting_date"):
label, value = next(exrow)
- self.assertDictEqual(row, {'voucher_no': label, 'amount': value})
- self.assertListEqual(data.get('report_summary'),
- [{'label': label, 'datatype': 'Currency', 'value': value} for label, value in expected])
+ self.assertDictEqual(row, {"voucher_no": label, "amount": value})
+ self.assertListEqual(
+ data.get("report_summary"),
+ [{"label": label, "datatype": "Currency", "value": value} for label, value in expected],
+ )
self.rm_testdocs()
def test_filter_match(self):
# None - treated as -inf number except range
- self.assertTrue(filter_match(None, '!='))
- self.assertTrue(filter_match(None, '<'))
- self.assertTrue(filter_match(None, '
'
+ msg += "
"
msg += _("Please cancel payment entry manually first")
frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
- frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
+ frappe.db.sql(
+ """update `tabPayment Entry` set total_allocated_amount=%s,
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
- where name=%s""", (pe_doc.total_allocated_amount, pe_doc.base_total_allocated_amount,
- pe_doc.unallocated_amount, now(), frappe.session.user, pe))
+ where name=%s""",
+ (
+ pe_doc.total_allocated_amount,
+ pe_doc.base_total_allocated_amount,
+ pe_doc.unallocated_amount,
+ now(),
+ frappe.session.user,
+ pe,
+ ),
+ )
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
+
@frappe.whitelist()
def get_company_default(company, fieldname, ignore_validation=False):
- value = frappe.get_cached_value('Company', company, fieldname)
+ value = frappe.get_cached_value("Company", company, fieldname)
if not ignore_validation and not value:
- throw(_("Please set default {0} in Company {1}")
- .format(frappe.get_meta("Company").get_label(fieldname), company))
+ throw(
+ _("Please set default {0} in Company {1}").format(
+ frappe.get_meta("Company").get_label(fieldname), company
+ )
+ )
return value
+
def fix_total_debit_credit():
- vouchers = frappe.db.sql("""select voucher_type, voucher_no,
+ vouchers = frappe.db.sql(
+ """select voucher_type, voucher_no,
sum(debit) - sum(credit) as diff
from `tabGL Entry`
group by voucher_type, voucher_no
- having sum(debit) != sum(credit)""", as_dict=1)
+ having sum(debit) != sum(credit)""",
+ as_dict=1,
+ )
for d in vouchers:
if abs(d.diff) > 0:
dr_or_cr = d.voucher_type == "Sales Invoice" and "credit" or "debit"
- frappe.db.sql("""update `tabGL Entry` set %s = %s + %s
- where voucher_type = %s and voucher_no = %s and %s > 0 limit 1""" %
- (dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
- (d.diff, d.voucher_type, d.voucher_no))
+ frappe.db.sql(
+ """update `tabGL Entry` set %s = %s + %s
+ where voucher_type = %s and voucher_no = %s and %s > 0 limit 1"""
+ % (dr_or_cr, dr_or_cr, "%s", "%s", "%s", dr_or_cr),
+ (d.diff, d.voucher_type, d.voucher_no),
+ )
+
def get_currency_precision():
precision = cint(frappe.db.get_default("currency_precision"))
@@ -610,29 +747,41 @@ def get_currency_precision():
return precision
-def get_stock_rbnb_difference(posting_date, company):
- stock_items = frappe.db.sql_list("""select distinct item_code
- from `tabStock Ledger Entry` where company=%s""", company)
- pr_valuation_amount = frappe.db.sql("""
+def get_stock_rbnb_difference(posting_date, company):
+ stock_items = frappe.db.sql_list(
+ """select distinct item_code
+ from `tabStock Ledger Entry` where company=%s""",
+ company,
+ )
+
+ pr_valuation_amount = frappe.db.sql(
+ """
select sum(pr_item.valuation_rate * pr_item.qty * pr_item.conversion_factor)
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name = pr_item.parent and pr.docstatus=1 and pr.company=%s
- and pr.posting_date <= %s and pr_item.item_code in (%s)""" %
- ('%s', '%s', ', '.join(['%s']*len(stock_items))), tuple([company, posting_date] + stock_items))[0][0]
+ and pr.posting_date <= %s and pr_item.item_code in (%s)"""
+ % ("%s", "%s", ", ".join(["%s"] * len(stock_items))),
+ tuple([company, posting_date] + stock_items),
+ )[0][0]
- pi_valuation_amount = frappe.db.sql("""
+ pi_valuation_amount = frappe.db.sql(
+ """
select sum(pi_item.valuation_rate * pi_item.qty * pi_item.conversion_factor)
from `tabPurchase Invoice Item` pi_item, `tabPurchase Invoice` pi
where pi.name = pi_item.parent and pi.docstatus=1 and pi.company=%s
- and pi.posting_date <= %s and pi_item.item_code in (%s)""" %
- ('%s', '%s', ', '.join(['%s']*len(stock_items))), tuple([company, posting_date] + stock_items))[0][0]
+ and pi.posting_date <= %s and pi_item.item_code in (%s)"""
+ % ("%s", "%s", ", ".join(["%s"] * len(stock_items))),
+ tuple([company, posting_date] + stock_items),
+ )[0][0]
# Balance should be
stock_rbnb = flt(pr_valuation_amount, 2) - flt(pi_valuation_amount, 2)
# Balance as per system
- stock_rbnb_account = "Stock Received But Not Billed - " + frappe.get_cached_value('Company', company, "abbr")
+ stock_rbnb_account = "Stock Received But Not Billed - " + frappe.get_cached_value(
+ "Company", company, "abbr"
+ )
sys_bal = get_balance_on(stock_rbnb_account, posting_date, in_account_currency=False)
# Amount should be credited
@@ -645,12 +794,12 @@ def get_held_invoices(party_type, party):
"""
held_invoices = None
- if party_type == 'Supplier':
+ if party_type == "Supplier":
held_invoices = frappe.db.sql(
- 'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
- as_dict=1
+ "select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()",
+ as_dict=1,
)
- held_invoices = set(d['name'] for d in held_invoices)
+ held_invoices = set(d["name"] for d in held_invoices)
return held_invoices
@@ -660,13 +809,15 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
if account:
- root_type, account_type = frappe.get_cached_value("Account", account, ["root_type", "account_type"])
+ root_type, account_type = frappe.get_cached_value(
+ "Account", account, ["root_type", "account_type"]
+ )
party_account_type = "Receivable" if root_type == "Asset" else "Payable"
party_account_type = account_type or party_account_type
else:
party_account_type = erpnext.get_party_account_type(party_type)
- if party_account_type == 'Receivable':
+ if party_account_type == "Receivable":
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
else:
@@ -675,7 +826,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
held_invoices = get_held_invoices(party_type, party)
- invoice_list = frappe.db.sql("""
+ invoice_list = frappe.db.sql(
+ """
select
voucher_no, voucher_type, posting_date, due_date,
ifnull(sum({dr_or_cr}), 0) as invoice_amount,
@@ -692,15 +844,18 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
or (voucher_type not in ('Journal Entry', 'Payment Entry')))
group by voucher_type, voucher_no
order by posting_date, name""".format(
- dr_or_cr=dr_or_cr,
- condition=condition or ""
- ), {
+ dr_or_cr=dr_or_cr, condition=condition or ""
+ ),
+ {
"party_type": party_type,
"party": party,
"account": account,
- }, as_dict=True)
+ },
+ as_dict=True,
+ )
- payment_entries = frappe.db.sql("""
+ payment_entries = frappe.db.sql(
+ """
select against_voucher_type, against_voucher,
ifnull(sum({payment_dr_or_cr}), 0) as payment_amount
from `tabGL Entry`
@@ -710,11 +865,12 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
and against_voucher is not null and against_voucher != ''
and is_cancelled=0
group by against_voucher_type, against_voucher
- """.format(payment_dr_or_cr=payment_dr_or_cr), {
- "party_type": party_type,
- "party": party,
- "account": account
- }, as_dict=True)
+ """.format(
+ payment_dr_or_cr=payment_dr_or_cr
+ ),
+ {"party_type": party_type, "party": party, "account": account},
+ as_dict=True,
+ )
pe_map = frappe._dict()
for d in payment_entries:
@@ -724,73 +880,87 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0)
outstanding_amount = flt(d.invoice_amount - payment_amount, precision)
if outstanding_amount > 0.5 / (10**precision):
- if (filters and filters.get("outstanding_amt_greater_than") and
- not (outstanding_amount >= filters.get("outstanding_amt_greater_than") and
- outstanding_amount <= filters.get("outstanding_amt_less_than"))):
+ if (
+ filters
+ and filters.get("outstanding_amt_greater_than")
+ and not (
+ outstanding_amount >= filters.get("outstanding_amt_greater_than")
+ and outstanding_amount <= filters.get("outstanding_amt_less_than")
+ )
+ ):
continue
if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
outstanding_invoices.append(
- frappe._dict({
- 'voucher_no': d.voucher_no,
- 'voucher_type': d.voucher_type,
- 'posting_date': d.posting_date,
- 'invoice_amount': flt(d.invoice_amount),
- 'payment_amount': payment_amount,
- 'outstanding_amount': outstanding_amount,
- 'due_date': d.due_date,
- 'currency': d.currency
- })
+ frappe._dict(
+ {
+ "voucher_no": d.voucher_no,
+ "voucher_type": d.voucher_type,
+ "posting_date": d.posting_date,
+ "invoice_amount": flt(d.invoice_amount),
+ "payment_amount": payment_amount,
+ "outstanding_amount": outstanding_amount,
+ "due_date": d.due_date,
+ "currency": d.currency,
+ }
+ )
)
- outstanding_invoices = sorted(outstanding_invoices, key=lambda k: k['due_date'] or getdate(nowdate()))
+ outstanding_invoices = sorted(
+ outstanding_invoices, key=lambda k: k["due_date"] or getdate(nowdate())
+ )
return outstanding_invoices
-def get_account_name(account_type=None, root_type=None, is_group=None, account_currency=None, company=None):
+def get_account_name(
+ account_type=None, root_type=None, is_group=None, account_currency=None, company=None
+):
"""return account based on matching conditions"""
- return frappe.db.get_value("Account", {
- "account_type": account_type or '',
- "root_type": root_type or '',
- "is_group": is_group or 0,
- "account_currency": account_currency or frappe.defaults.get_defaults().currency,
- "company": company or frappe.defaults.get_defaults().company
- }, "name")
+ return frappe.db.get_value(
+ "Account",
+ {
+ "account_type": account_type or "",
+ "root_type": root_type or "",
+ "is_group": is_group or 0,
+ "account_currency": account_currency or frappe.defaults.get_defaults().currency,
+ "company": company or frappe.defaults.get_defaults().company,
+ },
+ "name",
+ )
+
@frappe.whitelist()
def get_companies():
"""get a list of companies based on permission"""
- return [d.name for d in frappe.get_list("Company", fields=["name"],
- order_by="name")]
+ return [d.name for d in frappe.get_list("Company", fields=["name"], order_by="name")]
+
@frappe.whitelist()
def get_children(doctype, parent, company, is_root=False):
from erpnext.accounts.report.financial_statements import sort_accounts
- parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
- fields = [
- 'name as value',
- 'is_group as expandable'
- ]
- filters = [['docstatus', '<', 2]]
+ parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")
+ fields = ["name as value", "is_group as expandable"]
+ filters = [["docstatus", "<", 2]]
- filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), '=', '' if is_root else parent])
+ filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), "=", "" if is_root else parent])
if is_root:
- fields += ['root_type', 'report_type', 'account_currency'] if doctype == 'Account' else []
- filters.append(['company', '=', company])
+ fields += ["root_type", "report_type", "account_currency"] if doctype == "Account" else []
+ filters.append(["company", "=", company])
else:
- fields += ['root_type', 'account_currency'] if doctype == 'Account' else []
- fields += [parent_fieldname + ' as parent']
+ fields += ["root_type", "account_currency"] if doctype == "Account" else []
+ fields += [parent_fieldname + " as parent"]
acc = frappe.get_list(doctype, fields=fields, filters=filters)
- if doctype == 'Account':
+ if doctype == "Account":
sort_accounts(acc, is_root, key="value")
return acc
+
@frappe.whitelist()
def get_account_balances(accounts, company):
@@ -800,16 +970,19 @@ def get_account_balances(accounts, company):
if not accounts:
return []
- company_currency = frappe.get_cached_value("Company", company, "default_currency")
+ company_currency = frappe.get_cached_value("Company", company, "default_currency")
for account in accounts:
account["company_currency"] = company_currency
- account["balance"] = flt(get_balance_on(account["value"], in_account_currency=False, company=company))
+ account["balance"] = flt(
+ get_balance_on(account["value"], in_account_currency=False, company=company)
+ )
if account["account_currency"] and account["account_currency"] != company_currency:
account["balance_in_account_currency"] = flt(get_balance_on(account["value"], company=company))
return accounts
+
def create_payment_gateway_account(gateway, payment_channel="Email"):
from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account
@@ -818,13 +991,21 @@ def create_payment_gateway_account(gateway, payment_channel="Email"):
return
# NOTE: we translate Payment Gateway account name because that is going to be used by the end user
- bank_account = frappe.db.get_value("Account", {"account_name": _(gateway), "company": company},
- ["name", 'account_currency'], as_dict=1)
+ bank_account = frappe.db.get_value(
+ "Account",
+ {"account_name": _(gateway), "company": company},
+ ["name", "account_currency"],
+ as_dict=1,
+ )
if not bank_account:
# check for untranslated one
- bank_account = frappe.db.get_value("Account", {"account_name": gateway, "company": company},
- ["name", 'account_currency'], as_dict=1)
+ bank_account = frappe.db.get_value(
+ "Account",
+ {"account_name": gateway, "company": company},
+ ["name", "account_currency"],
+ as_dict=1,
+ )
if not bank_account:
# try creating one
@@ -835,30 +1016,35 @@ def create_payment_gateway_account(gateway, payment_channel="Email"):
return
# if payment gateway account exists, return
- if frappe.db.exists("Payment Gateway Account",
- {"payment_gateway": gateway, "currency": bank_account.account_currency}):
+ if frappe.db.exists(
+ "Payment Gateway Account",
+ {"payment_gateway": gateway, "currency": bank_account.account_currency},
+ ):
return
try:
- frappe.get_doc({
- "doctype": "Payment Gateway Account",
- "is_default": 1,
- "payment_gateway": gateway,
- "payment_account": bank_account.name,
- "currency": bank_account.account_currency,
- "payment_channel": payment_channel
- }).insert(ignore_permissions=True, ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "Payment Gateway Account",
+ "is_default": 1,
+ "payment_gateway": gateway,
+ "payment_account": bank_account.name,
+ "currency": bank_account.account_currency,
+ "payment_channel": payment_channel,
+ }
+ ).insert(ignore_permissions=True, ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
# already exists, due to a reinstall?
pass
+
@frappe.whitelist()
def update_cost_center(docname, cost_center_name, cost_center_number, company, merge):
- '''
- Renames the document by adding the number as a prefix to the current name and updates
- all transaction where it was present.
- '''
+ """
+ Renames the document by adding the number as a prefix to the current name and updates
+ all transaction where it was present.
+ """
validate_field_number("Cost Center", docname, cost_center_number, company, "cost_center_number")
if cost_center_number:
@@ -873,8 +1059,9 @@ def update_cost_center(docname, cost_center_name, cost_center_number, company, m
frappe.rename_doc("Cost Center", docname, new_name, force=1, merge=merge)
return new_name
+
def validate_field_number(doctype_name, docname, number_value, company, field_name):
- ''' Validate if the number entered isn't already assigned to some other document. '''
+ """Validate if the number entered isn't already assigned to some other document."""
if number_value:
filters = {field_name: number_value, "name": ["!=", docname]}
if company:
@@ -883,20 +1070,25 @@ def validate_field_number(doctype_name, docname, number_value, company, field_na
doctype_with_same_number = frappe.db.get_value(doctype_name, filters)
if doctype_with_same_number:
- frappe.throw(_("{0} Number {1} is already used in {2} {3}")
- .format(doctype_name, number_value, doctype_name.lower(), doctype_with_same_number))
+ frappe.throw(
+ _("{0} Number {1} is already used in {2} {3}").format(
+ doctype_name, number_value, doctype_name.lower(), doctype_with_same_number
+ )
+ )
+
def get_autoname_with_number(number_value, doc_title, name, company):
- ''' append title with prefix as number and suffix as company's abbreviation separated by '-' '''
+ """append title with prefix as number and suffix as company's abbreviation separated by '-'"""
if name:
- name_split=name.split("-")
- parts = [doc_title.strip(), name_split[len(name_split)-1].strip()]
+ name_split = name.split("-")
+ parts = [doc_title.strip(), name_split[len(name_split) - 1].strip()]
else:
- abbr = frappe.get_cached_value('Company', company, ["abbr"], as_dict=True)
+ abbr = frappe.get_cached_value("Company", company, ["abbr"], as_dict=True)
parts = [doc_title.strip(), abbr.abbr]
if cstr(number_value).strip():
parts.insert(0, cstr(number_value).strip())
- return ' - '.join(parts)
+ return " - ".join(parts)
+
@frappe.whitelist()
def get_coa(doctype, parent, is_root, chart=None):
@@ -908,24 +1100,38 @@ def get_coa(doctype, parent, is_root, chart=None):
chart = chart if chart else frappe.flags.chart
frappe.flags.chart = chart
- parent = None if parent==_('All Accounts') else parent
- accounts = build_tree_from_json(chart) # returns alist of dict in a tree render-able form
+ parent = None if parent == _("All Accounts") else parent
+ accounts = build_tree_from_json(chart) # returns alist of dict in a tree render-able form
# filter out to show data for the selected node only
- accounts = [d for d in accounts if d['parent_account']==parent]
+ accounts = [d for d in accounts if d["parent_account"] == parent]
return accounts
-def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
- warehouse_account=None, company=None):
- stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items, company)
+
+def update_gl_entries_after(
+ posting_date,
+ posting_time,
+ for_warehouses=None,
+ for_items=None,
+ warehouse_account=None,
+ company=None,
+):
+ stock_vouchers = get_future_stock_vouchers(
+ posting_date, posting_time, for_warehouses, for_items, company
+ )
repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company, warehouse_account)
-def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, warehouse_account=None):
+def repost_gle_for_stock_vouchers(
+ stock_vouchers, posting_date, company=None, warehouse_account=None
+):
def _delete_gl_entries(voucher_type, voucher_no):
- frappe.db.sql("""delete from `tabGL Entry`
- where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
+ frappe.db.sql(
+ """delete from `tabGL Entry`
+ where voucher_type=%s and voucher_no=%s""",
+ (voucher_type, voucher_no),
+ )
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
@@ -938,13 +1144,18 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa
voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
- if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
+ if not existing_gle or not compare_existing_and_expected_gle(
+ existing_gle, expected_gle, precision
+ ):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
-def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None):
+
+def get_future_stock_vouchers(
+ posting_date, posting_time, for_warehouses=None, for_items=None, company=None
+):
values = []
condition = ""
@@ -960,25 +1171,31 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
condition += " and company = %s"
values.append(company)
- future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
+ future_stock_vouchers = frappe.db.sql(
+ """select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where
timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s)
and is_cancelled = 0
{condition}
- order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
- tuple([posting_date, posting_time] + values), as_dict=True)
+ order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(
+ condition=condition
+ ),
+ tuple([posting_date, posting_time] + values),
+ as_dict=True,
+ )
return [(d.voucher_type, d.voucher_no) for d in future_stock_vouchers]
+
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
- """ Get voucherwise list of GL entries.
+ """Get voucherwise list of GL entries.
Only fetches GLE fields required for comparing with new GLE.
Check compare_existing_and_expected_gle function below.
returns:
- Dict[Tuple[voucher_type, voucher_no], List[GL Entries]]
+ Dict[Tuple[voucher_type, voucher_no], List[GL Entries]]
"""
gl_entries = {}
if not future_stock_vouchers:
@@ -986,19 +1203,23 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
voucher_nos = [d[1] for d in future_stock_vouchers]
- gles = frappe.db.sql("""
+ gles = frappe.db.sql(
+ """
select name, account, credit, debit, cost_center, project, voucher_type, voucher_no
from `tabGL Entry`
where
- posting_date >= %s and voucher_no in (%s)""" %
- ('%s', ', '.join(['%s'] * len(voucher_nos))),
- tuple([posting_date] + voucher_nos), as_dict=1)
+ posting_date >= %s and voucher_no in (%s)"""
+ % ("%s", ", ".join(["%s"] * len(voucher_nos))),
+ tuple([posting_date] + voucher_nos),
+ as_dict=1,
+ )
for d in gles:
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries
+
def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
if len(existing_gle) != len(expected_gle):
return False
@@ -1009,10 +1230,14 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
for e in existing_gle:
if entry.account == e.account:
account_existed = True
- if (entry.account == e.account
- and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
- and ( flt(entry.debit, precision) != flt(e.debit, precision) or
- flt(entry.credit, precision) != flt(e.credit, precision))):
+ if (
+ entry.account == e.account
+ and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
+ and (
+ flt(entry.debit, precision) != flt(e.debit, precision)
+ or flt(entry.credit, precision) != flt(e.credit, precision)
+ )
+ ):
matched = False
break
if not account_existed:
@@ -1020,7 +1245,10 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
break
return matched
-def check_if_stock_and_account_balance_synced(posting_date, company, voucher_type=None, voucher_no=None):
+
+def check_if_stock_and_account_balance_synced(
+ posting_date, company, voucher_type=None, voucher_no=None
+):
if not cint(erpnext.is_perpetual_inventory_enabled(company)):
return
@@ -1028,61 +1256,81 @@ def check_if_stock_and_account_balance_synced(posting_date, company, voucher_typ
stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account")
for account in accounts:
- account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
- posting_date, company)
+ account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
+ account, posting_date, company
+ )
if abs(account_bal - stock_bal) > 0.1:
- precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
- currency=frappe.get_cached_value('Company', company, "default_currency"))
+ precision = get_field_precision(
+ frappe.get_meta("GL Entry").get_field("debit"),
+ currency=frappe.get_cached_value("Company", company, "default_currency"),
+ )
diff = flt(stock_bal - account_bal, precision)
- error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format(
- stock_bal, account_bal, frappe.bold(account), posting_date)
- error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\
- .format(frappe.bold(diff), frappe.bold(posting_date))
+ error_reason = _(
+ "Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}."
+ ).format(stock_bal, account_bal, frappe.bold(account), posting_date)
+ error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}").format(
+ frappe.bold(diff), frappe.bold(posting_date)
+ )
frappe.msgprint(
msg="""{0}
{1}
""".format(error_reason, error_resolution),
raise_exception=StockValueAndAccountBalanceOutOfSync,
- title=_('Values Out Of Sync'),
+ title=_("Values Out Of Sync"),
primary_action={
- 'label': _('Make Journal Entry'),
- 'client_action': 'erpnext.route_to_adjustment_jv',
- 'args': get_journal_entry(account, stock_adjustment_account, diff)
- })
+ "label": _("Make Journal Entry"),
+ "client_action": "erpnext.route_to_adjustment_jv",
+ "args": get_journal_entry(account, stock_adjustment_account, diff),
+ },
+ )
+
def get_stock_accounts(company, voucher_type=None, voucher_no=None):
- stock_accounts = [d.name for d in frappe.db.get_all("Account", {
- "account_type": "Stock",
- "company": company,
- "is_group": 0
- })]
+ stock_accounts = [
+ d.name
+ for d in frappe.db.get_all(
+ "Account", {"account_type": "Stock", "company": company, "is_group": 0}
+ )
+ ]
if voucher_type and voucher_no:
if voucher_type == "Journal Entry":
- stock_accounts = [d.account for d in frappe.db.get_all("Journal Entry Account", {
- "parent": voucher_no,
- "account": ["in", stock_accounts]
- }, "account")]
+ stock_accounts = [
+ d.account
+ for d in frappe.db.get_all(
+ "Journal Entry Account", {"parent": voucher_no, "account": ["in", stock_accounts]}, "account"
+ )
+ ]
else:
- stock_accounts = [d.account for d in frappe.db.get_all("GL Entry", {
- "voucher_type": voucher_type,
- "voucher_no": voucher_no,
- "account": ["in", stock_accounts]
- }, "account")]
+ stock_accounts = [
+ d.account
+ for d in frappe.db.get_all(
+ "GL Entry",
+ {"voucher_type": voucher_type, "voucher_no": voucher_no, "account": ["in", stock_accounts]},
+ "account",
+ )
+ ]
return stock_accounts
+
def get_stock_and_account_balance(account=None, posting_date=None, company=None):
- if not posting_date: posting_date = nowdate()
+ if not posting_date:
+ posting_date = nowdate()
warehouse_account = get_warehouse_account_map(company)
- account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
+ account_balance = get_balance_on(
+ account, posting_date, in_account_currency=False, ignore_account_permission=True
+ )
- related_warehouses = [wh for wh, wh_details in warehouse_account.items()
- if wh_details.account == account and not wh_details.is_group]
+ related_warehouses = [
+ wh
+ for wh, wh_details in warehouse_account.items()
+ if wh_details.account == account and not wh_details.is_group
+ ]
total_stock_value = 0.0
for warehouse in related_warehouses:
@@ -1092,27 +1340,26 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
+
def get_journal_entry(account, stock_adjustment_account, amount):
- db_or_cr_warehouse_account =('credit_in_account_currency' if amount < 0 else 'debit_in_account_currency')
- db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if amount < 0 else 'credit_in_account_currency')
+ db_or_cr_warehouse_account = (
+ "credit_in_account_currency" if amount < 0 else "debit_in_account_currency"
+ )
+ db_or_cr_stock_adjustment_account = (
+ "debit_in_account_currency" if amount < 0 else "credit_in_account_currency"
+ )
return {
- 'accounts':[{
- 'account': account,
- db_or_cr_warehouse_account: abs(amount)
- }, {
- 'account': stock_adjustment_account,
- db_or_cr_stock_adjustment_account : abs(amount)
- }]
+ "accounts": [
+ {"account": account, db_or_cr_warehouse_account: abs(amount)},
+ {"account": stock_adjustment_account, db_or_cr_stock_adjustment_account: abs(amount)},
+ ]
}
+
def check_and_delete_linked_reports(report):
- """ Check if reports are referenced in Desktop Icon """
- icons = frappe.get_all("Desktop Icon",
- fields = ['name'],
- filters = {
- "_report": report
- })
+ """Check if reports are referenced in Desktop Icon"""
+ icons = frappe.get_all("Desktop Icon", fields=["name"], filters={"_report": report})
if icons:
for icon in icons:
frappe.delete_doc("Desktop Icon", icon)
diff --git a/erpnext/assets/dashboard_fixtures.py b/erpnext/assets/dashboard_fixtures.py
index 39f0f1a88b..fc9ba386a3 100644
--- a/erpnext/assets/dashboard_fixtures.py
+++ b/erpnext/assets/dashboard_fixtures.py
@@ -18,30 +18,36 @@ def get_data():
if not fiscal_year:
return frappe._dict()
- year_start_date = get_date_str(fiscal_year.get('year_start_date'))
- year_end_date = get_date_str(fiscal_year.get('year_end_date'))
+ year_start_date = get_date_str(fiscal_year.get("year_start_date"))
+ year_end_date = get_date_str(fiscal_year.get("year_end_date"))
+
+ return frappe._dict(
+ {
+ "dashboards": get_dashboards(),
+ "charts": get_charts(fiscal_year, year_start_date, year_end_date),
+ "number_cards": get_number_cards(fiscal_year, year_start_date, year_end_date),
+ }
+ )
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(fiscal_year, year_start_date, year_end_date),
- "number_cards": get_number_cards(fiscal_year, year_start_date, year_end_date),
- })
def get_dashboards():
- return [{
- "name": "Asset",
- "dashboard_name": "Asset",
- "charts": [
- { "chart": "Asset Value Analytics", "width": "Full" },
- { "chart": "Category-wise Asset Value", "width": "Half" },
- { "chart": "Location-wise Asset Value", "width": "Half" },
- ],
- "cards": [
- {"card": "Total Assets"},
- {"card": "New Assets (This Year)"},
- {"card": "Asset Value"}
- ]
- }]
+ return [
+ {
+ "name": "Asset",
+ "dashboard_name": "Asset",
+ "charts": [
+ {"chart": "Asset Value Analytics", "width": "Full"},
+ {"chart": "Category-wise Asset Value", "width": "Half"},
+ {"chart": "Location-wise Asset Value", "width": "Half"},
+ ],
+ "cards": [
+ {"card": "Total Assets"},
+ {"card": "New Assets (This Year)"},
+ {"card": "Asset Value"},
+ ],
+ }
+ ]
+
def get_charts(fiscal_year, year_start_date, year_end_date):
company = get_company_for_dashboards()
@@ -58,26 +64,30 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
"timespan": "Last Year",
"time_interval": "Yearly",
"timeseries": 0,
- "filters_json": json.dumps({
- "company": company,
- "status": "In Location",
- "filter_based_on": "Fiscal Year",
- "from_fiscal_year": fiscal_year.get('name'),
- "to_fiscal_year": fiscal_year.get('name'),
- "period_start_date": year_start_date,
- "period_end_date": year_end_date,
- "date_based_on": "Purchase Date",
- "group_by": "--Select a group--"
- }),
+ "filters_json": json.dumps(
+ {
+ "company": company,
+ "status": "In Location",
+ "filter_based_on": "Fiscal Year",
+ "from_fiscal_year": fiscal_year.get("name"),
+ "to_fiscal_year": fiscal_year.get("name"),
+ "period_start_date": year_start_date,
+ "period_end_date": year_end_date,
+ "date_based_on": "Purchase Date",
+ "group_by": "--Select a group--",
+ }
+ ),
"type": "Bar",
- "custom_options": json.dumps({
- "type": "bar",
- "barOptions": { "stacked": 1 },
- "axisOptions": { "shortenYAxisNumbers": 1 },
- "tooltipOptions": {}
- }),
+ "custom_options": json.dumps(
+ {
+ "type": "bar",
+ "barOptions": {"stacked": 1},
+ "axisOptions": {"shortenYAxisNumbers": 1},
+ "tooltipOptions": {},
+ }
+ ),
"doctype": "Dashboard Chart",
- "y_axis": []
+ "y_axis": [],
},
{
"name": "Category-wise Asset Value",
@@ -86,12 +96,14 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
"report_name": "Fixed Asset Register",
"x_field": "asset_category",
"timeseries": 0,
- "filters_json": json.dumps({
- "company": company,
- "status":"In Location",
- "group_by":"Asset Category",
- "is_existing_asset":0
- }),
+ "filters_json": json.dumps(
+ {
+ "company": company,
+ "status": "In Location",
+ "group_by": "Asset Category",
+ "is_existing_asset": 0,
+ }
+ ),
"type": "Donut",
"doctype": "Dashboard Chart",
"y_axis": [
@@ -100,14 +112,12 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
"parentfield": "y_axis",
"parenttype": "Dashboard Chart",
"y_field": "asset_value",
- "doctype": "Dashboard Chart Field"
+ "doctype": "Dashboard Chart Field",
}
],
- "custom_options": json.dumps({
- "type": "donut",
- "height": 300,
- "axisOptions": {"shortenYAxisNumbers": 1}
- })
+ "custom_options": json.dumps(
+ {"type": "donut", "height": 300, "axisOptions": {"shortenYAxisNumbers": 1}}
+ ),
},
{
"name": "Location-wise Asset Value",
@@ -116,12 +126,9 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
"report_name": "Fixed Asset Register",
"x_field": "location",
"timeseries": 0,
- "filters_json": json.dumps({
- "company": company,
- "status":"In Location",
- "group_by":"Location",
- "is_existing_asset":0
- }),
+ "filters_json": json.dumps(
+ {"company": company, "status": "In Location", "group_by": "Location", "is_existing_asset": 0}
+ ),
"type": "Donut",
"doctype": "Dashboard Chart",
"y_axis": [
@@ -130,17 +137,16 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
"parentfield": "y_axis",
"parenttype": "Dashboard Chart",
"y_field": "asset_value",
- "doctype": "Dashboard Chart Field"
+ "doctype": "Dashboard Chart Field",
}
],
- "custom_options": json.dumps({
- "type": "donut",
- "height": 300,
- "axisOptions": {"shortenYAxisNumbers": 1}
- })
- }
+ "custom_options": json.dumps(
+ {"type": "donut", "height": 300, "axisOptions": {"shortenYAxisNumbers": 1}}
+ ),
+ },
]
+
def get_number_cards(fiscal_year, year_start_date, year_end_date):
return [
{
@@ -162,9 +168,9 @@ def get_number_cards(fiscal_year, year_start_date, year_end_date):
"is_public": 1,
"show_percentage_stats": 1,
"stats_time_interval": "Monthly",
- "filters_json": json.dumps([
- ['Asset', 'creation', 'between', [year_start_date, year_end_date]]
- ]),
+ "filters_json": json.dumps(
+ [["Asset", "creation", "between", [year_start_date, year_end_date]]]
+ ),
"doctype": "Number Card",
},
{
@@ -177,6 +183,6 @@ def get_number_cards(fiscal_year, year_start_date, year_end_date):
"show_percentage_stats": 1,
"stats_time_interval": "Monthly",
"filters_json": "[]",
- "doctype": "Number Card"
- }
+ "doctype": "Number Card",
+ },
]
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index ea473fa7bb..257488dfc3 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -58,21 +58,26 @@ class Asset(AccountsController):
self.cancel_movement_entries()
self.delete_depreciation_entries()
self.set_status()
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
- make_reverse_gl_entries(voucher_type='Asset', voucher_no=self.name)
- self.db_set('booked_fixed_asset', 0)
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+ make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
+ self.db_set("booked_fixed_asset", 0)
def validate_asset_and_reference(self):
if self.purchase_invoice or self.purchase_receipt:
- reference_doc = 'Purchase Invoice' if self.purchase_invoice else 'Purchase Receipt'
+ reference_doc = "Purchase Invoice" if self.purchase_invoice else "Purchase Receipt"
reference_name = self.purchase_invoice or self.purchase_receipt
reference_doc = frappe.get_doc(reference_doc, reference_name)
- if reference_doc.get('company') != self.company:
- frappe.throw(_("Company of asset {0} and purchase document {1} doesn't matches.").format(self.name, reference_doc.get('name')))
-
+ if reference_doc.get("company") != self.company:
+ frappe.throw(
+ _("Company of asset {0} and purchase document {1} doesn't matches.").format(
+ self.name, reference_doc.get("name")
+ )
+ )
if self.is_existing_asset and self.purchase_invoice:
- frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
+ frappe.throw(
+ _("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)
+ )
def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None):
if self.calculate_depreciation:
@@ -82,12 +87,14 @@ class Asset(AccountsController):
self.set_accumulated_depreciation(date_of_sale, date_of_return)
else:
self.finance_books = []
- self.value_after_depreciation = (flt(self.gross_purchase_amount) -
- flt(self.opening_accumulated_depreciation))
+ self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
+ self.opening_accumulated_depreciation
+ )
def validate_item(self):
- item = frappe.get_cached_value("Item", self.item_code,
- ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1)
+ item = frappe.get_cached_value(
+ "Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1
+ )
if not item:
frappe.throw(_("Item {0} does not exist").format(self.item_code))
elif item.disabled:
@@ -98,16 +105,16 @@ class Asset(AccountsController):
frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code))
def validate_cost_center(self):
- if not self.cost_center: return
+ if not self.cost_center:
+ return
- cost_center_company = frappe.db.get_value('Cost Center', self.cost_center, 'company')
+ cost_center_company = frappe.db.get_value("Cost Center", self.cost_center, "company")
if cost_center_company != self.company:
frappe.throw(
_("Selected Cost Center {} doesn't belongs to {}").format(
- frappe.bold(self.cost_center),
- frappe.bold(self.company)
+ frappe.bold(self.cost_center), frappe.bold(self.company)
),
- title=_("Invalid Cost Center")
+ title=_("Invalid Cost Center"),
)
def validate_in_use_date(self):
@@ -116,16 +123,20 @@ class Asset(AccountsController):
for d in self.finance_books:
if d.depreciation_start_date == self.available_for_use_date:
- frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
- title=_("Incorrect Date"))
+ frappe.throw(
+ _("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(
+ d.idx
+ ),
+ title=_("Incorrect Date"),
+ )
def set_missing_values(self):
if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
- if self.item_code and not self.get('finance_books'):
+ if self.item_code and not self.get("finance_books"):
finance_books = get_item_details(self.item_code, self.asset_category)
- self.set('finance_books', finance_books)
+ self.set("finance_books", finance_books)
def validate_asset_values(self):
if not self.asset_category:
@@ -136,13 +147,20 @@ class Asset(AccountsController):
if is_cwip_accounting_enabled(self.asset_category):
if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
- frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}").
- format(self.item_code))
+ frappe.throw(
+ _("Please create purchase receipt or purchase invoice for the item {0}").format(
+ self.item_code
+ )
+ )
- if (not self.purchase_receipt and self.purchase_invoice
- and not frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')):
- frappe.throw(_("Update stock must be enable for the purchase invoice {0}").
- format(self.purchase_invoice))
+ if (
+ not self.purchase_receipt
+ and self.purchase_invoice
+ and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock")
+ ):
+ frappe.throw(
+ _("Update stock must be enable for the purchase invoice {0}").format(self.purchase_invoice)
+ )
if not self.calculate_depreciation:
return
@@ -152,49 +170,63 @@ class Asset(AccountsController):
if self.is_existing_asset:
return
- if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
+ if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(
+ self.purchase_date
+ ):
frappe.throw(_("Available-for-use Date should be after purchase date"))
def validate_gross_and_purchase_amount(self):
- if self.is_existing_asset: return
+ if self.is_existing_asset:
+ return
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
- error_message = _("Gross Purchase Amount should be equal to purchase amount of one single Asset.")
+ error_message = _(
+ "Gross Purchase Amount should be equal to purchase amount of one single Asset."
+ )
error_message += "
"
error_message += _("Please do not book expense of multiple assets against one single Asset.")
frappe.throw(error_message, title=_("Invalid Gross Purchase Amount"))
def make_asset_movement(self):
- reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
+ reference_doctype = "Purchase Receipt" if self.purchase_receipt else "Purchase Invoice"
reference_docname = self.purchase_receipt or self.purchase_invoice
transaction_date = getdate(self.purchase_date)
if reference_docname:
- posting_date, posting_time = frappe.db.get_value(reference_doctype, reference_docname, ["posting_date", "posting_time"])
+ posting_date, posting_time = frappe.db.get_value(
+ reference_doctype, reference_docname, ["posting_date", "posting_time"]
+ )
transaction_date = get_datetime("{} {}".format(posting_date, posting_time))
- assets = [{
- 'asset': self.name,
- 'asset_name': self.asset_name,
- 'target_location': self.location,
- 'to_employee': self.custodian
- }]
- asset_movement = frappe.get_doc({
- 'doctype': 'Asset Movement',
- 'assets': assets,
- 'purpose': 'Receipt',
- 'company': self.company,
- 'transaction_date': transaction_date,
- 'reference_doctype': reference_doctype,
- 'reference_name': reference_docname
- }).insert()
+ assets = [
+ {
+ "asset": self.name,
+ "asset_name": self.asset_name,
+ "target_location": self.location,
+ "to_employee": self.custodian,
+ }
+ ]
+ asset_movement = frappe.get_doc(
+ {
+ "doctype": "Asset Movement",
+ "assets": assets,
+ "purpose": "Receipt",
+ "company": self.company,
+ "transaction_date": transaction_date,
+ "reference_doctype": reference_doctype,
+ "reference_name": reference_docname,
+ }
+ ).insert()
asset_movement.submit()
def set_depreciation_rate(self):
for d in self.get("finance_books"):
- d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True),
- d.precision("rate_of_depreciation"))
+ d.rate_of_depreciation = flt(
+ self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
+ )
def make_depreciation_schedule(self, date_of_sale):
- if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.get('schedules'):
+ if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
+ "schedules"
+ ):
self.schedules = []
if not self.available_for_use_date:
@@ -202,7 +234,7 @@ class Asset(AccountsController):
start = self.clear_depreciation_schedule()
- for finance_book in self.get('finance_books'):
+ for finance_book in self.get("finance_books"):
self._make_depreciation_schedule(finance_book, start, date_of_sale)
def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
@@ -211,8 +243,9 @@ class Asset(AccountsController):
value_after_depreciation = self._get_value_after_depreciation(finance_book)
finance_book.value_after_depreciation = value_after_depreciation
- number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
- cint(self.number_of_depreciations_booked)
+ number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
+ self.number_of_depreciations_booked
+ )
has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata:
@@ -220,70 +253,89 @@ class Asset(AccountsController):
skip_row = False
- for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
+ for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
- if skip_row: continue
+ if skip_row:
+ continue
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
- schedule_date = add_months(finance_book.depreciation_start_date,
- n * cint(finance_book.frequency_of_depreciation))
+ schedule_date = add_months(
+ finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
+ )
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
- monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
+ monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_sale:
from_date = self.get_from_date(finance_book.finance_book)
- depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
- from_date, date_of_sale)
+ depreciation_amount, days, months = self.get_pro_rata_amt(
+ finance_book, depreciation_amount, from_date, date_of_sale
+ )
if depreciation_amount > 0:
- self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method,
- finance_book.finance_book, finance_book.idx)
+ self._add_depreciation_row(
+ date_of_sale,
+ depreciation_amount,
+ finance_book.depreciation_method,
+ finance_book.finance_book,
+ finance_book.idx,
+ )
break
# For first row
- if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
- from_date = add_days(self.available_for_use_date, -1) # needed to calc depr amount for available_for_use_date too
- depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
- from_date, finance_book.depreciation_start_date)
+ if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
+ from_date = add_days(
+ self.available_for_use_date, -1
+ ) # needed to calc depr amount for available_for_use_date too
+ depreciation_amount, days, months = self.get_pro_rata_amt(
+ finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date
+ )
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
- monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
+ monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not self.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
- self.to_date = add_months(self.available_for_use_date,
- (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
+ self.to_date = add_months(
+ self.available_for_use_date,
+ (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation),
+ )
depreciation_amount_without_pro_rata = depreciation_amount
- depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
- depreciation_amount, schedule_date, self.to_date)
+ depreciation_amount, days, months = self.get_pro_rata_amt(
+ finance_book, depreciation_amount, schedule_date, self.to_date
+ )
- depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
- depreciation_amount, finance_book.finance_book)
+ depreciation_amount = self.get_adjusted_depreciation_amount(
+ depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
+ )
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
- if not depreciation_amount: continue
- value_after_depreciation -= flt(depreciation_amount,
- self.precision("gross_purchase_amount"))
+ if not depreciation_amount:
+ continue
+ value_after_depreciation -= flt(depreciation_amount, self.precision("gross_purchase_amount"))
# Adjust depreciation amount in the last period based on the expected value after useful life
- if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
- and value_after_depreciation != finance_book.expected_value_after_useful_life)
- or value_after_depreciation < finance_book.expected_value_after_useful_life):
- depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
+ if finance_book.expected_value_after_useful_life and (
+ (
+ n == cint(number_of_pending_depreciations) - 1
+ and value_after_depreciation != finance_book.expected_value_after_useful_life
+ )
+ or value_after_depreciation < finance_book.expected_value_after_useful_life
+ ):
+ depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
@@ -291,12 +343,15 @@ class Asset(AccountsController):
if self.allow_monthly_depreciation:
# month range is 1 to 12
# In pro rata case, for first and last depreciation, month range would be different
- month_range = months \
- if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
+ month_range = (
+ months
+ if (has_pro_rata and n == 0)
+ or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1)
else finance_book.frequency_of_depreciation
+ )
for r in range(month_range):
- if (has_pro_rata and n == 0):
+ if has_pro_rata and n == 0:
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
@@ -308,7 +363,9 @@ class Asset(AccountsController):
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / (month_range - 1)
- elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
+ elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(
+ month_range
+ ) - 1:
# For last entry of monthly depr
date = last_schedule_date
amount = depreciation_amount / month_range
@@ -316,28 +373,40 @@ class Asset(AccountsController):
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
- self._add_depreciation_row(date, amount, finance_book.depreciation_method,
- finance_book.finance_book, finance_book.idx)
+ self._add_depreciation_row(
+ date, amount, finance_book.depreciation_method, finance_book.finance_book, finance_book.idx
+ )
else:
- self._add_depreciation_row(schedule_date, depreciation_amount, finance_book.depreciation_method,
- finance_book.finance_book, finance_book.idx)
+ self._add_depreciation_row(
+ schedule_date,
+ depreciation_amount,
+ finance_book.depreciation_method,
+ finance_book.finance_book,
+ finance_book.idx,
+ )
- def _add_depreciation_row(self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id):
- self.append("schedules", {
- "schedule_date": schedule_date,
- "depreciation_amount": depreciation_amount,
- "depreciation_method": depreciation_method,
- "finance_book": finance_book,
- "finance_book_id": finance_book_id
- })
+ def _add_depreciation_row(
+ self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
+ ):
+ self.append(
+ "schedules",
+ {
+ "schedule_date": schedule_date,
+ "depreciation_amount": depreciation_amount,
+ "depreciation_method": depreciation_method,
+ "finance_book": finance_book,
+ "finance_book_id": finance_book_id,
+ },
+ )
def _get_value_after_depreciation(self, finance_book):
# value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation:
value_after_depreciation = flt(finance_book.value_after_depreciation)
else:
- value_after_depreciation = (flt(self.gross_purchase_amount) -
- flt(self.opening_accumulated_depreciation))
+ value_after_depreciation = flt(self.gross_purchase_amount) - flt(
+ self.opening_accumulated_depreciation
+ )
return value_after_depreciation
@@ -348,7 +417,7 @@ class Asset(AccountsController):
num_of_depreciations_completed = 0
depr_schedule = []
- for schedule in self.get('schedules'):
+ for schedule in self.get("schedules"):
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
if len(start) == (int(schedule.finance_book_id) - 2):
start.append(num_of_depreciations_completed)
@@ -376,14 +445,14 @@ class Asset(AccountsController):
return start
def get_from_date(self, finance_book):
- if not self.get('schedules'):
+ if not self.get("schedules"):
return self.available_for_use_date
if len(self.finance_books) == 1:
return self.schedules[-1].schedule_date
from_date = ""
- for schedule in self.get('schedules'):
+ for schedule in self.get("schedules"):
if schedule.finance_book == finance_book:
from_date = schedule.schedule_date
@@ -412,17 +481,25 @@ class Asset(AccountsController):
return has_pro_rata
def get_modified_available_for_use_date(self, row):
- return add_months(self.available_for_use_date, (self.number_of_depreciations_booked * row.frequency_of_depreciation))
+ return add_months(
+ self.available_for_use_date,
+ (self.number_of_depreciations_booked * row.frequency_of_depreciation),
+ )
def validate_asset_finance_books(self, row):
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
- frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount")
- .format(row.idx), title=_("Invalid Schedule"))
+ frappe.throw(
+ _("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount").format(
+ row.idx
+ ),
+ title=_("Invalid Schedule"),
+ )
if not row.depreciation_start_date:
if not self.available_for_use_date:
- frappe.throw(_("Row {0}: Depreciation Start Date is required")
- .format(row.idx), title=_("Invalid Schedule"))
+ frappe.throw(
+ _("Row {0}: Depreciation Start Date is required").format(row.idx), title=_("Invalid Schedule")
+ )
row.depreciation_start_date = get_last_day(self.available_for_use_date)
if not self.is_existing_asset:
@@ -431,8 +508,11 @@ class Asset(AccountsController):
else:
depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
- frappe.throw(_("Opening Accumulated Depreciation must be less than equal to {0}")
- .format(depreciable_amount))
+ frappe.throw(
+ _("Opening Accumulated Depreciation must be less than equal to {0}").format(
+ depreciable_amount
+ )
+ )
if self.opening_accumulated_depreciation:
if not self.number_of_depreciations_booked:
@@ -441,24 +521,45 @@ class Asset(AccountsController):
self.number_of_depreciations_booked = 0
if flt(row.total_number_of_depreciations) <= cint(self.number_of_depreciations_booked):
- frappe.throw(_("Row {0}: Total Number of Depreciations cannot be less than or equal to Number of Depreciations Booked")
- .format(row.idx), title=_("Invalid Schedule"))
+ frappe.throw(
+ _(
+ "Row {0}: Total Number of Depreciations cannot be less than or equal to Number of Depreciations Booked"
+ ).format(row.idx),
+ title=_("Invalid Schedule"),
+ )
- if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date):
- frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date")
- .format(row.idx))
+ if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(
+ self.purchase_date
+ ):
+ frappe.throw(
+ _("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date").format(
+ row.idx
+ )
+ )
- if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.available_for_use_date):
- frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date")
- .format(row.idx))
+ if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(
+ self.available_for_use_date
+ ):
+ frappe.throw(
+ _(
+ "Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date"
+ ).format(row.idx)
+ )
# to ensure that final accumulated depreciation amount is accurate
- def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book):
+ def get_adjusted_depreciation_amount(
+ self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book
+ ):
if not self.opening_accumulated_depreciation:
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
- if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata:
- depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
+ if (
+ depreciation_amount_for_first_row + depreciation_amount_for_last_row
+ != depreciation_amount_without_pro_rata
+ ):
+ depreciation_amount_for_last_row = (
+ depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
+ )
return depreciation_amount_for_last_row
@@ -474,8 +575,12 @@ class Asset(AccountsController):
if len(self.finance_books) == 1:
return True
- def set_accumulated_depreciation(self, date_of_sale=None, date_of_return=None, ignore_booked_entry = False):
- straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line']
+ def set_accumulated_depreciation(
+ self, date_of_sale=None, date_of_return=None, ignore_booked_entry=False
+ ):
+ straight_line_idx = [
+ d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
+ ]
finance_books = []
for i, d in enumerate(self.get("schedules")):
@@ -491,41 +596,64 @@ class Asset(AccountsController):
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
- if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale and not date_of_return:
- book = self.get('finance_books')[cint(d.finance_book_id) - 1]
- depreciation_amount += flt(value_after_depreciation -
- flt(book.expected_value_after_useful_life), d.precision("depreciation_amount"))
+ if (
+ straight_line_idx
+ and i == max(straight_line_idx) - 1
+ and not date_of_sale
+ and not date_of_return
+ ):
+ book = self.get("finance_books")[cint(d.finance_book_id) - 1]
+ depreciation_amount += flt(
+ value_after_depreciation - flt(book.expected_value_after_useful_life),
+ d.precision("depreciation_amount"),
+ )
d.depreciation_amount = depreciation_amount
accumulated_depreciation += d.depreciation_amount
- d.accumulated_depreciation_amount = flt(accumulated_depreciation,
- d.precision("accumulated_depreciation_amount"))
+ d.accumulated_depreciation_amount = flt(
+ accumulated_depreciation, d.precision("accumulated_depreciation_amount")
+ )
def get_value_after_depreciation(self, idx):
- return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
+ return flt(self.get("finance_books")[cint(idx) - 1].value_after_depreciation)
def validate_expected_value_after_useful_life(self):
- for row in self.get('finance_books'):
- accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
- for d in self.get("schedules") if cint(d.finance_book_id) == row.idx]
+ for row in self.get("finance_books"):
+ accumulated_depreciation_after_full_schedule = [
+ d.accumulated_depreciation_amount
+ for d in self.get("schedules")
+ if cint(d.finance_book_id) == row.idx
+ ]
if accumulated_depreciation_after_full_schedule:
- accumulated_depreciation_after_full_schedule = max(accumulated_depreciation_after_full_schedule)
+ accumulated_depreciation_after_full_schedule = max(
+ accumulated_depreciation_after_full_schedule
+ )
asset_value_after_full_schedule = flt(
- flt(self.gross_purchase_amount) -
- flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount'))
+ flt(self.gross_purchase_amount) - flt(accumulated_depreciation_after_full_schedule),
+ self.precision("gross_purchase_amount"),
+ )
- if (row.expected_value_after_useful_life and
- row.expected_value_after_useful_life < asset_value_after_full_schedule):
- frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}")
- .format(row.idx, asset_value_after_full_schedule))
+ if (
+ row.expected_value_after_useful_life
+ and row.expected_value_after_useful_life < asset_value_after_full_schedule
+ ):
+ frappe.throw(
+ _(
+ "Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}"
+ ).format(row.idx, asset_value_after_full_schedule)
+ )
elif not row.expected_value_after_useful_life:
row.expected_value_after_useful_life = asset_value_after_full_schedule
def validate_cancellation(self):
if self.status in ("In Maintenance", "Out of Order"):
- frappe.throw(_("There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset."))
+ frappe.throw(
+ _(
+ "There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset."
+ )
+ )
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status))
@@ -533,10 +661,13 @@ class Asset(AccountsController):
movements = frappe.db.sql(
"""SELECT asm.name, asm.docstatus
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
- WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""", self.name, as_dict=1)
+ WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""",
+ self.name,
+ as_dict=1,
+ )
for movement in movements:
- movement = frappe.get_doc('Asset Movement', movement.get('name'))
+ movement = frappe.get_doc("Asset Movement", movement.get("name"))
movement.cancel()
def delete_depreciation_entries(self):
@@ -545,17 +676,19 @@ class Asset(AccountsController):
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
d.db_set("journal_entry", None)
- self.db_set("value_after_depreciation",
- (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)))
+ self.db_set(
+ "value_after_depreciation",
+ (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
+ )
def set_status(self, status=None):
- '''Get and update status'''
+ """Get and update status"""
if not status:
status = self.get_status()
self.db_set("status", status)
def get_status(self):
- '''Returns status based on whether it is draft, submitted, scrapped or depreciated'''
+ """Returns status based on whether it is draft, submitted, scrapped or depreciated"""
if self.docstatus == 0:
status = "Draft"
elif self.docstatus == 1:
@@ -572,17 +705,17 @@ class Asset(AccountsController):
if flt(value_after_depreciation) <= expected_value_after_useful_life:
status = "Fully Depreciated"
elif flt(value_after_depreciation) < flt(self.gross_purchase_amount):
- status = 'Partially Depreciated'
+ status = "Partially Depreciated"
elif self.docstatus == 2:
status = "Cancelled"
return status
def get_default_finance_book_idx(self):
- if not self.get('default_finance_book') and self.company:
+ if not self.get("default_finance_book") and self.company:
self.default_finance_book = erpnext.get_default_finance_book(self.company)
- if self.get('default_finance_book'):
- for d in self.get('finance_books'):
+ if self.get("default_finance_book"):
+ for d in self.get("finance_books"):
if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1
@@ -591,7 +724,7 @@ class Asset(AccountsController):
if not purchase_document:
return False
- asset_bought_with_invoice = (purchase_document == self.purchase_invoice)
+ asset_bought_with_invoice = purchase_document == self.purchase_invoice
fixed_asset_account = self.get_fixed_asset_account()
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
@@ -621,13 +754,17 @@ class Asset(AccountsController):
return cwip_booked
def get_purchase_document(self):
- asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
+ asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value(
+ "Purchase Invoice", self.purchase_invoice, "update_stock"
+ )
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
return purchase_document
def get_fixed_asset_account(self):
- fixed_asset_account = get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
+ fixed_asset_account = get_asset_category_account(
+ "fixed_asset_account", None, self.name, None, self.asset_category, self.company
+ )
if not fixed_asset_account:
frappe.throw(
_("Set {0} in asset category {1} for company {2}").format(
@@ -642,7 +779,9 @@ class Asset(AccountsController):
def get_cwip_account(self, cwip_enabled=False):
cwip_account = None
try:
- cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company)
+ cwip_account = get_asset_account(
+ "capital_work_in_progress_account", self.name, self.asset_category, self.company
+ )
except Exception:
# if no cwip account found in category or company and "cwip is enabled" then raise else silently pass
if cwip_enabled:
@@ -656,33 +795,45 @@ class Asset(AccountsController):
purchase_document = self.get_purchase_document()
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
- if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
+ if (
+ purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()
+ ):
- gl_entries.append(self.get_gl_dict({
- "account": cwip_account,
- "against": fixed_asset_account,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "posting_date": self.available_for_use_date,
- "credit": self.purchase_receipt_amount,
- "credit_in_account_currency": self.purchase_receipt_amount,
- "cost_center": self.cost_center
- }, item=self))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": cwip_account,
+ "against": fixed_asset_account,
+ "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
+ "posting_date": self.available_for_use_date,
+ "credit": self.purchase_receipt_amount,
+ "credit_in_account_currency": self.purchase_receipt_amount,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
+ )
- gl_entries.append(self.get_gl_dict({
- "account": fixed_asset_account,
- "against": cwip_account,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "posting_date": self.available_for_use_date,
- "debit": self.purchase_receipt_amount,
- "debit_in_account_currency": self.purchase_receipt_amount,
- "cost_center": self.cost_center
- }, item=self))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": fixed_asset_account,
+ "against": cwip_account,
+ "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
+ "posting_date": self.available_for_use_date,
+ "debit": self.purchase_receipt_amount,
+ "debit_in_account_currency": self.purchase_receipt_amount,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
+ )
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)
- self.db_set('booked_fixed_asset', 1)
+ self.db_set("booked_fixed_asset", 1)
@frappe.whitelist()
def get_depreciation_rate(self, args, on_validate=False):
@@ -691,18 +842,21 @@ class Asset(AccountsController):
float_precision = cint(frappe.db.get_default("float_precision")) or 2
- if args.get("depreciation_method") == 'Double Declining Balance':
+ if args.get("depreciation_method") == "Double Declining Balance":
return 200.0 / args.get("total_number_of_depreciations")
if args.get("depreciation_method") == "Written Down Value":
if args.get("rate_of_depreciation") and on_validate:
return args.get("rate_of_depreciation")
- no_of_years = flt(args.get("total_number_of_depreciations") * flt(args.get("frequency_of_depreciation"))) / 12
+ no_of_years = (
+ flt(args.get("total_number_of_depreciations") * flt(args.get("frequency_of_depreciation")))
+ / 12
+ )
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
# square root of flt(salvage_value) / flt(asset_cost)
- depreciation_rate = math.pow(value, 1.0/flt(no_of_years, 2))
+ depreciation_rate = math.pow(value, 1.0 / flt(no_of_years, 2))
return 100 * (1 - flt(depreciation_rate, float_precision))
@@ -713,93 +867,104 @@ class Asset(AccountsController):
return (depreciation_amount * flt(days)) / flt(total_days), days, months
+
def update_maintenance_status():
- assets = frappe.get_all(
- "Asset", filters={"docstatus": 1, "maintenance_required": 1}
- )
+ assets = frappe.get_all("Asset", filters={"docstatus": 1, "maintenance_required": 1})
for asset in assets:
asset = frappe.get_doc("Asset", asset.name)
if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}):
asset.set_status("Out of Order")
- elif frappe.db.exists("Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}):
+ elif frappe.db.exists(
+ "Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}
+ ):
asset.set_status("In Maintenance")
else:
asset.set_status()
+
def make_post_gl_entry():
- asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting'])
+ asset_categories = frappe.db.get_all("Asset Category", fields=["name", "enable_cwip_accounting"])
for asset_category in asset_categories:
if cint(asset_category.enable_cwip_accounting):
- assets = frappe.db.sql_list(""" select name from `tabAsset`
+ assets = frappe.db.sql_list(
+ """ select name from `tabAsset`
where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0
- and available_for_use_date = %s""", (asset_category.name, nowdate()))
+ and available_for_use_date = %s""",
+ (asset_category.name, nowdate()),
+ )
for asset in assets:
- doc = frappe.get_doc('Asset', asset)
+ doc = frappe.get_doc("Asset", asset)
doc.make_gl_entries()
+
def get_asset_naming_series():
- meta = frappe.get_meta('Asset')
+ meta = frappe.get_meta("Asset")
return meta.get_field("naming_series").options
+
@frappe.whitelist()
def make_sales_invoice(asset, item_code, company, serial_no=None):
si = frappe.new_doc("Sales Invoice")
si.company = company
- si.currency = frappe.get_cached_value('Company', company, "default_currency")
+ si.currency = frappe.get_cached_value("Company", company, "default_currency")
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(company)
- si.append("items", {
- "item_code": item_code,
- "is_fixed_asset": 1,
- "asset": asset,
- "income_account": disposal_account,
- "serial_no": serial_no,
- "cost_center": depreciation_cost_center,
- "qty": 1
- })
+ si.append(
+ "items",
+ {
+ "item_code": item_code,
+ "is_fixed_asset": 1,
+ "asset": asset,
+ "income_account": disposal_account,
+ "serial_no": serial_no,
+ "cost_center": depreciation_cost_center,
+ "qty": 1,
+ },
+ )
si.set_missing_values()
return si
+
@frappe.whitelist()
def create_asset_maintenance(asset, item_code, item_name, asset_category, company):
asset_maintenance = frappe.new_doc("Asset Maintenance")
- asset_maintenance.update({
- "asset_name": asset,
- "company": company,
- "item_code": item_code,
- "item_name": item_name,
- "asset_category": asset_category
- })
+ asset_maintenance.update(
+ {
+ "asset_name": asset,
+ "company": company,
+ "item_code": item_code,
+ "item_name": item_name,
+ "asset_category": asset_category,
+ }
+ )
return asset_maintenance
+
@frappe.whitelist()
def create_asset_repair(asset, asset_name):
asset_repair = frappe.new_doc("Asset Repair")
- asset_repair.update({
- "asset": asset,
- "asset_name": asset_name
- })
+ asset_repair.update({"asset": asset, "asset_name": asset_name})
return asset_repair
+
@frappe.whitelist()
def create_asset_value_adjustment(asset, asset_category, company):
asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
- asset_value_adjustment.update({
- "asset": asset,
- "company": company,
- "asset_category": asset_category
- })
+ asset_value_adjustment.update(
+ {"asset": asset, "company": company, "asset_category": asset_category}
+ )
return asset_value_adjustment
+
@frappe.whitelist()
def transfer_asset(args):
args = json.loads(args)
- if args.get('serial_no'):
- args['quantity'] = len(args.get('serial_no').split('\n'))
+ if args.get("serial_no"):
+ args["quantity"] = len(args.get("serial_no").split("\n"))
movement_entry = frappe.new_doc("Asset Movement")
movement_entry.update(args)
@@ -808,52 +973,73 @@ def transfer_asset(args):
frappe.db.commit()
- frappe.msgprint(_("Asset Movement record {0} created").format("{0}").format(movement_entry.name))
+ frappe.msgprint(
+ _("Asset Movement record {0} created")
+ .format("{0}")
+ .format(movement_entry.name)
+ )
+
@frappe.whitelist()
def get_item_details(item_code, asset_category):
- asset_category_doc = frappe.get_doc('Asset Category', asset_category)
+ asset_category_doc = frappe.get_doc("Asset Category", asset_category)
books = []
for d in asset_category_doc.finance_books:
- books.append({
- 'finance_book': d.finance_book,
- 'depreciation_method': d.depreciation_method,
- 'total_number_of_depreciations': d.total_number_of_depreciations,
- 'frequency_of_depreciation': d.frequency_of_depreciation,
- 'start_date': nowdate()
- })
+ books.append(
+ {
+ "finance_book": d.finance_book,
+ "depreciation_method": d.depreciation_method,
+ "total_number_of_depreciations": d.total_number_of_depreciations,
+ "frequency_of_depreciation": d.frequency_of_depreciation,
+ "start_date": nowdate(),
+ }
+ )
return books
+
def get_asset_account(account_name, asset=None, asset_category=None, company=None):
account = None
if asset:
- account = get_asset_category_account(account_name, asset=asset,
- asset_category = asset_category, company = company)
+ account = get_asset_category_account(
+ account_name, asset=asset, asset_category=asset_category, company=company
+ )
if not asset and not account:
- account = get_asset_category_account(account_name, asset_category = asset_category, company = company)
+ account = get_asset_category_account(
+ account_name, asset_category=asset_category, company=company
+ )
if not account:
- account = frappe.get_cached_value('Company', company, account_name)
+ account = frappe.get_cached_value("Company", company, account_name)
if not account:
if not asset_category:
- frappe.throw(_("Set {0} in company {1}").format(account_name.replace('_', ' ').title(), company))
+ frappe.throw(
+ _("Set {0} in company {1}").format(account_name.replace("_", " ").title(), company)
+ )
else:
- frappe.throw(_("Set {0} in asset category {1} or company {2}")
- .format(account_name.replace('_', ' ').title(), asset_category, company))
+ frappe.throw(
+ _("Set {0} in asset category {1} or company {2}").format(
+ account_name.replace("_", " ").title(), asset_category, company
+ )
+ )
return account
+
@frappe.whitelist()
def make_journal_entry(asset_name):
asset = frappe.get_doc("Asset", asset_name)
- fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \
- get_depreciation_accounts(asset)
+ (
+ fixed_asset_account,
+ accumulated_depreciation_account,
+ depreciation_expense_account,
+ ) = get_depreciation_accounts(asset)
- depreciation_cost_center, depreciation_series = frappe.db.get_value("Company", asset.company,
- ["depreciation_cost_center", "series_for_depreciation_entry"])
+ depreciation_cost_center, depreciation_series = frappe.db.get_value(
+ "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
+ )
depreciation_cost_center = asset.cost_center or depreciation_cost_center
je = frappe.new_doc("Journal Entry")
@@ -862,21 +1048,28 @@ def make_journal_entry(asset_name):
je.company = asset.company
je.remark = "Depreciation Entry against asset {0}".format(asset_name)
- je.append("accounts", {
- "account": depreciation_expense_account,
- "reference_type": "Asset",
- "reference_name": asset.name,
- "cost_center": depreciation_cost_center
- })
+ je.append(
+ "accounts",
+ {
+ "account": depreciation_expense_account,
+ "reference_type": "Asset",
+ "reference_name": asset.name,
+ "cost_center": depreciation_cost_center,
+ },
+ )
- je.append("accounts", {
- "account": accumulated_depreciation_account,
- "reference_type": "Asset",
- "reference_name": asset.name
- })
+ je.append(
+ "accounts",
+ {
+ "account": accumulated_depreciation_account,
+ "reference_type": "Asset",
+ "reference_name": asset.name,
+ },
+ )
return je
+
@frappe.whitelist()
def make_asset_movement(assets, purpose=None):
import json
@@ -885,48 +1078,56 @@ def make_asset_movement(assets, purpose=None):
assets = json.loads(assets)
if len(assets) == 0:
- frappe.throw(_('Atleast one asset has to be selected.'))
+ frappe.throw(_("Atleast one asset has to be selected."))
asset_movement = frappe.new_doc("Asset Movement")
asset_movement.quantity = len(assets)
for asset in assets:
- asset = frappe.get_doc('Asset', asset.get('name'))
- asset_movement.company = asset.get('company')
- asset_movement.append("assets", {
- 'asset': asset.get('name'),
- 'source_location': asset.get('location'),
- 'from_employee': asset.get('custodian')
- })
+ asset = frappe.get_doc("Asset", asset.get("name"))
+ asset_movement.company = asset.get("company")
+ asset_movement.append(
+ "assets",
+ {
+ "asset": asset.get("name"),
+ "source_location": asset.get("location"),
+ "from_employee": asset.get("custodian"),
+ },
+ )
- if asset_movement.get('assets'):
+ if asset_movement.get("assets"):
return asset_movement.as_dict()
+
def is_cwip_accounting_enabled(asset_category):
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
+
def get_total_days(date, frequency):
- period_start_date = add_months(date,
- cint(frequency) * -1)
+ period_start_date = add_months(date, cint(frequency) * -1)
return date_diff(date, period_start_date)
+
@erpnext.allow_regional
def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
- depreciation_amount = (flt(asset.gross_purchase_amount) -
- flt(row.expected_value_after_useful_life)) / flt(row.total_number_of_depreciations)
+ depreciation_amount = (
+ flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
+ ) / flt(row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
- depreciation_amount = (flt(row.value_after_depreciation) -
- flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
+ depreciation_amount = (
+ flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
+ ) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount
+
@frappe.whitelist()
def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name)
@@ -942,34 +1143,61 @@ def split_asset(asset_name, split_qty):
return new_asset
+
def update_existing_asset(asset, remaining_qty):
- remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity)
- opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity)
+ remaining_gross_purchase_amount = flt(
+ (asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity
+ )
+ opening_accumulated_depreciation = flt(
+ (asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity
+ )
- frappe.db.set_value("Asset", asset.name, {
- 'opening_accumulated_depreciation': opening_accumulated_depreciation,
- 'gross_purchase_amount': remaining_gross_purchase_amount,
- 'asset_quantity': remaining_qty
- })
+ frappe.db.set_value(
+ "Asset",
+ asset.name,
+ {
+ "opening_accumulated_depreciation": opening_accumulated_depreciation,
+ "gross_purchase_amount": remaining_gross_purchase_amount,
+ "asset_quantity": remaining_qty,
+ },
+ )
- for finance_book in asset.get('finance_books'):
- value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.asset_quantity)
- expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * remaining_qty)/asset.asset_quantity)
- frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation)
- frappe.db.set_value('Asset Finance Book', finance_book.name, 'expected_value_after_useful_life', expected_value_after_useful_life)
+ for finance_book in asset.get("finance_books"):
+ value_after_depreciation = flt(
+ (finance_book.value_after_depreciation * remaining_qty) / asset.asset_quantity
+ )
+ expected_value_after_useful_life = flt(
+ (finance_book.expected_value_after_useful_life * remaining_qty) / asset.asset_quantity
+ )
+ frappe.db.set_value(
+ "Asset Finance Book", finance_book.name, "value_after_depreciation", value_after_depreciation
+ )
+ frappe.db.set_value(
+ "Asset Finance Book",
+ finance_book.name,
+ "expected_value_after_useful_life",
+ expected_value_after_useful_life,
+ )
accumulated_depreciation = 0
- for term in asset.get('schedules'):
- depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.asset_quantity)
- frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount)
+ for term in asset.get("schedules"):
+ depreciation_amount = flt((term.depreciation_amount * remaining_qty) / asset.asset_quantity)
+ frappe.db.set_value(
+ "Depreciation Schedule", term.name, "depreciation_amount", depreciation_amount
+ )
accumulated_depreciation += depreciation_amount
- frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', accumulated_depreciation)
+ frappe.db.set_value(
+ "Depreciation Schedule", term.name, "accumulated_depreciation_amount", accumulated_depreciation
+ )
+
def create_new_asset_after_split(asset, split_qty):
new_asset = frappe.copy_doc(asset)
new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity)
- opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity)
+ opening_accumulated_depreciation = flt(
+ (asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity
+ )
new_asset.gross_purchase_amount = new_gross_purchase_amount
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
@@ -977,12 +1205,16 @@ def create_new_asset_after_split(asset, split_qty):
new_asset.split_from = asset.name
accumulated_depreciation = 0
- for finance_book in new_asset.get('finance_books'):
- finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.asset_quantity)
- finance_book.expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * split_qty)/asset.asset_quantity)
+ for finance_book in new_asset.get("finance_books"):
+ finance_book.value_after_depreciation = flt(
+ (finance_book.value_after_depreciation * split_qty) / asset.asset_quantity
+ )
+ finance_book.expected_value_after_useful_life = flt(
+ (finance_book.expected_value_after_useful_life * split_qty) / asset.asset_quantity
+ )
- for term in new_asset.get('schedules'):
- depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.asset_quantity)
+ for term in new_asset.get("schedules"):
+ depreciation_amount = flt((term.depreciation_amount * split_qty) / asset.asset_quantity)
term.depreciation_amount = depreciation_amount
accumulated_depreciation += depreciation_amount
term.accumulated_depreciation_amount = accumulated_depreciation
@@ -990,29 +1222,34 @@ def create_new_asset_after_split(asset, split_qty):
new_asset.submit()
new_asset.set_status()
- for term in new_asset.get('schedules'):
+ for term in new_asset.get("schedules"):
# Update references in JV
if term.journal_entry:
- add_reference_in_jv_on_split(term.journal_entry, new_asset.name, asset.name, term.depreciation_amount)
+ add_reference_in_jv_on_split(
+ term.journal_entry, new_asset.name, asset.name, term.depreciation_amount
+ )
return new_asset
-def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount):
- journal_entry = frappe.get_doc('Journal Entry', entry_name)
- entries_to_add = []
- idx = len(journal_entry.get('accounts')) + 1
- for account in journal_entry.get('accounts'):
+def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount):
+ journal_entry = frappe.get_doc("Journal Entry", entry_name)
+ entries_to_add = []
+ idx = len(journal_entry.get("accounts")) + 1
+
+ for account in journal_entry.get("accounts"):
if account.reference_name == old_asset_name:
entries_to_add.append(frappe.copy_doc(account).as_dict())
if account.credit:
account.credit = account.credit - depreciation_amount
- account.credit_in_account_currency = account.credit_in_account_currency - \
- account.exchange_rate * depreciation_amount
+ account.credit_in_account_currency = (
+ account.credit_in_account_currency - account.exchange_rate * depreciation_amount
+ )
elif account.debit:
account.debit = account.debit - depreciation_amount
- account.debit_in_account_currency = account.debit_in_account_currency - \
- account.exchange_rate * depreciation_amount
+ account.debit_in_account_currency = (
+ account.debit_in_account_currency - account.exchange_rate * depreciation_amount
+ )
for entry in entries_to_add:
entry.reference_name = new_asset_name
@@ -1026,7 +1263,7 @@ def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, dep
entry.idx = idx
idx += 1
- journal_entry.append('accounts', entry)
+ journal_entry.append("accounts", entry)
journal_entry.flags.ignore_validate_update_after_submit = True
journal_entry.save()
@@ -1035,4 +1272,4 @@ def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, dep
journal_entry.docstatus = 2
journal_entry.make_gl_entries(1)
journal_entry.docstatus = 1
- journal_entry.make_gl_entries()
\ No newline at end of file
+ journal_entry.make_gl_entries()
diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py
index c81b611a41..9a45bcb0f9 100644
--- a/erpnext/assets/doctype/asset/asset_dashboard.py
+++ b/erpnext/assets/doctype/asset/asset_dashboard.py
@@ -3,13 +3,6 @@ from frappe import _
def get_data():
return {
- 'non_standard_fieldnames': {
- 'Asset Movement': 'asset'
- },
- 'transactions': [
- {
- 'label': _('Movement'),
- 'items': ['Asset Movement']
- }
- ]
+ "non_standard_fieldnames": {"Asset Movement": "asset"},
+ "transactions": [{"label": _("Movement"), "items": ["Asset Movement"]}],
}
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 6e04242210..3f7e945994 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -13,7 +13,9 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled
- if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")):
+ if not cint(
+ frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
+ ):
return
if not date:
@@ -22,26 +24,35 @@ def post_depreciation_entries(date=None):
make_depreciation_entry(asset, date)
frappe.db.commit()
+
def get_depreciable_assets(date):
- return frappe.db.sql_list("""select distinct a.name
+ return frappe.db.sql_list(
+ """select distinct a.name
from tabAsset a, `tabDepreciation Schedule` ds
where a.name = ds.parent and a.docstatus=1 and ds.schedule_date<=%s and a.calculate_depreciation = 1
and a.status in ('Submitted', 'Partially Depreciated')
- and ifnull(ds.journal_entry, '')=''""", date)
+ and ifnull(ds.journal_entry, '')=''""",
+ date,
+ )
+
@frappe.whitelist()
def make_depreciation_entry(asset_name, date=None):
- frappe.has_permission('Journal Entry', throw=True)
+ frappe.has_permission("Journal Entry", throw=True)
if not date:
date = today()
asset = frappe.get_doc("Asset", asset_name)
- fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \
- get_depreciation_accounts(asset)
+ (
+ fixed_asset_account,
+ accumulated_depreciation_account,
+ depreciation_expense_account,
+ ) = get_depreciation_accounts(asset)
- depreciation_cost_center, depreciation_series = frappe.get_cached_value('Company', asset.company,
- ["depreciation_cost_center", "series_for_depreciation_entry"])
+ depreciation_cost_center, depreciation_series = frappe.get_cached_value(
+ "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
+ )
depreciation_cost_center = asset.cost_center or depreciation_cost_center
@@ -57,14 +68,16 @@ def make_depreciation_entry(asset_name, date=None):
je.finance_book = d.finance_book
je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount)
- credit_account, debit_account = get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account)
+ credit_account, debit_account = get_credit_and_debit_accounts(
+ accumulated_depreciation_account, depreciation_expense_account
+ )
credit_entry = {
"account": credit_account,
"credit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
"reference_name": asset.name,
- "cost_center": depreciation_cost_center
+ "cost_center": depreciation_cost_center,
}
debit_entry = {
@@ -72,19 +85,25 @@ def make_depreciation_entry(asset_name, date=None):
"debit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
"reference_name": asset.name,
- "cost_center": depreciation_cost_center
+ "cost_center": depreciation_cost_center,
}
for dimension in accounting_dimensions:
- if (asset.get(dimension['fieldname']) or dimension.get('mandatory_for_bs')):
- credit_entry.update({
- dimension['fieldname']: asset.get(dimension['fieldname']) or dimension.get('default_dimension')
- })
+ if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_bs"):
+ credit_entry.update(
+ {
+ dimension["fieldname"]: asset.get(dimension["fieldname"])
+ or dimension.get("default_dimension")
+ }
+ )
- if (asset.get(dimension['fieldname']) or dimension.get('mandatory_for_pl')):
- debit_entry.update({
- dimension['fieldname']: asset.get(dimension['fieldname']) or dimension.get('default_dimension')
- })
+ if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_pl"):
+ debit_entry.update(
+ {
+ dimension["fieldname"]: asset.get(dimension["fieldname"])
+ or dimension.get("default_dimension")
+ }
+ )
je.append("accounts", credit_entry)
@@ -98,7 +117,7 @@ def make_depreciation_entry(asset_name, date=None):
d.db_set("journal_entry", je.name)
idx = cint(d.finance_book_id)
- finance_books = asset.get('finance_books')[idx - 1]
+ finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
@@ -106,13 +125,20 @@ def make_depreciation_entry(asset_name, date=None):
return asset
+
def get_depreciation_accounts(asset):
fixed_asset_account = accumulated_depreciation_account = depreciation_expense_account = None
- accounts = frappe.db.get_value("Asset Category Account",
- filters={'parent': asset.asset_category, 'company_name': asset.company},
- fieldname = ['fixed_asset_account', 'accumulated_depreciation_account',
- 'depreciation_expense_account'], as_dict=1)
+ accounts = frappe.db.get_value(
+ "Asset Category Account",
+ filters={"parent": asset.asset_category, "company_name": asset.company},
+ fieldname=[
+ "fixed_asset_account",
+ "accumulated_depreciation_account",
+ "depreciation_expense_account",
+ ],
+ as_dict=1,
+ )
if accounts:
fixed_asset_account = accounts.fixed_asset_account
@@ -120,20 +146,29 @@ def get_depreciation_accounts(asset):
depreciation_expense_account = accounts.depreciation_expense_account
if not accumulated_depreciation_account or not depreciation_expense_account:
- accounts = frappe.get_cached_value('Company', asset.company,
- ["accumulated_depreciation_account", "depreciation_expense_account"])
+ accounts = frappe.get_cached_value(
+ "Company", asset.company, ["accumulated_depreciation_account", "depreciation_expense_account"]
+ )
if not accumulated_depreciation_account:
accumulated_depreciation_account = accounts[0]
if not depreciation_expense_account:
depreciation_expense_account = accounts[1]
- if not fixed_asset_account or not accumulated_depreciation_account or not depreciation_expense_account:
- frappe.throw(_("Please set Depreciation related Accounts in Asset Category {0} or Company {1}")
- .format(asset.asset_category, asset.company))
+ if (
+ not fixed_asset_account
+ or not accumulated_depreciation_account
+ or not depreciation_expense_account
+ ):
+ frappe.throw(
+ _("Please set Depreciation related Accounts in Asset Category {0} or Company {1}").format(
+ asset.asset_category, asset.company
+ )
+ )
return fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account
+
def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account):
root_type = frappe.get_value("Account", depreciation_expense_account, "root_type")
@@ -148,6 +183,7 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
return credit_account, debit_account
+
@frappe.whitelist()
def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
@@ -155,9 +191,13 @@ def scrap_asset(asset_name):
if asset.docstatus != 1:
frappe.throw(_("Asset {0} must be submitted").format(asset.name))
elif asset.status in ("Cancelled", "Sold", "Scrapped"):
- frappe.throw(_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status))
+ frappe.throw(
+ _("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
+ )
- depreciation_series = frappe.get_cached_value('Company', asset.company, "series_for_depreciation_entry")
+ depreciation_series = frappe.get_cached_value(
+ "Company", asset.company, "series_for_depreciation_entry"
+ )
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Journal Entry"
@@ -167,10 +207,7 @@ def scrap_asset(asset_name):
je.remark = "Scrap Entry for asset {0}".format(asset_name)
for entry in get_gl_entries_on_asset_disposal(asset):
- entry.update({
- "reference_type": "Asset",
- "reference_name": asset_name
- })
+ entry.update({"reference_type": "Asset", "reference_name": asset_name})
je.append("accounts", entry)
je.flags.ignore_permissions = True
@@ -182,6 +219,7 @@ def scrap_asset(asset_name):
frappe.msgprint(_("Asset scrapped via Journal Entry {0}").format(je.name))
+
@frappe.whitelist()
def restore_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
@@ -195,23 +233,31 @@ def restore_asset(asset_name):
asset.set_status()
+
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
- fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
- get_asset_details(asset, finance_book)
+ (
+ fixed_asset_account,
+ asset,
+ depreciation_cost_center,
+ accumulated_depr_account,
+ accumulated_depr_amount,
+ disposal_account,
+ value_after_depreciation,
+ ) = get_asset_details(asset, finance_book)
gl_entries = [
{
"account": fixed_asset_account,
"debit_in_account_currency": asset.gross_purchase_amount,
"debit": asset.gross_purchase_amount,
- "cost_center": depreciation_cost_center
+ "cost_center": depreciation_cost_center,
},
{
"account": accumulated_depr_account,
"credit_in_account_currency": accumulated_depr_amount,
"credit": accumulated_depr_amount,
- "cost_center": depreciation_cost_center
- }
+ "cost_center": depreciation_cost_center,
+ },
]
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
@@ -220,23 +266,31 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
return gl_entries
+
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
- fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
- get_asset_details(asset, finance_book)
+ (
+ fixed_asset_account,
+ asset,
+ depreciation_cost_center,
+ accumulated_depr_account,
+ accumulated_depr_amount,
+ disposal_account,
+ value_after_depreciation,
+ ) = get_asset_details(asset, finance_book)
gl_entries = [
{
"account": fixed_asset_account,
"credit_in_account_currency": asset.gross_purchase_amount,
"credit": asset.gross_purchase_amount,
- "cost_center": depreciation_cost_center
+ "cost_center": depreciation_cost_center,
},
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
- "cost_center": depreciation_cost_center
- }
+ "cost_center": depreciation_cost_center,
+ },
]
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
@@ -245,8 +299,11 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
return gl_entries
+
def get_asset_details(asset, finance_book=None):
- fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
+ fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(
+ asset
+ )
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
@@ -257,28 +314,46 @@ def get_asset_details(asset, finance_book=None):
idx = d.idx
break
- value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation
- if asset.calculate_depreciation else asset.value_after_depreciation)
+ value_after_depreciation = (
+ asset.finance_books[idx - 1].value_after_depreciation
+ if asset.calculate_depreciation
+ else asset.value_after_depreciation
+ )
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
- return fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation
+ return (
+ fixed_asset_account,
+ asset,
+ depreciation_cost_center,
+ accumulated_depr_account,
+ accumulated_depr_amount,
+ disposal_account,
+ value_after_depreciation,
+ )
+
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
debit_or_credit = "debit" if profit_amount < 0 else "credit"
- gl_entries.append({
- "account": disposal_account,
- "cost_center": depreciation_cost_center,
- debit_or_credit: abs(profit_amount),
- debit_or_credit + "_in_account_currency": abs(profit_amount)
- })
+ gl_entries.append(
+ {
+ "account": disposal_account,
+ "cost_center": depreciation_cost_center,
+ debit_or_credit: abs(profit_amount),
+ debit_or_credit + "_in_account_currency": abs(profit_amount),
+ }
+ )
+
@frappe.whitelist()
def get_disposal_account_and_cost_center(company):
- disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,
- ["disposal_account", "depreciation_cost_center"])
+ disposal_account, depreciation_cost_center = frappe.get_cached_value(
+ "Company", company, ["disposal_account", "depreciation_cost_center"]
+ )
if not disposal_account:
- frappe.throw(_("Please set 'Gain/Loss Account on Asset Disposal' in Company {0}").format(company))
+ frappe.throw(
+ _("Please set 'Gain/Loss Account on Asset Disposal' in Company {0}").format(company)
+ )
if not depreciation_cost_center:
frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company))
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index ffd1065efc..f681b3480c 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -32,6 +32,7 @@ class AssetSetup(unittest.TestCase):
def tearDownClass(cls):
frappe.db.rollback()
+
class TestAsset(AssetSetup):
def test_asset_category_is_fetched(self):
"""Tests if the Item's Asset Category value is assigned to the Asset, if the field is empty."""
@@ -52,7 +53,7 @@ class TestAsset(AssetSetup):
"""Tests if either PI or PR is present if CWIP is enabled and is_existing_asset=0."""
asset = create_asset(item_code="Macbook Pro", do_not_save=1)
- asset.is_existing_asset=0
+ asset.is_existing_asset = 0
self.assertRaises(frappe.ValidationError, asset.save)
@@ -86,11 +87,12 @@ class TestAsset(AssetSetup):
self.assertRaises(frappe.ValidationError, asset.save)
def test_purchase_asset(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+ )
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset = frappe.get_doc("Asset", asset_name)
asset.calculate_depreciation = 1
month_end_date = get_last_day(nowdate())
@@ -98,13 +100,16 @@ class TestAsset(AssetSetup):
asset.available_for_use_date = purchase_date
asset.purchase_date = purchase_date
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": month_end_date
- })
+ asset.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 10000,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ "depreciation_start_date": month_end_date,
+ },
+ )
asset.submit()
pi = make_invoice(pr.name)
@@ -119,12 +124,15 @@ class TestAsset(AssetSetup):
expected_gle = (
("Asset Received But Not Billed - _TC", 100000.0, 0.0),
- ("Creditors - _TC", 0.0, 100000.0)
+ ("Creditors - _TC", 0.0, 100000.0),
)
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no = %s
- order by account""", pi.name)
+ order by account""",
+ pi.name,
+ )
self.assertEqual(gle, expected_gle)
pi.cancel()
@@ -138,8 +146,8 @@ class TestAsset(AssetSetup):
create_fixed_asset_item("Rack", is_grouped_asset=1)
pr = make_purchase_receipt(item_code="Rack", qty=3, rate=100000.0, location="Test Location")
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset = frappe.get_doc("Asset", asset_name)
self.assertEqual(asset.asset_quantity, 3)
asset.calculate_depreciation = 1
@@ -148,40 +156,39 @@ class TestAsset(AssetSetup):
asset.available_for_use_date = purchase_date
asset.purchase_date = purchase_date
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": month_end_date
- })
+ asset.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 10000,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ "depreciation_start_date": month_end_date,
+ },
+ )
asset.submit()
def test_is_fixed_asset_set(self):
- asset = create_asset(is_existing_asset = 1)
- doc = frappe.new_doc('Purchase Invoice')
- doc.supplier = '_Test Supplier'
- doc.append('items', {
- 'item_code': 'Macbook Pro',
- 'qty': 1,
- 'asset': asset.name
- })
+ asset = create_asset(is_existing_asset=1)
+ doc = frappe.new_doc("Purchase Invoice")
+ doc.supplier = "_Test Supplier"
+ doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
doc.set_missing_values()
self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_scrap_asset(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = '2020-01-01',
- purchase_date = '2020-01-01',
- expected_value_after_useful_life = 10000,
- total_number_of_depreciations = 10,
- frequency_of_depreciation = 1,
- submit = 1
+ calculate_depreciation=1,
+ available_for_use_date="2020-01-01",
+ purchase_date="2020-01-01",
+ expected_value_after_useful_life=10000,
+ total_number_of_depreciations=10,
+ frequency_of_depreciation=1,
+ submit=1,
)
- post_depreciation_entries(date=add_months('2020-01-01', 4))
+ post_depreciation_entries(date=add_months("2020-01-01", 4))
scrap_asset(asset.name)
@@ -192,12 +199,15 @@ class TestAsset(AssetSetup):
expected_gle = (
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
- ("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
+ ("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
)
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_no = %s
- order by account""", asset.journal_entry_for_scrap)
+ order by account""",
+ asset.journal_entry_for_scrap,
+ )
self.assertEqual(gle, expected_gle)
restore_asset(asset.name)
@@ -208,14 +218,14 @@ class TestAsset(AssetSetup):
def test_gle_made_by_asset_sale(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = '2020-06-06',
- purchase_date = '2020-01-01',
- expected_value_after_useful_life = 10000,
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 10,
- depreciation_start_date = '2020-12-31',
- submit = 1
+ calculate_depreciation=1,
+ available_for_use_date="2020-06-06",
+ purchase_date="2020-01-01",
+ expected_value_after_useful_life=10000,
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=10,
+ depreciation_start_date="2020-12-31",
+ submit=1,
)
post_depreciation_entries(date="2021-01-01")
@@ -233,12 +243,15 @@ class TestAsset(AssetSetup):
("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
- ("Debtors - _TC", 25000.0, 0.0)
+ ("Debtors - _TC", 25000.0, 0.0),
)
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no = %s
- order by account""", si.name)
+ order by account""",
+ si.name,
+ )
self.assertEqual(gle, expected_gle)
@@ -247,18 +260,18 @@ class TestAsset(AssetSetup):
def test_asset_splitting(self):
asset = create_asset(
- calculate_depreciation = 1,
+ calculate_depreciation=1,
asset_quantity=10,
- available_for_use_date = '2020-01-01',
- purchase_date = '2020-01-01',
- expected_value_after_useful_life = 0,
- total_number_of_depreciations = 6,
- number_of_depreciations_booked = 1,
- frequency_of_depreciation = 10,
- depreciation_start_date = '2021-01-01',
+ available_for_use_date="2020-01-01",
+ purchase_date="2020-01-01",
+ expected_value_after_useful_life=0,
+ total_number_of_depreciations=6,
+ number_of_depreciations_booked=1,
+ frequency_of_depreciation=10,
+ depreciation_start_date="2021-01-01",
opening_accumulated_depreciation=20000,
gross_purchase_amount=120000,
- submit = 1
+ submit=1,
)
post_depreciation_entries(date="2021-01-01")
@@ -285,7 +298,7 @@ class TestAsset(AssetSetup):
journal_entry = asset.schedules[0].journal_entry
- jv = frappe.get_doc('Journal Entry', journal_entry)
+ jv = frappe.get_doc("Journal Entry", journal_entry)
self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000)
self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000)
self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000)
@@ -297,45 +310,56 @@ class TestAsset(AssetSetup):
self.assertEqual(jv.accounts[3].reference_name, new_asset.name)
def test_expense_head(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=2, rate=200000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location"
+ )
doc = make_invoice(pr.name)
- self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
+ self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
# CWIP: Capital Work In Progress
def test_cwip_accounting(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=5000, do_not_submit=True, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
+ )
- pr.set('taxes', [{
- 'category': 'Total',
- 'add_deduct_tax': 'Add',
- 'charge_type': 'On Net Total',
- 'account_head': '_Test Account Service Tax - _TC',
- 'description': '_Test Account Service Tax',
- 'cost_center': 'Main - _TC',
- 'rate': 5.0
- }, {
- 'category': 'Valuation and Total',
- 'add_deduct_tax': 'Add',
- 'charge_type': 'On Net Total',
- 'account_head': '_Test Account Shipping Charges - _TC',
- 'description': '_Test Account Shipping Charges',
- 'cost_center': 'Main - _TC',
- 'rate': 5.0
- }])
+ pr.set(
+ "taxes",
+ [
+ {
+ "category": "Total",
+ "add_deduct_tax": "Add",
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "description": "_Test Account Service Tax",
+ "cost_center": "Main - _TC",
+ "rate": 5.0,
+ },
+ {
+ "category": "Valuation and Total",
+ "add_deduct_tax": "Add",
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Shipping Charges - _TC",
+ "description": "_Test Account Shipping Charges",
+ "cost_center": "Main - _TC",
+ "rate": 5.0,
+ },
+ ],
+ )
pr.submit()
expected_gle = (
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
- ("CWIP Account - _TC", 5250.0, 0.0)
+ ("CWIP Account - _TC", 5250.0, 0.0),
)
- pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ pr_gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no = %s
- order by account""", pr.name)
+ order by account""",
+ pr.name,
+ )
self.assertEqual(pr_gle, expected_gle)
@@ -350,45 +374,53 @@ class TestAsset(AssetSetup):
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
)
- pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ pi_gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no = %s
- order by account""", pi.name)
+ order by account""",
+ pi.name,
+ )
self.assertEqual(pi_gle, expected_gle)
- asset = frappe.db.get_value('Asset',
- {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
+ asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name")
- asset_doc = frappe.get_doc('Asset', asset)
+ asset_doc = frappe.get_doc("Asset", asset)
month_end_date = get_last_day(nowdate())
- asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
+ asset_doc.available_for_use_date = (
+ nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
+ )
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
- asset_doc.append("finance_books", {
- "expected_value_after_useful_life": 200,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": month_end_date
- })
+ asset_doc.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 200,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ "depreciation_start_date": month_end_date,
+ },
+ )
asset_doc.submit()
- expected_gle = (
- ("_Test Fixed Asset - _TC", 5250.0, 0.0),
- ("CWIP Account - _TC", 0.0, 5250.0)
- )
+ expected_gle = (("_Test Fixed Asset - _TC", 5250.0, 0.0), ("CWIP Account - _TC", 0.0, 5250.0))
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where voucher_type='Asset' and voucher_no = %s
- order by account""", asset_doc.name)
-
+ order by account""",
+ asset_doc.name,
+ )
self.assertEqual(gle, expected_gle)
def test_asset_cwip_toggling_cases(self):
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
- name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
+ name = frappe.db.get_value(
+ "Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"]
+ )
cwip_acc = "CWIP Account - _TC"
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
@@ -396,197 +428,231 @@ class TestAsset(AssetSetup):
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "")
# case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set
- pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
- asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
- asset_doc = frappe.get_doc('Asset', asset)
+ pi = make_purchase_invoice(
+ item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1
+ )
+ asset = frappe.db.get_value("Asset", {"purchase_invoice": pi.name, "docstatus": 0}, "name")
+ asset_doc = frappe.get_doc("Asset", asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
- gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
+ asset_doc.name,
+ )
self.assertFalse(gle)
# case 1 -- PR with cwip disabled, Asset with cwip enabled
- pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location"
+ )
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
- asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
- asset_doc = frappe.get_doc('Asset', asset)
+ asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name")
+ asset_doc = frappe.get_doc("Asset", asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
- gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
+ asset_doc.name,
+ )
self.assertFalse(gle)
# case 2 -- PR with cwip enabled, Asset with cwip disabled
- pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location"
+ )
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
- asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
- asset_doc = frappe.get_doc('Asset', asset)
+ asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name")
+ asset_doc = frappe.get_doc("Asset", asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
- gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
+ asset_doc.name,
+ )
self.assertTrue(gle)
# case 3 -- PI with cwip disabled, Asset with cwip enabled
- pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
+ pi = make_purchase_invoice(
+ item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1
+ )
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
- asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
- asset_doc = frappe.get_doc('Asset', asset)
+ asset = frappe.db.get_value("Asset", {"purchase_invoice": pi.name, "docstatus": 0}, "name")
+ asset_doc = frappe.get_doc("Asset", asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
- gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
+ asset_doc.name,
+ )
self.assertFalse(gle)
# case 4 -- PI with cwip enabled, Asset with cwip disabled
- pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
+ pi = make_purchase_invoice(
+ item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1
+ )
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
- asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
- asset_doc = frappe.get_doc('Asset', asset)
+ asset = frappe.db.get_value("Asset", {"purchase_invoice": pi.name, "docstatus": 0}, "name")
+ asset_doc = frappe.get_doc("Asset", asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
- gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
+ gle = frappe.db.sql(
+ """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
+ asset_doc.name,
+ )
self.assertTrue(gle)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
+
class TestDepreciationMethods(AssetSetup):
def test_schedule_for_straight_line_method(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-01-01",
- purchase_date = "2030-01-01",
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2030-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-01-01",
+ purchase_date="2030-01-01",
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2030-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [
["2030-12-31", 30000.00, 30000.00],
["2031-12-31", 30000.00, 60000.00],
- ["2032-12-31", 30000.00, 90000.00]
+ ["2032-12-31", 30000.00, 90000.00],
]
- schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
+ schedules = [
+ [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")
+ ]
self.assertEqual(schedules, expected_schedules)
def test_schedule_for_straight_line_method_for_existing_asset(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-06-06",
- is_existing_asset = 1,
- number_of_depreciations_booked = 2,
- opening_accumulated_depreciation = 47095.89,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2032-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-06-06",
+ is_existing_asset=1,
+ number_of_depreciations_booked=2,
+ opening_accumulated_depreciation=47095.89,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2032-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
self.assertEqual(asset.status, "Draft")
- expected_schedules = [
- ["2032-12-31", 30000.0, 77095.89],
- ["2033-06-06", 12904.11, 90000.0]
+ expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
+ schedules = [
+ [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")
]
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
self.assertEqual(schedules, expected_schedules)
def test_schedule_for_double_declining_method(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-01-01",
- purchase_date = "2030-01-01",
- depreciation_method = "Double Declining Balance",
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2030-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-01-01",
+ purchase_date="2030-01-01",
+ depreciation_method="Double Declining Balance",
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2030-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [
- ['2030-12-31', 66667.00, 66667.00],
- ['2031-12-31', 22222.11, 88889.11],
- ['2032-12-31', 1110.89, 90000.0]
+ ["2030-12-31", 66667.00, 66667.00],
+ ["2031-12-31", 22222.11, 88889.11],
+ ["2032-12-31", 1110.89, 90000.0],
]
- schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
+ schedules = [
+ [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")
+ ]
self.assertEqual(schedules, expected_schedules)
def test_schedule_for_double_declining_method_for_existing_asset(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-01-01",
- is_existing_asset = 1,
- depreciation_method = "Double Declining Balance",
- number_of_depreciations_booked = 1,
- opening_accumulated_depreciation = 50000,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2030-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-01-01",
+ is_existing_asset=1,
+ depreciation_method="Double Declining Balance",
+ number_of_depreciations_booked=1,
+ opening_accumulated_depreciation=50000,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2030-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
self.assertEqual(asset.status, "Draft")
- expected_schedules = [
- ["2030-12-31", 33333.50, 83333.50],
- ["2031-12-31", 6666.50, 90000.0]
- ]
+ expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]]
- schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
- for d in asset.get("schedules")]
+ schedules = [
+ [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+ for d in asset.get("schedules")
+ ]
self.assertEqual(schedules, expected_schedules)
def test_schedule_for_prorated_straight_line_method(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-01-30",
- purchase_date = "2030-01-30",
- depreciation_method = "Straight Line",
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2030-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-01-30",
+ purchase_date="2030-01-30",
+ depreciation_method="Straight Line",
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2030-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
expected_schedules = [
- ['2030-12-31', 27616.44, 27616.44],
- ['2031-12-31', 30000.0, 57616.44],
- ['2032-12-31', 30000.0, 87616.44],
- ['2033-01-30', 2383.56, 90000.0]
+ ["2030-12-31", 27616.44, 27616.44],
+ ["2031-12-31", 30000.0, 57616.44],
+ ["2032-12-31", 30000.0, 87616.44],
+ ["2033-01-30", 2383.56, 90000.0],
]
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
- for d in asset.get("schedules")]
+ schedules = [
+ [
+ cstr(d.schedule_date),
+ flt(d.depreciation_amount, 2),
+ flt(d.accumulated_depreciation_amount, 2),
+ ]
+ for d in asset.get("schedules")
+ ]
self.assertEqual(schedules, expected_schedules)
# WDV: Written Down Value method
def test_depreciation_entry_for_wdv_without_pro_rata(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-01-01",
- purchase_date = "2030-01-01",
- depreciation_method = "Written Down Value",
- expected_value_after_useful_life = 12500,
- depreciation_start_date = "2030-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-01-01",
+ purchase_date="2030-01-01",
+ depreciation_method="Written Down Value",
+ expected_value_after_useful_life=12500,
+ depreciation_start_date="2030-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
@@ -597,35 +663,47 @@ class TestDepreciationMethods(AssetSetup):
["2032-12-31", 12500.0, 87500.0],
]
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
- for d in asset.get("schedules")]
+ schedules = [
+ [
+ cstr(d.schedule_date),
+ flt(d.depreciation_amount, 2),
+ flt(d.accumulated_depreciation_amount, 2),
+ ]
+ for d in asset.get("schedules")
+ ]
self.assertEqual(schedules, expected_schedules)
# WDV: Written Down Value method
def test_pro_rata_depreciation_entry_for_wdv(self):
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-06-06",
- purchase_date = "2030-01-01",
- depreciation_method = "Written Down Value",
- expected_value_after_useful_life = 12500,
- depreciation_start_date = "2030-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-06-06",
+ purchase_date="2030-01-01",
+ depreciation_method="Written Down Value",
+ expected_value_after_useful_life=12500,
+ depreciation_start_date="2030-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
- ['2030-12-31', 28630.14, 28630.14],
- ['2031-12-31', 35684.93, 64315.07],
- ['2032-12-31', 17842.47, 82157.54],
- ['2033-06-06', 5342.46, 87500.0]
+ ["2030-12-31", 28630.14, 28630.14],
+ ["2031-12-31", 35684.93, 64315.07],
+ ["2032-12-31", 17842.47, 82157.54],
+ ["2033-06-06", 5342.46, 87500.0],
]
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
- for d in asset.get("schedules")]
+ schedules = [
+ [
+ cstr(d.schedule_date),
+ flt(d.depreciation_amount, 2),
+ flt(d.accumulated_depreciation_amount, 2),
+ ]
+ for d in asset.get("schedules")
+ ]
self.assertEqual(schedules, expected_schedules)
@@ -637,18 +715,18 @@ class TestDepreciationMethods(AssetSetup):
finance_book = frappe.new_doc("Finance Book")
finance_book.finance_book_name = "Income Tax"
finance_book.for_income_tax = 1
- finance_book.insert(ignore_if_duplicate = True)
+ finance_book.insert(ignore_if_duplicate=True)
asset = create_asset(
- calculate_depreciation = 1,
- available_for_use_date = "2030-07-12",
- purchase_date = "2030-01-01",
- finance_book = finance_book.name,
- depreciation_method = "Written Down Value",
- expected_value_after_useful_life = 12500,
- depreciation_start_date = "2030-12-31",
- total_number_of_depreciations = 3,
- frequency_of_depreciation = 12
+ calculate_depreciation=1,
+ available_for_use_date="2030-07-12",
+ purchase_date="2030-01-01",
+ finance_book=finance_book.name,
+ depreciation_method="Written Down Value",
+ expected_value_after_useful_life=12500,
+ depreciation_start_date="2030-12-31",
+ total_number_of_depreciations=3,
+ frequency_of_depreciation=12,
)
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
@@ -657,33 +735,40 @@ class TestDepreciationMethods(AssetSetup):
["2030-12-31", 11849.32, 11849.32],
["2031-12-31", 44075.34, 55924.66],
["2032-12-31", 22037.67, 77962.33],
- ["2033-07-12", 9537.67, 87500.0]
+ ["2033-07-12", 9537.67, 87500.0],
]
- schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
- for d in asset.get("schedules")]
+ schedules = [
+ [
+ cstr(d.schedule_date),
+ flt(d.depreciation_amount, 2),
+ flt(d.accumulated_depreciation_amount, 2),
+ ]
+ for d in asset.get("schedules")
+ ]
self.assertEqual(schedules, expected_schedules)
# reset indian company
frappe.flags.company = company_flag
+
class TestDepreciationBasics(AssetSetup):
def test_depreciation_without_pro_rata(self):
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = getdate("2019-12-31"),
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = getdate("2020-12-31"),
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date=getdate("2019-12-31"),
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date=getdate("2020-12-31"),
+ submit=1,
)
expected_values = [
["2020-12-31", 30000, 30000],
["2021-12-31", 30000, 60000],
- ["2022-12-31", 30000, 90000]
+ ["2022-12-31", 30000, 90000],
]
for i, schedule in enumerate(asset.schedules):
@@ -693,20 +778,20 @@ class TestDepreciationBasics(AssetSetup):
def test_depreciation_with_pro_rata(self):
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = getdate("2020-01-01"),
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = getdate("2020-07-01"),
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date=getdate("2020-01-01"),
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date=getdate("2020-07-01"),
+ submit=1,
)
expected_values = [
["2020-07-01", 15000, 15000],
["2021-07-01", 30000, 45000],
["2022-07-01", 30000, 75000],
- ["2023-01-01", 15000, 90000]
+ ["2023-01-01", 15000, 90000],
]
for i, schedule in enumerate(asset.schedules):
@@ -719,19 +804,19 @@ class TestDepreciationBasics(AssetSetup):
from erpnext.assets.doctype.asset.asset import get_depreciation_amount
- asset = create_asset(
- item_code = "Macbook Pro",
- available_for_use_date = "2019-12-31"
- )
+ asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31")
asset.calculate_depreciation = 1
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 12,
- "total_number_of_depreciations": 3,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-12-31"
- })
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31",
+ },
+ )
depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
self.assertEqual(depreciation_amount, 30000)
@@ -740,21 +825,17 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if make_depreciation_schedule() returns the right values."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- depreciation_method = "Straight Line",
- frequency_of_depreciation = 12,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2020-12-31"
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ depreciation_method="Straight Line",
+ frequency_of_depreciation=12,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2020-12-31",
)
- expected_values = [
- ['2020-12-31', 30000.0],
- ['2021-12-31', 30000.0],
- ['2022-12-31', 30000.0]
- ]
+ expected_values = [["2020-12-31", 30000.0], ["2021-12-31", 30000.0], ["2022-12-31", 30000.0]]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(expected_values[i][0], schedule.schedule_date)
@@ -764,14 +845,14 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if set_accumulated_depreciation() returns the right values."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- depreciation_method = "Straight Line",
- frequency_of_depreciation = 12,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2020-12-31"
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ depreciation_method="Straight Line",
+ frequency_of_depreciation=12,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2020-12-31",
)
expected_values = [30000.0, 60000.0, 90000.0]
@@ -782,32 +863,34 @@ class TestDepreciationBasics(AssetSetup):
def test_check_is_pro_rata(self):
"""Tests if check_is_pro_rata() returns the right value(i.e. checks if has_pro_rata is accurate)."""
- asset = create_asset(
- item_code = "Macbook Pro",
- available_for_use_date = "2019-12-31",
- do_not_save = 1
- )
+ asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1)
asset.calculate_depreciation = 1
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 12,
- "total_number_of_depreciations": 3,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-12-31"
- })
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31",
+ },
+ )
has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
self.assertFalse(has_pro_rata)
asset.finance_books = []
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 12,
- "total_number_of_depreciations": 3,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-07-01"
- })
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-07-01",
+ },
+ )
has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
self.assertTrue(has_pro_rata)
@@ -816,13 +899,13 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000)."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 110000,
- depreciation_start_date = "2020-07-01",
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=110000,
+ depreciation_start_date="2020-07-01",
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
@@ -831,11 +914,11 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if an error is raised when neither depreciation_start_date nor available_for_use_date are specified."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 110000,
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=110000,
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
@@ -844,14 +927,14 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if an error is raised when opening_accumulated_depreciation > (gross_purchase_amount - expected_value_after_useful_life)."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2020-07-01",
- opening_accumulated_depreciation = 100000,
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2020-07-01",
+ opening_accumulated_depreciation=100000,
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
@@ -860,14 +943,14 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2020-07-01",
- opening_accumulated_depreciation = 10000,
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2020-07-01",
+ opening_accumulated_depreciation=10000,
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
@@ -877,56 +960,56 @@ class TestDepreciationBasics(AssetSetup):
# number_of_depreciations_booked > total_number_of_depreciations
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2020-07-01",
- opening_accumulated_depreciation = 10000,
- number_of_depreciations_booked = 5,
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2020-07-01",
+ opening_accumulated_depreciation=10000,
+ number_of_depreciations_booked=5,
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
# number_of_depreciations_booked = total_number_of_depreciations
asset_2 = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- total_number_of_depreciations = 5,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2020-07-01",
- opening_accumulated_depreciation = 10000,
- number_of_depreciations_booked = 5,
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ total_number_of_depreciations=5,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2020-07-01",
+ opening_accumulated_depreciation=10000,
+ number_of_depreciations_booked=5,
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset_2.save)
def test_depreciation_start_date_is_before_purchase_date(self):
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2014-07-01",
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2014-07-01",
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
def test_depreciation_start_date_is_before_available_for_use_date(self):
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- depreciation_start_date = "2018-07-01",
- do_not_save = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ depreciation_start_date="2018-07-01",
+ do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
@@ -941,14 +1024,14 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if post_depreciation_entries() works as expected."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- depreciation_start_date = "2020-12-31",
- frequency_of_depreciation = 12,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ depreciation_start_date="2020-12-31",
+ frequency_of_depreciation=12,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ submit=1,
)
post_depreciation_entries(date="2021-06-01")
@@ -962,21 +1045,24 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if the Depreciation Expense Account gets debited and the Accumulated Depreciation Account gets credited when the former's an Expense Account."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- depreciation_start_date = "2020-12-31",
- frequency_of_depreciation = 12,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ depreciation_start_date="2020-12-31",
+ frequency_of_depreciation=12,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ submit=1,
)
post_depreciation_entries(date="2021-06-01")
asset.load_from_db()
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
- accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
+ accounting_entries = [
+ {"account": entry.account, "debit": entry.debit, "credit": entry.credit}
+ for entry in je.accounts
+ ]
for entry in accounting_entries:
if entry["account"] == "_Test Depreciations - _TC":
@@ -995,21 +1081,24 @@ class TestDepreciationBasics(AssetSetup):
depr_expense_account.save()
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- depreciation_start_date = "2020-12-31",
- frequency_of_depreciation = 12,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ depreciation_start_date="2020-12-31",
+ frequency_of_depreciation=12,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ submit=1,
)
post_depreciation_entries(date="2021-06-01")
asset.load_from_db()
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
- accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
+ accounting_entries = [
+ {"account": entry.account, "debit": entry.debit, "credit": entry.credit}
+ for entry in je.accounts
+ ]
for entry in accounting_entries:
if entry["account"] == "_Test Depreciations - _TC":
@@ -1028,14 +1117,14 @@ class TestDepreciationBasics(AssetSetup):
"""Tests if clear_depreciation_schedule() works as expected."""
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2019-12-31",
- depreciation_start_date = "2020-12-31",
- frequency_of_depreciation = 12,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2019-12-31",
+ depreciation_start_date="2020-12-31",
+ frequency_of_depreciation=12,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ submit=1,
)
post_depreciation_entries(date="2021-06-01")
@@ -1046,34 +1135,39 @@ class TestDepreciationBasics(AssetSetup):
self.assertEqual(len(asset.schedules), 1)
def test_clear_depreciation_schedule_for_multiple_finance_books(self):
- asset = create_asset(
- item_code = "Macbook Pro",
- available_for_use_date = "2019-12-31",
- do_not_save = 1
- )
+ asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1)
asset.calculate_depreciation = 1
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 1,
- "total_number_of_depreciations": 3,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-01-31"
- })
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 1,
- "total_number_of_depreciations": 6,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-01-31"
- })
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 12,
- "total_number_of_depreciations": 3,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-12-31"
- })
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 1,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-01-31",
+ },
+ )
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 1,
+ "total_number_of_depreciations": 6,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-01-31",
+ },
+ )
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31",
+ },
+ )
asset.submit()
post_depreciation_entries(date="2020-04-01")
@@ -1090,27 +1184,29 @@ class TestDepreciationBasics(AssetSetup):
self.assertEqual(schedule.finance_book_id, "2")
def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self):
- asset = create_asset(
- item_code = "Macbook Pro",
- available_for_use_date = "2019-12-31",
- do_not_save = 1
- )
+ asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1)
asset.calculate_depreciation = 1
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 12,
- "total_number_of_depreciations": 3,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-12-31"
- })
- asset.append("finance_books", {
- "depreciation_method": "Straight Line",
- "frequency_of_depreciation": 12,
- "total_number_of_depreciations": 6,
- "expected_value_after_useful_life": 10000,
- "depreciation_start_date": "2020-12-31"
- })
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31",
+ },
+ )
+ asset.append(
+ "finance_books",
+ {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 6,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31",
+ },
+ )
asset.save()
self.assertEqual(len(asset.schedules), 9)
@@ -1123,15 +1219,15 @@ class TestDepreciationBasics(AssetSetup):
def test_depreciation_entry_cancellation(self):
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- purchase_date = "2020-06-06",
- available_for_use_date = "2020-06-06",
- depreciation_start_date = "2020-12-31",
- frequency_of_depreciation = 10,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ purchase_date="2020-06-06",
+ available_for_use_date="2020-06-06",
+ depreciation_start_date="2020-12-31",
+ frequency_of_depreciation=10,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ submit=1,
)
post_depreciation_entries(date="2021-01-01")
@@ -1149,34 +1245,38 @@ class TestDepreciationBasics(AssetSetup):
def test_asset_expected_value_after_useful_life(self):
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- available_for_use_date = "2020-06-06",
- purchase_date = "2020-06-06",
- frequency_of_depreciation = 10,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ available_for_use_date="2020-06-06",
+ purchase_date="2020-06-06",
+ frequency_of_depreciation=10,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
)
- accumulated_depreciation_after_full_schedule = \
- max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
+ accumulated_depreciation_after_full_schedule = max(
+ d.accumulated_depreciation_amount for d in asset.get("schedules")
+ )
- asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) -
- flt(accumulated_depreciation_after_full_schedule))
+ asset_value_after_full_schedule = flt(asset.gross_purchase_amount) - flt(
+ accumulated_depreciation_after_full_schedule
+ )
- self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule)
+ self.assertTrue(
+ asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule
+ )
def test_gle_made_by_depreciation_entries(self):
asset = create_asset(
- item_code = "Macbook Pro",
- calculate_depreciation = 1,
- purchase_date = "2020-01-30",
- available_for_use_date = "2020-01-30",
- depreciation_start_date = "2020-12-31",
- frequency_of_depreciation = 10,
- total_number_of_depreciations = 3,
- expected_value_after_useful_life = 10000,
- submit = 1
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ purchase_date="2020-01-30",
+ available_for_use_date="2020-01-30",
+ depreciation_start_date="2020-12-31",
+ frequency_of_depreciation=10,
+ total_number_of_depreciations=3,
+ expected_value_after_useful_life=10000,
+ submit=1,
)
self.assertEqual(asset.status, "Submitted")
@@ -1190,20 +1290,23 @@ class TestDepreciationBasics(AssetSetup):
expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
- ("_Test Depreciations - _TC", 30000.0, 0.0)
+ ("_Test Depreciations - _TC", 30000.0, 0.0),
)
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where against_voucher_type='Asset' and against_voucher = %s
- order by account""", asset.name)
+ order by account""",
+ asset.name,
+ )
self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0)
def test_expected_value_change(self):
"""
- tests if changing `expected_value_after_useful_life`
- affects `value_after_depreciation`
+ tests if changing `expected_value_after_useful_life`
+ affects `value_after_depreciation`
"""
asset = create_asset(calculate_depreciation=1)
@@ -1222,7 +1325,7 @@ class TestDepreciationBasics(AssetSetup):
self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0)
def test_asset_cost_center(self):
- asset = create_asset(is_existing_asset = 1, do_not_save=1)
+ asset = create_asset(is_existing_asset=1, do_not_save=1)
asset.cost_center = "Main - WP"
self.assertRaises(frappe.ValidationError, asset.submit)
@@ -1230,6 +1333,7 @@ class TestDepreciationBasics(AssetSetup):
asset.cost_center = "Main - _TC"
asset.submit()
+
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
@@ -1238,45 +1342,48 @@ def create_asset_data():
create_fixed_asset_item()
if not frappe.db.exists("Location", "Test Location"):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': 'Test Location'
- }).insert()
+ frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
+
def create_asset(**args):
args = frappe._dict(args)
create_asset_data()
- asset = frappe.get_doc({
- "doctype": "Asset",
- "asset_name": args.asset_name or "Macbook Pro 1",
- "asset_category": args.asset_category or "Computers",
- "item_code": args.item_code or "Macbook Pro",
- "company": args.company or "_Test Company",
- "purchase_date": args.purchase_date or "2015-01-01",
- "calculate_depreciation": args.calculate_depreciation or 0,
- "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0,
- "number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
- "gross_purchase_amount": args.gross_purchase_amount or 100000,
- "purchase_receipt_amount": args.purchase_receipt_amount or 100000,
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "available_for_use_date": args.available_for_use_date or "2020-06-06",
- "location": args.location or "Test Location",
- "asset_owner": args.asset_owner or "Company",
- "is_existing_asset": args.is_existing_asset or 1,
- "asset_quantity": args.get("asset_quantity") or 1
- })
+ asset = frappe.get_doc(
+ {
+ "doctype": "Asset",
+ "asset_name": args.asset_name or "Macbook Pro 1",
+ "asset_category": args.asset_category or "Computers",
+ "item_code": args.item_code or "Macbook Pro",
+ "company": args.company or "_Test Company",
+ "purchase_date": args.purchase_date or "2015-01-01",
+ "calculate_depreciation": args.calculate_depreciation or 0,
+ "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0,
+ "number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
+ "gross_purchase_amount": args.gross_purchase_amount or 100000,
+ "purchase_receipt_amount": args.purchase_receipt_amount or 100000,
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "available_for_use_date": args.available_for_use_date or "2020-06-06",
+ "location": args.location or "Test Location",
+ "asset_owner": args.asset_owner or "Company",
+ "is_existing_asset": args.is_existing_asset or 1,
+ "asset_quantity": args.get("asset_quantity") or 1,
+ }
+ )
if asset.calculate_depreciation:
- asset.append("finance_books", {
- "finance_book": args.finance_book,
- "depreciation_method": args.depreciation_method or "Straight Line",
- "frequency_of_depreciation": args.frequency_of_depreciation or 12,
- "total_number_of_depreciations": args.total_number_of_depreciations or 5,
- "expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
- "depreciation_start_date": args.depreciation_start_date
- })
+ asset.append(
+ "finance_books",
+ {
+ "finance_book": args.finance_book,
+ "depreciation_method": args.depreciation_method or "Straight Line",
+ "frequency_of_depreciation": args.frequency_of_depreciation or 12,
+ "total_number_of_depreciations": args.total_number_of_depreciations or 5,
+ "expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
+ "depreciation_start_date": args.depreciation_start_date,
+ },
+ )
if not args.do_not_save:
try:
@@ -1289,43 +1396,51 @@ def create_asset(**args):
return asset
+
def create_asset_category():
asset_category = frappe.new_doc("Asset Category")
asset_category.asset_category_name = "Computers"
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
asset_category.enable_cwip_accounting = 1
- asset_category.append("accounts", {
- "company_name": "_Test Company",
- "fixed_asset_account": "_Test Fixed Asset - _TC",
- "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
- "depreciation_expense_account": "_Test Depreciations - _TC"
- })
+ asset_category.append(
+ "accounts",
+ {
+ "company_name": "_Test Company",
+ "fixed_asset_account": "_Test Fixed Asset - _TC",
+ "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
+ "depreciation_expense_account": "_Test Depreciations - _TC",
+ },
+ )
asset_category.insert()
+
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0):
- meta = frappe.get_meta('Asset')
- naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-'
+ meta = frappe.get_meta("Asset")
+ naming_series = meta.get_field("naming_series").options.splitlines()[0] or "ACC-ASS-.YYYY.-"
try:
- item = frappe.get_doc({
- "doctype": "Item",
- "item_code": item_code or "Macbook Pro",
- "item_name": "Macbook Pro",
- "description": "Macbook Pro Retina Display",
- "asset_category": "Computers",
- "item_group": "All Item Groups",
- "stock_uom": "Nos",
- "is_stock_item": 0,
- "is_fixed_asset": 1,
- "auto_create_assets": auto_create_assets,
- "is_grouped_asset": is_grouped_asset,
- "asset_naming_series": naming_series
- })
+ item = frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": item_code or "Macbook Pro",
+ "item_name": "Macbook Pro",
+ "description": "Macbook Pro Retina Display",
+ "asset_category": "Computers",
+ "item_group": "All Item Groups",
+ "stock_uom": "Nos",
+ "is_stock_item": 0,
+ "is_fixed_asset": 1,
+ "auto_create_assets": auto_create_assets,
+ "is_grouped_asset": is_grouped_asset,
+ "asset_naming_series": naming_series,
+ }
+ )
item.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
return item
+
def set_depreciation_settings_in_company():
company = frappe.get_doc("Company", "_Test Company")
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
@@ -1337,5 +1452,6 @@ def set_depreciation_settings_in_company():
# Enable booking asset depreciation entry automatically
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
+
def enable_cwip_accounting(asset_category, enable=1):
frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable)
diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py
index bd573bf479..7291daf2b3 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.py
+++ b/erpnext/assets/doctype/asset_category/asset_category.py
@@ -18,79 +18,104 @@ class AssetCategory(Document):
def validate_finance_books(self):
for d in self.finance_books:
for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
- if cint(d.get(frappe.scrub(field)))<1:
- frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
+ if cint(d.get(frappe.scrub(field))) < 1:
+ frappe.throw(
+ _("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError
+ )
def validate_account_currency(self):
account_types = [
- 'fixed_asset_account', 'accumulated_depreciation_account', 'depreciation_expense_account', 'capital_work_in_progress_account'
+ "fixed_asset_account",
+ "accumulated_depreciation_account",
+ "depreciation_expense_account",
+ "capital_work_in_progress_account",
]
invalid_accounts = []
for d in self.accounts:
- company_currency = frappe.get_value('Company', d.get('company_name'), 'default_currency')
+ company_currency = frappe.get_value("Company", d.get("company_name"), "default_currency")
for type_of_account in account_types:
if d.get(type_of_account):
account_currency = frappe.get_value("Account", d.get(type_of_account), "account_currency")
if account_currency != company_currency:
- invalid_accounts.append(frappe._dict({ 'type': type_of_account, 'idx': d.idx, 'account': d.get(type_of_account) }))
+ invalid_accounts.append(
+ frappe._dict({"type": type_of_account, "idx": d.idx, "account": d.get(type_of_account)})
+ )
for d in invalid_accounts:
- frappe.throw(_("Row #{}: Currency of {} - {} doesn't matches company currency.")
- .format(d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)),
- title=_("Invalid Account"))
-
+ frappe.throw(
+ _("Row #{}: Currency of {} - {} doesn't matches company currency.").format(
+ d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)
+ ),
+ title=_("Invalid Account"),
+ )
def validate_account_types(self):
account_type_map = {
- 'fixed_asset_account': {'account_type': ['Fixed Asset']},
- 'accumulated_depreciation_account': {'account_type': ['Accumulated Depreciation']},
- 'depreciation_expense_account': {'root_type': ['Expense', 'Income']},
- 'capital_work_in_progress_account': {'account_type': ['Capital Work in Progress']}
+ "fixed_asset_account": {"account_type": ["Fixed Asset"]},
+ "accumulated_depreciation_account": {"account_type": ["Accumulated Depreciation"]},
+ "depreciation_expense_account": {"root_type": ["Expense", "Income"]},
+ "capital_work_in_progress_account": {"account_type": ["Capital Work in Progress"]},
}
for d in self.accounts:
for fieldname in account_type_map.keys():
if d.get(fieldname):
selected_account = d.get(fieldname)
- key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
- selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match)
+ key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
+ selected_key_type = frappe.db.get_value("Account", selected_account, key_to_match)
expected_key_types = account_type_map[fieldname][key_to_match]
if selected_key_type not in expected_key_types:
- frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
- .format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_types)),
- title=_("Invalid Account"))
+ frappe.throw(
+ _(
+ "Row #{}: {} of {} should be {}. Please modify the account or select a different account."
+ ).format(
+ d.idx,
+ frappe.unscrub(key_to_match),
+ frappe.bold(selected_account),
+ frappe.bold(expected_key_types),
+ ),
+ title=_("Invalid Account"),
+ )
def valide_cwip_account(self):
if self.enable_cwip_accounting:
missing_cwip_accounts_for_company = []
for d in self.accounts:
- if (not d.capital_work_in_progress_account and
- not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")):
+ if not d.capital_work_in_progress_account and not frappe.db.get_value(
+ "Company", d.company_name, "capital_work_in_progress_account"
+ ):
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
if missing_cwip_accounts_for_company:
msg = _("""To enable Capital Work in Progress Accounting, """)
msg += _("""you must select Capital Work in Progress Account in accounts table""")
msg += "
"
- msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company))
+ msg += _("You can also set default CWIP account in Company {}").format(
+ ", ".join(missing_cwip_accounts_for_company)
+ )
frappe.throw(msg, title=_("Missing Account"))
@frappe.whitelist()
-def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):
+def get_asset_category_account(
+ fieldname, item=None, asset=None, account=None, asset_category=None, company=None
+):
if item and frappe.db.get_value("Item", item, "is_fixed_asset"):
asset_category = frappe.db.get_value("Item", item, ["asset_category"])
elif not asset_category or not company:
if account:
if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset":
- account=None
+ account = None
if not account:
asset_details = frappe.db.get_value("Asset", asset, ["asset_category", "company"])
asset_category, company = asset_details or [None, None]
- account = frappe.db.get_value("Asset Category Account",
- filters={"parent": asset_category, "company_name": company}, fieldname=fieldname)
+ account = frappe.db.get_value(
+ "Asset Category Account",
+ filters={"parent": asset_category, "company_name": company},
+ fieldname=fieldname,
+ )
return account
diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.py b/erpnext/assets/doctype/asset_category/test_asset_category.py
index 2f52248edb..2c92656576 100644
--- a/erpnext/assets/doctype/asset_category/test_asset_category.py
+++ b/erpnext/assets/doctype/asset_category/test_asset_category.py
@@ -15,12 +15,15 @@ class TestAssetCategory(unittest.TestCase):
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
- asset_category.append("accounts", {
- "company_name": "_Test Company",
- "fixed_asset_account": "_Test Fixed Asset - _TC",
- "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
- "depreciation_expense_account": "_Test Depreciations - _TC"
- })
+ asset_category.append(
+ "accounts",
+ {
+ "company_name": "_Test Company",
+ "fixed_asset_account": "_Test Fixed Asset - _TC",
+ "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
+ "depreciation_expense_account": "_Test Depreciations - _TC",
+ },
+ )
try:
asset_category.insert(ignore_if_duplicate=True)
@@ -28,7 +31,9 @@ class TestAssetCategory(unittest.TestCase):
pass
def test_cwip_accounting(self):
- company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account")
+ company_cwip_acc = frappe.db.get_value(
+ "Company", "_Test Company", "capital_work_in_progress_account"
+ )
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "")
asset_category = frappe.new_doc("Asset Category")
@@ -37,11 +42,14 @@ class TestAssetCategory(unittest.TestCase):
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
- asset_category.append("accounts", {
- "company_name": "_Test Company",
- "fixed_asset_account": "_Test Fixed Asset - _TC",
- "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
- "depreciation_expense_account": "_Test Depreciations - _TC"
- })
+ asset_category.append(
+ "accounts",
+ {
+ "company_name": "_Test Company",
+ "fixed_asset_account": "_Test Fixed Asset - _TC",
+ "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
+ "depreciation_expense_account": "_Test Depreciations - _TC",
+ },
+ )
self.assertRaises(frappe.ValidationError, asset_category.insert)
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 4fc4c4cb99..e603d34626 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -11,7 +11,7 @@ from frappe.utils import add_days, add_months, add_years, getdate, nowdate
class AssetMaintenance(Document):
def validate(self):
- for task in self.get('asset_maintenance_tasks'):
+ for task in self.get("asset_maintenance_tasks"):
if task.end_date and (getdate(task.start_date) >= getdate(task.end_date)):
throw(_("Start date should be less than end date for task {0}").format(task.maintenance_task))
if getdate(task.next_due_date) < getdate(nowdate()):
@@ -20,83 +20,109 @@ class AssetMaintenance(Document):
throw(_("Row #{}: Please asign task to a member.").format(task.idx))
def on_update(self):
- for task in self.get('asset_maintenance_tasks'):
+ for task in self.get("asset_maintenance_tasks"):
assign_tasks(self.name, task.assign_to, task.maintenance_task, task.next_due_date)
self.sync_maintenance_tasks()
def sync_maintenance_tasks(self):
tasks_names = []
- for task in self.get('asset_maintenance_tasks'):
+ for task in self.get("asset_maintenance_tasks"):
tasks_names.append(task.name)
- update_maintenance_log(asset_maintenance = self.name, item_code = self.item_code, item_name = self.item_name, task = task)
- asset_maintenance_logs = frappe.get_all("Asset Maintenance Log", fields=["name"], filters = {"asset_maintenance": self.name,
- "task": ("not in", tasks_names)})
+ update_maintenance_log(
+ asset_maintenance=self.name, item_code=self.item_code, item_name=self.item_name, task=task
+ )
+ asset_maintenance_logs = frappe.get_all(
+ "Asset Maintenance Log",
+ fields=["name"],
+ filters={"asset_maintenance": self.name, "task": ("not in", tasks_names)},
+ )
if asset_maintenance_logs:
for asset_maintenance_log in asset_maintenance_logs:
- maintenance_log = frappe.get_doc('Asset Maintenance Log', asset_maintenance_log.name)
- maintenance_log.db_set('maintenance_status', 'Cancelled')
+ maintenance_log = frappe.get_doc("Asset Maintenance Log", asset_maintenance_log.name)
+ maintenance_log.db_set("maintenance_status", "Cancelled")
+
@frappe.whitelist()
def assign_tasks(asset_maintenance_name, assign_to_member, maintenance_task, next_due_date):
- team_member = frappe.db.get_value('User', assign_to_member, "email")
+ team_member = frappe.db.get_value("User", assign_to_member, "email")
args = {
- 'doctype' : 'Asset Maintenance',
- 'assign_to' : [team_member],
- 'name' : asset_maintenance_name,
- 'description' : maintenance_task,
- 'date' : next_due_date
+ "doctype": "Asset Maintenance",
+ "assign_to": [team_member],
+ "name": asset_maintenance_name,
+ "description": maintenance_task,
+ "date": next_due_date,
}
- if not frappe.db.sql("""select owner from `tabToDo`
+ if not frappe.db.sql(
+ """select owner from `tabToDo`
where reference_type=%(doctype)s and reference_name=%(name)s and status="Open"
- and owner=%(assign_to)s""", args):
+ and owner=%(assign_to)s""",
+ args,
+ ):
assign_to.add(args)
+
@frappe.whitelist()
-def calculate_next_due_date(periodicity, start_date = None, end_date = None, last_completion_date = None, next_due_date = None):
+def calculate_next_due_date(
+ periodicity, start_date=None, end_date=None, last_completion_date=None, next_due_date=None
+):
if not start_date and not last_completion_date:
start_date = frappe.utils.now()
- if last_completion_date and ((start_date and last_completion_date > start_date) or not start_date):
+ if last_completion_date and (
+ (start_date and last_completion_date > start_date) or not start_date
+ ):
start_date = last_completion_date
- if periodicity == 'Daily':
+ if periodicity == "Daily":
next_due_date = add_days(start_date, 1)
- if periodicity == 'Weekly':
+ if periodicity == "Weekly":
next_due_date = add_days(start_date, 7)
- if periodicity == 'Monthly':
+ if periodicity == "Monthly":
next_due_date = add_months(start_date, 1)
- if periodicity == 'Yearly':
+ if periodicity == "Yearly":
next_due_date = add_years(start_date, 1)
- if periodicity == '2 Yearly':
+ if periodicity == "2 Yearly":
next_due_date = add_years(start_date, 2)
- if periodicity == 'Quarterly':
+ if periodicity == "Quarterly":
next_due_date = add_months(start_date, 3)
- if end_date and ((start_date and start_date >= end_date) or (last_completion_date and last_completion_date >= end_date) or next_due_date):
+ if end_date and (
+ (start_date and start_date >= end_date)
+ or (last_completion_date and last_completion_date >= end_date)
+ or next_due_date
+ ):
next_due_date = ""
return next_due_date
def update_maintenance_log(asset_maintenance, item_code, item_name, task):
- asset_maintenance_log = frappe.get_value("Asset Maintenance Log", {"asset_maintenance": asset_maintenance,
- "task": task.name, "maintenance_status": ('in',['Planned','Overdue'])})
+ asset_maintenance_log = frappe.get_value(
+ "Asset Maintenance Log",
+ {
+ "asset_maintenance": asset_maintenance,
+ "task": task.name,
+ "maintenance_status": ("in", ["Planned", "Overdue"]),
+ },
+ )
if not asset_maintenance_log:
- asset_maintenance_log = frappe.get_doc({
- "doctype": "Asset Maintenance Log",
- "asset_maintenance": asset_maintenance,
- "asset_name": asset_maintenance,
- "item_code": item_code,
- "item_name": item_name,
- "task": task.name,
- "has_certificate": task.certificate_required,
- "description": task.description,
- "assign_to_name": task.assign_to_name,
- "periodicity": str(task.periodicity),
- "maintenance_type": task.maintenance_type,
- "due_date": task.next_due_date
- })
+ asset_maintenance_log = frappe.get_doc(
+ {
+ "doctype": "Asset Maintenance Log",
+ "asset_maintenance": asset_maintenance,
+ "asset_name": asset_maintenance,
+ "item_code": item_code,
+ "item_name": item_name,
+ "task": task.name,
+ "has_certificate": task.certificate_required,
+ "description": task.description,
+ "assign_to_name": task.assign_to_name,
+ "periodicity": str(task.periodicity),
+ "maintenance_type": task.maintenance_type,
+ "due_date": task.next_due_date,
+ }
+ )
asset_maintenance_log.insert()
else:
- maintenance_log = frappe.get_doc('Asset Maintenance Log', asset_maintenance_log)
+ maintenance_log = frappe.get_doc("Asset Maintenance Log", asset_maintenance_log)
maintenance_log.assign_to_name = task.assign_to_name
maintenance_log.has_certificate = task.certificate_required
maintenance_log.description = task.description
@@ -105,15 +131,22 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task):
maintenance_log.due_date = task.next_due_date
maintenance_log.save()
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_team_members(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") }, "team_member")
+ return frappe.db.get_values(
+ "Maintenance Team Member", {"parent": filters.get("maintenance_team")}, "team_member"
+ )
+
@frappe.whitelist()
def get_maintenance_log(asset_name):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select maintenance_status, count(asset_name) as count, asset_name
from `tabAsset Maintenance Log`
where asset_name=%s group by maintenance_status""",
- (asset_name), as_dict=1)
+ (asset_name),
+ as_dict=1,
+ )
diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
index 8acb61b967..e40a5519eb 100644
--- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
@@ -17,11 +17,12 @@ class TestAssetMaintenance(unittest.TestCase):
create_maintenance_team()
def test_create_asset_maintenance(self):
- pr = make_purchase_receipt(item_code="Photocopier",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Photocopier", qty=1, rate=100000.0, location="Test Location"
+ )
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset_doc = frappe.get_doc('Asset', asset_name)
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset_doc = frappe.get_doc("Asset", asset_name)
month_end_date = get_last_day(nowdate())
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
@@ -30,66 +31,74 @@ class TestAssetMaintenance(unittest.TestCase):
asset_doc.purchase_date = purchase_date
asset_doc.calculate_depreciation = 1
- asset_doc.append("finance_books", {
- "expected_value_after_useful_life": 200,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": month_end_date
- })
+ asset_doc.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 200,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ "depreciation_start_date": month_end_date,
+ },
+ )
asset_doc.save()
if not frappe.db.exists("Asset Maintenance", "Photocopier"):
- asset_maintenance = frappe.get_doc({
+ asset_maintenance = frappe.get_doc(
+ {
"doctype": "Asset Maintenance",
"asset_name": "Photocopier",
"maintenance_team": "Team Awesome",
"company": "_Test Company",
- "asset_maintenance_tasks": get_maintenance_tasks()
- }).insert()
+ "asset_maintenance_tasks": get_maintenance_tasks(),
+ }
+ ).insert()
next_due_date = calculate_next_due_date(nowdate(), "Monthly")
self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
def test_create_asset_maintenance_log(self):
if not frappe.db.exists("Asset Maintenance Log", "Photocopier"):
- asset_maintenance_log = frappe.get_doc({
+ asset_maintenance_log = frappe.get_doc(
+ {
"doctype": "Asset Maintenance Log",
"asset_maintenance": "Photocopier",
"task": "Change Oil",
"completion_date": add_days(nowdate(), 2),
- "maintenance_status": "Completed"
- }).insert()
+ "maintenance_status": "Completed",
+ }
+ ).insert()
asset_maintenance = frappe.get_doc("Asset Maintenance", "Photocopier")
next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly")
self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
+
def create_asset_data():
if not frappe.db.exists("Asset Category", "Equipment"):
create_asset_category()
if not frappe.db.exists("Location", "Test Location"):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': 'Test Location'
- }).insert()
+ frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
if not frappe.db.exists("Item", "Photocopier"):
- meta = frappe.get_meta('Asset')
+ meta = frappe.get_meta("Asset")
naming_series = meta.get_field("naming_series").options
- frappe.get_doc({
- "doctype": "Item",
- "item_code": "Photocopier",
- "item_name": "Photocopier",
- "item_group": "All Item Groups",
- "company": "_Test Company",
- "is_fixed_asset": 1,
- "is_stock_item": 0,
- "asset_category": "Equipment",
- "auto_create_assets": 1,
- "asset_naming_series": naming_series
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": "Photocopier",
+ "item_name": "Photocopier",
+ "item_group": "All Item Groups",
+ "company": "_Test Company",
+ "is_fixed_asset": 1,
+ "is_stock_item": 0,
+ "asset_category": "Equipment",
+ "auto_create_assets": 1,
+ "asset_naming_series": naming_series,
+ }
+ ).insert()
+
def create_maintenance_team():
user_list = ["marcus@abc.com", "thalia@abc.com", "mathias@abc.com"]
@@ -97,60 +106,73 @@ def create_maintenance_team():
frappe.get_doc({"doctype": "Role", "role_name": "Technician"}).insert()
for user in user_list:
if not frappe.db.get_value("User", user):
- frappe.get_doc({
- "doctype": "User",
- "email": user,
- "first_name": user,
- "new_password": "password",
- "roles": [{"doctype": "Has Role", "role": "Technician"}]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "email": user,
+ "first_name": user,
+ "new_password": "password",
+ "roles": [{"doctype": "Has Role", "role": "Technician"}],
+ }
+ ).insert()
if not frappe.db.exists("Asset Maintenance Team", "Team Awesome"):
- frappe.get_doc({
- "doctype": "Asset Maintenance Team",
- "maintenance_manager": "marcus@abc.com",
- "maintenance_team_name": "Team Awesome",
- "company": "_Test Company",
- "maintenance_team_members": get_maintenance_team(user_list)
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Asset Maintenance Team",
+ "maintenance_manager": "marcus@abc.com",
+ "maintenance_team_name": "Team Awesome",
+ "company": "_Test Company",
+ "maintenance_team_members": get_maintenance_team(user_list),
+ }
+ ).insert()
+
def get_maintenance_team(user_list):
- return [{"team_member": user,
- "full_name": user,
- "maintenance_role": "Technician"
- }
- for user in user_list[1:]]
+ return [
+ {"team_member": user, "full_name": user, "maintenance_role": "Technician"}
+ for user in user_list[1:]
+ ]
+
def get_maintenance_tasks():
- return [{"maintenance_task": "Change Oil",
+ return [
+ {
+ "maintenance_task": "Change Oil",
"start_date": nowdate(),
"periodicity": "Monthly",
"maintenance_type": "Preventive Maintenance",
"maintenance_status": "Planned",
- "assign_to": "marcus@abc.com"
- },
- {"maintenance_task": "Check Gears",
+ "assign_to": "marcus@abc.com",
+ },
+ {
+ "maintenance_task": "Check Gears",
"start_date": nowdate(),
"periodicity": "Yearly",
"maintenance_type": "Calibration",
"maintenance_status": "Planned",
- "assign_to": "thalia@abc.com"
- }
- ]
+ "assign_to": "thalia@abc.com",
+ },
+ ]
+
def create_asset_category():
asset_category = frappe.new_doc("Asset Category")
asset_category.asset_category_name = "Equipment"
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
- asset_category.append("accounts", {
- "company_name": "_Test Company",
- "fixed_asset_account": "_Test Fixed Asset - _TC",
- "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
- "depreciation_expense_account": "_Test Depreciations - _TC"
- })
+ asset_category.append(
+ "accounts",
+ {
+ "company_name": "_Test Company",
+ "fixed_asset_account": "_Test Fixed Asset - _TC",
+ "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
+ "depreciation_expense_account": "_Test Depreciations - _TC",
+ },
+ )
asset_category.insert()
+
def set_depreciation_settings_in_company():
company = frappe.get_doc("Company", "_Test Company")
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
index 7d3453fc98..ff791b2754 100644
--- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
+++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
@@ -12,7 +12,10 @@ from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate
class AssetMaintenanceLog(Document):
def validate(self):
- if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in ["Completed", "Cancelled"]:
+ if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in [
+ "Completed",
+ "Cancelled",
+ ]:
self.maintenance_status = "Overdue"
if self.maintenance_status == "Completed" and not self.completion_date:
@@ -22,15 +25,17 @@ class AssetMaintenanceLog(Document):
frappe.throw(_("Please select Maintenance Status as Completed or remove Completion Date"))
def on_submit(self):
- if self.maintenance_status not in ['Completed', 'Cancelled']:
+ if self.maintenance_status not in ["Completed", "Cancelled"]:
frappe.throw(_("Maintenance Status has to be Cancelled or Completed to Submit"))
self.update_maintenance_task()
def update_maintenance_task(self):
- asset_maintenance_doc = frappe.get_doc('Asset Maintenance Task', self.task)
+ asset_maintenance_doc = frappe.get_doc("Asset Maintenance Task", self.task)
if self.maintenance_status == "Completed":
if asset_maintenance_doc.last_completion_date != self.completion_date:
- next_due_date = calculate_next_due_date(periodicity = self.periodicity, last_completion_date = self.completion_date)
+ next_due_date = calculate_next_due_date(
+ periodicity=self.periodicity, last_completion_date=self.completion_date
+ )
asset_maintenance_doc.last_completion_date = self.completion_date
asset_maintenance_doc.next_due_date = next_due_date
asset_maintenance_doc.maintenance_status = "Planned"
@@ -38,11 +43,14 @@ class AssetMaintenanceLog(Document):
if self.maintenance_status == "Cancelled":
asset_maintenance_doc.maintenance_status = "Cancelled"
asset_maintenance_doc.save()
- asset_maintenance_doc = frappe.get_doc('Asset Maintenance', self.asset_maintenance)
+ asset_maintenance_doc = frappe.get_doc("Asset Maintenance", self.asset_maintenance)
asset_maintenance_doc.save()
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters):
- asset_maintenance_tasks = frappe.db.get_values('Asset Maintenance Task', {'parent':filters.get("asset_maintenance")}, 'maintenance_task')
+ asset_maintenance_tasks = frappe.db.get_values(
+ "Asset Maintenance Task", {"parent": filters.get("asset_maintenance")}, "maintenance_task"
+ )
return asset_maintenance_tasks
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index 07bea616da..e61efadb12 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -16,7 +16,7 @@ class AssetMovement(Document):
def validate_asset(self):
for d in self.assets:
status, company = frappe.db.get_value("Asset", d.asset, ["status", "company"])
- if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"):
+ if self.purpose == "Transfer" and status in ("Draft", "Scrapped", "Sold"):
frappe.throw(_("{0} asset cannot be transferred").format(status))
if company != self.company:
@@ -27,7 +27,7 @@ class AssetMovement(Document):
def validate_location(self):
for d in self.assets:
- if self.purpose in ['Transfer', 'Issue']:
+ if self.purpose in ["Transfer", "Issue"]:
if not d.source_location:
d.source_location = frappe.db.get_value("Asset", d.asset, "location")
@@ -38,52 +38,76 @@ class AssetMovement(Document):
current_location = frappe.db.get_value("Asset", d.asset, "location")
if current_location != d.source_location:
- frappe.throw(_("Asset {0} does not belongs to the location {1}").
- format(d.asset, d.source_location))
+ frappe.throw(
+ _("Asset {0} does not belongs to the location {1}").format(d.asset, d.source_location)
+ )
- if self.purpose == 'Issue':
+ if self.purpose == "Issue":
if d.target_location:
- frappe.throw(_("Issuing cannot be done to a location. \
- Please enter employee who has issued Asset {0}").format(d.asset), title="Incorrect Movement Purpose")
+ frappe.throw(
+ _(
+ "Issuing cannot be done to a location. \
+ Please enter employee who has issued Asset {0}"
+ ).format(d.asset),
+ title="Incorrect Movement Purpose",
+ )
if not d.to_employee:
frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset))
- if self.purpose == 'Transfer':
+ if self.purpose == "Transfer":
if d.to_employee:
- frappe.throw(_("Transferring cannot be done to an Employee. \
- Please enter location where Asset {0} has to be transferred").format(
- d.asset), title="Incorrect Movement Purpose")
+ frappe.throw(
+ _(
+ "Transferring cannot be done to an Employee. \
+ Please enter location where Asset {0} has to be transferred"
+ ).format(d.asset),
+ title="Incorrect Movement Purpose",
+ )
if not d.target_location:
frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset))
if d.source_location == d.target_location:
frappe.throw(_("Source and Target Location cannot be same"))
- if self.purpose == 'Receipt':
+ if self.purpose == "Receipt":
# only when asset is bought and first entry is made
if not d.source_location and not (d.target_location or d.to_employee):
- frappe.throw(_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset))
+ frappe.throw(
+ _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
+ )
elif d.source_location:
# when asset is received from an employee
if d.target_location and not d.from_employee:
- frappe.throw(_("From employee is required while receiving Asset {0} to a target location").format(d.asset))
+ frappe.throw(
+ _("From employee is required while receiving Asset {0} to a target location").format(
+ d.asset
+ )
+ )
if d.from_employee and not d.target_location:
- frappe.throw(_("Target Location is required while receiving Asset {0} from an employee").format(d.asset))
+ frappe.throw(
+ _("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
+ )
if d.to_employee and d.target_location:
- frappe.throw(_("Asset {0} cannot be received at a location and \
- given to employee in a single movement").format(d.asset))
+ frappe.throw(
+ _(
+ "Asset {0} cannot be received at a location and \
+ given to employee in a single movement"
+ ).format(d.asset)
+ )
def validate_employee(self):
for d in self.assets:
if d.from_employee:
- current_custodian = frappe.db.get_value("Asset", d.asset, "custodian")
+ current_custodian = frappe.db.get_value("Asset", d.asset, "custodian")
- if current_custodian != d.from_employee:
- frappe.throw(_("Asset {0} does not belongs to the custodian {1}").
- format(d.asset, d.from_employee))
+ if current_custodian != d.from_employee:
+ frappe.throw(
+ _("Asset {0} does not belongs to the custodian {1}").format(d.asset, d.from_employee)
+ )
if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company:
- frappe.throw(_("Employee {0} does not belongs to the company {1}").
- format(d.to_employee, self.company))
+ frappe.throw(
+ _("Employee {0} does not belongs to the company {1}").format(d.to_employee, self.company)
+ )
def on_submit(self):
self.set_latest_location_in_asset()
@@ -92,14 +116,11 @@ class AssetMovement(Document):
self.set_latest_location_in_asset()
def set_latest_location_in_asset(self):
- current_location, current_employee = '', ''
+ current_location, current_employee = "", ""
cond = "1=1"
for d in self.assets:
- args = {
- 'asset': d.asset,
- 'company': self.company
- }
+ args = {"asset": d.asset, "company": self.company}
# latest entry corresponds to current document's location, employee when transaction date > previous dates
# In case of cancellation it corresponds to previous latest document's location, employee
@@ -114,10 +135,14 @@ class AssetMovement(Document):
asm.docstatus=1 and {0}
ORDER BY
asm.transaction_date desc limit 1
- """.format(cond), args)
+ """.format(
+ cond
+ ),
+ args,
+ )
if latest_movement_entry:
current_location = latest_movement_entry[0][0]
current_employee = latest_movement_entry[0][1]
- frappe.db.set_value('Asset', d.asset, 'location', current_location)
- frappe.db.set_value('Asset', d.asset, 'custodian', current_employee)
+ frappe.db.set_value("Asset", d.asset, "location", current_location)
+ frappe.db.set_value("Asset", d.asset, "custodian", current_employee)
diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
index 025facc4fd..a5fe52cefa 100644
--- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
@@ -13,95 +13,122 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
class TestAssetMovement(unittest.TestCase):
def setUp(self):
- frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC")
+ frappe.db.set_value(
+ "Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC"
+ )
create_asset_data()
make_location()
def test_movement(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+ )
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset = frappe.get_doc("Asset", asset_name)
asset.calculate_depreciation = 1
- asset.available_for_use_date = '2020-06-06'
- asset.purchase_date = '2020-06-06'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "next_depreciation_date": "2020-12-31",
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10
- })
+ asset.available_for_use_date = "2020-06-06"
+ asset.purchase_date = "2020-06-06"
+ asset.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 10000,
+ "next_depreciation_date": "2020-12-31",
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ },
+ )
if asset.docstatus == 0:
asset.submit()
# check asset movement is created
if not frappe.db.exists("Location", "Test Location 2"):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': 'Test Location 2'
- }).insert()
+ frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert()
- movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
- assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
- reference_doctype = 'Purchase Receipt', reference_name = pr.name)
+ movement1 = create_asset_movement(
+ purpose="Transfer",
+ company=asset.company,
+ assets=[
+ {"asset": asset.name, "source_location": "Test Location", "target_location": "Test Location 2"}
+ ],
+ reference_doctype="Purchase Receipt",
+ reference_name=pr.name,
+ )
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
- create_asset_movement(purpose = 'Transfer', company = asset.company,
- assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}],
- reference_doctype = 'Purchase Receipt', reference_name = pr.name)
+ create_asset_movement(
+ purpose="Transfer",
+ company=asset.company,
+ assets=[
+ {"asset": asset.name, "source_location": "Test Location 2", "target_location": "Test Location"}
+ ],
+ reference_doctype="Purchase Receipt",
+ reference_name=pr.name,
+ )
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
employee = make_employee("testassetmovemp@example.com", company="_Test Company")
- create_asset_movement(purpose = 'Issue', company = asset.company,
- assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}],
- reference_doctype = 'Purchase Receipt', reference_name = pr.name)
+ create_asset_movement(
+ purpose="Issue",
+ company=asset.company,
+ assets=[{"asset": asset.name, "source_location": "Test Location", "to_employee": employee}],
+ reference_doctype="Purchase Receipt",
+ reference_name=pr.name,
+ )
# after issuing asset should belong to an employee not at a location
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
def test_last_movement_cancellation(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+ )
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset = frappe.get_doc('Asset', asset_name)
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset = frappe.get_doc("Asset", asset_name)
asset.calculate_depreciation = 1
- asset.available_for_use_date = '2020-06-06'
- asset.purchase_date = '2020-06-06'
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10000,
- "next_depreciation_date": "2020-12-31",
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10
- })
+ asset.available_for_use_date = "2020-06-06"
+ asset.purchase_date = "2020-06-06"
+ asset.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 10000,
+ "next_depreciation_date": "2020-12-31",
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ },
+ )
if asset.docstatus == 0:
asset.submit()
if not frappe.db.exists("Location", "Test Location 2"):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': 'Test Location 2'
- }).insert()
+ frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert()
- movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name })
+ movement = frappe.get_doc({"doctype": "Asset Movement", "reference_name": pr.name})
self.assertRaises(frappe.ValidationError, movement.cancel)
- movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
- assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
- reference_doctype = 'Purchase Receipt', reference_name = pr.name)
+ movement1 = create_asset_movement(
+ purpose="Transfer",
+ company=asset.company,
+ assets=[
+ {"asset": asset.name, "source_location": "Test Location", "target_location": "Test Location 2"}
+ ],
+ reference_doctype="Purchase Receipt",
+ reference_name=pr.name,
+ )
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
+
def create_asset_movement(**args):
args = frappe._dict(args)
@@ -109,24 +136,26 @@ def create_asset_movement(**args):
args.transaction_date = now()
movement = frappe.new_doc("Asset Movement")
- movement.update({
- "assets": args.assets,
- "transaction_date": args.transaction_date,
- "company": args.company,
- 'purpose': args.purpose or 'Receipt',
- 'reference_doctype': args.reference_doctype,
- 'reference_name': args.reference_name
- })
+ movement.update(
+ {
+ "assets": args.assets,
+ "transaction_date": args.transaction_date,
+ "company": args.company,
+ "purpose": args.purpose or "Receipt",
+ "reference_doctype": args.reference_doctype,
+ "reference_name": args.reference_name,
+ }
+ )
movement.insert()
movement.submit()
return movement
+
def make_location():
- for location in ['Pune', 'Mumbai', 'Nagpur']:
- if not frappe.db.exists('Location', location):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': location
- }).insert(ignore_permissions = True)
+ for location in ["Pune", "Mumbai", "Nagpur"]:
+ if not frappe.db.exists("Location", location):
+ frappe.get_doc({"doctype": "Location", "location_name": location}).insert(
+ ignore_permissions=True
+ )
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 36848e9f15..5bf6011cf8 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -13,21 +13,21 @@ from erpnext.controllers.accounts_controller import AccountsController
class AssetRepair(AccountsController):
def validate(self):
- self.asset_doc = frappe.get_doc('Asset', self.asset)
+ self.asset_doc = frappe.get_doc("Asset", self.asset)
self.update_status()
- if self.get('stock_items'):
+ if self.get("stock_items"):
self.set_total_value()
self.calculate_total_repair_cost()
def update_status(self):
- if self.repair_status == 'Pending':
- frappe.db.set_value('Asset', self.asset, 'status', 'Out of Order')
+ if self.repair_status == "Pending":
+ frappe.db.set_value("Asset", self.asset, "status", "Out of Order")
else:
self.asset_doc.set_status()
def set_total_value(self):
- for item in self.get('stock_items'):
+ for item in self.get("stock_items"):
item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
def calculate_total_repair_cost(self):
@@ -39,14 +39,17 @@ class AssetRepair(AccountsController):
def before_submit(self):
self.check_repair_status()
- if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
+ if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.increase_asset_value()
- if self.get('stock_consumption'):
+ if self.get("stock_consumption"):
self.check_for_stock_items_and_warehouse()
self.decrease_stock_quantity()
- if self.get('capitalize_repair_cost'):
+ if self.get("capitalize_repair_cost"):
self.make_gl_entries()
- if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
+ if (
+ frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
+ and self.increase_in_asset_life
+ ):
self.modify_depreciation_schedule()
self.asset_doc.flags.ignore_validate_update_after_submit = True
@@ -54,16 +57,19 @@ class AssetRepair(AccountsController):
self.asset_doc.save()
def before_cancel(self):
- self.asset_doc = frappe.get_doc('Asset', self.asset)
+ self.asset_doc = frappe.get_doc("Asset", self.asset)
- if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
+ if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.decrease_asset_value()
- if self.get('stock_consumption'):
+ if self.get("stock_consumption"):
self.increase_stock_quantity()
- if self.get('capitalize_repair_cost'):
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ if self.get("capitalize_repair_cost"):
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
- if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
+ if (
+ frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
+ and self.increase_in_asset_life
+ ):
self.revert_depreciation_schedule_on_cancellation()
self.asset_doc.flags.ignore_validate_update_after_submit = True
@@ -75,10 +81,15 @@ class AssetRepair(AccountsController):
frappe.throw(_("Please update Repair Status."))
def check_for_stock_items_and_warehouse(self):
- if not self.get('stock_items'):
- frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items"))
+ if not self.get("stock_items"):
+ frappe.throw(
+ _("Please enter Stock Items consumed during the Repair."), title=_("Missing Items")
+ )
if not self.warehouse:
- frappe.throw(_("Please enter Warehouse from which Stock Items consumed during the Repair were taken."), title=_("Missing Warehouse"))
+ frappe.throw(
+ _("Please enter Warehouse from which Stock Items consumed during the Repair were taken."),
+ title=_("Missing Warehouse"),
+ )
def increase_asset_value(self):
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
@@ -102,35 +113,36 @@ class AssetRepair(AccountsController):
def get_total_value_of_stock_consumed(self):
total_value_of_stock_consumed = 0
- if self.get('stock_consumption'):
- for item in self.get('stock_items'):
+ if self.get("stock_consumption"):
+ for item in self.get("stock_items"):
total_value_of_stock_consumed += item.total_value
return total_value_of_stock_consumed
def decrease_stock_quantity(self):
- stock_entry = frappe.get_doc({
- "doctype": "Stock Entry",
- "stock_entry_type": "Material Issue",
- "company": self.company
- })
+ stock_entry = frappe.get_doc(
+ {"doctype": "Stock Entry", "stock_entry_type": "Material Issue", "company": self.company}
+ )
- for stock_item in self.get('stock_items'):
- stock_entry.append('items', {
- "s_warehouse": self.warehouse,
- "item_code": stock_item.item_code,
- "qty": stock_item.consumed_quantity,
- "basic_rate": stock_item.valuation_rate,
- "serial_no": stock_item.serial_no
- })
+ for stock_item in self.get("stock_items"):
+ stock_entry.append(
+ "items",
+ {
+ "s_warehouse": self.warehouse,
+ "item_code": stock_item.item_code,
+ "qty": stock_item.consumed_quantity,
+ "basic_rate": stock_item.valuation_rate,
+ "serial_no": stock_item.serial_no,
+ },
+ )
stock_entry.insert()
stock_entry.submit()
- self.db_set('stock_entry', stock_entry.name)
+ self.db_set("stock_entry", stock_entry.name)
def increase_stock_quantity(self):
- stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
+ stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
stock_entry.flags.ignore_links = True
stock_entry.cancel()
@@ -141,63 +153,78 @@ class AssetRepair(AccountsController):
def get_gl_entries(self):
gl_entries = []
- repair_and_maintenance_account = frappe.db.get_value('Company', self.company, 'repair_and_maintenance_account')
- fixed_asset_account = get_asset_account("fixed_asset_account", asset=self.asset, company=self.company)
- expense_account = frappe.get_doc('Purchase Invoice', self.purchase_invoice).items[0].expense_account
-
- gl_entries.append(
- self.get_gl_dict({
- "account": expense_account,
- "credit": self.repair_cost,
- "credit_in_account_currency": self.repair_cost,
- "against": repair_and_maintenance_account,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "cost_center": self.cost_center,
- "posting_date": getdate(),
- "company": self.company
- }, item=self)
+ repair_and_maintenance_account = frappe.db.get_value(
+ "Company", self.company, "repair_and_maintenance_account"
+ )
+ fixed_asset_account = get_asset_account(
+ "fixed_asset_account", asset=self.asset, company=self.company
+ )
+ expense_account = (
+ frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account
)
- if self.get('stock_consumption'):
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": expense_account,
+ "credit": self.repair_cost,
+ "credit_in_account_currency": self.repair_cost,
+ "against": repair_and_maintenance_account,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(),
+ "company": self.company,
+ },
+ item=self,
+ )
+ )
+
+ if self.get("stock_consumption"):
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
- stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
+ stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
for item in stock_entry.items:
gl_entries.append(
- self.get_gl_dict({
- "account": item.expense_account,
- "credit": item.amount,
- "credit_in_account_currency": item.amount,
- "against": repair_and_maintenance_account,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "cost_center": self.cost_center,
- "posting_date": getdate(),
- "company": self.company
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": item.expense_account,
+ "credit": item.amount,
+ "credit_in_account_currency": item.amount,
+ "against": repair_and_maintenance_account,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(),
+ "company": self.company,
+ },
+ item=self,
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": fixed_asset_account,
- "debit": self.total_repair_cost,
- "debit_in_account_currency": self.total_repair_cost,
- "against": expense_account,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "cost_center": self.cost_center,
- "posting_date": getdate(),
- "against_voucher_type": "Purchase Invoice",
- "against_voucher": self.purchase_invoice,
- "company": self.company
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": fixed_asset_account,
+ "debit": self.total_repair_cost,
+ "debit_in_account_currency": self.total_repair_cost,
+ "against": expense_account,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(),
+ "against_voucher_type": "Purchase Invoice",
+ "against_voucher": self.purchase_invoice,
+ "company": self.company,
+ },
+ item=self,
+ )
)
return gl_entries
def modify_depreciation_schedule(self):
for row in self.asset_doc.finance_books:
- row.total_number_of_depreciations += self.increase_in_asset_life/row.frequency_of_depreciation
+ row.total_number_of_depreciations += self.increase_in_asset_life / row.frequency_of_depreciation
self.asset_doc.flags.increase_in_asset_life = False
extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
@@ -207,26 +234,29 @@ class AssetRepair(AccountsController):
# to help modify depreciation schedule when increase_in_asset_life is not a multiple of frequency_of_depreciation
def calculate_last_schedule_date(self, asset, row, extra_months):
asset.flags.increase_in_asset_life = True
- number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
- cint(asset.number_of_depreciations_booked)
+ number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
+ asset.number_of_depreciations_booked
+ )
# the Schedule Date in the final row of the old Depreciation Schedule
- last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
+ last_schedule_date = asset.schedules[len(asset.schedules) - 1].schedule_date
# the Schedule Date in the final row of the new Depreciation Schedule
asset.to_date = add_months(last_schedule_date, extra_months)
# the latest possible date at which the depreciation can occur, without increasing the Total Number of Depreciations
# if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
- schedule_date = add_months(row.depreciation_start_date,
- number_of_pending_depreciations * cint(row.frequency_of_depreciation))
+ schedule_date = add_months(
+ row.depreciation_start_date,
+ number_of_pending_depreciations * cint(row.frequency_of_depreciation),
+ )
if asset.to_date > schedule_date:
row.total_number_of_depreciations += 1
def revert_depreciation_schedule_on_cancellation(self):
for row in self.asset_doc.finance_books:
- row.total_number_of_depreciations -= self.increase_in_asset_life/row.frequency_of_depreciation
+ row.total_number_of_depreciations -= self.increase_in_asset_life / row.frequency_of_depreciation
self.asset_doc.flags.increase_in_asset_life = False
extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
@@ -235,23 +265,27 @@ class AssetRepair(AccountsController):
def calculate_last_schedule_date_before_modification(self, asset, row, extra_months):
asset.flags.increase_in_asset_life = True
- number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
- cint(asset.number_of_depreciations_booked)
+ number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
+ asset.number_of_depreciations_booked
+ )
# the Schedule Date in the final row of the modified Depreciation Schedule
- last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
+ last_schedule_date = asset.schedules[len(asset.schedules) - 1].schedule_date
# the Schedule Date in the final row of the original Depreciation Schedule
asset.to_date = add_months(last_schedule_date, -extra_months)
# the latest possible date at which the depreciation can occur, without decreasing the Total Number of Depreciations
# if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
- schedule_date = add_months(row.depreciation_start_date,
- (number_of_pending_depreciations - 1) * cint(row.frequency_of_depreciation))
+ schedule_date = add_months(
+ row.depreciation_start_date,
+ (number_of_pending_depreciations - 1) * cint(row.frequency_of_depreciation),
+ )
if asset.to_date < schedule_date:
row.total_number_of_depreciations -= 1
+
@frappe.whitelist()
def get_downtime(failure_date, completion_date):
downtime = time_diff_in_hours(completion_date, failure_date)
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
index 7c0d05748e..4e7cf78090 100644
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
@@ -25,7 +25,7 @@ class TestAssetRepair(unittest.TestCase):
def test_update_status(self):
asset = create_asset(submit=1)
initial_status = asset.status
- asset_repair = create_asset_repair(asset = asset)
+ asset_repair = create_asset_repair(asset=asset)
if asset_repair.repair_status == "Pending":
asset.reload()
@@ -37,14 +37,14 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(asset_status, initial_status)
def test_stock_item_total_value(self):
- asset_repair = create_asset_repair(stock_consumption = 1)
+ asset_repair = create_asset_repair(stock_consumption=1)
for item in asset_repair.stock_items:
total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
self.assertEqual(item.total_value, total_value)
def test_total_repair_cost(self):
- asset_repair = create_asset_repair(stock_consumption = 1)
+ asset_repair = create_asset_repair(stock_consumption=1)
total_repair_cost = asset_repair.repair_cost
self.assertEqual(total_repair_cost, asset_repair.repair_cost)
@@ -54,22 +54,22 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(total_repair_cost, asset_repair.total_repair_cost)
def test_repair_status_after_submit(self):
- asset_repair = create_asset_repair(submit = 1)
+ asset_repair = create_asset_repair(submit=1)
self.assertNotEqual(asset_repair.repair_status, "Pending")
def test_stock_items(self):
- asset_repair = create_asset_repair(stock_consumption = 1)
+ asset_repair = create_asset_repair(stock_consumption=1)
self.assertTrue(asset_repair.stock_consumption)
self.assertTrue(asset_repair.stock_items)
def test_warehouse(self):
- asset_repair = create_asset_repair(stock_consumption = 1)
+ asset_repair = create_asset_repair(stock_consumption=1)
self.assertTrue(asset_repair.stock_consumption)
self.assertTrue(asset_repair.warehouse)
def test_decrease_stock_quantity(self):
- asset_repair = create_asset_repair(stock_consumption = 1, submit = 1)
- stock_entry = frappe.get_last_doc('Stock Entry')
+ asset_repair = create_asset_repair(stock_consumption=1, submit=1)
+ stock_entry = frappe.get_last_doc("Stock Entry")
self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
@@ -85,58 +85,72 @@ class TestAssetRepair(unittest.TestCase):
serial_no = serial_nos.split("\n")[0]
# should not raise any error
- create_asset_repair(stock_consumption = 1, item_code = stock_entry.get("items")[0].item_code,
- warehouse = "_Test Warehouse - _TC", serial_no = serial_no, submit = 1)
+ create_asset_repair(
+ stock_consumption=1,
+ item_code=stock_entry.get("items")[0].item_code,
+ warehouse="_Test Warehouse - _TC",
+ serial_no=serial_no,
+ submit=1,
+ )
# should raise error
- asset_repair = create_asset_repair(stock_consumption = 1, warehouse = "_Test Warehouse - _TC",
- item_code = stock_entry.get("items")[0].item_code)
+ asset_repair = create_asset_repair(
+ stock_consumption=1,
+ warehouse="_Test Warehouse - _TC",
+ item_code=stock_entry.get("items")[0].item_code,
+ )
asset_repair.repair_status = "Completed"
self.assertRaises(SerialNoRequiredError, asset_repair.submit)
def test_increase_in_asset_value_due_to_stock_consumption(self):
- asset = create_asset(calculate_depreciation = 1, submit=1)
+ asset = create_asset(calculate_depreciation=1, submit=1)
initial_asset_value = get_asset_value(asset)
- asset_repair = create_asset_repair(asset= asset, stock_consumption = 1, submit = 1)
+ asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1)
asset.reload()
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
- asset = create_asset(calculate_depreciation = 1, submit=1)
+ asset = create_asset(calculate_depreciation=1, submit=1)
initial_asset_value = get_asset_value(asset)
- asset_repair = create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
+ asset_repair = create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
asset.reload()
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
def test_purchase_invoice(self):
- asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
+ asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
self.assertTrue(asset_repair.purchase_invoice)
def test_gl_entries(self):
- asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
- gl_entry = frappe.get_last_doc('GL Entry')
+ asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
+ gl_entry = frappe.get_last_doc("GL Entry")
self.assertEqual(asset_repair.name, gl_entry.voucher_no)
def test_increase_in_asset_life(self):
- asset = create_asset(calculate_depreciation = 1, submit=1)
+ asset = create_asset(calculate_depreciation=1, submit=1)
initial_num_of_depreciations = num_of_depreciations(asset)
- create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
+ create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
asset.reload()
self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset))
- self.assertEqual(asset.schedules[-1].accumulated_depreciation_amount, asset.finance_books[0].value_after_depreciation)
+ self.assertEqual(
+ asset.schedules[-1].accumulated_depreciation_amount,
+ asset.finance_books[0].value_after_depreciation,
+ )
+
def get_asset_value(asset):
return asset.finance_books[0].value_after_depreciation
+
def num_of_depreciations(asset):
return asset.finance_books[0].total_number_of_depreciations
+
def create_asset_repair(**args):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -146,26 +160,33 @@ def create_asset_repair(**args):
if args.asset:
asset = args.asset
else:
- asset = create_asset(is_existing_asset = 1, submit=1)
+ asset = create_asset(is_existing_asset=1, submit=1)
asset_repair = frappe.new_doc("Asset Repair")
- asset_repair.update({
- "asset": asset.name,
- "asset_name": asset.asset_name,
- "failure_date": nowdate(),
- "description": "Test Description",
- "repair_cost": 0,
- "company": asset.company
- })
+ asset_repair.update(
+ {
+ "asset": asset.name,
+ "asset_name": asset.asset_name,
+ "failure_date": nowdate(),
+ "description": "Test Description",
+ "repair_cost": 0,
+ "company": asset.company,
+ }
+ )
if args.stock_consumption:
asset_repair.stock_consumption = 1
- asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company = asset.company)
- asset_repair.append("stock_items", {
- "item_code": args.item_code or "_Test Stock Item",
- "valuation_rate": args.rate if args.get("rate") is not None else 100,
- "consumed_quantity": args.qty or 1,
- "serial_no": args.serial_no
- })
+ asset_repair.warehouse = args.warehouse or create_warehouse(
+ "Test Warehouse", company=asset.company
+ )
+ asset_repair.append(
+ "stock_items",
+ {
+ "item_code": args.item_code or "_Test Stock Item",
+ "valuation_rate": args.rate if args.get("rate") is not None else 100,
+ "consumed_quantity": args.qty or 1,
+ "serial_no": args.serial_no,
+ },
+ )
asset_repair.insert(ignore_if_duplicate=True)
@@ -174,16 +195,17 @@ def create_asset_repair(**args):
asset_repair.cost_center = "_Test Cost Center - _TC"
if args.stock_consumption:
- stock_entry = frappe.get_doc({
- "doctype": "Stock Entry",
- "stock_entry_type": "Material Receipt",
- "company": asset.company
- })
- stock_entry.append('items', {
- "t_warehouse": asset_repair.warehouse,
- "item_code": asset_repair.stock_items[0].item_code,
- "qty": asset_repair.stock_items[0].consumed_quantity
- })
+ stock_entry = frappe.get_doc(
+ {"doctype": "Stock Entry", "stock_entry_type": "Material Receipt", "company": asset.company}
+ )
+ stock_entry.append(
+ "items",
+ {
+ "t_warehouse": asset_repair.warehouse,
+ "item_code": asset_repair.stock_items[0].item_code,
+ "qty": asset_repair.stock_items[0].consumed_quantity,
+ },
+ )
stock_entry.submit()
if args.capitalize_repair_cost:
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 0b646ed4ed..9953c61a81 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -31,10 +31,14 @@ class AssetValueAdjustment(Document):
self.reschedule_depreciations(self.current_asset_value)
def validate_date(self):
- asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
+ asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date")
if getdate(self.date) < getdate(asset_purchase_date):
- frappe.throw(_("Asset Value Adjustment cannot be posted before Asset's purchase date {0}.")
- .format(formatdate(asset_purchase_date)), title="Incorrect Date")
+ frappe.throw(
+ _("Asset Value Adjustment cannot be posted before Asset's purchase date {0}.").format(
+ formatdate(asset_purchase_date)
+ ),
+ title="Incorrect Date",
+ )
def set_difference_amount(self):
self.difference_amount = flt(self.current_asset_value - self.new_asset_value)
@@ -45,11 +49,15 @@ class AssetValueAdjustment(Document):
def make_depreciation_entry(self):
asset = frappe.get_doc("Asset", self.asset)
- fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \
- get_depreciation_accounts(asset)
+ (
+ fixed_asset_account,
+ accumulated_depreciation_account,
+ depreciation_expense_account,
+ ) = get_depreciation_accounts(asset)
- depreciation_cost_center, depreciation_series = frappe.get_cached_value('Company', asset.company,
- ["depreciation_cost_center", "series_for_depreciation_entry"])
+ depreciation_cost_center, depreciation_series = frappe.get_cached_value(
+ "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
+ )
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Depreciation Entry"
@@ -62,27 +70,33 @@ class AssetValueAdjustment(Document):
credit_entry = {
"account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount,
- "cost_center": depreciation_cost_center or self.cost_center
+ "cost_center": depreciation_cost_center or self.cost_center,
}
debit_entry = {
"account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount,
- "cost_center": depreciation_cost_center or self.cost_center
+ "cost_center": depreciation_cost_center or self.cost_center,
}
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
for dimension in accounting_dimensions:
- if dimension.get('mandatory_for_bs'):
- credit_entry.update({
- dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
- })
+ if dimension.get("mandatory_for_bs"):
+ credit_entry.update(
+ {
+ dimension["fieldname"]: self.get(dimension["fieldname"])
+ or dimension.get("default_dimension")
+ }
+ )
- if dimension.get('mandatory_for_pl'):
- debit_entry.update({
- dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
- })
+ if dimension.get("mandatory_for_pl"):
+ debit_entry.update(
+ {
+ dimension["fieldname"]: self.get(dimension["fieldname"])
+ or dimension.get("default_dimension")
+ }
+ )
je.append("accounts", credit_entry)
je.append("accounts", debit_entry)
@@ -93,8 +107,8 @@ class AssetValueAdjustment(Document):
self.db_set("journal_entry", je.name)
def reschedule_depreciations(self, asset_value):
- asset = frappe.get_doc('Asset', self.asset)
- country = frappe.get_value('Company', self.company, 'country')
+ asset = frappe.get_doc("Asset", self.asset)
+ country = frappe.get_value("Company", self.company, "country")
for d in asset.finance_books:
d.value_after_depreciation = asset_value
@@ -105,8 +119,11 @@ class AssetValueAdjustment(Document):
rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
from_date = self.date
else:
- no_of_depreciations = len([s.name for s in asset.schedules
- if (cint(s.finance_book_id) == d.idx and not s.journal_entry)])
+ no_of_depreciations = len(
+ [
+ s.name for s in asset.schedules if (cint(s.finance_book_id) == d.idx and not s.journal_entry)
+ ]
+ )
value_after_depreciation = d.value_after_depreciation
for data in asset.schedules:
@@ -132,10 +149,11 @@ class AssetValueAdjustment(Document):
if not asset_data.journal_entry:
asset_data.db_update()
+
@frappe.whitelist()
def get_current_asset_value(asset, finance_book=None):
- cond = {'parent': asset, 'parenttype': 'Asset'}
+ cond = {"parent": asset, "parenttype": "Asset"}
if finance_book:
- cond.update({'finance_book': finance_book})
+ cond.update({"finance_book": finance_book})
- return frappe.db.get_value('Asset Finance Book', cond, 'value_after_depreciation')
+ return frappe.db.get_value("Asset Finance Book", cond, "value_after_depreciation")
diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
index ef13c5617f..ebeb174d13 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
@@ -18,11 +18,12 @@ class TestAssetValueAdjustment(unittest.TestCase):
create_asset_data()
def test_current_asset_value(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+ )
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset_doc = frappe.get_doc('Asset', asset_name)
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset_doc = frappe.get_doc("Asset", asset_name)
month_end_date = get_last_day(nowdate())
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
@@ -30,24 +31,28 @@ class TestAssetValueAdjustment(unittest.TestCase):
asset_doc.available_for_use_date = purchase_date
asset_doc.purchase_date = purchase_date
asset_doc.calculate_depreciation = 1
- asset_doc.append("finance_books", {
- "expected_value_after_useful_life": 200,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": month_end_date
- })
+ asset_doc.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 200,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ "depreciation_start_date": month_end_date,
+ },
+ )
asset_doc.submit()
current_value = get_current_asset_value(asset_doc.name)
self.assertEqual(current_value, 100000.0)
def test_asset_depreciation_value_adjustment(self):
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+ )
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
- asset_doc = frappe.get_doc('Asset', asset_name)
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset_doc = frappe.get_doc("Asset", asset_name)
asset_doc.calculate_depreciation = 1
month_end_date = get_last_day(nowdate())
@@ -56,42 +61,52 @@ class TestAssetValueAdjustment(unittest.TestCase):
asset_doc.available_for_use_date = purchase_date
asset_doc.purchase_date = purchase_date
asset_doc.calculate_depreciation = 1
- asset_doc.append("finance_books", {
- "expected_value_after_useful_life": 200,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": month_end_date
- })
+ asset_doc.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 200,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ "depreciation_start_date": month_end_date,
+ },
+ )
asset_doc.submit()
current_value = get_current_asset_value(asset_doc.name)
- adj_doc = make_asset_value_adjustment(asset = asset_doc.name,
- current_asset_value = current_value, new_asset_value = 50000.0)
+ adj_doc = make_asset_value_adjustment(
+ asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
+ )
adj_doc.submit()
expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 50000.0),
- ("_Test Depreciations - _TC", 50000.0, 0.0)
+ ("_Test Depreciations - _TC", 50000.0, 0.0),
)
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ gle = frappe.db.sql(
+ """select account, debit, credit from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_no = %s
- order by account""", adj_doc.journal_entry)
+ order by account""",
+ adj_doc.journal_entry,
+ )
self.assertEqual(gle, expected_gle)
+
def make_asset_value_adjustment(**args):
args = frappe._dict(args)
- doc = frappe.get_doc({
- "doctype": "Asset Value Adjustment",
- "company": args.company or "_Test Company",
- "asset": args.asset,
- "date": args.date or nowdate(),
- "new_asset_value": args.new_asset_value,
- "current_asset_value": args.current_asset_value,
- "cost_center": args.cost_center or "Main - _TC"
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Asset Value Adjustment",
+ "company": args.company or "_Test Company",
+ "asset": args.asset,
+ "date": args.date or nowdate(),
+ "new_asset_value": args.new_asset_value,
+ "current_asset_value": args.current_asset_value,
+ "cost_center": args.cost_center or "Main - _TC",
+ }
+ ).insert()
return doc
diff --git a/erpnext/assets/doctype/location/location.py b/erpnext/assets/doctype/location/location.py
index abc7325cf6..0d87bb2bf4 100644
--- a/erpnext/assets/doctype/location/location.py
+++ b/erpnext/assets/doctype/location/location.py
@@ -13,12 +13,12 @@ EARTH_RADIUS = 6378137
class Location(NestedSet):
- nsm_parent_field = 'parent_location'
+ nsm_parent_field = "parent_location"
def validate(self):
self.calculate_location_area()
- if not self.is_new() and self.get('parent_location'):
+ if not self.is_new() and self.get("parent_location"):
self.update_ancestor_location_features()
def on_update(self):
@@ -42,7 +42,7 @@ class Location(NestedSet):
if not self.location:
return []
- features = json.loads(self.location).get('features')
+ features = json.loads(self.location).get("features")
if not isinstance(features, list):
features = json.loads(features)
@@ -54,15 +54,15 @@ class Location(NestedSet):
self.location = '{"type":"FeatureCollection","features":[]}'
location = json.loads(self.location)
- location['features'] = features
+ location["features"] = features
- self.db_set('location', json.dumps(location), commit=True)
+ self.db_set("location", json.dumps(location), commit=True)
def update_ancestor_location_features(self):
self_features = set(self.add_child_property())
for ancestor in self.get_ancestors():
- ancestor_doc = frappe.get_doc('Location', ancestor)
+ ancestor_doc = frappe.get_doc("Location", ancestor)
child_features, ancestor_features = ancestor_doc.feature_seperator(child_feature=self.name)
ancestor_features = list(set(ancestor_features))
@@ -84,25 +84,27 @@ class Location(NestedSet):
ancestor_features[index] = json.loads(feature)
ancestor_doc.set_location_features(features=ancestor_features)
- ancestor_doc.db_set('area', ancestor_doc.area + self.area_difference, commit=True)
+ ancestor_doc.db_set("area", ancestor_doc.area + self.area_difference, commit=True)
def remove_ancestor_location_features(self):
for ancestor in self.get_ancestors():
- ancestor_doc = frappe.get_doc('Location', ancestor)
+ ancestor_doc = frappe.get_doc("Location", ancestor)
child_features, ancestor_features = ancestor_doc.feature_seperator(child_feature=self.name)
for index, feature in enumerate(ancestor_features):
ancestor_features[index] = json.loads(feature)
ancestor_doc.set_location_features(features=ancestor_features)
- ancestor_doc.db_set('area', ancestor_doc.area - self.area, commit=True)
+ ancestor_doc.db_set("area", ancestor_doc.area - self.area, commit=True)
def add_child_property(self):
features = self.get_location_features()
- filter_features = [feature for feature in features if not feature.get('properties').get('child_feature')]
+ filter_features = [
+ feature for feature in features if not feature.get("properties").get("child_feature")
+ ]
for index, feature in enumerate(filter_features):
- feature['properties'].update({'child_feature': True, 'feature_of': self.location_name})
+ feature["properties"].update({"child_feature": True, "feature_of": self.location_name})
filter_features[index] = json.dumps(filter_features[index])
return filter_features
@@ -112,7 +114,7 @@ class Location(NestedSet):
features = self.get_location_features()
for feature in features:
- if feature.get('properties').get('feature_of') == child_feature:
+ if feature.get("properties").get("feature_of") == child_feature:
child_features.extend([json.dumps(feature)])
else:
non_child_features.extend([json.dumps(feature)])
@@ -126,22 +128,22 @@ def compute_area(features):
Reference from https://github.com/scisco/area.
Args:
- `features` (list of dict): Features marked on the map as
- GeoJSON data
+ `features` (list of dict): Features marked on the map as
+ GeoJSON data
Returns:
- float: The approximate signed geodesic area (in sq. meters)
+ float: The approximate signed geodesic area (in sq. meters)
"""
layer_area = 0.0
for feature in features:
- feature_type = feature.get('geometry', {}).get('type')
+ feature_type = feature.get("geometry", {}).get("type")
- if feature_type == 'Polygon':
- layer_area += _polygon_area(coords=feature.get('geometry').get('coordinates'))
- elif feature_type == 'Point' and feature.get('properties').get('point_type') == 'circle':
- layer_area += math.pi * math.pow(feature.get('properties').get('radius'), 2)
+ if feature_type == "Polygon":
+ layer_area += _polygon_area(coords=feature.get("geometry").get("coordinates"))
+ elif feature_type == "Point" and feature.get("properties").get("point_type") == "circle":
+ layer_area += math.pi * math.pow(feature.get("properties").get("radius"), 2)
return layer_area
@@ -192,7 +194,8 @@ def get_children(doctype, parent=None, location=None, is_root=False):
if parent is None or parent == "All Locations":
parent = ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
name as value,
is_group as expandable
@@ -201,17 +204,20 @@ def get_children(doctype, parent=None, location=None, is_root=False):
where
ifnull(parent_location, "")={parent}
""".format(
- doctype=doctype,
- parent=frappe.db.escape(parent)
- ), as_dict=1)
+ doctype=doctype, parent=frappe.db.escape(parent)
+ ),
+ as_dict=1,
+ )
+
@frappe.whitelist()
def add_node():
from frappe.desk.treeview import make_tree_args
+
args = frappe.form_dict
args = make_tree_args(**args)
- if args.parent_location == 'All Locations':
+ if args.parent_location == "All Locations":
args.parent_location = None
frappe.get_doc(args).insert()
diff --git a/erpnext/assets/doctype/location/test_location.py b/erpnext/assets/doctype/location/test_location.py
index 36e1dd4ce4..b8563cb0a2 100644
--- a/erpnext/assets/doctype/location/test_location.py
+++ b/erpnext/assets/doctype/location/test_location.py
@@ -6,29 +6,34 @@ import unittest
import frappe
-test_records = frappe.get_test_records('Location')
+test_records = frappe.get_test_records("Location")
+
class TestLocation(unittest.TestCase):
def runTest(self):
- locations = ['Basil Farm', 'Division 1', 'Field 1', 'Block 1']
+ locations = ["Basil Farm", "Division 1", "Field 1", "Block 1"]
area = 0
formatted_locations = []
for location in locations:
- doc = frappe.get_doc('Location', location)
+ doc = frappe.get_doc("Location", location)
doc.save()
area += doc.area
temp = json.loads(doc.location)
- temp['features'][0]['properties']['child_feature'] = True
- temp['features'][0]['properties']['feature_of'] = location
- formatted_locations.extend(temp['features'])
+ temp["features"][0]["properties"]["child_feature"] = True
+ temp["features"][0]["properties"]["feature_of"] = location
+ formatted_locations.extend(temp["features"])
- test_location = frappe.get_doc('Location', 'Test Location Area')
+ test_location = frappe.get_doc("Location", "Test Location Area")
test_location.save()
- test_location_features = json.loads(test_location.get('location'))['features']
- ordered_test_location_features = sorted(test_location_features, key=lambda x: x['properties']['feature_of'])
- ordered_formatted_locations = sorted(formatted_locations, key=lambda x: x['properties']['feature_of'])
+ test_location_features = json.loads(test_location.get("location"))["features"]
+ ordered_test_location_features = sorted(
+ test_location_features, key=lambda x: x["properties"]["feature_of"]
+ )
+ ordered_formatted_locations = sorted(
+ formatted_locations, key=lambda x: x["properties"]["feature_of"]
+ )
self.assertEqual(ordered_formatted_locations, ordered_test_location_features)
- self.assertEqual(area, test_location.get('area'))
+ self.assertEqual(area, test_location.get("area"))
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index db513364f4..6b14dce084 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -17,16 +17,21 @@ def execute(filters=None):
filters = frappe._dict(filters or {})
columns = get_columns(filters)
data = get_data(filters)
- chart = prepare_chart_data(data, filters) if filters.get("group_by") not in ("Asset Category", "Location") else {}
+ chart = (
+ prepare_chart_data(data, filters)
+ if filters.get("group_by") not in ("Asset Category", "Location")
+ else {}
+ )
return columns, data, None, chart
+
def get_conditions(filters):
- conditions = { 'docstatus': 1 }
+ conditions = {"docstatus": 1}
status = filters.status
date_field = frappe.scrub(filters.date_based_on or "Purchase Date")
- if filters.get('company'):
+ if filters.get("company"):
conditions["company"] = filters.company
if filters.filter_based_on == "Date Range":
conditions[date_field] = ["between", [filters.from_date, filters.to_date]]
@@ -37,23 +42,24 @@ def get_conditions(filters):
filters.year_end_date = getdate(fiscal_year.year_end_date)
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
- if filters.get('is_existing_asset'):
- conditions["is_existing_asset"] = filters.get('is_existing_asset')
- if filters.get('asset_category'):
- conditions["asset_category"] = filters.get('asset_category')
- if filters.get('cost_center'):
- conditions["cost_center"] = filters.get('cost_center')
+ if filters.get("is_existing_asset"):
+ conditions["is_existing_asset"] = filters.get("is_existing_asset")
+ if filters.get("asset_category"):
+ conditions["asset_category"] = filters.get("asset_category")
+ if filters.get("cost_center"):
+ conditions["cost_center"] = filters.get("cost_center")
if status:
# In Store assets are those that are not sold or scrapped
- operand = 'not in'
- if status not in 'In Location':
- operand = 'in'
+ operand = "not in"
+ if status not in "In Location":
+ operand = "in"
- conditions['status'] = (operand, ['Sold', 'Scrapped'])
+ conditions["status"] = (operand, ["Sold", "Scrapped"])
return conditions
+
def get_data(filters):
data = []
@@ -74,21 +80,37 @@ def get_data(filters):
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields, group_by=group_by)
else:
- fields = ["name as asset_id", "asset_name", "status", "department", "cost_center", "purchase_receipt",
- "asset_category", "purchase_date", "gross_purchase_amount", "location",
- "available_for_use_date", "purchase_invoice", "opening_accumulated_depreciation"]
+ fields = [
+ "name as asset_id",
+ "asset_name",
+ "status",
+ "department",
+ "cost_center",
+ "purchase_receipt",
+ "asset_category",
+ "purchase_date",
+ "gross_purchase_amount",
+ "location",
+ "available_for_use_date",
+ "purchase_invoice",
+ "opening_accumulated_depreciation",
+ ]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
for asset in assets_record:
- asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
+ asset_value = (
+ asset.gross_purchase_amount
+ - flt(asset.opening_accumulated_depreciation)
- flt(depreciation_amount_map.get(asset.name))
+ )
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
"cost_center": asset.cost_center,
- "vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
+ "vendor_name": pr_supplier_map.get(asset.purchase_receipt)
+ or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
@@ -96,21 +118,31 @@ def get_data(filters):
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
- "asset_value": asset_value
+ "asset_value": asset_value,
}
data.append(row)
return data
+
def prepare_chart_data(data, filters):
labels_values_map = {}
date_field = frappe.scrub(filters.date_based_on)
- period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
- filters.from_date, filters.to_date, filters.filter_based_on, "Monthly", company=filters.company)
+ period_list = get_period_list(
+ filters.from_fiscal_year,
+ filters.to_fiscal_year,
+ filters.from_date,
+ filters.to_date,
+ filters.filter_based_on,
+ "Monthly",
+ company=filters.company,
+ )
for d in period_list:
- labels_values_map.setdefault(d.get('label'), frappe._dict({'asset_value': 0, 'depreciated_amount': 0}))
+ labels_values_map.setdefault(
+ d.get("label"), frappe._dict({"asset_value": 0, "depreciated_amount": 0})
+ )
for d in data:
date = d.get(date_field)
@@ -120,23 +152,30 @@ def prepare_chart_data(data, filters):
labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount")
return {
- "data" : {
+ "data": {
"labels": labels_values_map.keys(),
"datasets": [
- { 'name': _('Asset Value'), 'values': [d.get("asset_value") for d in labels_values_map.values()] },
- { 'name': _('Depreciatied Amount'), 'values': [d.get("depreciated_amount") for d in labels_values_map.values()] }
- ]
+ {
+ "name": _("Asset Value"),
+ "values": [d.get("asset_value") for d in labels_values_map.values()],
+ },
+ {
+ "name": _("Depreciatied Amount"),
+ "values": [d.get("depreciated_amount") for d in labels_values_map.values()],
+ },
+ ],
},
"type": "bar",
- "barOptions": {
- "stacked": 1
- },
+ "barOptions": {"stacked": 1},
}
+
def get_finance_book_value_map(filters):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
- return frappe._dict(frappe.db.sql(''' Select
+ return frappe._dict(
+ frappe.db.sql(
+ """ Select
parent, SUM(depreciation_amount)
FROM `tabDepreciation Schedule`
WHERE
@@ -144,27 +183,41 @@ def get_finance_book_value_map(filters):
AND schedule_date<=%s
AND journal_entry IS NOT NULL
AND ifnull(finance_book, '')=%s
- GROUP BY parent''', (date, cstr(filters.finance_book or ''))))
+ GROUP BY parent""",
+ (date, cstr(filters.finance_book or "")),
+ )
+ )
+
def get_purchase_receipt_supplier_map():
- return frappe._dict(frappe.db.sql(''' Select
+ return frappe._dict(
+ frappe.db.sql(
+ """ Select
pr.name, pr.supplier
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri
WHERE
pri.parent = pr.name
AND pri.is_fixed_asset=1
AND pr.docstatus=1
- AND pr.is_return=0'''))
+ AND pr.is_return=0"""
+ )
+ )
+
def get_purchase_invoice_supplier_map():
- return frappe._dict(frappe.db.sql(''' Select
+ return frappe._dict(
+ frappe.db.sql(
+ """ Select
pi.name, pi.supplier
FROM `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pii
WHERE
pii.parent = pi.name
AND pii.is_fixed_asset=1
AND pi.docstatus=1
- AND pi.is_return=0'''))
+ AND pi.is_return=0"""
+ )
+ )
+
def get_columns(filters):
if filters.get("group_by") in ["Asset Category", "Location"]:
@@ -174,36 +227,36 @@ def get_columns(filters):
"fieldtype": "Link",
"fieldname": frappe.scrub(filters.get("group_by")),
"options": filters.get("group_by"),
- "width": 120
+ "width": 120,
},
{
"label": _("Gross Purchase Amount"),
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 100
+ "width": 100,
},
{
"label": _("Opening Accumulated Depreciation"),
"fieldname": "opening_accumulated_depreciation",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 90
+ "width": 90,
},
{
"label": _("Depreciated Amount"),
"fieldname": "depreciated_amount",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 100
+ "width": 100,
},
{
"label": _("Asset Value"),
"fieldname": "asset_value",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 100
- }
+ "width": 100,
+ },
]
return [
@@ -212,92 +265,72 @@ def get_columns(filters):
"fieldtype": "Link",
"fieldname": "asset_id",
"options": "Asset",
- "width": 60
- },
- {
- "label": _("Asset Name"),
- "fieldtype": "Data",
- "fieldname": "asset_name",
- "width": 140
+ "width": 60,
},
+ {"label": _("Asset Name"), "fieldtype": "Data", "fieldname": "asset_name", "width": 140},
{
"label": _("Asset Category"),
"fieldtype": "Link",
"fieldname": "asset_category",
"options": "Asset Category",
- "width": 100
- },
- {
- "label": _("Status"),
- "fieldtype": "Data",
- "fieldname": "status",
- "width": 80
- },
- {
- "label": _("Purchase Date"),
- "fieldtype": "Date",
- "fieldname": "purchase_date",
- "width": 90
+ "width": 100,
},
+ {"label": _("Status"), "fieldtype": "Data", "fieldname": "status", "width": 80},
+ {"label": _("Purchase Date"), "fieldtype": "Date", "fieldname": "purchase_date", "width": 90},
{
"label": _("Available For Use Date"),
"fieldtype": "Date",
"fieldname": "available_for_use_date",
- "width": 90
+ "width": 90,
},
{
"label": _("Gross Purchase Amount"),
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 100
+ "width": 100,
},
{
"label": _("Asset Value"),
"fieldname": "asset_value",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 100
+ "width": 100,
},
{
"label": _("Opening Accumulated Depreciation"),
"fieldname": "opening_accumulated_depreciation",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 90
+ "width": 90,
},
{
"label": _("Depreciated Amount"),
"fieldname": "depreciated_amount",
"fieldtype": "Currency",
"options": "company:currency",
- "width": 100
+ "width": 100,
},
{
"label": _("Cost Center"),
"fieldtype": "Link",
"fieldname": "cost_center",
"options": "Cost Center",
- "width": 100
+ "width": 100,
},
{
"label": _("Department"),
"fieldtype": "Link",
"fieldname": "department",
"options": "Department",
- "width": 100
- },
- {
- "label": _("Vendor Name"),
- "fieldtype": "Data",
- "fieldname": "vendor_name",
- "width": 100
+ "width": 100,
},
+ {"label": _("Vendor Name"), "fieldtype": "Data", "fieldname": "vendor_name", "width": 100},
{
"label": _("Location"),
"fieldtype": "Link",
"fieldname": "location",
"options": "Location",
- "width": 100
+ "width": 100,
},
]
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
index 92f37f5667..0596be4462 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
@@ -27,7 +27,7 @@ def retry_failing_transaction(log_date=None):
.where(btp.date == log_date)
).run(as_dict=True)
- if data :
+ if data:
if len(data) > 10:
frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
else:
@@ -35,6 +35,7 @@ def retry_failing_transaction(log_date=None):
else:
return "No Failed Records"
+
def job(data, log_date):
for d in data:
failed = []
@@ -51,7 +52,7 @@ def job(data, log_date):
d.to_doctype,
status="Failed",
log_date=log_date,
- restarted=1
+ restarted=1,
)
if not failed:
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
index a78e697b6f..646dba51ce 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
@@ -10,7 +10,6 @@ from erpnext.utilities.bulk_transaction import transaction_processing
class TestBulkTransactionLog(unittest.TestCase):
-
def setUp(self):
create_company()
create_customer()
@@ -19,7 +18,11 @@ class TestBulkTransactionLog(unittest.TestCase):
def test_for_single_record(self):
so_name = create_so()
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
- data = frappe.db.get_list("Sales Invoice", filters = {"posting_date": date.today(), "customer": "Bulk Customer"}, fields=["*"])
+ data = frappe.db.get_list(
+ "Sales Invoice",
+ filters={"posting_date": date.today(), "customer": "Bulk Customer"},
+ fields=["*"],
+ )
if not data:
self.fail("No Sales Invoice Created !")
@@ -36,32 +39,35 @@ class TestBulkTransactionLog(unittest.TestCase):
self.assertEqual(d.retried, 0)
-
def create_company():
- if not frappe.db.exists('Company', '_Test Company'):
- frappe.get_doc({
- 'doctype': 'Company',
- 'company_name': '_Test Company',
- 'country': 'India',
- 'default_currency': 'INR'
- }).insert()
+ if not frappe.db.exists("Company", "_Test Company"):
+ frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": "_Test Company",
+ "country": "India",
+ "default_currency": "INR",
+ }
+ ).insert()
+
def create_customer():
- if not frappe.db.exists('Customer', 'Bulk Customer'):
- frappe.get_doc({
- 'doctype': 'Customer',
- 'customer_name': 'Bulk Customer'
- }).insert()
+ if not frappe.db.exists("Customer", "Bulk Customer"):
+ frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert()
+
def create_item():
if not frappe.db.exists("Item", "MK"):
- frappe.get_doc({
- "doctype": "Item",
- "item_code": "MK",
- "item_name": "Milk",
- "description": "Milk",
- "item_group": "Products"
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": "MK",
+ "item_name": "Milk",
+ "description": "Milk",
+ "item_group": "Products",
+ }
+ ).insert()
+
def create_so(intent=None):
so = frappe.new_doc("Sales Order")
@@ -70,12 +76,15 @@ def create_so(intent=None):
so.transaction_date = date.today()
so.set_warehouse = "Finished Goods - _TC"
- so.append("items", {
- "item_code": "MK",
- "delivery_date": date.today(),
- "qty": 10,
- "rate": 80,
- })
+ so.append(
+ "items",
+ {
+ "item_code": "MK",
+ "delivery_date": date.today(),
+ "qty": 10,
+ "rate": 80,
+ },
+ )
so.insert()
so.submit()
- return so.name
\ No newline at end of file
+ return so.name
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py
index 2b6ff43530..5507254bbc 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.py
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.py
@@ -14,5 +14,10 @@ class BuyingSettings(Document):
frappe.db.set_default(key, self.get(key, ""))
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
- set_by_naming_series("Supplier", "supplier_name",
- self.get("supp_master_name")=="Naming Series", hide_name_field=False)
+
+ set_by_naming_series(
+ "Supplier",
+ "supplier_name",
+ self.get("supp_master_name") == "Naming Series",
+ hide_name_field=False,
+ )
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index f93f9feb88..582bd8d1db 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -26,24 +26,25 @@ from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase
from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
from erpnext.stock.utils import get_bin
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
+
class PurchaseOrder(BuyingController):
def __init__(self, *args, **kwargs):
super(PurchaseOrder, self).__init__(*args, **kwargs)
- self.status_updater = [{
- 'source_dt': 'Purchase Order Item',
- 'target_dt': 'Material Request Item',
- 'join_field': 'material_request_item',
- 'target_field': 'ordered_qty',
- 'target_parent_dt': 'Material Request',
- 'target_parent_field': 'per_ordered',
- 'target_ref_field': 'stock_qty',
- 'source_field': 'stock_qty',
- 'percent_join_field': 'material_request'
- }]
+ self.status_updater = [
+ {
+ "source_dt": "Purchase Order Item",
+ "target_dt": "Material Request Item",
+ "join_field": "material_request_item",
+ "target_field": "ordered_qty",
+ "target_parent_dt": "Material Request",
+ "target_parent_field": "per_ordered",
+ "target_ref_field": "stock_qty",
+ "source_field": "stock_qty",
+ "percent_join_field": "material_request",
+ }
+ ]
def onload(self):
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
@@ -71,35 +72,44 @@ class PurchaseOrder(BuyingController):
self.validate_bom_for_subcontracting_items()
self.create_raw_materials_supplied("supplied_items")
self.set_received_qty_for_drop_ship_items()
- validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_order_reference)
+ validate_inter_company_party(
+ self.doctype, self.supplier, self.company, self.inter_company_order_reference
+ )
self.reset_default_field_value("set_warehouse", "items", "warehouse")
def validate_with_previous_doc(self):
- super(PurchaseOrder, self).validate_with_previous_doc({
- "Supplier Quotation": {
- "ref_dn_field": "supplier_quotation",
- "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
- },
- "Supplier Quotation Item": {
- "ref_dn_field": "supplier_quotation_item",
- "compare_fields": [["project", "="], ["item_code", "="],
- ["uom", "="], ["conversion_factor", "="]],
- "is_child_table": True
- },
- "Material Request": {
- "ref_dn_field": "material_request",
- "compare_fields": [["company", "="]],
- },
- "Material Request Item": {
- "ref_dn_field": "material_request_item",
- "compare_fields": [["project", "="], ["item_code", "="]],
- "is_child_table": True
+ super(PurchaseOrder, self).validate_with_previous_doc(
+ {
+ "Supplier Quotation": {
+ "ref_dn_field": "supplier_quotation",
+ "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
+ },
+ "Supplier Quotation Item": {
+ "ref_dn_field": "supplier_quotation_item",
+ "compare_fields": [
+ ["project", "="],
+ ["item_code", "="],
+ ["uom", "="],
+ ["conversion_factor", "="],
+ ],
+ "is_child_table": True,
+ },
+ "Material Request": {
+ "ref_dn_field": "material_request",
+ "compare_fields": [["company", "="]],
+ },
+ "Material Request Item": {
+ "ref_dn_field": "material_request_item",
+ "compare_fields": [["project", "="], ["item_code", "="]],
+ "is_child_table": True,
+ },
}
- })
+ )
-
- if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
- self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]])
+ if cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")):
+ self.validate_rate_with_reference_doc(
+ [["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]]
+ )
def set_tax_withholding(self):
if not self.apply_tds:
@@ -119,8 +129,11 @@ class PurchaseOrder(BuyingController):
if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("taxes", tax_withholding_details)
- to_remove = [d for d in self.taxes
- if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
+ to_remove = [
+ d
+ for d in self.taxes
+ if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")
+ ]
for d in to_remove:
self.remove(d)
@@ -129,26 +142,43 @@ class PurchaseOrder(BuyingController):
self.calculate_taxes_and_totals()
def validate_supplier(self):
- prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
+ prevent_po = frappe.db.get_value("Supplier", self.supplier, "prevent_pos")
if prevent_po:
- standing = frappe.db.get_value("Supplier Scorecard", self.supplier, 'status')
+ standing = frappe.db.get_value("Supplier Scorecard", self.supplier, "status")
if standing:
- frappe.throw(_("Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.")
- .format(self.supplier, standing))
+ frappe.throw(
+ _("Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.").format(
+ self.supplier, standing
+ )
+ )
- warn_po = frappe.db.get_value("Supplier", self.supplier, 'warn_pos')
+ warn_po = frappe.db.get_value("Supplier", self.supplier, "warn_pos")
if warn_po:
- standing = frappe.db.get_value("Supplier Scorecard",self.supplier, 'status')
- frappe.msgprint(_("{0} currently has a {1} Supplier Scorecard standing, and Purchase Orders to this supplier should be issued with caution.").format(self.supplier, standing), title=_("Caution"), indicator='orange')
+ standing = frappe.db.get_value("Supplier Scorecard", self.supplier, "status")
+ frappe.msgprint(
+ _(
+ "{0} currently has a {1} Supplier Scorecard standing, and Purchase Orders to this supplier should be issued with caution."
+ ).format(self.supplier, standing),
+ title=_("Caution"),
+ indicator="orange",
+ )
self.party_account_currency = get_party_account_currency("Supplier", self.supplier, self.company)
def validate_minimum_order_qty(self):
- if not self.get("items"): return
+ if not self.get("items"):
+ return
items = list(set(d.item_code for d in self.get("items")))
- itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty
- from tabItem where name in ({0})""".format(", ".join(["%s"] * len(items))), items))
+ itemwise_min_order_qty = frappe._dict(
+ frappe.db.sql(
+ """select name, min_order_qty
+ from tabItem where name in ({0})""".format(
+ ", ".join(["%s"] * len(items))
+ ),
+ items,
+ )
+ )
itemwise_qty = frappe._dict()
for d in self.get("items"):
@@ -157,36 +187,43 @@ class PurchaseOrder(BuyingController):
for item_code, qty in itemwise_qty.items():
if flt(qty) < flt(itemwise_min_order_qty.get(item_code)):
- frappe.throw(_("Item {0}: Ordered qty {1} cannot be less than minimum order qty {2} (defined in Item).").format(item_code,
- qty, itemwise_min_order_qty.get(item_code)))
+ frappe.throw(
+ _(
+ "Item {0}: Ordered qty {1} cannot be less than minimum order qty {2} (defined in Item)."
+ ).format(item_code, qty, itemwise_min_order_qty.get(item_code))
+ )
def validate_bom_for_subcontracting_items(self):
if self.is_subcontracted == "Yes":
for item in self.items:
if not item.bom:
- frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}")
- .format(item.item_code, item.idx))
+ frappe.throw(
+ _("BOM is not specified for subcontracting item {0} at row {1}").format(
+ item.item_code, item.idx
+ )
+ )
def get_schedule_dates(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.material_request_item and not d.schedule_date:
- d.schedule_date = frappe.db.get_value("Material Request Item",
- d.material_request_item, "schedule_date")
-
+ d.schedule_date = frappe.db.get_value(
+ "Material Request Item", d.material_request_item, "schedule_date"
+ )
@frappe.whitelist()
def get_last_purchase_rate(self):
"""get last purchase rates for all items"""
- conversion_rate = flt(self.get('conversion_rate')) or 1.0
+ conversion_rate = flt(self.get("conversion_rate")) or 1.0
for d in self.get("items"):
if d.item_code:
last_purchase_details = get_last_purchase_details(d.item_code, self.name)
if last_purchase_details:
- d.base_price_list_rate = (last_purchase_details['base_price_list_rate'] *
- (flt(d.conversion_factor) or 1.0))
- d.discount_percentage = last_purchase_details['discount_percentage']
- d.base_rate = last_purchase_details['base_rate'] * (flt(d.conversion_factor) or 1.0)
+ d.base_price_list_rate = last_purchase_details["base_price_list_rate"] * (
+ flt(d.conversion_factor) or 1.0
+ )
+ d.discount_percentage = last_purchase_details["discount_percentage"]
+ d.base_rate = last_purchase_details["base_rate"] * (flt(d.conversion_factor) or 1.0)
d.price_list_rate = d.base_price_list_rate / conversion_rate
d.rate = d.base_rate / conversion_rate
d.last_purchase_rate = d.rate
@@ -194,16 +231,21 @@ class PurchaseOrder(BuyingController):
item_last_purchase_rate = frappe.get_cached_value("Item", d.item_code, "last_purchase_rate")
if item_last_purchase_rate:
- d.base_price_list_rate = d.base_rate = d.price_list_rate \
- = d.rate = d.last_purchase_rate = item_last_purchase_rate
+ d.base_price_list_rate = (
+ d.base_rate
+ ) = d.price_list_rate = d.rate = d.last_purchase_rate = item_last_purchase_rate
# Check for Closed status
def check_on_hold_or_closed_status(self):
- check_list =[]
- for d in self.get('items'):
- if d.meta.get_field('material_request') and d.material_request and d.material_request not in check_list:
+ check_list = []
+ for d in self.get("items"):
+ if (
+ d.meta.get_field("material_request")
+ and d.material_request
+ and d.material_request not in check_list
+ ):
check_list.append(d.material_request)
- check_on_hold_or_closed_status('Material Request', d.material_request)
+ check_on_hold_or_closed_status("Material Request", d.material_request)
def update_requested_qty(self):
material_request_map = {}
@@ -216,7 +258,9 @@ class PurchaseOrder(BuyingController):
mr_obj = frappe.get_doc("Material Request", mr)
if mr_obj.status in ["Stopped", "Cancelled"]:
- frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError)
+ frappe.throw(
+ _("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError
+ )
mr_obj.update_requested_qty(mr_item_rows)
@@ -224,24 +268,26 @@ class PurchaseOrder(BuyingController):
"""update requested qty (before ordered_qty is updated)"""
item_wh_list = []
for d in self.get("items"):
- if (not po_item_rows or d.name in po_item_rows) \
- and [d.item_code, d.warehouse] not in item_wh_list \
- and frappe.get_cached_value("Item", d.item_code, "is_stock_item") \
- and d.warehouse and not d.delivered_by_supplier:
- item_wh_list.append([d.item_code, d.warehouse])
+ if (
+ (not po_item_rows or d.name in po_item_rows)
+ and [d.item_code, d.warehouse] not in item_wh_list
+ and frappe.get_cached_value("Item", d.item_code, "is_stock_item")
+ and d.warehouse
+ and not d.delivered_by_supplier
+ ):
+ item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
- update_bin_qty(item_code, warehouse, {
- "ordered_qty": get_ordered_qty(item_code, warehouse)
- })
+ update_bin_qty(item_code, warehouse, {"ordered_qty": get_ordered_qty(item_code, warehouse)})
def check_modified_date(self):
- mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",
- self.name)
+ mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s", self.name)
date_diff = frappe.db.sql("select '%s' - '%s' " % (mod_db[0][0], cstr(self.modified)))
if date_diff and date_diff[0][0]:
- msgprint(_("{0} {1} has been modified. Please refresh.").format(self.doctype, self.name),
- raise_exception=True)
+ msgprint(
+ _("{0} {1} has been modified. Please refresh.").format(self.doctype, self.name),
+ raise_exception=True,
+ )
def update_status(self, status):
self.check_modified_date()
@@ -268,8 +314,9 @@ class PurchaseOrder(BuyingController):
if self.is_subcontracted == "Yes":
self.update_reserved_qty_for_subcontract()
- frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
- self.company, self.base_grand_total)
+ frappe.get_doc("Authorization Control").validate_approving_authority(
+ self.doctype, self.company, self.base_grand_total
+ )
self.update_blanket_order()
@@ -289,7 +336,7 @@ class PurchaseOrder(BuyingController):
self.check_on_hold_or_closed_status()
- frappe.db.set(self,'status','Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
self.update_prevdoc_status()
@@ -306,26 +353,30 @@ class PurchaseOrder(BuyingController):
pass
def update_status_updater(self):
- self.status_updater.append({
- 'source_dt': 'Purchase Order Item',
- 'target_dt': 'Sales Order Item',
- 'target_field': 'ordered_qty',
- 'target_parent_dt': 'Sales Order',
- 'target_parent_field': '',
- 'join_field': 'sales_order_item',
- 'target_ref_field': 'stock_qty',
- 'source_field': 'stock_qty'
- })
- self.status_updater.append({
- 'source_dt': 'Purchase Order Item',
- 'target_dt': 'Packed Item',
- 'target_field': 'ordered_qty',
- 'target_parent_dt': 'Sales Order',
- 'target_parent_field': '',
- 'join_field': 'sales_order_packed_item',
- 'target_ref_field': 'qty',
- 'source_field': 'stock_qty'
- })
+ self.status_updater.append(
+ {
+ "source_dt": "Purchase Order Item",
+ "target_dt": "Sales Order Item",
+ "target_field": "ordered_qty",
+ "target_parent_dt": "Sales Order",
+ "target_parent_field": "",
+ "join_field": "sales_order_item",
+ "target_ref_field": "stock_qty",
+ "source_field": "stock_qty",
+ }
+ )
+ self.status_updater.append(
+ {
+ "source_dt": "Purchase Order Item",
+ "target_dt": "Packed Item",
+ "target_field": "ordered_qty",
+ "target_parent_dt": "Sales Order",
+ "target_parent_field": "",
+ "join_field": "sales_order_packed_item",
+ "target_ref_field": "qty",
+ "source_field": "stock_qty",
+ }
+ )
def update_delivered_qty_in_sales_order(self):
"""Update delivered qty in Sales Order for drop ship"""
@@ -364,24 +415,28 @@ class PurchaseOrder(BuyingController):
received_qty += item.received_qty
total_qty += item.qty
if total_qty:
- self.db_set("per_received", flt(received_qty/total_qty) * 100, update_modified=False)
+ self.db_set("per_received", flt(received_qty / total_qty) * 100, update_modified=False)
else:
self.db_set("per_received", 0, update_modified=False)
-def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor= 1.0):
+
+def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
"""get last purchase rate for an item"""
conversion_rate = flt(conversion_rate) or 1.0
- last_purchase_details = get_last_purchase_details(item_code, name)
+ last_purchase_details = get_last_purchase_details(item_code, name)
if last_purchase_details:
- last_purchase_rate = (last_purchase_details['base_net_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate
+ last_purchase_rate = (
+ last_purchase_details["base_net_rate"] * (flt(conversion_factor) or 1.0)
+ ) / conversion_rate
return last_purchase_rate
else:
item_last_purchase_rate = frappe.get_cached_value("Item", item_code, "last_purchase_rate")
if item_last_purchase_rate:
return item_last_purchase_rate
+
@frappe.whitelist()
def close_or_unclose_purchase_orders(names, status):
if not frappe.has_permission("Purchase Order", "write"):
@@ -392,7 +447,7 @@ def close_or_unclose_purchase_orders(names, status):
po = frappe.get_doc("Purchase Order", name)
if po.docstatus == 1:
if status == "Closed":
- if po.status not in ( "Cancelled", "Closed") and (po.per_received < 100 or po.per_billed < 100):
+ if po.status not in ("Cancelled", "Closed") and (po.per_received < 100 or po.per_billed < 100):
po.update_status(status)
else:
if po.status == "Closed":
@@ -401,70 +456,78 @@ def close_or_unclose_purchase_orders(names, status):
frappe.local.message_log = []
+
def set_missing_values(source, target):
target.run_method("set_missing_values")
target.run_method("calculate_taxes_and_totals")
+
@frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None):
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.received_qty)
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
- target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
- flt(obj.rate) * flt(source_parent.conversion_rate)
+ target.base_amount = (
+ (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) * flt(source_parent.conversion_rate)
+ )
- doc = get_mapped_doc("Purchase Order", source_name, {
- "Purchase Order": {
- "doctype": "Purchase Receipt",
- "field_map": {
- "supplier_warehouse":"supplier_warehouse"
+ doc = get_mapped_doc(
+ "Purchase Order",
+ source_name,
+ {
+ "Purchase Order": {
+ "doctype": "Purchase Receipt",
+ "field_map": {"supplier_warehouse": "supplier_warehouse"},
+ "validation": {
+ "docstatus": ["=", 1],
+ },
},
- "validation": {
- "docstatus": ["=", 1],
- }
- },
- "Purchase Order Item": {
- "doctype": "Purchase Receipt Item",
- "field_map": {
- "name": "purchase_order_item",
- "parent": "purchase_order",
- "bom": "bom",
- "material_request": "material_request",
- "material_request_item": "material_request_item"
+ "Purchase Order Item": {
+ "doctype": "Purchase Receipt Item",
+ "field_map": {
+ "name": "purchase_order_item",
+ "parent": "purchase_order",
+ "bom": "bom",
+ "material_request": "material_request",
+ "material_request_item": "material_request_item",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
+ and doc.delivered_by_supplier != 1,
},
- "postprocess": update_item,
- "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+ "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
},
- "Purchase Taxes and Charges": {
- "doctype": "Purchase Taxes and Charges",
- "add_if_empty": True
- }
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
- doc.set_onload('ignore_price_list', True)
+ doc.set_onload("ignore_price_list", True)
return doc
+
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
return get_mapped_purchase_invoice(source_name, target_doc)
+
@frappe.whitelist()
def make_purchase_invoice_from_portal(purchase_order_name):
doc = get_mapped_purchase_invoice(purchase_order_name, ignore_permissions=True)
if doc.contact_email != frappe.session.user:
- frappe.throw(_('Not Permitted'), frappe.PermissionError)
+ frappe.throw(_("Not Permitted"), frappe.PermissionError)
doc.save()
frappe.db.commit()
- frappe.response['type'] = 'redirect'
- frappe.response.location = '/purchase-invoices/' + doc.name
+ frappe.response["type"] = "redirect"
+ frappe.response.location = "/purchase-invoices/" + doc.name
+
def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False):
def postprocess(source, target):
target.flags.ignore_permissions = ignore_permissions
set_missing_values(source, target)
- #Get the advance paid Journal Entries in Purchase Invoice Advance
+ # Get the advance paid Journal Entries in Purchase Invoice Advance
if target.get("allocate_advances_automatically"):
target.set_advances()
@@ -473,26 +536,30 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate)
- target.qty = target.amount / flt(obj.rate) if (flt(obj.rate) and flt(obj.billed_amt)) else flt(obj.qty)
+ target.qty = (
+ target.amount / flt(obj.rate) if (flt(obj.rate) and flt(obj.billed_amt)) else flt(obj.qty)
+ )
item = get_item_defaults(target.item_code, source_parent.company)
item_group = get_item_group_defaults(target.item_code, source_parent.company)
- target.cost_center = (obj.cost_center
+ target.cost_center = (
+ obj.cost_center
or frappe.db.get_value("Project", obj.project, "cost_center")
or item.get("buying_cost_center")
- or item_group.get("buying_cost_center"))
+ or item_group.get("buying_cost_center")
+ )
fields = {
"Purchase Order": {
"doctype": "Purchase Invoice",
"field_map": {
"party_account_currency": "party_account_currency",
- "supplier_warehouse":"supplier_warehouse"
+ "supplier_warehouse": "supplier_warehouse",
},
- "field_no_map" : ["payment_terms_template"],
+ "field_no_map": ["payment_terms_template"],
"validation": {
"docstatus": ["=", 1],
- }
+ },
},
"Purchase Order Item": {
"doctype": "Purchase Invoice Item",
@@ -501,20 +568,24 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"parent": "purchase_order",
},
"postprocess": update_item,
- "condition": lambda doc: (doc.base_amount==0 or abs(doc.billed_amt) < abs(doc.amount))
- },
- "Purchase Taxes and Charges": {
- "doctype": "Purchase Taxes and Charges",
- "add_if_empty": True
+ "condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
},
+ "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
}
- doc = get_mapped_doc("Purchase Order", source_name, fields,
- target_doc, postprocess, ignore_permissions=ignore_permissions)
- doc.set_onload('ignore_price_list', True)
+ doc = get_mapped_doc(
+ "Purchase Order",
+ source_name,
+ fields,
+ target_doc,
+ postprocess,
+ ignore_permissions=ignore_permissions,
+ )
+ doc.set_onload("ignore_price_list", True)
return doc
+
@frappe.whitelist()
def make_rm_stock_entry(purchase_order, rm_items):
rm_items_list = rm_items
@@ -555,14 +626,14 @@ def make_rm_stock_entry(purchase_order, rm_items):
rm_item_code: {
"po_detail": rm_item_data.get("name"),
"item_name": rm_item_data["item_name"],
- "description": item_wh.get(rm_item_code, {}).get('description', ""),
- 'qty': rm_item_data["qty"],
- 'from_warehouse': rm_item_data["warehouse"],
- 'stock_uom': rm_item_data["stock_uom"],
- 'serial_no': rm_item_data.get('serial_no'),
- 'batch_no': rm_item_data.get('batch_no'),
- 'main_item_code': rm_item_data["item_code"],
- 'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
+ "description": item_wh.get(rm_item_code, {}).get("description", ""),
+ "qty": rm_item_data["qty"],
+ "from_warehouse": rm_item_data["warehouse"],
+ "stock_uom": rm_item_data["stock_uom"],
+ "serial_no": rm_item_data.get("serial_no"),
+ "batch_no": rm_item_data.get("batch_no"),
+ "main_item_code": rm_item_data["item_code"],
+ "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
@@ -571,55 +642,72 @@ def make_rm_stock_entry(purchase_order, rm_items):
frappe.throw(_("No Items selected for transfer"))
return purchase_order.name
+
def get_item_details(items):
item_details = {}
- for d in frappe.db.sql("""select item_code, description, allow_alternative_item from `tabItem`
- where name in ({0})""".format(", ".join(["%s"] * len(items))), items, as_dict=1):
+ for d in frappe.db.sql(
+ """select item_code, description, allow_alternative_item from `tabItem`
+ where name in ({0})""".format(
+ ", ".join(["%s"] * len(items))
+ ),
+ items,
+ as_dict=1,
+ ):
item_details[d.item_code] = d
return item_details
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
+
list_context = get_list_context(context)
- list_context.update({
- 'show_sidebar': True,
- 'show_search': True,
- 'no_breadcrumbs': True,
- 'title': _('Purchase Orders'),
- })
+ list_context.update(
+ {
+ "show_sidebar": True,
+ "show_search": True,
+ "no_breadcrumbs": True,
+ "title": _("Purchase Orders"),
+ }
+ )
return list_context
+
@frappe.whitelist()
def update_status(status, name):
po = frappe.get_doc("Purchase Order", name)
po.update_status(status)
po.update_delivered_qty_in_sales_order()
+
@frappe.whitelist()
def make_inter_company_sales_order(source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
+
return make_inter_company_transaction("Purchase Order", source_name, target_doc)
+
@frappe.whitelist()
def get_materials_from_supplier(purchase_order, po_details):
if isinstance(po_details, str):
po_details = json.loads(po_details)
- doc = frappe.get_cached_doc('Purchase Order', purchase_order)
+ doc = frappe.get_cached_doc("Purchase Order", purchase_order)
doc.initialized_fields()
doc.purchase_orders = [doc.name]
doc.get_available_materials()
if not doc.available_materials:
- frappe.throw(_('Materials are already received against the purchase order {0}')
- .format(purchase_order))
+ frappe.throw(
+ _("Materials are already received against the purchase order {0}").format(purchase_order)
+ )
return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details)
+
def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details):
- ste_doc = frappe.new_doc('Stock Entry')
- ste_doc.purpose = 'Material Transfer'
+ ste_doc = frappe.new_doc("Stock Entry")
+ ste_doc.purpose = "Material Transfer"
ste_doc.purchase_order = po_doc.name
ste_doc.company = po_doc.company
ste_doc.is_return = 1
@@ -640,18 +728,21 @@ def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_deta
return ste_doc
+
def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
- item = ste_doc.append('items', row.item_details)
+ item = ste_doc.append("items", row.item_details)
po_detail = list(set(row.po_details).intersection(po_details))
- item.update({
- 'qty': qty,
- 'batch_no': batch_no,
- 'basic_rate': row.item_details['rate'],
- 'po_detail': po_detail[0] if po_detail else '',
- 's_warehouse': row.item_details['t_warehouse'],
- 't_warehouse': row.item_details['s_warehouse'],
- 'item_code': row.item_details['rm_item_code'],
- 'subcontracted_item': row.item_details['main_item_code'],
- 'serial_no': '\n'.join(row.serial_no) if row.serial_no else ''
- })
+ item.update(
+ {
+ "qty": qty,
+ "batch_no": batch_no,
+ "basic_rate": row.item_details["rate"],
+ "po_detail": po_detail[0] if po_detail else "",
+ "s_warehouse": row.item_details["t_warehouse"],
+ "t_warehouse": row.item_details["s_warehouse"],
+ "item_code": row.item_details["rm_item_code"],
+ "subcontracted_item": row.item_details["main_item_code"],
+ "serial_no": "\n".join(row.serial_no) if row.serial_no else "",
+ }
+ )
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
index d288f881de..81f20100c3 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
@@ -3,34 +3,25 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'purchase_order',
- 'non_standard_fieldnames': {
- 'Journal Entry': 'reference_name',
- 'Payment Entry': 'reference_name',
- 'Payment Request': 'reference_name',
- 'Auto Repeat': 'reference_document'
+ "fieldname": "purchase_order",
+ "non_standard_fieldnames": {
+ "Journal Entry": "reference_name",
+ "Payment Entry": "reference_name",
+ "Payment Request": "reference_name",
+ "Auto Repeat": "reference_document",
},
- 'internal_links': {
- 'Material Request': ['items', 'material_request'],
- 'Supplier Quotation': ['items', 'supplier_quotation'],
- 'Project': ['items', 'project'],
+ "internal_links": {
+ "Material Request": ["items", "material_request"],
+ "Supplier Quotation": ["items", "supplier_quotation"],
+ "Project": ["items", "project"],
},
- 'transactions': [
+ "transactions": [
+ {"label": _("Related"), "items": ["Purchase Receipt", "Purchase Invoice"]},
+ {"label": _("Payment"), "items": ["Payment Entry", "Journal Entry", "Payment Request"]},
{
- 'label': _('Related'),
- 'items': ['Purchase Receipt', 'Purchase Invoice']
+ "label": _("Reference"),
+ "items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
},
- {
- 'label': _('Payment'),
- 'items': ['Payment Entry', 'Journal Entry', 'Payment Request']
- },
- {
- 'label': _('Reference'),
- 'items': ['Material Request', 'Supplier Quotation', 'Project', 'Auto Repeat']
- },
- {
- 'label': _('Sub-contracting'),
- 'items': ['Stock Entry']
- },
- ]
+ {"label": _("Sub-contracting"), "items": ["Stock Entry"]},
+ ],
}
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index efa2ab1268..e4fb970c3f 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -51,7 +51,7 @@ class TestPurchaseOrder(FrappeTestCase):
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 4)
- frappe.db.set_value('Item', '_Test Item', 'over_delivery_receipt_allowance', 50)
+ frappe.db.set_value("Item", "_Test Item", "over_delivery_receipt_allowance", 50)
pr = create_pr_against_po(po.name, received_qty=8)
self.assertEqual(get_ordered_qty(), existing_ordered_qty)
@@ -71,8 +71,8 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 10)
- frappe.db.set_value('Item', '_Test Item', 'over_delivery_receipt_allowance', 50)
- frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 20)
+ frappe.db.set_value("Item", "_Test Item", "over_delivery_receipt_allowance", 50)
+ frappe.db.set_value("Item", "_Test Item", "over_billing_allowance", 20)
pi = make_pi_from_po(po.name)
pi.update_stock = 1
@@ -91,8 +91,8 @@ class TestPurchaseOrder(FrappeTestCase):
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 0)
- frappe.db.set_value('Item', '_Test Item', 'over_delivery_receipt_allowance', 0)
- frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 0)
+ frappe.db.set_value("Item", "_Test Item", "over_delivery_receipt_allowance", 0)
+ frappe.db.set_value("Item", "_Test Item", "over_billing_allowance", 0)
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
def test_update_remove_child_linked_to_mr(self):
@@ -104,41 +104,41 @@ class TestPurchaseOrder(FrappeTestCase):
po.submit()
first_item_of_po = po.get("items")[0]
- existing_ordered_qty = get_ordered_qty() # 10
- existing_requested_qty = get_requested_qty() # 0
+ existing_ordered_qty = get_ordered_qty() # 10
+ existing_requested_qty = get_requested_qty() # 0
# decrease ordered qty by 3 (10 -> 7) and add item
- trans_item = json.dumps([
- {
- 'item_code': first_item_of_po.item_code,
- 'rate': first_item_of_po.rate,
- 'qty': 7,
- 'docname': first_item_of_po.name
- },
- {'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
- ])
- update_child_qty_rate('Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [
+ {
+ "item_code": first_item_of_po.item_code,
+ "rate": first_item_of_po.rate,
+ "qty": 7,
+ "docname": first_item_of_po.name,
+ },
+ {"item_code": "_Test Item 2", "rate": 200, "qty": 2},
+ ]
+ )
+ update_child_qty_rate("Purchase Order", trans_item, po.name)
mr.reload()
# requested qty increases as ordered qty decreases
- self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3
+ self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3
self.assertEqual(mr.items[0].ordered_qty, 7)
- self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7
+ self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7
# delete first item linked to Material Request
- trans_item = json.dumps([
- {'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
- ])
- update_child_qty_rate('Purchase Order', trans_item, po.name)
+ trans_item = json.dumps([{"item_code": "_Test Item 2", "rate": 200, "qty": 2}])
+ update_child_qty_rate("Purchase Order", trans_item, po.name)
mr.reload()
# requested qty increases as ordered qty is 0 (deleted row)
- self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10
+ self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10
self.assertEqual(mr.items[0].ordered_qty, 0)
# ordered qty decreases as ordered qty is 0 (deleted row)
- self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
+ self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
def test_update_child(self):
mr = make_material_request(qty=10)
@@ -155,8 +155,10 @@ class TestPurchaseOrder(FrappeTestCase):
existing_ordered_qty = get_ordered_qty()
existing_requested_qty = get_requested_qty()
- trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}])
- update_child_qty_rate('Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [{"item_code": "_Test Item", "rate": 200, "qty": 7, "docname": po.items[0].name}]
+ )
+ update_child_qty_rate("Purchase Order", trans_item, po.name)
mr.reload()
self.assertEqual(mr.items[0].ordered_qty, 7)
@@ -180,20 +182,22 @@ class TestPurchaseOrder(FrappeTestCase):
existing_ordered_qty = get_ordered_qty()
first_item_of_po = po.get("items")[0]
- trans_item = json.dumps([
- {
- 'item_code': first_item_of_po.item_code,
- 'rate': first_item_of_po.rate,
- 'qty': first_item_of_po.qty,
- 'docname': first_item_of_po.name
- },
- {'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7}
- ])
- update_child_qty_rate('Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [
+ {
+ "item_code": first_item_of_po.item_code,
+ "rate": first_item_of_po.rate,
+ "qty": first_item_of_po.qty,
+ "docname": first_item_of_po.name,
+ },
+ {"item_code": "_Test Item", "rate": 200, "qty": 7},
+ ]
+ )
+ update_child_qty_rate("Purchase Order", trans_item, po.name)
po.reload()
- self.assertEqual(len(po.get('items')), 2)
- self.assertEqual(po.status, 'To Receive and Bill')
+ self.assertEqual(len(po.get("items")), 2)
+ self.assertEqual(po.status, "To Receive and Bill")
# ordered qty should increase on row addition
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
@@ -208,15 +212,18 @@ class TestPurchaseOrder(FrappeTestCase):
first_item_of_po = po.get("items")[0]
existing_ordered_qty = get_ordered_qty()
# add an item
- trans_item = json.dumps([
- {
- 'item_code': first_item_of_po.item_code,
- 'rate': first_item_of_po.rate,
- 'qty': first_item_of_po.qty,
- 'docname': first_item_of_po.name
- },
- {'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7}])
- update_child_qty_rate('Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [
+ {
+ "item_code": first_item_of_po.item_code,
+ "rate": first_item_of_po.rate,
+ "qty": first_item_of_po.qty,
+ "docname": first_item_of_po.name,
+ },
+ {"item_code": "_Test Item", "rate": 200, "qty": 7},
+ ]
+ )
+ update_child_qty_rate("Purchase Order", trans_item, po.name)
po.reload()
@@ -224,115 +231,145 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
# check if can remove received item
- trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}])
- self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [{"item_code": "_Test Item", "rate": 200, "qty": 7, "docname": po.get("items")[1].name}]
+ )
+ self.assertRaises(
+ frappe.ValidationError, update_child_qty_rate, "Purchase Order", trans_item, po.name
+ )
first_item_of_po = po.get("items")[0]
- trans_item = json.dumps([
- {
- 'item_code': first_item_of_po.item_code,
- 'rate': first_item_of_po.rate,
- 'qty': first_item_of_po.qty,
- 'docname': first_item_of_po.name
- }
- ])
- update_child_qty_rate('Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [
+ {
+ "item_code": first_item_of_po.item_code,
+ "rate": first_item_of_po.rate,
+ "qty": first_item_of_po.qty,
+ "docname": first_item_of_po.name,
+ }
+ ]
+ )
+ update_child_qty_rate("Purchase Order", trans_item, po.name)
po.reload()
- self.assertEqual(len(po.get('items')), 1)
- self.assertEqual(po.status, 'To Receive and Bill')
+ self.assertEqual(len(po.get("items")), 1)
+ self.assertEqual(po.status, "To Receive and Bill")
# ordered qty should decrease (back to initial) on row deletion
self.assertEqual(get_ordered_qty(), existing_ordered_qty)
def test_update_child_perm(self):
- po = create_purchase_order(item_code= "_Test Item", qty=4)
+ po = create_purchase_order(item_code="_Test Item", qty=4)
- user = 'test@example.com'
- test_user = frappe.get_doc('User', user)
+ user = "test@example.com"
+ test_user = frappe.get_doc("User", user)
test_user.add_roles("Accounts User")
frappe.set_user(user)
# update qty
- trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}])
- self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [{"item_code": "_Test Item", "rate": 200, "qty": 7, "docname": po.items[0].name}]
+ )
+ self.assertRaises(
+ frappe.ValidationError, update_child_qty_rate, "Purchase Order", trans_item, po.name
+ )
# add new item
- trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
- self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+ trans_item = json.dumps([{"item_code": "_Test Item", "rate": 100, "qty": 2}])
+ self.assertRaises(
+ frappe.ValidationError, update_child_qty_rate, "Purchase Order", trans_item, po.name
+ )
frappe.set_user("Administrator")
def test_update_child_with_tax_template(self):
"""
- Test Action: Create a PO with one item having its tax account head already in the PO.
- Add the same item + new item with tax template via Update Items.
- Expected result: First Item's tax row is updated. New tax row is added for second Item.
+ Test Action: Create a PO with one item having its tax account head already in the PO.
+ Add the same item + new item with tax template via Update Items.
+ Expected result: First Item's tax row is updated. New tax row is added for second Item.
"""
if not frappe.db.exists("Item", "Test Item with Tax"):
- make_item("Test Item with Tax", {
- 'is_stock_item': 1,
- })
+ make_item(
+ "Test Item with Tax",
+ {
+ "is_stock_item": 1,
+ },
+ )
- if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}):
- frappe.get_doc({
- 'doctype': 'Item Tax Template',
- 'title': 'Test Update Items Template',
- 'company': '_Test Company',
- 'taxes': [
- {
- 'tax_type': "_Test Account Service Tax - _TC",
- 'tax_rate': 10,
- }
- ]
- }).insert()
+ if not frappe.db.exists("Item Tax Template", {"title": "Test Update Items Template"}):
+ frappe.get_doc(
+ {
+ "doctype": "Item Tax Template",
+ "title": "Test Update Items Template",
+ "company": "_Test Company",
+ "taxes": [
+ {
+ "tax_type": "_Test Account Service Tax - _TC",
+ "tax_rate": 10,
+ }
+ ],
+ }
+ ).insert()
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
- if not frappe.db.exists("Item Tax",
- {"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"}):
- new_item_with_tax.append("taxes", {
- "item_tax_template": "Test Update Items Template - _TC",
- "valid_from": nowdate()
- })
+ if not frappe.db.exists(
+ "Item Tax",
+ {"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"},
+ ):
+ new_item_with_tax.append(
+ "taxes", {"item_tax_template": "Test Update Items Template - _TC", "valid_from": nowdate()}
+ )
new_item_with_tax.save()
tax_template = "_Test Account Excise Duty @ 10 - _TC"
- item = "_Test Item Home Desktop 100"
- if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
+ item = "_Test Item Home Desktop 100"
+ if not frappe.db.exists("Item Tax", {"parent": item, "item_tax_template": tax_template}):
item_doc = frappe.get_doc("Item", item)
- item_doc.append("taxes", {
- "item_tax_template": tax_template,
- "valid_from": nowdate()
- })
+ item_doc.append("taxes", {"item_tax_template": tax_template, "valid_from": nowdate()})
item_doc.save()
else:
# update valid from
- frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
+ frappe.db.sql(
+ """UPDATE `tabItem Tax` set valid_from = CURDATE()
where parent = %(item)s and item_tax_template = %(tax)s""",
- {"item": item, "tax": tax_template})
+ {"item": item, "tax": tax_template},
+ )
po = create_purchase_order(item_code=item, qty=1, do_not_save=1)
- po.append("taxes", {
- "account_head": "_Test Account Excise Duty - _TC",
- "charge_type": "On Net Total",
- "cost_center": "_Test Cost Center - _TC",
- "description": "Excise Duty",
- "doctype": "Purchase Taxes and Charges",
- "rate": 10
- })
+ po.append(
+ "taxes",
+ {
+ "account_head": "_Test Account Excise Duty - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Excise Duty",
+ "doctype": "Purchase Taxes and Charges",
+ "rate": 10,
+ },
+ )
po.insert()
po.submit()
self.assertEqual(po.taxes[0].tax_amount, 50)
self.assertEqual(po.taxes[0].total, 550)
- items = json.dumps([
- {'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
- {'item_code' : item, 'rate' : 100, 'qty' : 1}, # added item whose tax account head already exists in PO
- {'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO
- ])
- update_child_qty_rate('Purchase Order', items, po.name)
+ items = json.dumps(
+ [
+ {"item_code": item, "rate": 500, "qty": 1, "docname": po.items[0].name},
+ {
+ "item_code": item,
+ "rate": 100,
+ "qty": 1,
+ }, # added item whose tax account head already exists in PO
+ {
+ "item_code": new_item_with_tax.name,
+ "rate": 100,
+ "qty": 1,
+ }, # added item whose tax account head is missing in PO
+ ]
+ )
+ update_child_qty_rate("Purchase Order", items, po.name)
po.reload()
self.assertEqual(po.taxes[0].tax_amount, 70)
@@ -342,8 +379,11 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(po.taxes[1].total, 840)
# teardown
- frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
- where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template})
+ frappe.db.sql(
+ """UPDATE `tabItem Tax` set valid_from = NULL
+ where parent = %(item)s and item_tax_template = %(tax)s""",
+ {"item": item, "tax": tax_template},
+ )
po.cancel()
po.delete()
new_item_with_tax.delete()
@@ -353,18 +393,24 @@ class TestPurchaseOrder(FrappeTestCase):
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
- trans_item = json.dumps([{
- 'item_code': po.get("items")[0].item_code,
- 'rate': po.get("items")[0].rate,
- 'qty': po.get("items")[0].qty,
- 'uom': "_Test UOM 1",
- 'conversion_factor': 2,
- 'docname': po.get("items")[0].name
- }])
- update_child_qty_rate('Purchase Order', trans_item, po.name)
+ trans_item = json.dumps(
+ [
+ {
+ "item_code": po.get("items")[0].item_code,
+ "rate": po.get("items")[0].rate,
+ "qty": po.get("items")[0].qty,
+ "uom": "_Test UOM 1",
+ "conversion_factor": 2,
+ "docname": po.get("items")[0].name,
+ }
+ ]
+ )
+ update_child_qty_rate("Purchase Order", trans_item, po.name)
po.reload()
- total_reqd_qty_after_change = sum(d.get("required_qty") for d in po.as_dict().get("supplied_items"))
+ total_reqd_qty_after_change = sum(
+ d.get("required_qty") for d in po.as_dict().get("supplied_items")
+ )
self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
@@ -401,8 +447,6 @@ class TestPurchaseOrder(FrappeTestCase):
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
-
-
def test_return_against_purchase_order(self):
po = create_purchase_order()
@@ -428,17 +472,20 @@ class TestPurchaseOrder(FrappeTestCase):
make_purchase_receipt as make_purchase_receipt_return,
)
- pr1 = make_purchase_receipt_return(is_return=1, return_against=pr.name, qty=-3, do_not_submit=True)
+ pr1 = make_purchase_receipt_return(
+ is_return=1, return_against=pr.name, qty=-3, do_not_submit=True
+ )
pr1.items[0].purchase_order = po.name
pr1.items[0].purchase_order_item = po.items[0].name
pr1.submit()
- pi1= make_purchase_invoice_return(is_return=1, return_against=pi2.name, qty=-1, update_stock=1, do_not_submit=True)
+ pi1 = make_purchase_invoice_return(
+ is_return=1, return_against=pi2.name, qty=-1, update_stock=1, do_not_submit=True
+ )
pi1.items[0].purchase_order = po.name
pi1.items[0].po_detail = po.items[0].name
pi1.submit()
-
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 5)
@@ -484,13 +531,12 @@ class TestPurchaseOrder(FrappeTestCase):
def test_purchase_order_on_hold(self):
po = create_purchase_order(item_code="_Test Product Bundle Item")
- po.db_set('Status', "On Hold")
+ po.db_set("Status", "On Hold")
pi = make_pi_from_po(po.name)
pr = make_purchase_receipt(po.name)
self.assertRaises(frappe.ValidationError, pr.submit)
self.assertRaises(frappe.ValidationError, pi.submit)
-
def test_make_purchase_invoice_with_terms(self):
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
@@ -501,9 +547,7 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
- po.update(
- {"payment_terms_template": "_Test Payment Term Template"}
- )
+ po.update({"payment_terms_template": "_Test Payment Term Template"})
po.save()
po.submit()
@@ -511,7 +555,9 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(po.payment_schedule[0].payment_amount, 2500.0)
self.assertEqual(getdate(po.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(po.payment_schedule[1].payment_amount, 2500.0)
- self.assertEqual(getdate(po.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
+ self.assertEqual(
+ getdate(po.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30)
+ )
pi = make_pi_from_po(po.name)
pi.save()
@@ -521,7 +567,9 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(pi.payment_schedule[0].payment_amount, 2500.0)
self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0)
- self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
+ self.assertEqual(
+ getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30)
+ )
automatically_fetch_payment_terms(enable=0)
def test_subcontracting(self):
@@ -530,66 +578,78 @@ class TestPurchaseOrder(FrappeTestCase):
def test_warehouse_company_validation(self):
from erpnext.stock.utils import InvalidWarehouseCompany
+
po = create_purchase_order(company="_Test Company 1", do_not_save=True)
self.assertRaises(InvalidWarehouseCompany, po.insert)
def test_uom_integer_validation(self):
from erpnext.utilities.transaction_base import UOMMustBeIntegerError
+
po = create_purchase_order(qty=3.4, do_not_save=True)
self.assertRaises(UOMMustBeIntegerError, po.insert)
def test_ordered_qty_for_closing_po(self):
- bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
- fields=["ordered_qty"])
+ bin = frappe.get_all(
+ "Bin",
+ filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
+ fields=["ordered_qty"],
+ )
existing_ordered_qty = bin[0].ordered_qty if bin else 0.0
- po = create_purchase_order(item_code= "_Test Item", qty=1)
+ po = create_purchase_order(item_code="_Test Item", qty=1)
- self.assertEqual(get_ordered_qty(item_code= "_Test Item", warehouse="_Test Warehouse - _TC"), existing_ordered_qty+1)
+ self.assertEqual(
+ get_ordered_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"),
+ existing_ordered_qty + 1,
+ )
po.update_status("Closed")
- self.assertEqual(get_ordered_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"), existing_ordered_qty)
+ self.assertEqual(
+ get_ordered_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"), existing_ordered_qty
+ )
def test_group_same_items(self):
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
- frappe.get_doc({
- "doctype": "Purchase Order",
- "company": "_Test Company",
- "supplier" : "_Test Supplier",
- "is_subcontracted" : "No",
- "schedule_date": add_days(nowdate(), 1),
- "currency" : frappe.get_cached_value('Company', "_Test Company", "default_currency"),
- "conversion_factor" : 1,
- "items" : get_same_items(),
- "group_same_items": 1
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Purchase Order",
+ "company": "_Test Company",
+ "supplier": "_Test Supplier",
+ "is_subcontracted": "No",
+ "schedule_date": add_days(nowdate(), 1),
+ "currency": frappe.get_cached_value("Company", "_Test Company", "default_currency"),
+ "conversion_factor": 1,
+ "items": get_same_items(),
+ "group_same_items": 1,
+ }
+ ).insert(ignore_permissions=True)
def test_make_po_without_terms(self):
po = create_purchase_order(do_not_save=1)
- self.assertFalse(po.get('payment_schedule'))
+ self.assertFalse(po.get("payment_schedule"))
po.insert()
- self.assertTrue(po.get('payment_schedule'))
+ self.assertTrue(po.get("payment_schedule"))
def test_po_for_blocked_supplier_all(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
supplier.save()
- self.assertEqual(supplier.hold_type, 'All')
+ self.assertEqual(supplier.hold_type, "All")
self.assertRaises(frappe.ValidationError, create_purchase_order)
supplier.on_hold = 0
supplier.save()
def test_po_for_blocked_supplier_invoices(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Invoices'
+ supplier.hold_type = "Invoices"
supplier.save()
self.assertRaises(frappe.ValidationError, create_purchase_order)
@@ -598,30 +658,40 @@ class TestPurchaseOrder(FrappeTestCase):
supplier.save()
def test_po_for_blocked_supplier_payments(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
+ supplier.hold_type = "Payments"
supplier.save()
po = create_purchase_order()
self.assertRaises(
- frappe.ValidationError, get_payment_entry, dt='Purchase Order', dn=po.name, bank_account="_Test Bank - _TC")
+ frappe.ValidationError,
+ get_payment_entry,
+ dt="Purchase Order",
+ dn=po.name,
+ bank_account="_Test Bank - _TC",
+ )
supplier.on_hold = 0
supplier.save()
def test_po_for_blocked_supplier_payments_with_today_date(self):
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
supplier.release_date = nowdate()
- supplier.hold_type = 'Payments'
+ supplier.hold_type = "Payments"
supplier.save()
po = create_purchase_order()
self.assertRaises(
- frappe.ValidationError, get_payment_entry, dt='Purchase Order', dn=po.name, bank_account="_Test Bank - _TC")
+ frappe.ValidationError,
+ get_payment_entry,
+ dt="Purchase Order",
+ dn=po.name,
+ bank_account="_Test Bank - _TC",
+ )
supplier.on_hold = 0
supplier.save()
@@ -630,14 +700,14 @@ class TestPurchaseOrder(FrappeTestCase):
# this test is meant to fail only if something fails in the try block
with self.assertRaises(Exception):
try:
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
- supplier.hold_type = 'Payments'
- supplier.release_date = '2018-03-01'
+ supplier.hold_type = "Payments"
+ supplier.release_date = "2018-03-01"
supplier.save()
po = create_purchase_order()
- get_payment_entry('Purchase Order', po.name, bank_account='_Test Bank - _TC')
+ get_payment_entry("Purchase Order", po.name, bank_account="_Test Bank - _TC")
supplier.on_hold = 0
supplier.save()
@@ -648,88 +718,124 @@ class TestPurchaseOrder(FrappeTestCase):
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
po = create_purchase_order(do_not_save=1)
- po.payment_terms_template = '_Test Payment Term Template'
+ po.payment_terms_template = "_Test Payment Term Template"
po.save()
po.submit()
- frappe.db.set_value('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1')
+ frappe.db.set_value("Company", "_Test Company", "payment_terms", "_Test Payment Term Template 1")
pi = make_pi_from_po(po.name)
pi.save()
- self.assertEqual(pi.get('payment_terms_template'), '_Test Payment Term Template 1')
- frappe.db.set_value('Company', '_Test Company', 'payment_terms', '')
+ self.assertEqual(pi.get("payment_terms_template"), "_Test Payment Term Template 1")
+ frappe.db.set_value("Company", "_Test Company", "payment_terms", "")
def test_terms_copied(self):
po = create_purchase_order(do_not_save=1)
- po.payment_terms_template = '_Test Payment Term Template'
+ po.payment_terms_template = "_Test Payment Term Template"
po.insert()
po.submit()
- self.assertTrue(po.get('payment_schedule'))
+ self.assertTrue(po.get("payment_schedule"))
pi = make_pi_from_po(po.name)
pi.insert()
- self.assertTrue(pi.get('payment_schedule'))
+ self.assertTrue(pi.get("payment_schedule"))
def test_reserved_qty_subcontract_po(self):
# Make stock available for raw materials
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
- make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100",
- qty=20, basic_rate=100)
- make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item",
- qty=30, basic_rate=100)
- make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item Home Desktop 100",
- qty=30, basic_rate=100)
+ make_stock_entry(
+ target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
+ )
+ make_stock_entry(
+ target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100
+ )
+ make_stock_entry(
+ target="_Test Warehouse 1 - _TC",
+ item_code="_Test Item Home Desktop 100",
+ qty=30,
+ basic_rate=100,
+ )
- bin1 = frappe.db.get_value("Bin",
+ bin1 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], as_dict=1)
+ fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
+ as_dict=1,
+ )
# Submit PO
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
- bin2 = frappe.db.get_value("Bin",
+ bin2 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], as_dict=1)
+ fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
+ as_dict=1,
+ )
self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
self.assertNotEqual(bin1.modified, bin2.modified)
# Create stock transfer
- rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
- "qty":6,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":600,"stock_uom":"Nos"}]
+ rm_item = [
+ {
+ "item_code": "_Test FG Item",
+ "rm_item_code": "_Test Item",
+ "item_name": "_Test Item",
+ "qty": 6,
+ "warehouse": "_Test Warehouse - _TC",
+ "rate": 100,
+ "amount": 600,
+ "stock_uom": "Nos",
+ }
+ ]
rm_item_string = json.dumps(rm_item)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.to_warehouse = "_Test Warehouse 1 - _TC"
se.save()
se.submit()
- bin3 = frappe.db.get_value("Bin",
+ bin3 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# close PO
po.update_status("Closed")
- bin4 = frappe.db.get_value("Bin",
+ bin4 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Re-open PO
po.update_status("Submitted")
- bin5 = frappe.db.get_value("Bin",
+ bin5 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
- make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item",
- qty=40, basic_rate=100)
- make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item Home Desktop 100",
- qty=40, basic_rate=100)
+ make_stock_entry(
+ target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100
+ )
+ make_stock_entry(
+ target="_Test Warehouse 1 - _TC",
+ item_code="_Test Item Home Desktop 100",
+ qty=40,
+ basic_rate=100,
+ )
# make Purchase Receipt against PO
pr = make_purchase_receipt(po.name)
@@ -737,17 +843,23 @@ class TestPurchaseOrder(FrappeTestCase):
pr.save()
pr.submit()
- bin6 = frappe.db.get_value("Bin",
+ bin6 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pr.cancel()
- bin7 = frappe.db.get_value("Bin",
+ bin7 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
@@ -757,34 +869,46 @@ class TestPurchaseOrder(FrappeTestCase):
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.insert()
pi.submit()
- bin8 = frappe.db.get_value("Bin",
+ bin8 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pi.cancel()
- bin9 = frappe.db.get_value("Bin",
+ bin9 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Cancel Stock Entry
se.cancel()
- bin10 = frappe.db.get_value("Bin",
+ bin10 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
# Cancel PO
po.reload()
po.cancel()
- bin11 = frappe.db.get_value("Bin",
+ bin11 = frappe.db.get_value(
+ "Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract", as_dict=1)
+ fieldname="reserved_qty_for_sub_contract",
+ as_dict=1,
+ )
self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
@@ -792,56 +916,101 @@ class TestPurchaseOrder(FrappeTestCase):
item_code = "_Test Subcontracted FG Item 11"
make_subcontracted_item(item_code=item_code)
- po = create_purchase_order(item_code=item_code, qty=1,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
+ po = create_purchase_order(
+ item_code=item_code,
+ qty=1,
+ is_subcontracted="Yes",
+ supplier_warehouse="_Test Warehouse 1 - _TC",
+ include_exploded_items=1,
+ )
- name = frappe.db.get_value('BOM', {'item': item_code}, 'name')
- bom = frappe.get_doc('BOM', name)
+ name = frappe.db.get_value("BOM", {"item": item_code}, "name")
+ bom = frappe.get_doc("BOM", name)
- exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')])
+ exploded_items = sorted(
+ [d.item_code for d in bom.exploded_items if not d.get("sourced_by_supplier")]
+ )
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
self.assertEqual(exploded_items, supplied_items)
- po1 = create_purchase_order(item_code=item_code, qty=1,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
+ po1 = create_purchase_order(
+ item_code=item_code,
+ qty=1,
+ is_subcontracted="Yes",
+ supplier_warehouse="_Test Warehouse 1 - _TC",
+ include_exploded_items=0,
+ )
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
- bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')])
+ bom_items = sorted([d.item_code for d in bom.items if not d.get("sourced_by_supplier")])
self.assertEqual(supplied_items1, bom_items)
def test_backflush_based_on_stock_entry(self):
item_code = "_Test Subcontracted FG Item 1"
make_subcontracted_item(item_code=item_code)
- make_item('Sub Contracted Raw Material 1', {
- 'is_stock_item': 1,
- 'is_sub_contracted_item': 1
- })
+ make_item("Sub Contracted Raw Material 1", {"is_stock_item": 1, "is_sub_contracted_item": 1})
update_backflush_based_on("Material Transferred for Subcontract")
order_qty = 5
- po = create_purchase_order(item_code=item_code, qty=order_qty,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ item_code=item_code,
+ qty=order_qty,
+ is_subcontracted="Yes",
+ supplier_warehouse="_Test Warehouse 1 - _TC",
+ )
- make_stock_entry(target="_Test Warehouse - _TC",
- item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100)
- make_stock_entry(target="_Test Warehouse - _TC",
- item_code = "Test Extra Item 1", qty=100, basic_rate=100)
- make_stock_entry(target="_Test Warehouse - _TC",
- item_code = "Test Extra Item 2", qty=10, basic_rate=100)
- make_stock_entry(target="_Test Warehouse - _TC",
- item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100)
+ make_stock_entry(
+ target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
+ )
+ make_stock_entry(
+ target="_Test Warehouse - _TC", item_code="Test Extra Item 1", qty=100, basic_rate=100
+ )
+ make_stock_entry(
+ target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=10, basic_rate=100
+ )
+ make_stock_entry(
+ target="_Test Warehouse - _TC",
+ item_code="Sub Contracted Raw Material 1",
+ qty=10,
+ basic_rate=100,
+ )
rm_items = [
- {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item",
- "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
- {"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
- "qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
- {"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
- "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
- {'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos',
- 'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}]
+ {
+ "item_code": item_code,
+ "rm_item_code": "Sub Contracted Raw Material 1",
+ "item_name": "_Test Item",
+ "qty": 10,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos",
+ },
+ {
+ "item_code": item_code,
+ "rm_item_code": "_Test Item Home Desktop 100",
+ "item_name": "_Test Item Home Desktop 100",
+ "qty": 20,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos",
+ },
+ {
+ "item_code": item_code,
+ "rm_item_code": "Test Extra Item 1",
+ "item_name": "Test Extra Item 1",
+ "qty": 10,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos",
+ },
+ {
+ "item_code": item_code,
+ "rm_item_code": "Test Extra Item 2",
+ "stock_uom": "Nos",
+ "qty": 10,
+ "warehouse": "_Test Warehouse - _TC",
+ "item_name": "Test Extra Item 2",
+ },
+ ]
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
@@ -851,61 +1020,80 @@ class TestPurchaseOrder(FrappeTestCase):
received_qty = 2
# partial receipt
- pr.get('items')[0].qty = received_qty
+ pr.get("items")[0].qty = received_qty
pr.save()
pr.submit()
- transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
- issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
+ transferred_items = sorted(
+ [d.item_code for d in se.get("items") if se.purchase_order == po.name]
+ )
+ issued_items = sorted([d.rm_item_code for d in pr.get("supplied_items")])
self.assertEqual(transferred_items, issued_items)
- self.assertEqual(pr.get('items')[0].rm_supp_cost, 2000)
-
+ self.assertEqual(pr.get("items")[0].rm_supp_cost, 2000)
transferred_rm_map = frappe._dict()
for item in rm_items:
- transferred_rm_map[item.get('rm_item_code')] = item
+ transferred_rm_map[item.get("rm_item_code")] = item
update_backflush_based_on("BOM")
def test_supplied_qty_against_subcontracted_po(self):
item_code = "_Test Subcontracted FG Item 5"
- make_item('Sub Contracted Raw Material 4', {
- 'is_stock_item': 1,
- 'is_sub_contracted_item': 1
- })
+ make_item("Sub Contracted Raw Material 4", {"is_stock_item": 1, "is_sub_contracted_item": 1})
make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"])
update_backflush_based_on("Material Transferred for Subcontract")
order_qty = 250
- po = create_purchase_order(item_code=item_code, qty=order_qty,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", do_not_save=True)
+ po = create_purchase_order(
+ item_code=item_code,
+ qty=order_qty,
+ is_subcontracted="Yes",
+ supplier_warehouse="_Test Warehouse 1 - _TC",
+ do_not_save=True,
+ )
# Add same subcontracted items multiple times
- po.append("items", {
- "item_code": item_code,
- "qty": order_qty,
- "schedule_date": add_days(nowdate(), 1),
- "warehouse": "_Test Warehouse - _TC"
- })
+ po.append(
+ "items",
+ {
+ "item_code": item_code,
+ "qty": order_qty,
+ "schedule_date": add_days(nowdate(), 1),
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ )
po.set_missing_values()
po.submit()
# Material receipt entry for the raw materials which will be send to supplier
- make_stock_entry(target="_Test Warehouse - _TC",
- item_code = "Sub Contracted Raw Material 4", qty=500, basic_rate=100)
+ make_stock_entry(
+ target="_Test Warehouse - _TC",
+ item_code="Sub Contracted Raw Material 4",
+ qty=500,
+ basic_rate=100,
+ )
rm_items = [
{
- "item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
- "qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name
+ "item_code": item_code,
+ "rm_item_code": "Sub Contracted Raw Material 4",
+ "item_name": "_Test Item",
+ "qty": 250,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos",
+ "name": po.supplied_items[0].name,
},
{
- "item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
- "qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"
+ "item_code": item_code,
+ "rm_item_code": "Sub Contracted Raw Material 4",
+ "item_name": "_Test Item",
+ "qty": 250,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos",
},
]
@@ -927,8 +1115,10 @@ class TestPurchaseOrder(FrappeTestCase):
def test_advance_payment_entry_unlink_against_purchase_order(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
- frappe.db.set_value("Accounts Settings", "Accounts Settings",
- "unlink_advance_payment_on_cancelation_of_order", 1)
+
+ frappe.db.set_value(
+ "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 1
+ )
po_doc = create_purchase_order()
@@ -943,24 +1133,23 @@ class TestPurchaseOrder(FrappeTestCase):
pe.save(ignore_permissions=True)
pe.submit()
- po_doc = frappe.get_doc('Purchase Order', po_doc.name)
+ po_doc = frappe.get_doc("Purchase Order", po_doc.name)
po_doc.cancel()
- pe_doc = frappe.get_doc('Payment Entry', pe.name)
+ pe_doc = frappe.get_doc("Payment Entry", pe.name)
pe_doc.cancel()
- frappe.db.set_value("Accounts Settings", "Accounts Settings",
- "unlink_advance_payment_on_cancelation_of_order", 0)
+ frappe.db.set_value(
+ "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0
+ )
def test_schedule_date(self):
po = create_purchase_order(do_not_submit=True)
po.schedule_date = None
- po.append("items", {
- "item_code": "_Test Item",
- "qty": 1,
- "rate": 100,
- "schedule_date": add_days(nowdate(), 5)
- })
+ po.append(
+ "items",
+ {"item_code": "_Test Item", "qty": 1, "rate": 100, "schedule_date": add_days(nowdate(), 5)},
+ )
po.save()
self.assertEqual(po.schedule_date, add_days(nowdate(), 1))
@@ -968,22 +1157,21 @@ class TestPurchaseOrder(FrappeTestCase):
po.save()
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
-
def test_po_optional_blanket_order(self):
"""
- Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
- Second Purchase Order should not add on to Blanket Orders Ordered Quantity.
+ Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
+ Second Purchase Order should not add on to Blanket Orders Ordered Quantity.
"""
- bo = make_blanket_order(blanket_order_type = "Purchasing", quantity = 10, rate = 10)
+ bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10)
- po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 1)
- po_doc = frappe.get_doc('Purchase Order', po.get('name'))
+ po = create_purchase_order(item_code="_Test Item", qty=5, against_blanket_order=1)
+ po_doc = frappe.get_doc("Purchase Order", po.get("name"))
# To test if the PO has a Blanket Order
self.assertTrue(po_doc.items[0].blanket_order)
- po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 0)
- po_doc = frappe.get_doc('Purchase Order', po.get('name'))
+ po = create_purchase_order(item_code="_Test Item", qty=5, against_blanket_order=0)
+ po_doc = frappe.get_doc("Purchase Order", po.get("name"))
# To test if the PO does NOT have a Blanket Order
self.assertEqual(po_doc.items[0].blanket_order, None)
@@ -1001,7 +1189,7 @@ class TestPurchaseOrder(FrappeTestCase):
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
- po.payment_terms_template = 'Test Receivable Template'
+ po.payment_terms_template = "Test Receivable Template"
po.submit()
pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1)
@@ -1014,6 +1202,7 @@ class TestPurchaseOrder(FrappeTestCase):
automatically_fetch_payment_terms(enable=0)
+
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)
pr.get("items")[0].qty = received_qty or 5
@@ -1021,39 +1210,51 @@ def make_pr_against_po(po, received_qty=0):
pr.submit()
return pr
+
def make_subcontracted_item(**args):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
args = frappe._dict(args)
- if not frappe.db.exists('Item', args.item_code):
- make_item(args.item_code, {
- 'is_stock_item': 1,
- 'is_sub_contracted_item': 1,
- 'has_batch_no': args.get("has_batch_no") or 0
- })
+ if not frappe.db.exists("Item", args.item_code):
+ make_item(
+ args.item_code,
+ {
+ "is_stock_item": 1,
+ "is_sub_contracted_item": 1,
+ "has_batch_no": args.get("has_batch_no") or 0,
+ },
+ )
if not args.raw_materials:
- if not frappe.db.exists('Item', "Test Extra Item 1"):
- make_item("Test Extra Item 1", {
- 'is_stock_item': 1,
- })
+ if not frappe.db.exists("Item", "Test Extra Item 1"):
+ make_item(
+ "Test Extra Item 1",
+ {
+ "is_stock_item": 1,
+ },
+ )
- if not frappe.db.exists('Item', "Test Extra Item 2"):
- make_item("Test Extra Item 2", {
- 'is_stock_item': 1,
- })
+ if not frappe.db.exists("Item", "Test Extra Item 2"):
+ make_item(
+ "Test Extra Item 2",
+ {
+ "is_stock_item": 1,
+ },
+ )
- args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
+ args.raw_materials = ["_Test FG Item", "Test Extra Item 1"]
+
+ if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"):
+ make_bom(item=args.item_code, raw_materials=args.get("raw_materials"))
- if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
- make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
def update_backflush_based_on(based_on):
- doc = frappe.get_doc('Buying Settings')
+ doc = frappe.get_doc("Buying Settings")
doc.backflush_raw_materials_of_subcontract_based_on = based_on
doc.save()
+
def get_same_items():
return [
{
@@ -1061,17 +1262,18 @@ def get_same_items():
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 500,
- "schedule_date": add_days(nowdate(), 1)
+ "schedule_date": add_days(nowdate(), 1),
},
{
"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 4,
"rate": 500,
- "schedule_date": add_days(nowdate(), 1)
- }
+ "schedule_date": add_days(nowdate(), 1),
+ },
]
+
def create_purchase_order(**args):
po = frappe.new_doc("Purchase Order")
args = frappe._dict(args)
@@ -1082,7 +1284,7 @@ def create_purchase_order(**args):
po.company = args.company or "_Test Company"
po.supplier = args.supplier or "_Test Supplier"
po.is_subcontracted = args.is_subcontracted or "No"
- po.currency = args.currency or frappe.get_cached_value('Company', po.company, "default_currency")
+ po.currency = args.currency or frappe.get_cached_value("Company", po.company, "default_currency")
po.conversion_factor = args.conversion_factor or 1
po.supplier_warehouse = args.supplier_warehouse or None
@@ -1090,15 +1292,18 @@ def create_purchase_order(**args):
for row in args.rm_items:
po.append("items", row)
else:
- po.append("items", {
- "item_code": args.item or args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 10,
- "rate": args.rate or 500,
- "schedule_date": add_days(nowdate(), 1),
- "include_exploded_items": args.get('include_exploded_items', 1),
- "against_blanket_order": args.against_blanket_order
- })
+ po.append(
+ "items",
+ {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 10,
+ "rate": args.rate or 500,
+ "schedule_date": add_days(nowdate(), 1),
+ "include_exploded_items": args.get("include_exploded_items", 1),
+ "against_blanket_order": args.against_blanket_order,
+ },
+ )
po.set_missing_values()
if not args.do_not_save:
@@ -1113,6 +1318,7 @@ def create_purchase_order(**args):
return po
+
def create_pr_against_po(po, received_qty=4):
pr = make_purchase_receipt(po)
pr.get("items")[0].qty = received_qty
@@ -1120,14 +1326,19 @@ def create_pr_against_po(po, received_qty=4):
pr.submit()
return pr
+
def get_ordered_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"):
- return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
- "ordered_qty"))
+ return flt(
+ frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "ordered_qty")
+ )
+
def get_requested_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"):
- return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
- "indented_qty"))
+ return flt(
+ frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty")
+ )
+
test_dependencies = ["BOM", "Item Price"]
-test_records = frappe.get_test_records('Purchase Order')
+test_records = frappe.get_test_records("Purchase Order")
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
index 0cef0deee5..a8bafda002 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
@@ -9,5 +9,6 @@ from frappe.model.document import Document
class PurchaseOrderItem(Document):
pass
+
def on_doctype_update():
frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"])
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 2db750e75c..d39aec19ba 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -20,6 +20,7 @@ from erpnext.stock.doctype.material_request.material_request import set_missing_
STANDARD_USERS = ("Guest", "Administrator")
+
class RequestforQuotation(BuyingController):
def validate(self):
self.validate_duplicate_supplier()
@@ -30,7 +31,7 @@ class RequestforQuotation(BuyingController):
if self.docstatus < 1:
# after amend and save, status still shows as cancelled, until submit
- frappe.db.set(self, 'status', 'Draft')
+ frappe.db.set(self, "status", "Draft")
def validate_duplicate_supplier(self):
supplier_list = [d.supplier for d in self.suppliers]
@@ -39,14 +40,24 @@ class RequestforQuotation(BuyingController):
def validate_supplier_list(self):
for d in self.suppliers:
- prevent_rfqs = frappe.db.get_value("Supplier", d.supplier, 'prevent_rfqs')
+ prevent_rfqs = frappe.db.get_value("Supplier", d.supplier, "prevent_rfqs")
if prevent_rfqs:
- standing = frappe.db.get_value("Supplier Scorecard",d.supplier, 'status')
- frappe.throw(_("RFQs are not allowed for {0} due to a scorecard standing of {1}").format(d.supplier, standing))
- warn_rfqs = frappe.db.get_value("Supplier", d.supplier, 'warn_rfqs')
+ standing = frappe.db.get_value("Supplier Scorecard", d.supplier, "status")
+ frappe.throw(
+ _("RFQs are not allowed for {0} due to a scorecard standing of {1}").format(
+ d.supplier, standing
+ )
+ )
+ warn_rfqs = frappe.db.get_value("Supplier", d.supplier, "warn_rfqs")
if warn_rfqs:
- standing = frappe.db.get_value("Supplier Scorecard",d.supplier, 'status')
- frappe.msgprint(_("{0} currently has a {1} Supplier Scorecard standing, and RFQs to this supplier should be issued with caution.").format(d.supplier, standing), title=_("Caution"), indicator='orange')
+ standing = frappe.db.get_value("Supplier Scorecard", d.supplier, "status")
+ frappe.msgprint(
+ _(
+ "{0} currently has a {1} Supplier Scorecard standing, and RFQs to this supplier should be issued with caution."
+ ).format(d.supplier, standing),
+ title=_("Caution"),
+ indicator="orange",
+ )
def update_email_id(self):
for rfq_supplier in self.suppliers:
@@ -55,17 +66,21 @@ class RequestforQuotation(BuyingController):
def validate_email_id(self, args):
if not args.email_id:
- frappe.throw(_("Row {0}: For Supplier {1}, Email Address is Required to send an email").format(args.idx, frappe.bold(args.supplier)))
+ frappe.throw(
+ _("Row {0}: For Supplier {1}, Email Address is Required to send an email").format(
+ args.idx, frappe.bold(args.supplier)
+ )
+ )
def on_submit(self):
- frappe.db.set(self, 'status', 'Submitted')
+ frappe.db.set(self, "status", "Submitted")
for supplier in self.suppliers:
supplier.email_sent = 0
- supplier.quote_status = 'Pending'
+ supplier.quote_status = "Pending"
self.send_to_supplier()
def on_cancel(self):
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
@frappe.whitelist()
def get_supplier_email_preview(self, supplier):
@@ -75,7 +90,7 @@ class RequestforQuotation(BuyingController):
self.validate_email_id(rfq_supplier)
- message = self.supplier_rfq_mail(rfq_supplier, '', self.get_link(), True)
+ message = self.supplier_rfq_mail(rfq_supplier, "", self.get_link(), True)
return message
@@ -102,12 +117,13 @@ class RequestforQuotation(BuyingController):
def update_supplier_part_no(self, supplier):
self.vendor = supplier
for item in self.items:
- item.supplier_part_no = frappe.db.get_value('Item Supplier',
- {'parent': item.item_code, 'supplier': supplier}, 'supplier_part_no')
+ item.supplier_part_no = frappe.db.get_value(
+ "Item Supplier", {"parent": item.item_code, "supplier": supplier}, "supplier_part_no"
+ )
def update_supplier_contact(self, rfq_supplier, link):
- '''Create a new user for the supplier if not set in contact'''
- update_password_link, contact = '', ''
+ """Create a new user for the supplier if not set in contact"""
+ update_password_link, contact = "", ""
if frappe.db.exists("User", rfq_supplier.email_id):
user = frappe.get_doc("User", rfq_supplier.email_id)
@@ -125,14 +141,8 @@ class RequestforQuotation(BuyingController):
else:
contact = frappe.new_doc("Contact")
contact.first_name = rfq_supplier.supplier_name or rfq_supplier.supplier
- contact.append('links', {
- 'link_doctype': 'Supplier',
- 'link_name': rfq_supplier.supplier
- })
- contact.append('email_ids', {
- 'email_id': user.name,
- 'is_primary': 1
- })
+ contact.append("links", {"link_doctype": "Supplier", "link_name": rfq_supplier.supplier})
+ contact.append("email_ids", {"email_id": user.name, "is_primary": 1})
if not contact.email_id and not contact.user:
contact.email_id = user.name
@@ -145,39 +155,38 @@ class RequestforQuotation(BuyingController):
return contact.name
def create_user(self, rfq_supplier, link):
- user = frappe.get_doc({
- 'doctype': 'User',
- 'send_welcome_email': 0,
- 'email': rfq_supplier.email_id,
- 'first_name': rfq_supplier.supplier_name or rfq_supplier.supplier,
- 'user_type': 'Website User',
- 'redirect_url': link
- })
+ user = frappe.get_doc(
+ {
+ "doctype": "User",
+ "send_welcome_email": 0,
+ "email": rfq_supplier.email_id,
+ "first_name": rfq_supplier.supplier_name or rfq_supplier.supplier,
+ "user_type": "Website User",
+ "redirect_url": link,
+ }
+ )
user.save(ignore_permissions=True)
update_password_link = user.reset_password()
return user, update_password_link
def supplier_rfq_mail(self, data, update_password_link, rfq_link, preview=False):
- full_name = get_user_fullname(frappe.session['user'])
+ full_name = get_user_fullname(frappe.session["user"])
if full_name == "Guest":
full_name = "Administrator"
# send document dict and some important data from suppliers row
# to render message_for_supplier from any template
doc_args = self.as_dict()
- doc_args.update({
- 'supplier': data.get('supplier'),
- 'supplier_name': data.get('supplier_name')
- })
+ doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
args = {
- 'update_password_link': update_password_link,
- 'message': frappe.render_template(self.message_for_supplier, doc_args),
- 'rfq_link': rfq_link,
- 'user_fullname': full_name,
- 'supplier_name' : data.get('supplier_name'),
- 'supplier_salutation' : self.salutation or 'Dear Mx.',
+ "update_password_link": update_password_link,
+ "message": frappe.render_template(self.message_for_supplier, doc_args),
+ "rfq_link": rfq_link,
+ "user_fullname": full_name,
+ "supplier_name": data.get("supplier_name"),
+ "supplier_salutation": self.salutation or "Dear Mx.",
}
subject = self.subject or _("Request for Quotation")
@@ -193,9 +202,16 @@ class RequestforQuotation(BuyingController):
self.send_email(data, sender, subject, message, attachments)
def send_email(self, data, sender, subject, message, attachments):
- make(subject = subject, content=message,recipients=data.email_id,
- sender=sender,attachments = attachments, send_email=True,
- doctype=self.doctype, name=self.name)["name"]
+ make(
+ subject=subject,
+ content=message,
+ recipients=data.email_id,
+ sender=sender,
+ attachments=attachments,
+ send_email=True,
+ doctype=self.doctype,
+ name=self.name,
+ )["name"]
frappe.msgprint(_("Email Sent to Supplier {0}").format(data.supplier))
@@ -207,9 +223,10 @@ class RequestforQuotation(BuyingController):
def update_rfq_supplier_status(self, sup_name=None):
for supplier in self.suppliers:
if sup_name == None or supplier.supplier == sup_name:
- quote_status = _('Received')
+ quote_status = _("Received")
for item in self.items:
- sqi_count = frappe.db.sql("""
+ sqi_count = frappe.db.sql(
+ """
SELECT
COUNT(sqi.name) as count
FROM
@@ -219,42 +236,59 @@ class RequestforQuotation(BuyingController):
AND sqi.docstatus = 1
AND sqi.request_for_quotation_item = %(rqi)s
AND sqi.parent = sq.name""",
- {"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0]
+ {"supplier": supplier.supplier, "rqi": item.name},
+ as_dict=1,
+ )[0]
if (sqi_count.count) == 0:
- quote_status = _('Pending')
+ quote_status = _("Pending")
supplier.quote_status = quote_status
@frappe.whitelist()
def send_supplier_emails(rfq_name):
- check_portal_enabled('Request for Quotation')
+ check_portal_enabled("Request for Quotation")
rfq = frappe.get_doc("Request for Quotation", rfq_name)
- if rfq.docstatus==1:
+ if rfq.docstatus == 1:
rfq.send_to_supplier()
+
def check_portal_enabled(reference_doctype):
- if not frappe.db.get_value('Portal Menu Item',
- {'reference_doctype': reference_doctype}, 'enabled'):
- frappe.throw(_("The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings."))
+ if not frappe.db.get_value(
+ "Portal Menu Item", {"reference_doctype": reference_doctype}, "enabled"
+ ):
+ frappe.throw(
+ _(
+ "The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings."
+ )
+ )
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
+
list_context = get_list_context(context)
- list_context.update({
- 'show_sidebar': True,
- 'show_search': True,
- 'no_breadcrumbs': True,
- 'title': _('Request for Quotation'),
- })
+ list_context.update(
+ {
+ "show_sidebar": True,
+ "show_search": True,
+ "no_breadcrumbs": True,
+ "title": _("Request for Quotation"),
+ }
+ )
return list_context
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql("""select `tabContact`.name from `tabContact`, `tabDynamic Link`
+ return frappe.db.sql(
+ """select `tabContact`.name from `tabContact`, `tabDynamic Link`
where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
- limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')})
+ limit %(start)s, %(page_len)s""",
+ {"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")},
+ )
+
@frappe.whitelist()
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
@@ -262,28 +296,34 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=
if for_supplier:
target_doc.supplier = for_supplier
args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True)
- target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company)
- target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
+ target_doc.currency = args.currency or get_party_account_currency(
+ "Supplier", for_supplier, source.company
+ )
+ target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value(
+ "Buying Settings", None, "buying_price_list"
+ )
set_missing_values(source, target_doc)
- doclist = get_mapped_doc("Request for Quotation", source_name, {
- "Request for Quotation": {
- "doctype": "Supplier Quotation",
- "validation": {
- "docstatus": ["=", 1]
- }
- },
- "Request for Quotation Item": {
- "doctype": "Supplier Quotation Item",
- "field_map": {
- "name": "request_for_quotation_item",
- "parent": "request_for_quotation"
+ doclist = get_mapped_doc(
+ "Request for Quotation",
+ source_name,
+ {
+ "Request for Quotation": {
+ "doctype": "Supplier Quotation",
+ "validation": {"docstatus": ["=", 1]},
},
- }
- }, target_doc, postprocess)
+ "Request for Quotation Item": {
+ "doctype": "Supplier Quotation Item",
+ "field_map": {"name": "request_for_quotation_item", "parent": "request_for_quotation"},
+ },
+ },
+ target_doc,
+ postprocess,
+ )
return doclist
+
# This method is used to make supplier quotation from supplier's portal.
@frappe.whitelist()
def create_supplier_quotation(doc):
@@ -291,15 +331,19 @@ def create_supplier_quotation(doc):
doc = json.loads(doc)
try:
- sq_doc = frappe.get_doc({
- "doctype": "Supplier Quotation",
- "supplier": doc.get('supplier'),
- "terms": doc.get("terms"),
- "company": doc.get("company"),
- "currency": doc.get('currency') or get_party_account_currency('Supplier', doc.get('supplier'), doc.get('company')),
- "buying_price_list": doc.get('buying_price_list') or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
- })
- add_items(sq_doc, doc.get('supplier'), doc.get('items'))
+ sq_doc = frappe.get_doc(
+ {
+ "doctype": "Supplier Quotation",
+ "supplier": doc.get("supplier"),
+ "terms": doc.get("terms"),
+ "company": doc.get("company"),
+ "currency": doc.get("currency")
+ or get_party_account_currency("Supplier", doc.get("supplier"), doc.get("company")),
+ "buying_price_list": doc.get("buying_price_list")
+ or frappe.db.get_value("Buying Settings", None, "buying_price_list"),
+ }
+ )
+ add_items(sq_doc, doc.get("supplier"), doc.get("items"))
sq_doc.flags.ignore_permissions = True
sq_doc.run_method("set_missing_values")
sq_doc.save()
@@ -308,6 +352,7 @@ def create_supplier_quotation(doc):
except Exception:
return None
+
def add_items(sq_doc, supplier, items):
for data in items:
if data.get("qty") > 0:
@@ -316,21 +361,36 @@ def add_items(sq_doc, supplier, items):
create_rfq_items(sq_doc, supplier, data)
+
def create_rfq_items(sq_doc, supplier, data):
args = {}
- for field in ['item_code', 'item_name', 'description', 'qty', 'rate', 'conversion_factor',
- 'warehouse', 'material_request', 'material_request_item', 'stock_qty']:
+ for field in [
+ "item_code",
+ "item_name",
+ "description",
+ "qty",
+ "rate",
+ "conversion_factor",
+ "warehouse",
+ "material_request",
+ "material_request_item",
+ "stock_qty",
+ ]:
args[field] = data.get(field)
- args.update({
- "request_for_quotation_item": data.name,
- "request_for_quotation": data.parent,
- "supplier_part_no": frappe.db.get_value("Item Supplier",
- {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no")
- })
+ args.update(
+ {
+ "request_for_quotation_item": data.name,
+ "request_for_quotation": data.parent,
+ "supplier_part_no": frappe.db.get_value(
+ "Item Supplier", {"parent": data.item_code, "supplier": supplier}, "supplier_part_no"
+ ),
+ }
+ )
+
+ sq_doc.append("items", args)
- sq_doc.append('items', args)
@frappe.whitelist()
def get_pdf(doctype, name, supplier):
@@ -338,15 +398,18 @@ def get_pdf(doctype, name, supplier):
if doc:
download_pdf(doctype, name, doc=doc)
+
def get_rfq_doc(doctype, name, supplier):
if supplier:
doc = frappe.get_doc(doctype, name)
doc.update_supplier_part_no(supplier)
return doc
+
@frappe.whitelist()
-def get_item_from_material_requests_based_on_supplier(source_name, target_doc = None):
- mr_items_list = frappe.db.sql("""
+def get_item_from_material_requests_based_on_supplier(source_name, target_doc=None):
+ mr_items_list = frappe.db.sql(
+ """
SELECT
mr.name, mr_item.item_code
FROM
@@ -361,52 +424,65 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc =
AND mr.status != "Stopped"
AND mr.material_request_type = "Purchase"
AND mr.docstatus = 1
- AND mr.per_ordered < 99.99""", {"supplier": source_name}, as_dict=1)
+ AND mr.per_ordered < 99.99""",
+ {"supplier": source_name},
+ as_dict=1,
+ )
material_requests = {}
for d in mr_items_list:
material_requests.setdefault(d.name, []).append(d.item_code)
for mr, items in material_requests.items():
- target_doc = get_mapped_doc("Material Request", mr, {
- "Material Request": {
- "doctype": "Request for Quotation",
- "validation": {
- "docstatus": ["=", 1],
- "material_request_type": ["=", "Purchase"],
- }
+ target_doc = get_mapped_doc(
+ "Material Request",
+ mr,
+ {
+ "Material Request": {
+ "doctype": "Request for Quotation",
+ "validation": {
+ "docstatus": ["=", 1],
+ "material_request_type": ["=", "Purchase"],
+ },
+ },
+ "Material Request Item": {
+ "doctype": "Request for Quotation Item",
+ "condition": lambda row: row.item_code in items,
+ "field_map": [
+ ["name", "material_request_item"],
+ ["parent", "material_request"],
+ ["uom", "uom"],
+ ],
+ },
},
- "Material Request Item": {
- "doctype": "Request for Quotation Item",
- "condition": lambda row: row.item_code in items,
- "field_map": [
- ["name", "material_request_item"],
- ["parent", "material_request"],
- ["uom", "uom"]
- ]
- }
- }, target_doc)
+ target_doc,
+ )
return target_doc
+
@frappe.whitelist()
def get_supplier_tag():
filters = {"document_type": "Supplier"}
- tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag))
+ tags = list(
+ set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag)
+ )
return tags
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
conditions = ""
if txt:
- conditions += "and rfq.name like '%%"+txt+"%%' "
+ conditions += "and rfq.name like '%%" + txt + "%%' "
if filters.get("transaction_date"):
conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date"))
- rfq_data = frappe.db.sql("""
+ rfq_data = frappe.db.sql(
+ """
select
distinct rfq.name, rfq.transaction_date,
rfq.company
@@ -419,8 +495,11 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt
and rfq.company = '{1}'
{2}
order by rfq.transaction_date ASC
- limit %(page_len)s offset %(start)s """ \
- .format(filters.get("supplier"), filters.get("company"), conditions),
- {"page_len": page_len, "start": start}, as_dict=1)
+ limit %(page_len)s offset %(start)s """.format(
+ filters.get("supplier"), filters.get("company"), conditions
+ ),
+ {"page_len": page_len, "start": start},
+ as_dict=1,
+ )
return rfq_data
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py
index dc1cda126a..505e3e0f67 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py
@@ -1,10 +1,8 @@
def get_data():
return {
- 'docstatus': 1,
- 'fieldname': 'request_for_quotation',
- 'transactions': [
- {
- 'items': ['Supplier Quotation']
- },
- ]
+ "docstatus": 1,
+ "fieldname": "request_for_quotation",
+ "transactions": [
+ {"items": ["Supplier Quotation"]},
+ ],
}
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index 5b2112424c..dcdba095fb 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -20,36 +20,36 @@ class TestRequestforQuotation(FrappeTestCase):
def test_quote_status(self):
rfq = make_request_for_quotation()
- self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Pending')
- self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
+ self.assertEqual(rfq.get("suppliers")[0].quote_status, "Pending")
+ self.assertEqual(rfq.get("suppliers")[1].quote_status, "Pending")
# Submit the first supplier quotation
- sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
+ sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get("suppliers")[0].supplier)
sq.submit()
- rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier)
+ rfq.update_rfq_supplier_status() # rfq.get('suppliers')[1].supplier)
- self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Received')
- self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
+ self.assertEqual(rfq.get("suppliers")[0].quote_status, "Received")
+ self.assertEqual(rfq.get("suppliers")[1].quote_status, "Pending")
def test_make_supplier_quotation(self):
rfq = make_request_for_quotation()
- sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
+ sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get("suppliers")[0].supplier)
sq.submit()
- sq1 = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[1].supplier)
+ sq1 = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get("suppliers")[1].supplier)
sq1.submit()
- self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier)
- self.assertEqual(sq.get('items')[0].request_for_quotation, rfq.name)
- self.assertEqual(sq.get('items')[0].item_code, "_Test Item")
- self.assertEqual(sq.get('items')[0].qty, 5)
+ self.assertEqual(sq.supplier, rfq.get("suppliers")[0].supplier)
+ self.assertEqual(sq.get("items")[0].request_for_quotation, rfq.name)
+ self.assertEqual(sq.get("items")[0].item_code, "_Test Item")
+ self.assertEqual(sq.get("items")[0].qty, 5)
- self.assertEqual(sq1.supplier, rfq.get('suppliers')[1].supplier)
- self.assertEqual(sq1.get('items')[0].request_for_quotation, rfq.name)
- self.assertEqual(sq1.get('items')[0].item_code, "_Test Item")
- self.assertEqual(sq1.get('items')[0].qty, 5)
+ self.assertEqual(sq1.supplier, rfq.get("suppliers")[1].supplier)
+ self.assertEqual(sq1.get("items")[0].request_for_quotation, rfq.name)
+ self.assertEqual(sq1.get("items")[0].item_code, "_Test Item")
+ self.assertEqual(sq1.get("items")[0].qty, 5)
def test_make_supplier_quotation_with_special_characters(self):
frappe.delete_doc_if_exists("Supplier", "_Test Supplier '1", force=1)
@@ -60,52 +60,50 @@ class TestRequestforQuotation(FrappeTestCase):
rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
- sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier_wt_appos[0].get("supplier"))
+ sq = make_supplier_quotation_from_rfq(
+ rfq.name, for_supplier=supplier_wt_appos[0].get("supplier")
+ )
sq.submit()
frappe.form_dict = frappe.local("form_dict")
frappe.form_dict.name = rfq.name
- self.assertEqual(
- check_supplier_has_docname_access(supplier_wt_appos[0].get('supplier')),
- True
- )
+ self.assertEqual(check_supplier_has_docname_access(supplier_wt_appos[0].get("supplier")), True)
# reset form_dict
frappe.form_dict.name = None
def test_make_supplier_quotation_from_portal(self):
rfq = make_request_for_quotation()
- rfq.get('items')[0].rate = 100
+ rfq.get("items")[0].rate = 100
rfq.supplier = rfq.suppliers[0].supplier
supplier_quotation_name = create_supplier_quotation(rfq)
- supplier_quotation_doc = frappe.get_doc('Supplier Quotation', supplier_quotation_name)
+ supplier_quotation_doc = frappe.get_doc("Supplier Quotation", supplier_quotation_name)
- self.assertEqual(supplier_quotation_doc.supplier, rfq.get('suppliers')[0].supplier)
- self.assertEqual(supplier_quotation_doc.get('items')[0].request_for_quotation, rfq.name)
- self.assertEqual(supplier_quotation_doc.get('items')[0].item_code, "_Test Item")
- self.assertEqual(supplier_quotation_doc.get('items')[0].qty, 5)
- self.assertEqual(supplier_quotation_doc.get('items')[0].amount, 500)
+ self.assertEqual(supplier_quotation_doc.supplier, rfq.get("suppliers")[0].supplier)
+ self.assertEqual(supplier_quotation_doc.get("items")[0].request_for_quotation, rfq.name)
+ self.assertEqual(supplier_quotation_doc.get("items")[0].item_code, "_Test Item")
+ self.assertEqual(supplier_quotation_doc.get("items")[0].qty, 5)
+ self.assertEqual(supplier_quotation_doc.get("items")[0].amount, 500)
def test_make_multi_uom_supplier_quotation(self):
item_code = "_Test Multi UOM RFQ Item"
- if not frappe.db.exists('Item', item_code):
- item = make_item(item_code, {'stock_uom': '_Test UOM'})
- row = item.append('uoms', {
- 'uom': 'Kg',
- 'conversion_factor': 2
- })
+ if not frappe.db.exists("Item", item_code):
+ item = make_item(item_code, {"stock_uom": "_Test UOM"})
+ row = item.append("uoms", {"uom": "Kg", "conversion_factor": 2})
row.db_update()
- rfq = make_request_for_quotation(item_code="_Test Multi UOM RFQ Item", uom="Kg", conversion_factor=2)
- rfq.get('items')[0].rate = 100
+ rfq = make_request_for_quotation(
+ item_code="_Test Multi UOM RFQ Item", uom="Kg", conversion_factor=2
+ )
+ rfq.get("items")[0].rate = 100
rfq.supplier = rfq.suppliers[0].supplier
self.assertEqual(rfq.items[0].stock_qty, 10)
supplier_quotation_name = create_supplier_quotation(rfq)
- supplier_quotation = frappe.get_doc('Supplier Quotation', supplier_quotation_name)
+ supplier_quotation = frappe.get_doc("Supplier Quotation", supplier_quotation_name)
self.assertEqual(supplier_quotation.items[0].qty, 5)
self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
@@ -116,58 +114,62 @@ class TestRequestforQuotation(FrappeTestCase):
rfq = make_rfq(opportunity.name)
self.assertEqual(len(rfq.get("items")), len(opportunity.get("items")))
- rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
+ rfq.message_for_supplier = "Please supply the specified items at the best possible rates."
for item in rfq.items:
item.warehouse = "_Test Warehouse - _TC"
for data in supplier_data:
- rfq.append('suppliers', data)
+ rfq.append("suppliers", data)
- rfq.status = 'Draft'
+ rfq.status = "Draft"
rfq.submit()
+
def make_request_for_quotation(**args):
"""
:param supplier_data: List containing supplier data
"""
args = frappe._dict(args)
supplier_data = args.get("supplier_data") if args.get("supplier_data") else get_supplier_data()
- rfq = frappe.new_doc('Request for Quotation')
+ rfq = frappe.new_doc("Request for Quotation")
rfq.transaction_date = nowdate()
- rfq.status = 'Draft'
- rfq.company = '_Test Company'
- rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
+ rfq.status = "Draft"
+ rfq.company = "_Test Company"
+ rfq.message_for_supplier = "Please supply the specified items at the best possible rates."
for data in supplier_data:
- rfq.append('suppliers', data)
+ rfq.append("suppliers", data)
- rfq.append("items", {
- "item_code": args.item_code or "_Test Item",
- "description": "_Test Item",
- "uom": args.uom or "_Test UOM",
- "stock_uom": args.stock_uom or "_Test UOM",
- "qty": args.qty or 5,
- "conversion_factor": args.conversion_factor or 1.0,
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "schedule_date": nowdate()
- })
+ rfq.append(
+ "items",
+ {
+ "item_code": args.item_code or "_Test Item",
+ "description": "_Test Item",
+ "uom": args.uom or "_Test UOM",
+ "stock_uom": args.stock_uom or "_Test UOM",
+ "qty": args.qty or 5,
+ "conversion_factor": args.conversion_factor or 1.0,
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "schedule_date": nowdate(),
+ },
+ )
rfq.submit()
return rfq
-def get_supplier_data():
- return [{
- "supplier": "_Test Supplier",
- "supplier_name": "_Test Supplier"
- },
- {
- "supplier": "_Test Supplier 1",
- "supplier_name": "_Test Supplier 1"
- }]
-supplier_wt_appos = [{
- "supplier": "_Test Supplier '1",
- "supplier_name": "_Test Supplier '1",
-}]
+def get_supplier_data():
+ return [
+ {"supplier": "_Test Supplier", "supplier_name": "_Test Supplier"},
+ {"supplier": "_Test Supplier 1", "supplier_name": "_Test Supplier 1"},
+ ]
+
+
+supplier_wt_appos = [
+ {
+ "supplier": "_Test Supplier '1",
+ "supplier_name": "_Test Supplier '1",
+ }
+]
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 4f9ff43cd4..97d0ba0b9c 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -30,27 +30,27 @@ class Supplier(TransactionBase):
def before_save(self):
if not self.on_hold:
- self.hold_type = ''
- self.release_date = ''
+ self.hold_type = ""
+ self.release_date = ""
elif self.on_hold and not self.hold_type:
- self.hold_type = 'All'
+ self.hold_type = "All"
def load_dashboard_info(self):
info = get_dashboard_info(self.doctype, self.name)
- self.set_onload('dashboard_info', info)
+ self.set_onload("dashboard_info", info)
def autoname(self):
- supp_master_name = frappe.defaults.get_global_default('supp_master_name')
- if supp_master_name == 'Supplier Name':
+ supp_master_name = frappe.defaults.get_global_default("supp_master_name")
+ if supp_master_name == "Supplier Name":
self.name = self.supplier_name
- elif supp_master_name == 'Naming Series':
+ elif supp_master_name == "Naming Series":
set_name_by_naming_series(self)
else:
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
def on_update(self):
if not self.naming_series:
- self.naming_series = ''
+ self.naming_series = ""
self.create_primary_contact()
self.create_primary_address()
@@ -59,7 +59,7 @@ class Supplier(TransactionBase):
self.flags.is_new_doc = self.is_new()
# validation for Naming Series mandatory field...
- if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series':
+ if frappe.defaults.get_global_default("supp_master_name") == "Naming Series":
if not self.naming_series:
msgprint(_("Series is mandatory"), raise_exception=1)
@@ -68,13 +68,13 @@ class Supplier(TransactionBase):
@frappe.whitelist()
def get_supplier_group_details(self):
- doc = frappe.get_doc('Supplier Group', self.supplier_group)
+ doc = frappe.get_doc("Supplier Group", self.supplier_group)
self.payment_terms = ""
self.accounts = []
if doc.accounts:
for account in doc.accounts:
- child = self.append('accounts')
+ child = self.append("accounts")
child.company = account.company
child.account = account.account
@@ -84,12 +84,22 @@ class Supplier(TransactionBase):
self.save()
def validate_internal_supplier(self):
- internal_supplier = frappe.db.get_value("Supplier",
- {"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
+ internal_supplier = frappe.db.get_value(
+ "Supplier",
+ {
+ "is_internal_supplier": 1,
+ "represents_company": self.represents_company,
+ "name": ("!=", self.name),
+ },
+ "name",
+ )
if internal_supplier:
- frappe.throw(_("Internal Supplier for company {0} already exists").format(
- frappe.bold(self.represents_company)))
+ frappe.throw(
+ _("Internal Supplier for company {0} already exists").format(
+ frappe.bold(self.represents_company)
+ )
+ )
def create_primary_contact(self):
from erpnext.selling.doctype.customer.customer import make_contact
@@ -97,16 +107,16 @@ class Supplier(TransactionBase):
if not self.supplier_primary_contact:
if self.mobile_no or self.email_id:
contact = make_contact(self)
- self.db_set('supplier_primary_contact', contact.name)
- self.db_set('mobile_no', self.mobile_no)
- self.db_set('email_id', self.email_id)
+ self.db_set("supplier_primary_contact", contact.name)
+ self.db_set("mobile_no", self.mobile_no)
+ self.db_set("email_id", self.email_id)
def create_primary_address(self):
from frappe.contacts.doctype.address.address import get_address_display
from erpnext.selling.doctype.customer.customer import make_address
- if self.flags.is_new_doc and self.get('address_line1'):
+ if self.flags.is_new_doc and self.get("address_line1"):
address = make_address(self)
address_display = get_address_display(address.name)
@@ -115,7 +125,8 @@ class Supplier(TransactionBase):
def on_trash(self):
if self.supplier_primary_contact:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabSupplier`
SET
supplier_primary_contact=null,
@@ -123,19 +134,23 @@ class Supplier(TransactionBase):
mobile_no=null,
email_id=null,
primary_address=null
- WHERE name=%(name)s""", {"name": self.name})
+ WHERE name=%(name)s""",
+ {"name": self.name},
+ )
- delete_contact_and_address('Supplier', self.name)
+ delete_contact_and_address("Supplier", self.name)
def after_rename(self, olddn, newdn, merge=False):
- if frappe.defaults.get_global_default('supp_master_name') == 'Supplier Name':
+ if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name":
frappe.db.set(self, "supplier_name", newdn)
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
supplier = filters.get("supplier")
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
`tabContact`.name from `tabContact`,
`tabDynamic Link`
@@ -144,7 +159,6 @@ def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, fil
and `tabDynamic Link`.link_name = %(supplier)s
and `tabDynamic Link`.link_doctype = 'Supplier'
and `tabContact`.name like %(txt)s
- """, {
- 'supplier': supplier,
- 'txt': '%%%s%%' % txt
- })
+ """,
+ {"supplier": supplier, "txt": "%%%s%%" % txt},
+ )
diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py
index 78efd8eea0..11bb06e0ca 100644
--- a/erpnext/buying/doctype/supplier/supplier_dashboard.py
+++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py
@@ -3,29 +3,16 @@ from frappe import _
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on transactions against this Supplier. See timeline below for details'),
- 'fieldname': 'supplier',
- 'non_standard_fieldnames': {
- 'Payment Entry': 'party_name',
- 'Bank Account': 'party'
- },
- 'transactions': [
- {
- 'label': _('Procurement'),
- 'items': ['Request for Quotation', 'Supplier Quotation']
- },
- {
- 'label': _('Orders'),
- 'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
- },
- {
- 'label': _('Payments'),
- 'items': ['Payment Entry', 'Bank Account']
- },
- {
- 'label': _('Pricing'),
- 'items': ['Pricing Rule']
- }
- ]
+ "heatmap": True,
+ "heatmap_message": _(
+ "This is based on transactions against this Supplier. See timeline below for details"
+ ),
+ "fieldname": "supplier",
+ "non_standard_fieldnames": {"Payment Entry": "party_name", "Bank Account": "party"},
+ "transactions": [
+ {"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]},
+ {"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]},
+ {"label": _("Payments"), "items": ["Payment Entry", "Bank Account"]},
+ {"label": _("Pricing"), "items": ["Pricing Rule"]},
+ ],
}
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index 7358e2af22..55722686fe 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -8,8 +8,8 @@ from frappe.test_runner import make_test_records
from erpnext.accounts.party import get_due_date
from erpnext.exceptions import PartyDisabled
-test_dependencies = ['Payment Term', 'Payment Terms Template']
-test_records = frappe.get_test_records('Supplier')
+test_dependencies = ["Payment Term", "Payment Terms Template"]
+test_records = frappe.get_test_records("Supplier")
from frappe.tests.utils import FrappeTestCase
@@ -42,7 +42,8 @@ class TestSupplier(FrappeTestCase):
def test_supplier_default_payment_terms(self):
# Payment Term based on Days after invoice date
frappe.db.set_value(
- "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 3")
+ "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 3"
+ )
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2016-02-21")
@@ -52,7 +53,8 @@ class TestSupplier(FrappeTestCase):
# Payment Term based on last day of month
frappe.db.set_value(
- "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 1")
+ "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 1"
+ )
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2016-02-29")
@@ -63,13 +65,17 @@ class TestSupplier(FrappeTestCase):
frappe.db.set_value("Supplier", "_Test Supplier With Template 1", "payment_terms", "")
# Set credit limit for the supplier group instead of supplier and evaluate the due date
- frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 3")
+ frappe.db.set_value(
+ "Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 3"
+ )
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2016-02-21")
# Payment terms for Supplier Group instead of supplier and evaluate the due date
- frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 1")
+ frappe.db.set_value(
+ "Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 1"
+ )
# Leap year
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
@@ -105,15 +111,15 @@ class TestSupplier(FrappeTestCase):
def test_supplier_country(self):
# Test that country field exists in Supplier DocType
- supplier = frappe.get_doc('Supplier', '_Test Supplier with Country')
- self.assertTrue('country' in supplier.as_dict())
+ supplier = frappe.get_doc("Supplier", "_Test Supplier with Country")
+ self.assertTrue("country" in supplier.as_dict())
# Test if test supplier field record is 'Greece'
self.assertEqual(supplier.country, "Greece")
# Test update Supplier instance country value
- supplier = frappe.get_doc('Supplier', '_Test Supplier')
- supplier.country = 'Greece'
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
+ supplier.country = "Greece"
supplier.save()
self.assertEqual(supplier.country, "Greece")
@@ -126,19 +132,18 @@ class TestSupplier(FrappeTestCase):
details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
self.assertEqual(details.tax_category, "_Test Tax Category 1")
- address = frappe.get_doc(dict(
- doctype='Address',
- address_title='_Test Address With Tax Category',
- tax_category='_Test Tax Category 2',
- address_type='Billing',
- address_line1='Station Road',
- city='_Test City',
- country='India',
- links=[dict(
- link_doctype='Supplier',
- link_name='_Test Supplier With Tax Category'
- )]
- )).insert()
+ address = frappe.get_doc(
+ dict(
+ doctype="Address",
+ address_title="_Test Address With Tax Category",
+ tax_category="_Test Tax Category 2",
+ address_type="Billing",
+ address_line1="Station Road",
+ city="_Test City",
+ country="India",
+ links=[dict(link_doctype="Supplier", link_name="_Test Supplier With Tax Category")],
+ )
+ ).insert()
# Tax Category with Address
details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
@@ -147,18 +152,21 @@ class TestSupplier(FrappeTestCase):
# Rollback
address.delete()
+
def create_supplier(**args):
args = frappe._dict(args)
if frappe.db.exists("Supplier", args.supplier_name):
return frappe.get_doc("Supplier", args.supplier_name)
- doc = frappe.get_doc({
- "doctype": "Supplier",
- "supplier_name": args.supplier_name,
- "supplier_group": args.supplier_group or "Services",
- "supplier_type": args.supplier_type or "Company",
- "tax_withholding_category": args.tax_withholding_category
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Supplier",
+ "supplier_name": args.supplier_name,
+ "supplier_group": args.supplier_group or "Services",
+ "supplier_type": args.supplier_type or "Company",
+ "tax_withholding_category": args.tax_withholding_category,
+ }
+ ).insert()
return doc
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index d5788638f7..c19c1df180 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -10,9 +10,8 @@ from frappe.utils import flt, getdate, nowdate
from erpnext.buying.utils import validate_for_items
from erpnext.controllers.buying_controller import BuyingController
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
+
class SupplierQuotation(BuyingController):
def validate(self):
@@ -22,8 +21,8 @@ class SupplierQuotation(BuyingController):
self.status = "Draft"
from erpnext.controllers.status_updater import validate_status
- validate_status(self.status, ["Draft", "Submitted", "Stopped",
- "Cancelled"])
+
+ validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled"])
validate_for_items(self)
self.validate_with_previous_doc()
@@ -42,17 +41,19 @@ class SupplierQuotation(BuyingController):
pass
def validate_with_previous_doc(self):
- super(SupplierQuotation, self).validate_with_previous_doc({
- "Material Request": {
- "ref_dn_field": "prevdoc_docname",
- "compare_fields": [["company", "="]],
- },
- "Material Request Item": {
- "ref_dn_field": "prevdoc_detail_docname",
- "compare_fields": [["item_code", "="], ["uom", "="]],
- "is_child_table": True
+ super(SupplierQuotation, self).validate_with_previous_doc(
+ {
+ "Material Request": {
+ "ref_dn_field": "prevdoc_docname",
+ "compare_fields": [["company", "="]],
+ },
+ "Material Request Item": {
+ "ref_dn_field": "prevdoc_detail_docname",
+ "compare_fields": [["item_code", "="], ["uom", "="]],
+ "is_child_table": True,
+ },
}
- })
+ )
def validate_valid_till(self):
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
@@ -64,18 +65,28 @@ class SupplierQuotation(BuyingController):
if item.request_for_quotation:
rfq_list.add(item.request_for_quotation)
for rfq in rfq_list:
- doc = frappe.get_doc('Request for Quotation', rfq)
- doc_sup = frappe.get_all('Request for Quotation Supplier', filters=
- {'parent': doc.name, 'supplier': self.supplier}, fields=['name', 'quote_status'])
+ doc = frappe.get_doc("Request for Quotation", rfq)
+ doc_sup = frappe.get_all(
+ "Request for Quotation Supplier",
+ filters={"parent": doc.name, "supplier": self.supplier},
+ fields=["name", "quote_status"],
+ )
doc_sup = doc_sup[0] if doc_sup else None
if not doc_sup:
- frappe.throw(_("Supplier {0} not found in {1}").format(self.supplier,
- " Request for Quotation {0} ".format(doc.name)))
+ frappe.throw(
+ _("Supplier {0} not found in {1}").format(
+ self.supplier,
+ " Request for Quotation {0} ".format(
+ doc.name
+ ),
+ )
+ )
- quote_status = _('Received')
+ quote_status = _("Received")
for item in doc.items:
- sqi_count = frappe.db.sql("""
+ sqi_count = frappe.db.sql(
+ """
SELECT
COUNT(sqi.name) as count
FROM
@@ -86,26 +97,38 @@ class SupplierQuotation(BuyingController):
AND sq.name != %(me)s
AND sqi.request_for_quotation_item = %(rqi)s
AND sqi.parent = sq.name""",
- {"supplier": self.supplier, "rqi": item.name, 'me': self.name}, as_dict=1)[0]
- self_count = sum(my_item.request_for_quotation_item == item.name
- for my_item in self.items) if include_me else 0
+ {"supplier": self.supplier, "rqi": item.name, "me": self.name},
+ as_dict=1,
+ )[0]
+ self_count = (
+ sum(my_item.request_for_quotation_item == item.name for my_item in self.items)
+ if include_me
+ else 0
+ )
if (sqi_count.count + self_count) == 0:
- quote_status = _('Pending')
+ quote_status = _("Pending")
+
+ frappe.db.set_value(
+ "Request for Quotation Supplier", doc_sup.name, "quote_status", quote_status
+ )
- frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
+
list_context = get_list_context(context)
- list_context.update({
- 'show_sidebar': True,
- 'show_search': True,
- 'no_breadcrumbs': True,
- 'title': _('Supplier Quotation'),
- })
+ list_context.update(
+ {
+ "show_sidebar": True,
+ "show_search": True,
+ "no_breadcrumbs": True,
+ "title": _("Supplier Quotation"),
+ }
+ )
return list_context
+
@frappe.whitelist()
def make_purchase_order(source_name, target_doc=None):
def set_missing_values(source, target):
@@ -116,74 +139,91 @@ def make_purchase_order(source_name, target_doc=None):
def update_item(obj, target, source_parent):
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
- doclist = get_mapped_doc("Supplier Quotation", source_name, {
- "Supplier Quotation": {
- "doctype": "Purchase Order",
- "validation": {
- "docstatus": ["=", 1],
- }
+ doclist = get_mapped_doc(
+ "Supplier Quotation",
+ source_name,
+ {
+ "Supplier Quotation": {
+ "doctype": "Purchase Order",
+ "validation": {
+ "docstatus": ["=", 1],
+ },
+ },
+ "Supplier Quotation Item": {
+ "doctype": "Purchase Order Item",
+ "field_map": [
+ ["name", "supplier_quotation_item"],
+ ["parent", "supplier_quotation"],
+ ["material_request", "material_request"],
+ ["material_request_item", "material_request_item"],
+ ["sales_order", "sales_order"],
+ ],
+ "postprocess": update_item,
+ },
+ "Purchase Taxes and Charges": {
+ "doctype": "Purchase Taxes and Charges",
+ },
},
- "Supplier Quotation Item": {
- "doctype": "Purchase Order Item",
- "field_map": [
- ["name", "supplier_quotation_item"],
- ["parent", "supplier_quotation"],
- ["material_request", "material_request"],
- ["material_request_item", "material_request_item"],
- ["sales_order", "sales_order"]
- ],
- "postprocess": update_item
- },
- "Purchase Taxes and Charges": {
- "doctype": "Purchase Taxes and Charges",
- },
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
- doclist.set_onload('ignore_price_list', True)
+ doclist.set_onload("ignore_price_list", True)
return doclist
+
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
- doc = get_mapped_doc("Supplier Quotation", source_name, {
- "Supplier Quotation": {
- "doctype": "Purchase Invoice",
- "validation": {
- "docstatus": ["=", 1],
- }
+ doc = get_mapped_doc(
+ "Supplier Quotation",
+ source_name,
+ {
+ "Supplier Quotation": {
+ "doctype": "Purchase Invoice",
+ "validation": {
+ "docstatus": ["=", 1],
+ },
+ },
+ "Supplier Quotation Item": {"doctype": "Purchase Invoice Item"},
+ "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
},
- "Supplier Quotation Item": {
- "doctype": "Purchase Invoice Item"
- },
- "Purchase Taxes and Charges": {
- "doctype": "Purchase Taxes and Charges"
- }
- }, target_doc)
+ target_doc,
+ )
return doc
@frappe.whitelist()
def make_quotation(source_name, target_doc=None):
- doclist = get_mapped_doc("Supplier Quotation", source_name, {
- "Supplier Quotation": {
- "doctype": "Quotation",
- "field_map": {
- "name": "supplier_quotation",
- }
+ doclist = get_mapped_doc(
+ "Supplier Quotation",
+ source_name,
+ {
+ "Supplier Quotation": {
+ "doctype": "Quotation",
+ "field_map": {
+ "name": "supplier_quotation",
+ },
+ },
+ "Supplier Quotation Item": {
+ "doctype": "Quotation Item",
+ "condition": lambda doc: frappe.db.get_value("Item", doc.item_code, "is_sales_item") == 1,
+ "add_if_empty": True,
+ },
},
- "Supplier Quotation Item": {
- "doctype": "Quotation Item",
- "condition": lambda doc: frappe.db.get_value("Item", doc.item_code, "is_sales_item")==1,
- "add_if_empty": True
- }
- }, target_doc)
+ target_doc,
+ )
return doclist
+
def set_expired_status():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabSupplier Quotation` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s
- """, (nowdate()))
+ """,
+ (nowdate()),
+ )
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py
index 236b91ad58..369fc94dee 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py
@@ -3,28 +3,16 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'supplier_quotation',
- 'non_standard_fieldnames': {
- 'Auto Repeat': 'reference_document'
+ "fieldname": "supplier_quotation",
+ "non_standard_fieldnames": {"Auto Repeat": "reference_document"},
+ "internal_links": {
+ "Material Request": ["items", "material_request"],
+ "Request for Quotation": ["items", "request_for_quotation"],
+ "Project": ["items", "project"],
},
- 'internal_links': {
- 'Material Request': ['items', 'material_request'],
- 'Request for Quotation': ['items', 'request_for_quotation'],
- 'Project': ['items', 'project'],
- },
- 'transactions': [
- {
- 'label': _('Related'),
- 'items': ['Purchase Order', 'Quotation']
- },
- {
- 'label': _('Reference'),
- 'items': ['Material Request', 'Request for Quotation', 'Project']
- },
- {
- 'label': _('Subscription'),
- 'items': ['Auto Repeat']
- },
- ]
-
+ "transactions": [
+ {"label": _("Related"), "items": ["Purchase Order", "Quotation"]},
+ {"label": _("Reference"), "items": ["Material Request", "Request for Quotation", "Project"]},
+ {"label": _("Subscription"), "items": ["Auto Repeat"]},
+ ],
}
diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
index a4d45975c3..13c851c735 100644
--- a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
@@ -2,8 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
-
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -14,8 +12,7 @@ class TestPurchaseOrder(FrappeTestCase):
sq = frappe.copy_doc(test_records[0]).insert()
- self.assertRaises(frappe.ValidationError, make_purchase_order,
- sq.name)
+ self.assertRaises(frappe.ValidationError, make_purchase_order, sq.name)
sq = frappe.get_doc("Supplier Quotation", sq.name)
sq.submit()
@@ -32,4 +29,5 @@ class TestPurchaseOrder(FrappeTestCase):
po.insert()
-test_records = frappe.get_test_records('Supplier Quotation')
+
+test_records = frappe.get_test_records("Supplier Quotation")
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
index 3bcc0debae..992bc805a5 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
@@ -16,7 +16,6 @@ from erpnext.buying.doctype.supplier_scorecard_period.supplier_scorecard_period
class SupplierScorecard(Document):
-
def validate(self):
self.validate_standings()
self.validate_criteria_weights()
@@ -34,12 +33,16 @@ class SupplierScorecard(Document):
for c1 in self.standings:
for c2 in self.standings:
if c1 != c2:
- if (c1.max_grade > c2.min_grade and c1.min_grade < c2.max_grade):
- throw(_('Overlap in scoring between {0} and {1}').format(c1.standing_name,c2.standing_name))
+ if c1.max_grade > c2.min_grade and c1.min_grade < c2.max_grade:
+ throw(_("Overlap in scoring between {0} and {1}").format(c1.standing_name, c2.standing_name))
if c2.min_grade == score:
score = c2.max_grade
if score < 100:
- throw(_('Unable to find score starting at {0}. You need to have standing scores covering 0 to 100').format(score))
+ throw(
+ _(
+ "Unable to find score starting at {0}. You need to have standing scores covering 0 to 100"
+ ).format(score)
+ )
def validate_criteria_weights(self):
@@ -48,10 +51,11 @@ class SupplierScorecard(Document):
weight += c.weight
if weight != 100:
- throw(_('Criteria weights must add up to 100%'))
+ throw(_("Criteria weights must add up to 100%"))
def calculate_total_score(self):
- scorecards = frappe.db.sql("""
+ scorecards = frappe.db.sql(
+ """
SELECT
scp.name
FROM
@@ -61,18 +65,20 @@ class SupplierScorecard(Document):
AND scp.docstatus = 1
ORDER BY
scp.end_date DESC""",
- {"sc": self.name}, as_dict=1)
+ {"sc": self.name},
+ as_dict=1,
+ )
period = 0
total_score = 0
total_max_score = 0
for scp in scorecards:
- my_sc = frappe.get_doc('Supplier Scorecard Period', scp.name)
+ my_sc = frappe.get_doc("Supplier Scorecard Period", scp.name)
my_scp_weight = self.weighting_function
- my_scp_weight = my_scp_weight.replace('{period_number}', str(period))
+ my_scp_weight = my_scp_weight.replace("{period_number}", str(period))
- my_scp_maxweight = my_scp_weight.replace('{total_score}', '100')
- my_scp_weight = my_scp_weight.replace('{total_score}', str(my_sc.total_score))
+ my_scp_maxweight = my_scp_weight.replace("{total_score}", "100")
+ my_scp_weight = my_scp_weight.replace("{total_score}", str(my_sc.total_score))
max_score = my_sc.calculate_weighted_score(my_scp_maxweight)
score = my_sc.calculate_weighted_score(my_scp_weight)
@@ -81,24 +87,25 @@ class SupplierScorecard(Document):
total_max_score += max_score
period += 1
if total_max_score > 0:
- self.supplier_score = round(100.0 * (total_score / total_max_score) ,1)
+ self.supplier_score = round(100.0 * (total_score / total_max_score), 1)
else:
- self.supplier_score = 100
+ self.supplier_score = 100
def update_standing(self):
# Get the setup document
for standing in self.standings:
- if (not standing.min_grade or (standing.min_grade <= self.supplier_score)) and \
- (not standing.max_grade or (standing.max_grade > self.supplier_score)):
+ if (not standing.min_grade or (standing.min_grade <= self.supplier_score)) and (
+ not standing.max_grade or (standing.max_grade > self.supplier_score)
+ ):
self.status = standing.standing_name
self.indicator_color = standing.standing_color
self.notify_supplier = standing.notify_supplier
self.notify_employee = standing.notify_employee
self.employee_link = standing.employee_link
- #Update supplier standing info
- for fieldname in ('prevent_pos', 'prevent_rfqs','warn_rfqs','warn_pos'):
+ # Update supplier standing info
+ for fieldname in ("prevent_pos", "prevent_rfqs", "warn_rfqs", "warn_pos"):
self.set(fieldname, standing.get(fieldname))
frappe.db.set_value("Supplier", self.supplier, fieldname, self.get(fieldname))
@@ -109,7 +116,8 @@ def get_timeline_data(doctype, name):
scs = frappe.get_doc(doctype, name)
out = {}
timeline_data = {}
- scorecards = frappe.db.sql("""
+ scorecards = frappe.db.sql(
+ """
SELECT
sc.name
FROM
@@ -117,39 +125,48 @@ def get_timeline_data(doctype, name):
WHERE
sc.scorecard = %(scs)s
AND sc.docstatus = 1""",
- {"scs": scs.name}, as_dict=1)
+ {"scs": scs.name},
+ as_dict=1,
+ )
for sc in scorecards:
- start_date, end_date, total_score = frappe.db.get_value('Supplier Scorecard Period', sc.name, ['start_date', 'end_date', 'total_score'])
+ start_date, end_date, total_score = frappe.db.get_value(
+ "Supplier Scorecard Period", sc.name, ["start_date", "end_date", "total_score"]
+ )
for single_date in daterange(start_date, end_date):
- timeline_data[time.mktime(single_date.timetuple())] = total_score
+ timeline_data[time.mktime(single_date.timetuple())] = total_score
- out['timeline_data'] = timeline_data
+ out["timeline_data"] = timeline_data
return out
+
def daterange(start_date, end_date):
- for n in range(int ((end_date - start_date).days)+1):
- yield start_date + timedelta(n)
+ for n in range(int((end_date - start_date).days) + 1):
+ yield start_date + timedelta(n)
+
def refresh_scorecards():
- scorecards = frappe.db.sql("""
+ scorecards = frappe.db.sql(
+ """
SELECT
sc.name
FROM
`tabSupplier Scorecard` sc""",
- {}, as_dict=1)
+ {},
+ as_dict=1,
+ )
for sc in scorecards:
# Check to see if any new scorecard periods are created
if make_all_scorecards(sc.name) > 0:
# Save the scorecard to update the score and standings
- frappe.get_doc('Supplier Scorecard', sc.name).save()
+ frappe.get_doc("Supplier Scorecard", sc.name).save()
@frappe.whitelist()
def make_all_scorecards(docname):
- sc = frappe.get_doc('Supplier Scorecard', docname)
- supplier = frappe.get_doc('Supplier',sc.supplier)
+ sc = frappe.get_doc("Supplier Scorecard", docname)
+ supplier = frappe.get_doc("Supplier", sc.supplier)
start_date = getdate(supplier.creation)
end_date = get_scorecard_date(sc.period, start_date)
@@ -161,7 +178,8 @@ def make_all_scorecards(docname):
while (start_date < todays) and (end_date <= todays):
# check to make sure there is no scorecard period already created
- scorecards = frappe.db.sql("""
+ scorecards = frappe.db.sql(
+ """
SELECT
scp.name
FROM
@@ -177,7 +195,9 @@ def make_all_scorecards(docname):
AND scp.end_date > %(start_date)s))
ORDER BY
scp.end_date DESC""",
- {"sc": docname, "start_date": start_date, "end_date": end_date}, as_dict=1)
+ {"sc": docname, "start_date": start_date, "end_date": end_date},
+ as_dict=1,
+ )
if len(scorecards) == 0:
period_card = make_supplier_scorecard(docname, None)
period_card.start_date = start_date
@@ -189,82 +209,178 @@ def make_all_scorecards(docname):
first_start_date = start_date
last_end_date = end_date
- start_date = getdate(add_days(end_date,1))
+ start_date = getdate(add_days(end_date, 1))
end_date = get_scorecard_date(sc.period, start_date)
if scp_count > 0:
- frappe.msgprint(_("Created {0} scorecards for {1} between: ").format(scp_count, sc.supplier) + str(first_start_date) + " - " + str(last_end_date))
+ frappe.msgprint(
+ _("Created {0} scorecards for {1} between: ").format(scp_count, sc.supplier)
+ + str(first_start_date)
+ + " - "
+ + str(last_end_date)
+ )
return scp_count
+
def get_scorecard_date(period, start_date):
- if period == 'Per Week':
- end_date = getdate(add_days(start_date,7))
- elif period == 'Per Month':
+ if period == "Per Week":
+ end_date = getdate(add_days(start_date, 7))
+ elif period == "Per Month":
end_date = get_last_day(start_date)
- elif period == 'Per Year':
- end_date = add_days(add_years(start_date,1), -1)
+ elif period == "Per Year":
+ end_date = add_days(add_years(start_date, 1), -1)
return end_date
+
def make_default_records():
install_variable_docs = [
- {"param_name": "total_accepted_items", "variable_label": "Total Accepted Items", \
- "path": "get_total_accepted_items"},
- {"param_name": "total_accepted_amount", "variable_label": "Total Accepted Amount", \
- "path": "get_total_accepted_amount"},
- {"param_name": "total_rejected_items", "variable_label": "Total Rejected Items", \
- "path": "get_total_rejected_items"},
- {"param_name": "total_rejected_amount", "variable_label": "Total Rejected Amount", \
- "path": "get_total_rejected_amount"},
- {"param_name": "total_received_items", "variable_label": "Total Received Items", \
- "path": "get_total_received_items"},
- {"param_name": "total_received_amount", "variable_label": "Total Received Amount", \
- "path": "get_total_received_amount"},
- {"param_name": "rfq_response_days", "variable_label": "RFQ Response Days", \
- "path": "get_rfq_response_days"},
- {"param_name": "sq_total_items", "variable_label": "SQ Total Items", \
- "path": "get_sq_total_items"},
- {"param_name": "sq_total_number", "variable_label": "SQ Total Number", \
- "path": "get_sq_total_number"},
- {"param_name": "rfq_total_number", "variable_label": "RFQ Total Number", \
- "path": "get_rfq_total_number"},
- {"param_name": "rfq_total_items", "variable_label": "RFQ Total Items", \
- "path": "get_rfq_total_items"},
- {"param_name": "tot_item_days", "variable_label": "Total Item Days", \
- "path": "get_item_workdays"},
- {"param_name": "on_time_shipment_num", "variable_label": "# of On Time Shipments", "path": \
- "get_on_time_shipments"},
- {"param_name": "cost_of_delayed_shipments", "variable_label": "Cost of Delayed Shipments", \
- "path": "get_cost_of_delayed_shipments"},
- {"param_name": "cost_of_on_time_shipments", "variable_label": "Cost of On Time Shipments", \
- "path": "get_cost_of_on_time_shipments"},
- {"param_name": "total_working_days", "variable_label": "Total Working Days", \
- "path": "get_total_workdays"},
- {"param_name": "tot_cost_shipments", "variable_label": "Total Cost of Shipments", \
- "path": "get_total_cost_of_shipments"},
- {"param_name": "tot_days_late", "variable_label": "Total Days Late", \
- "path": "get_total_days_late"},
- {"param_name": "total_shipments", "variable_label": "Total Shipments", \
- "path": "get_total_shipments"}
+ {
+ "param_name": "total_accepted_items",
+ "variable_label": "Total Accepted Items",
+ "path": "get_total_accepted_items",
+ },
+ {
+ "param_name": "total_accepted_amount",
+ "variable_label": "Total Accepted Amount",
+ "path": "get_total_accepted_amount",
+ },
+ {
+ "param_name": "total_rejected_items",
+ "variable_label": "Total Rejected Items",
+ "path": "get_total_rejected_items",
+ },
+ {
+ "param_name": "total_rejected_amount",
+ "variable_label": "Total Rejected Amount",
+ "path": "get_total_rejected_amount",
+ },
+ {
+ "param_name": "total_received_items",
+ "variable_label": "Total Received Items",
+ "path": "get_total_received_items",
+ },
+ {
+ "param_name": "total_received_amount",
+ "variable_label": "Total Received Amount",
+ "path": "get_total_received_amount",
+ },
+ {
+ "param_name": "rfq_response_days",
+ "variable_label": "RFQ Response Days",
+ "path": "get_rfq_response_days",
+ },
+ {
+ "param_name": "sq_total_items",
+ "variable_label": "SQ Total Items",
+ "path": "get_sq_total_items",
+ },
+ {
+ "param_name": "sq_total_number",
+ "variable_label": "SQ Total Number",
+ "path": "get_sq_total_number",
+ },
+ {
+ "param_name": "rfq_total_number",
+ "variable_label": "RFQ Total Number",
+ "path": "get_rfq_total_number",
+ },
+ {
+ "param_name": "rfq_total_items",
+ "variable_label": "RFQ Total Items",
+ "path": "get_rfq_total_items",
+ },
+ {
+ "param_name": "tot_item_days",
+ "variable_label": "Total Item Days",
+ "path": "get_item_workdays",
+ },
+ {
+ "param_name": "on_time_shipment_num",
+ "variable_label": "# of On Time Shipments",
+ "path": "get_on_time_shipments",
+ },
+ {
+ "param_name": "cost_of_delayed_shipments",
+ "variable_label": "Cost of Delayed Shipments",
+ "path": "get_cost_of_delayed_shipments",
+ },
+ {
+ "param_name": "cost_of_on_time_shipments",
+ "variable_label": "Cost of On Time Shipments",
+ "path": "get_cost_of_on_time_shipments",
+ },
+ {
+ "param_name": "total_working_days",
+ "variable_label": "Total Working Days",
+ "path": "get_total_workdays",
+ },
+ {
+ "param_name": "tot_cost_shipments",
+ "variable_label": "Total Cost of Shipments",
+ "path": "get_total_cost_of_shipments",
+ },
+ {
+ "param_name": "tot_days_late",
+ "variable_label": "Total Days Late",
+ "path": "get_total_days_late",
+ },
+ {
+ "param_name": "total_shipments",
+ "variable_label": "Total Shipments",
+ "path": "get_total_shipments",
+ },
]
install_standing_docs = [
- {"min_grade": 0.0, "prevent_rfqs": 1, "notify_supplier": 0, "max_grade": 30.0, "prevent_pos": 1, \
- "standing_color": "Red", "notify_employee": 0, "standing_name": "Very Poor"},
- {"min_grade": 30.0, "prevent_rfqs": 1, "notify_supplier": 0, "max_grade": 50.0, "prevent_pos": 0, \
- "standing_color": "Red", "notify_employee": 0, "standing_name": "Poor"},
- {"min_grade": 50.0, "prevent_rfqs": 0, "notify_supplier": 0, "max_grade": 80.0, "prevent_pos": 0, \
- "standing_color": "Green", "notify_employee": 0, "standing_name": "Average"},
- {"min_grade": 80.0, "prevent_rfqs": 0, "notify_supplier": 0, "max_grade": 100.0, "prevent_pos": 0, \
- "standing_color": "Blue", "notify_employee": 0, "standing_name": "Excellent"},
+ {
+ "min_grade": 0.0,
+ "prevent_rfqs": 1,
+ "notify_supplier": 0,
+ "max_grade": 30.0,
+ "prevent_pos": 1,
+ "standing_color": "Red",
+ "notify_employee": 0,
+ "standing_name": "Very Poor",
+ },
+ {
+ "min_grade": 30.0,
+ "prevent_rfqs": 1,
+ "notify_supplier": 0,
+ "max_grade": 50.0,
+ "prevent_pos": 0,
+ "standing_color": "Red",
+ "notify_employee": 0,
+ "standing_name": "Poor",
+ },
+ {
+ "min_grade": 50.0,
+ "prevent_rfqs": 0,
+ "notify_supplier": 0,
+ "max_grade": 80.0,
+ "prevent_pos": 0,
+ "standing_color": "Green",
+ "notify_employee": 0,
+ "standing_name": "Average",
+ },
+ {
+ "min_grade": 80.0,
+ "prevent_rfqs": 0,
+ "notify_supplier": 0,
+ "max_grade": 100.0,
+ "prevent_pos": 0,
+ "standing_color": "Blue",
+ "notify_employee": 0,
+ "standing_name": "Excellent",
+ },
]
for d in install_variable_docs:
try:
- d['doctype'] = "Supplier Scorecard Variable"
+ d["doctype"] = "Supplier Scorecard Variable"
frappe.get_doc(d).insert()
except frappe.NameError:
pass
for d in install_standing_docs:
try:
- d['doctype'] = "Supplier Scorecard Standing"
+ d["doctype"] = "Supplier Scorecard Standing"
frappe.get_doc(d).insert()
except frappe.NameError:
pass
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py
index 5d693263f8..e3557bd0d8 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py
@@ -3,14 +3,9 @@ from frappe import _
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This covers all scorecards tied to this Setup'),
- 'fieldname': 'supplier',
- 'method' : 'erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.get_timeline_data',
- 'transactions': [
- {
- 'label': _('Scorecards'),
- 'items': ['Supplier Scorecard Period']
- }
- ]
+ "heatmap": True,
+ "heatmap_message": _("This covers all scorecards tied to this Setup"),
+ "fieldname": "supplier",
+ "method": "erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.get_timeline_data",
+ "transactions": [{"label": _("Scorecards"), "items": ["Supplier Scorecard Period"]}],
}
diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
index 8ecc2cd466..2694f96fbe 100644
--- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
@@ -7,7 +7,6 @@ from frappe.tests.utils import FrappeTestCase
class TestSupplierScorecard(FrappeTestCase):
-
def test_create_scorecard(self):
doc = make_supplier_scorecard().insert()
self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
@@ -17,7 +16,8 @@ class TestSupplierScorecard(FrappeTestCase):
my_doc = make_supplier_scorecard()
for d in my_doc.criteria:
d.weight = 0
- self.assertRaises(frappe.ValidationError,my_doc.insert)
+ self.assertRaises(frappe.ValidationError, my_doc.insert)
+
def make_supplier_scorecard():
my_doc = frappe.get_doc(valid_scorecard[0])
@@ -36,95 +36,106 @@ def delete_test_scorecards():
my_doc = make_supplier_scorecard()
if frappe.db.exists("Supplier Scorecard", my_doc.name):
# Delete all the periods, then delete the scorecard
- frappe.db.sql("""delete from `tabSupplier Scorecard Period` where scorecard = %(scorecard)s""", {'scorecard': my_doc.name})
- frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Criteria` where parenttype = 'Supplier Scorecard Period'""")
- frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Standing` where parenttype = 'Supplier Scorecard Period'""")
- frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Variable` where parenttype = 'Supplier Scorecard Period'""")
+ frappe.db.sql(
+ """delete from `tabSupplier Scorecard Period` where scorecard = %(scorecard)s""",
+ {"scorecard": my_doc.name},
+ )
+ frappe.db.sql(
+ """delete from `tabSupplier Scorecard Scoring Criteria` where parenttype = 'Supplier Scorecard Period'"""
+ )
+ frappe.db.sql(
+ """delete from `tabSupplier Scorecard Scoring Standing` where parenttype = 'Supplier Scorecard Period'"""
+ )
+ frappe.db.sql(
+ """delete from `tabSupplier Scorecard Scoring Variable` where parenttype = 'Supplier Scorecard Period'"""
+ )
frappe.delete_doc(my_doc.doctype, my_doc.name)
+
valid_scorecard = [
{
- "standings":[
+ "standings": [
{
- "min_grade":0.0,"name":"Very Poor",
- "prevent_rfqs":1,
- "notify_supplier":0,
- "doctype":"Supplier Scorecard Scoring Standing",
- "max_grade":30.0,
- "prevent_pos":1,
- "warn_pos":0,
- "warn_rfqs":0,
- "standing_color":"Red",
- "notify_employee":0,
- "standing_name":"Very Poor",
- "parenttype":"Supplier Scorecard",
- "parentfield":"standings"
+ "min_grade": 0.0,
+ "name": "Very Poor",
+ "prevent_rfqs": 1,
+ "notify_supplier": 0,
+ "doctype": "Supplier Scorecard Scoring Standing",
+ "max_grade": 30.0,
+ "prevent_pos": 1,
+ "warn_pos": 0,
+ "warn_rfqs": 0,
+ "standing_color": "Red",
+ "notify_employee": 0,
+ "standing_name": "Very Poor",
+ "parenttype": "Supplier Scorecard",
+ "parentfield": "standings",
},
{
- "min_grade":30.0,
- "name":"Poor",
- "prevent_rfqs":1,
- "notify_supplier":0,
- "doctype":"Supplier Scorecard Scoring Standing",
- "max_grade":50.0,
- "prevent_pos":0,
- "warn_pos":0,
- "warn_rfqs":0,
- "standing_color":"Red",
- "notify_employee":0,
- "standing_name":"Poor",
- "parenttype":"Supplier Scorecard",
- "parentfield":"standings"
+ "min_grade": 30.0,
+ "name": "Poor",
+ "prevent_rfqs": 1,
+ "notify_supplier": 0,
+ "doctype": "Supplier Scorecard Scoring Standing",
+ "max_grade": 50.0,
+ "prevent_pos": 0,
+ "warn_pos": 0,
+ "warn_rfqs": 0,
+ "standing_color": "Red",
+ "notify_employee": 0,
+ "standing_name": "Poor",
+ "parenttype": "Supplier Scorecard",
+ "parentfield": "standings",
},
{
- "min_grade":50.0,
- "name":"Average",
- "prevent_rfqs":0,
- "notify_supplier":0,
- "doctype":"Supplier Scorecard Scoring Standing",
- "max_grade":80.0,
- "prevent_pos":0,
- "warn_pos":0,
- "warn_rfqs":0,
- "standing_color":"Green",
- "notify_employee":0,
- "standing_name":"Average",
- "parenttype":"Supplier Scorecard",
- "parentfield":"standings"
+ "min_grade": 50.0,
+ "name": "Average",
+ "prevent_rfqs": 0,
+ "notify_supplier": 0,
+ "doctype": "Supplier Scorecard Scoring Standing",
+ "max_grade": 80.0,
+ "prevent_pos": 0,
+ "warn_pos": 0,
+ "warn_rfqs": 0,
+ "standing_color": "Green",
+ "notify_employee": 0,
+ "standing_name": "Average",
+ "parenttype": "Supplier Scorecard",
+ "parentfield": "standings",
},
{
- "min_grade":80.0,
- "name":"Excellent",
- "prevent_rfqs":0,
- "notify_supplier":0,
- "doctype":"Supplier Scorecard Scoring Standing",
- "max_grade":100.0,
- "prevent_pos":0,
- "warn_pos":0,
- "warn_rfqs":0,
- "standing_color":"Blue",
- "notify_employee":0,
- "standing_name":"Excellent",
- "parenttype":"Supplier Scorecard",
- "parentfield":"standings"
+ "min_grade": 80.0,
+ "name": "Excellent",
+ "prevent_rfqs": 0,
+ "notify_supplier": 0,
+ "doctype": "Supplier Scorecard Scoring Standing",
+ "max_grade": 100.0,
+ "prevent_pos": 0,
+ "warn_pos": 0,
+ "warn_rfqs": 0,
+ "standing_color": "Blue",
+ "notify_employee": 0,
+ "standing_name": "Excellent",
+ "parenttype": "Supplier Scorecard",
+ "parentfield": "standings",
+ },
+ ],
+ "prevent_pos": 0,
+ "period": "Per Month",
+ "doctype": "Supplier Scorecard",
+ "warn_pos": 0,
+ "warn_rfqs": 0,
+ "notify_supplier": 0,
+ "criteria": [
+ {
+ "weight": 100.0,
+ "doctype": "Supplier Scorecard Scoring Criteria",
+ "criteria_name": "Delivery",
+ "formula": "100",
}
],
- "prevent_pos":0,
- "period":"Per Month",
- "doctype":"Supplier Scorecard",
- "warn_pos":0,
- "warn_rfqs":0,
- "notify_supplier":0,
- "criteria":[
- {
- "weight":100.0,
- "doctype":"Supplier Scorecard Scoring Criteria",
- "criteria_name":"Delivery",
- "formula": "100"
- }
- ],
- "supplier":"_Test Supplier",
- "name":"_Test Supplier",
- "weighting_function":"{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )"
+ "supplier": "_Test Supplier",
+ "name": "_Test Supplier",
+ "weighting_function": "{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )",
}
]
diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py
index 7cd18c31e8..130adc97d4 100644
--- a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py
+++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py
@@ -9,7 +9,9 @@ from frappe import _
from frappe.model.document import Document
-class InvalidFormulaVariable(frappe.ValidationError): pass
+class InvalidFormulaVariable(frappe.ValidationError):
+ pass
+
class SupplierScorecardCriteria(Document):
def validate(self):
@@ -29,28 +31,34 @@ class SupplierScorecardCriteria(Document):
mylist = re.finditer(regex, test_formula, re.MULTILINE | re.DOTALL)
for dummy1, match in enumerate(mylist):
for dummy2 in range(0, len(match.groups())):
- test_formula = test_formula.replace('{' + match.group(1) + '}', "0")
+ test_formula = test_formula.replace("{" + match.group(1) + "}", "0")
try:
- frappe.safe_eval(test_formula, None, {'max':max, 'min': min})
+ frappe.safe_eval(test_formula, None, {"max": max, "min": min})
except Exception:
frappe.throw(_("Error evaluating the criteria formula"))
+
@frappe.whitelist()
def get_criteria_list():
- criteria = frappe.db.sql("""
+ criteria = frappe.db.sql(
+ """
SELECT
scs.name
FROM
`tabSupplier Scorecard Criteria` scs""",
- {}, as_dict=1)
+ {},
+ as_dict=1,
+ )
return criteria
+
def get_variables(criteria_name):
criteria = frappe.get_doc("Supplier Scorecard Criteria", criteria_name)
return _get_variables(criteria)
+
def _get_variables(criteria):
my_variables = []
regex = r"\{(.*?)\}"
@@ -59,16 +67,19 @@ def _get_variables(criteria):
for dummy1, match in enumerate(mylist):
for dummy2 in range(0, len(match.groups())):
try:
- var = frappe.db.sql("""
+ var = frappe.db.sql(
+ """
SELECT
scv.variable_label, scv.description, scv.param_name, scv.path
FROM
`tabSupplier Scorecard Variable` scv
WHERE
param_name=%(param)s""",
- {'param':match.group(1)}, as_dict=1)[0]
+ {"param": match.group(1)},
+ as_dict=1,
+ )[0]
my_variables.append(var)
except Exception:
- frappe.throw(_('Unable to find variable: ') + str(match.group(1)), InvalidFormulaVariable)
+ frappe.throw(_("Unable to find variable: ") + str(match.group(1)), InvalidFormulaVariable)
return my_variables
diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
index 7ff84c15e5..90468d6c16 100644
--- a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
+++ b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
@@ -12,19 +12,26 @@ class TestSupplierScorecardCriteria(FrappeTestCase):
for d in test_good_criteria:
frappe.get_doc(d).insert()
- self.assertRaises(frappe.ValidationError,frappe.get_doc(test_bad_criteria[0]).insert)
+ self.assertRaises(frappe.ValidationError, frappe.get_doc(test_bad_criteria[0]).insert)
def test_formula_validate(self):
delete_test_scorecards()
- self.assertRaises(frappe.ValidationError,frappe.get_doc(test_bad_criteria[1]).insert)
- self.assertRaises(frappe.ValidationError,frappe.get_doc(test_bad_criteria[2]).insert)
+ self.assertRaises(frappe.ValidationError, frappe.get_doc(test_bad_criteria[1]).insert)
+ self.assertRaises(frappe.ValidationError, frappe.get_doc(test_bad_criteria[2]).insert)
+
def delete_test_scorecards():
# Delete all the periods so we can delete all the criteria
frappe.db.sql("""delete from `tabSupplier Scorecard Period`""")
- frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Criteria` where parenttype = 'Supplier Scorecard Period'""")
- frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Standing` where parenttype = 'Supplier Scorecard Period'""")
- frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Variable` where parenttype = 'Supplier Scorecard Period'""")
+ frappe.db.sql(
+ """delete from `tabSupplier Scorecard Scoring Criteria` where parenttype = 'Supplier Scorecard Period'"""
+ )
+ frappe.db.sql(
+ """delete from `tabSupplier Scorecard Scoring Standing` where parenttype = 'Supplier Scorecard Period'"""
+ )
+ frappe.db.sql(
+ """delete from `tabSupplier Scorecard Scoring Variable` where parenttype = 'Supplier Scorecard Period'"""
+ )
for d in test_good_criteria:
if frappe.db.exists("Supplier Scorecard Criteria", d.get("name")):
@@ -36,40 +43,41 @@ def delete_test_scorecards():
# Delete all the periods, then delete the scorecard
frappe.delete_doc(d.get("doctype"), d.get("name"))
+
test_good_criteria = [
{
- "name":"Delivery",
- "weight":40.0,
- "doctype":"Supplier Scorecard Criteria",
- "formula":"(({cost_of_on_time_shipments} / {tot_cost_shipments}) if {tot_cost_shipments} > 0 else 1 )* 100",
- "criteria_name":"Delivery",
- "max_score":100.0
+ "name": "Delivery",
+ "weight": 40.0,
+ "doctype": "Supplier Scorecard Criteria",
+ "formula": "(({cost_of_on_time_shipments} / {tot_cost_shipments}) if {tot_cost_shipments} > 0 else 1 )* 100",
+ "criteria_name": "Delivery",
+ "max_score": 100.0,
},
]
test_bad_criteria = [
{
- "name":"Fake Criteria 1",
- "weight":40.0,
- "doctype":"Supplier Scorecard Criteria",
- "formula":"(({fake_variable} / {tot_cost_shipments}) if {tot_cost_shipments} > 0 else 1 )* 100", # Invalid variable name
- "criteria_name":"Fake Criteria 1",
- "max_score":100.0
+ "name": "Fake Criteria 1",
+ "weight": 40.0,
+ "doctype": "Supplier Scorecard Criteria",
+ "formula": "(({fake_variable} / {tot_cost_shipments}) if {tot_cost_shipments} > 0 else 1 )* 100", # Invalid variable name
+ "criteria_name": "Fake Criteria 1",
+ "max_score": 100.0,
},
{
- "name":"Fake Criteria 2",
- "weight":40.0,
- "doctype":"Supplier Scorecard Criteria",
- "formula":"(({cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Force 0 divided by 0
- "criteria_name":"Fake Criteria 2",
- "max_score":100.0
+ "name": "Fake Criteria 2",
+ "weight": 40.0,
+ "doctype": "Supplier Scorecard Criteria",
+ "formula": "(({cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Force 0 divided by 0
+ "criteria_name": "Fake Criteria 2",
+ "max_score": 100.0,
},
{
- "name":"Fake Criteria 3",
- "weight":40.0,
- "doctype":"Supplier Scorecard Criteria",
- "formula":"(({cost_of_on_time_shipments} {cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Two variables beside eachother
- "criteria_name":"Fake Criteria 3",
- "max_score":100.0
+ "name": "Fake Criteria 3",
+ "weight": 40.0,
+ "doctype": "Supplier Scorecard Criteria",
+ "formula": "(({cost_of_on_time_shipments} {cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Two variables beside eachother
+ "criteria_name": "Fake Criteria 3",
+ "max_score": 100.0,
},
]
diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
index c247241cf3..a8b76db093 100644
--- a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
+++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
@@ -14,7 +14,6 @@ from erpnext.buying.doctype.supplier_scorecard_criteria.supplier_scorecard_crite
class SupplierScorecardPeriod(Document):
-
def validate(self):
self.validate_criteria_weights()
self.calculate_variables()
@@ -28,69 +27,84 @@ class SupplierScorecardPeriod(Document):
weight += c.weight
if weight != 100:
- throw(_('Criteria weights must add up to 100%'))
+ throw(_("Criteria weights must add up to 100%"))
def calculate_variables(self):
for var in self.variables:
- if '.' in var.path:
+ if "." in var.path:
method_to_call = import_string_path(var.path)
var.value = method_to_call(self)
else:
method_to_call = getattr(variable_functions, var.path)
var.value = method_to_call(self)
-
-
def calculate_criteria(self):
for crit in self.criteria:
try:
- crit.score = min(crit.max_score, max( 0 ,frappe.safe_eval(self.get_eval_statement(crit.formula), None, {'max':max, 'min': min})))
+ crit.score = min(
+ crit.max_score,
+ max(
+ 0, frappe.safe_eval(self.get_eval_statement(crit.formula), None, {"max": max, "min": min})
+ ),
+ )
except Exception:
- frappe.throw(_("Could not solve criteria score function for {0}. Make sure the formula is valid.").format(crit.criteria_name),frappe.ValidationError)
+ frappe.throw(
+ _("Could not solve criteria score function for {0}. Make sure the formula is valid.").format(
+ crit.criteria_name
+ ),
+ frappe.ValidationError,
+ )
crit.score = 0
def calculate_score(self):
myscore = 0
for crit in self.criteria:
- myscore += crit.score * crit.weight/100.0
+ myscore += crit.score * crit.weight / 100.0
self.total_score = myscore
def calculate_weighted_score(self, weighing_function):
try:
- weighed_score = frappe.safe_eval(self.get_eval_statement(weighing_function), None, {'max':max, 'min': min})
+ weighed_score = frappe.safe_eval(
+ self.get_eval_statement(weighing_function), None, {"max": max, "min": min}
+ )
except Exception:
- frappe.throw(_("Could not solve weighted score function. Make sure the formula is valid."),frappe.ValidationError)
+ frappe.throw(
+ _("Could not solve weighted score function. Make sure the formula is valid."),
+ frappe.ValidationError,
+ )
weighed_score = 0
return weighed_score
-
def get_eval_statement(self, formula):
my_eval_statement = formula.replace("\r", "").replace("\n", "")
for var in self.variables:
- if var.value:
- if var.param_name in my_eval_statement:
- my_eval_statement = my_eval_statement.replace('{' + var.param_name + '}', "{:.2f}".format(var.value))
- else:
- if var.param_name in my_eval_statement:
- my_eval_statement = my_eval_statement.replace('{' + var.param_name + '}', '0.0')
+ if var.value:
+ if var.param_name in my_eval_statement:
+ my_eval_statement = my_eval_statement.replace(
+ "{" + var.param_name + "}", "{:.2f}".format(var.value)
+ )
+ else:
+ if var.param_name in my_eval_statement:
+ my_eval_statement = my_eval_statement.replace("{" + var.param_name + "}", "0.0")
return my_eval_statement
def import_string_path(path):
- components = path.split('.')
- mod = __import__(components[0])
- for comp in components[1:]:
- mod = getattr(mod, comp)
- return mod
+ components = path.split(".")
+ mod = __import__(components[0])
+ for comp in components[1:]:
+ mod = getattr(mod, comp)
+ return mod
@frappe.whitelist()
def make_supplier_scorecard(source_name, target_doc=None):
def update_criteria_fields(obj, target, source_parent):
- target.max_score, target.formula = frappe.db.get_value('Supplier Scorecard Criteria',
- obj.criteria_name, ['max_score', 'formula'])
+ target.max_score, target.formula = frappe.db.get_value(
+ "Supplier Scorecard Criteria", obj.criteria_name, ["max_score", "formula"]
+ )
def post_process(source, target):
variables = []
@@ -99,16 +113,21 @@ def make_supplier_scorecard(source_name, target_doc=None):
if var not in variables:
variables.append(var)
- target.extend('variables', variables)
+ target.extend("variables", variables)
- doc = get_mapped_doc("Supplier Scorecard", source_name, {
- "Supplier Scorecard": {
- "doctype": "Supplier Scorecard Period"
+ doc = get_mapped_doc(
+ "Supplier Scorecard",
+ source_name,
+ {
+ "Supplier Scorecard": {"doctype": "Supplier Scorecard Period"},
+ "Supplier Scorecard Scoring Criteria": {
+ "doctype": "Supplier Scorecard Scoring Criteria",
+ "postprocess": update_criteria_fields,
+ },
},
- "Supplier Scorecard Scoring Criteria": {
- "doctype": "Supplier Scorecard Scoring Criteria",
- "postprocess": update_criteria_fields,
- }
- }, target_doc, post_process, ignore_permissions=True)
+ target_doc,
+ post_process,
+ ignore_permissions=True,
+ )
return doc
diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py
index 11ebe6da13..929e8a363f 100644
--- a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py
+++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py
@@ -19,11 +19,14 @@ def get_scoring_standing(standing_name):
@frappe.whitelist()
def get_standings_list():
- standings = frappe.db.sql("""
+ standings = frappe.db.sql(
+ """
SELECT
scs.name
FROM
`tabSupplier Scorecard Standing` scs""",
- {}, as_dict=1)
+ {},
+ as_dict=1,
+ )
return standings
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
index 217aadba6b..fb8819eaf8 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
@@ -10,18 +10,21 @@ from frappe.model.document import Document
from frappe.utils import getdate
-class VariablePathNotFound(frappe.ValidationError): pass
+class VariablePathNotFound(frappe.ValidationError):
+ pass
+
class SupplierScorecardVariable(Document):
def validate(self):
self.validate_path_exists()
def validate_path_exists(self):
- if '.' in self.path:
+ if "." in self.path:
try:
from erpnext.buying.doctype.supplier_scorecard_period.supplier_scorecard_period import (
import_string_path,
)
+
import_string_path(self.path)
except AttributeError:
frappe.throw(_("Could not find path for " + self.path), VariablePathNotFound)
@@ -30,15 +33,18 @@ class SupplierScorecardVariable(Document):
if not hasattr(sys.modules[__name__], self.path):
frappe.throw(_("Could not find path for " + self.path), VariablePathNotFound)
+
def get_total_workdays(scorecard):
- """ Gets the number of days in this period"""
+ """Gets the number of days in this period"""
delta = getdate(scorecard.end_date) - getdate(scorecard.start_date)
return delta.days
+
def get_item_workdays(scorecard):
- """ Gets the number of days in this period"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
- total_item_days = frappe.db.sql("""
+ """Gets the number of days in this period"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
+ total_item_days = frappe.db.sql(
+ """
SELECT
SUM(DATEDIFF( %(end_date)s, po_item.schedule_date) * (po_item.qty))
FROM
@@ -49,20 +55,22 @@ def get_item_workdays(scorecard):
AND po_item.received_qty < po_item.qty
AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s
AND po_item.parent = po.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not total_item_days:
total_item_days = 0
return total_item_days
-
def get_total_cost_of_shipments(scorecard):
- """ Gets the total cost of all shipments in the period (based on Purchase Orders)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total cost of all shipments in the period (based on Purchase Orders)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
SUM(po_item.base_amount)
FROM
@@ -73,24 +81,29 @@ def get_total_cost_of_shipments(scorecard):
AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s
AND po_item.docstatus = 1
AND po_item.parent = po.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if data:
return data
else:
return 0
+
def get_cost_of_delayed_shipments(scorecard):
- """ Gets the total cost of all delayed shipments in the period (based on Purchase Receipts - POs)"""
+ """Gets the total cost of all delayed shipments in the period (based on Purchase Receipts - POs)"""
return get_total_cost_of_shipments(scorecard) - get_cost_of_on_time_shipments(scorecard)
+
def get_cost_of_on_time_shipments(scorecard):
- """ Gets the total cost of all on_time shipments in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total cost of all on_time shipments in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- total_delivered_on_time_costs = frappe.db.sql("""
+ total_delivered_on_time_costs = frappe.db.sql(
+ """
SELECT
SUM(pr_item.base_amount)
FROM
@@ -106,7 +119,9 @@ def get_cost_of_on_time_shipments(scorecard):
AND pr_item.purchase_order_item = po_item.name
AND po_item.parent = po.name
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if total_delivered_on_time_costs:
return total_delivered_on_time_costs
@@ -115,9 +130,10 @@ def get_cost_of_on_time_shipments(scorecard):
def get_total_days_late(scorecard):
- """ Gets the number of item days late in the period (based on Purchase Receipts vs POs)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
- total_delivered_late_days = frappe.db.sql("""
+ """Gets the number of item days late in the period (based on Purchase Receipts vs POs)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
+ total_delivered_late_days = frappe.db.sql(
+ """
SELECT
SUM(DATEDIFF(pr.posting_date,po_item.schedule_date)* pr_item.qty)
FROM
@@ -133,11 +149,14 @@ def get_total_days_late(scorecard):
AND pr_item.purchase_order_item = po_item.name
AND po_item.parent = po.name
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not total_delivered_late_days:
total_delivered_late_days = 0
- total_missed_late_days = frappe.db.sql("""
+ total_missed_late_days = frappe.db.sql(
+ """
SELECT
SUM(DATEDIFF( %(end_date)s, po_item.schedule_date) * (po_item.qty - po_item.received_qty))
FROM
@@ -148,19 +167,23 @@ def get_total_days_late(scorecard):
AND po_item.received_qty < po_item.qty
AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s
AND po_item.parent = po.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not total_missed_late_days:
total_missed_late_days = 0
return total_missed_late_days + total_delivered_late_days
-def get_on_time_shipments(scorecard):
- """ Gets the number of late shipments (counting each item) in the period (based on Purchase Receipts vs POs)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+def get_on_time_shipments(scorecard):
+ """Gets the number of late shipments (counting each item) in the period (based on Purchase Receipts vs POs)"""
+
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- total_items_delivered_on_time = frappe.db.sql("""
+ total_items_delivered_on_time = frappe.db.sql(
+ """
SELECT
COUNT(pr_item.qty)
FROM
@@ -177,22 +200,27 @@ def get_on_time_shipments(scorecard):
AND pr_item.purchase_order_item = po_item.name
AND po_item.parent = po.name
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not total_items_delivered_on_time:
total_items_delivered_on_time = 0
return total_items_delivered_on_time
+
def get_late_shipments(scorecard):
- """ Gets the number of late shipments (counting each item) in the period (based on Purchase Receipts vs POs)"""
+ """Gets the number of late shipments (counting each item) in the period (based on Purchase Receipts vs POs)"""
return get_total_shipments(scorecard) - get_on_time_shipments(scorecard)
+
def get_total_received(scorecard):
- """ Gets the total number of received shipments in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of received shipments in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
COUNT(pr_item.base_amount)
FROM
@@ -203,18 +231,22 @@ def get_total_received(scorecard):
AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s
AND pr_item.docstatus = 1
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_total_received_amount(scorecard):
- """ Gets the total amount (in company currency) received in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total amount (in company currency) received in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
SUM(pr_item.received_qty * pr_item.base_rate)
FROM
@@ -225,18 +257,22 @@ def get_total_received_amount(scorecard):
AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s
AND pr_item.docstatus = 1
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_total_received_items(scorecard):
- """ Gets the total number of received shipments in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of received shipments in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
SUM(pr_item.received_qty)
FROM
@@ -247,18 +283,22 @@ def get_total_received_items(scorecard):
AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s
AND pr_item.docstatus = 1
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_total_rejected_amount(scorecard):
- """ Gets the total amount (in company currency) rejected in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total amount (in company currency) rejected in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
SUM(pr_item.rejected_qty * pr_item.base_rate)
FROM
@@ -269,18 +309,22 @@ def get_total_rejected_amount(scorecard):
AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s
AND pr_item.docstatus = 1
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_total_rejected_items(scorecard):
- """ Gets the total number of rejected items in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of rejected items in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
SUM(pr_item.rejected_qty)
FROM
@@ -291,18 +335,22 @@ def get_total_rejected_items(scorecard):
AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s
AND pr_item.docstatus = 1
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_total_accepted_amount(scorecard):
- """ Gets the total amount (in company currency) accepted in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total amount (in company currency) accepted in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
SUM(pr_item.qty * pr_item.base_rate)
FROM
@@ -313,18 +361,22 @@ def get_total_accepted_amount(scorecard):
AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s
AND pr_item.docstatus = 1
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_total_accepted_items(scorecard):
- """ Gets the total number of rejected items in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of rejected items in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
SUM(pr_item.qty)
FROM
@@ -335,18 +387,22 @@ def get_total_accepted_items(scorecard):
AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s
AND pr_item.docstatus = 1
AND pr_item.parent = pr.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_total_shipments(scorecard):
- """ Gets the total number of ordered shipments to arrive in the period (based on Purchase Receipts)"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of ordered shipments to arrive in the period (based on Purchase Receipts)"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
COUNT(po_item.base_amount)
FROM
@@ -357,18 +413,22 @@ def get_total_shipments(scorecard):
AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s
AND po_item.docstatus = 1
AND po_item.parent = po.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_rfq_total_number(scorecard):
- """ Gets the total number of RFQs sent to supplier"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of RFQs sent to supplier"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
COUNT(rfq.name) as total_rfqs
FROM
@@ -381,18 +441,22 @@ def get_rfq_total_number(scorecard):
AND rfq_item.docstatus = 1
AND rfq_item.parent = rfq.name
AND rfq_sup.parent = rfq.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_rfq_total_items(scorecard):
- """ Gets the total number of RFQ items sent to supplier"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of RFQ items sent to supplier"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
COUNT(rfq_item.name) as total_rfqs
FROM
@@ -405,18 +469,21 @@ def get_rfq_total_items(scorecard):
AND rfq_item.docstatus = 1
AND rfq_item.parent = rfq.name
AND rfq_sup.parent = rfq.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
def get_sq_total_number(scorecard):
- """ Gets the total number of RFQ items sent to supplier"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of RFQ items sent to supplier"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
COUNT(sq.name) as total_sqs
FROM
@@ -435,17 +502,21 @@ def get_sq_total_number(scorecard):
AND sq_item.parent = sq.name
AND rfq_item.parent = rfq.name
AND rfq_sup.parent = rfq.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_sq_total_items(scorecard):
- """ Gets the total number of RFQ items sent to supplier"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
+ """Gets the total number of RFQ items sent to supplier"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
# Look up all PO Items with delivery dates between our dates
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
COUNT(sq_item.name) as total_sqs
FROM
@@ -464,15 +535,19 @@ def get_sq_total_items(scorecard):
AND rfq_item.docstatus = 1
AND rfq_item.parent = rfq.name
AND rfq_sup.parent = rfq.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not data:
data = 0
return data
+
def get_rfq_response_days(scorecard):
- """ Gets the total number of days it has taken a supplier to respond to rfqs in the period"""
- supplier = frappe.get_doc('Supplier', scorecard.supplier)
- total_sq_days = frappe.db.sql("""
+ """Gets the total number of days it has taken a supplier to respond to rfqs in the period"""
+ supplier = frappe.get_doc("Supplier", scorecard.supplier)
+ total_sq_days = frappe.db.sql(
+ """
SELECT
SUM(DATEDIFF(sq.transaction_date, rfq.transaction_date))
FROM
@@ -491,9 +566,10 @@ def get_rfq_response_days(scorecard):
AND rfq_item.docstatus = 1
AND rfq_item.parent = rfq.name
AND rfq_sup.parent = rfq.name""",
- {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0]
+ {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date},
+ as_dict=0,
+ )[0][0]
if not total_sq_days:
total_sq_days = 0
-
return total_sq_days
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
index 32005a37dc..60d84644cf 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
@@ -14,9 +14,9 @@ class TestSupplierScorecardVariable(FrappeTestCase):
def test_variable_exist(self):
for d in test_existing_variables:
my_doc = frappe.get_doc("Supplier Scorecard Variable", d.get("name"))
- self.assertEqual(my_doc.param_name, d.get('param_name'))
- self.assertEqual(my_doc.variable_label, d.get('variable_label'))
- self.assertEqual(my_doc.path, d.get('path'))
+ self.assertEqual(my_doc.param_name, d.get("param_name"))
+ self.assertEqual(my_doc.variable_label, d.get("variable_label"))
+ self.assertEqual(my_doc.path, d.get("path"))
def test_path_exists(self):
for d in test_good_variables:
@@ -25,34 +25,35 @@ class TestSupplierScorecardVariable(FrappeTestCase):
frappe.get_doc(d).insert()
for d in test_bad_variables:
- self.assertRaises(VariablePathNotFound,frappe.get_doc(d).insert)
+ self.assertRaises(VariablePathNotFound, frappe.get_doc(d).insert)
+
test_existing_variables = [
{
- "param_name":"total_accepted_items",
- "name":"Total Accepted Items",
- "doctype":"Supplier Scorecard Variable",
- "variable_label":"Total Accepted Items",
- "path":"get_total_accepted_items"
+ "param_name": "total_accepted_items",
+ "name": "Total Accepted Items",
+ "doctype": "Supplier Scorecard Variable",
+ "variable_label": "Total Accepted Items",
+ "path": "get_total_accepted_items",
},
]
test_good_variables = [
{
- "param_name":"good_variable1",
- "name":"Good Variable 1",
- "doctype":"Supplier Scorecard Variable",
- "variable_label":"Good Variable 1",
- "path":"get_total_accepted_items"
+ "param_name": "good_variable1",
+ "name": "Good Variable 1",
+ "doctype": "Supplier Scorecard Variable",
+ "variable_label": "Good Variable 1",
+ "path": "get_total_accepted_items",
},
]
test_bad_variables = [
{
- "param_name":"fake_variable1",
- "name":"Fake Variable 1",
- "doctype":"Supplier Scorecard Variable",
- "variable_label":"Fake Variable 1",
- "path":"get_fake_variable1"
+ "param_name": "fake_variable1",
+ "name": "Fake Variable 1",
+ "doctype": "Supplier Scorecard Variable",
+ "variable_label": "Fake Variable 1",
+ "path": "get_fake_variable1",
},
]
diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
index 295a19d052..e0b02ee4e2 100644
--- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
@@ -12,152 +12,143 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_columns(filters):
columns = [
{
"label": _("Material Request Date"),
"fieldname": "material_request_date",
"fieldtype": "Date",
- "width": 140
+ "width": 140,
},
{
"label": _("Material Request No"),
"options": "Material Request",
"fieldname": "material_request_no",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Cost Center"),
"options": "Cost Center",
"fieldname": "cost_center",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Project"),
"options": "Project",
"fieldname": "project",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Requesting Site"),
"options": "Warehouse",
"fieldname": "requesting_site",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Requestor"),
"options": "Employee",
"fieldname": "requestor",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Item"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 150
- },
- {
- "label": _("Quantity"),
- "fieldname": "quantity",
- "fieldtype": "Float",
- "width": 140
+ "width": 150,
},
+ {"label": _("Quantity"), "fieldname": "quantity", "fieldtype": "Float", "width": 140},
{
"label": _("Unit of Measure"),
"options": "UOM",
"fieldname": "unit_of_measurement",
"fieldtype": "Link",
- "width": 140
- },
- {
- "label": _("Status"),
- "fieldname": "status",
- "fieldtype": "data",
- "width": 140
+ "width": 140,
},
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "data", "width": 140},
{
"label": _("Purchase Order Date"),
"fieldname": "purchase_order_date",
"fieldtype": "Date",
- "width": 140
+ "width": 140,
},
{
"label": _("Purchase Order"),
"options": "Purchase Order",
"fieldname": "purchase_order",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Supplier"),
"options": "Supplier",
"fieldname": "supplier",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Estimated Cost"),
"fieldname": "estimated_cost",
"fieldtype": "Float",
- "width": 140
- },
- {
- "label": _("Actual Cost"),
- "fieldname": "actual_cost",
- "fieldtype": "Float",
- "width": 140
+ "width": 140,
},
+ {"label": _("Actual Cost"), "fieldname": "actual_cost", "fieldtype": "Float", "width": 140},
{
"label": _("Purchase Order Amount"),
"fieldname": "purchase_order_amt",
"fieldtype": "Float",
- "width": 140
+ "width": 140,
},
{
"label": _("Purchase Order Amount(Company Currency)"),
"fieldname": "purchase_order_amt_in_company_currency",
"fieldtype": "Float",
- "width": 140
+ "width": 140,
},
{
"label": _("Expected Delivery Date"),
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
- "width": 140
+ "width": 140,
},
{
"label": _("Actual Delivery Date"),
"fieldname": "actual_delivery_date",
"fieldtype": "Date",
- "width": 140
+ "width": 140,
},
]
return columns
+
def get_conditions(filters):
conditions = ""
if filters.get("company"):
- conditions += " AND parent.company=%s" % frappe.db.escape(filters.get('company'))
+ conditions += " AND parent.company=%s" % frappe.db.escape(filters.get("company"))
if filters.get("cost_center") or filters.get("project"):
conditions += """
AND (child.`cost_center`=%s OR child.`project`=%s)
- """ % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
+ """ % (
+ frappe.db.escape(filters.get("cost_center")),
+ frappe.db.escape(filters.get("project")),
+ )
if filters.get("from_date"):
- conditions += " AND parent.transaction_date>='%s'" % filters.get('from_date')
+ conditions += " AND parent.transaction_date>='%s'" % filters.get("from_date")
if filters.get("to_date"):
- conditions += " AND parent.transaction_date<='%s'" % filters.get('to_date')
+ conditions += " AND parent.transaction_date<='%s'" % filters.get("to_date")
return conditions
+
def get_data(filters):
conditions = get_conditions(filters)
purchase_order_entry = get_po_entries(conditions)
@@ -165,14 +156,14 @@ def get_data(filters):
pr_records = get_mapped_pr_records()
pi_records = get_mapped_pi_records()
- procurement_record=[]
+ procurement_record = []
if procurement_record_against_mr:
procurement_record += procurement_record_against_mr
for po in purchase_order_entry:
# fetch material records linked to the purchase order item
mr_record = mr_records.get(po.material_request_item, [{}])[0]
procurement_detail = {
- "material_request_date": mr_record.get('transaction_date'),
+ "material_request_date": mr_record.get("transaction_date"),
"cost_center": po.cost_center,
"project": po.project,
"requesting_site": po.warehouse,
@@ -185,19 +176,21 @@ def get_data(filters):
"purchase_order_date": po.transaction_date,
"purchase_order": po.parent,
"supplier": po.supplier,
- "estimated_cost": flt(mr_record.get('amount')),
+ "estimated_cost": flt(mr_record.get("amount")),
"actual_cost": flt(pi_records.get(po.name)),
"purchase_order_amt": flt(po.amount),
"purchase_order_amt_in_company_currency": flt(po.base_amount),
"expected_delivery_date": po.schedule_date,
- "actual_delivery_date": pr_records.get(po.name)
+ "actual_delivery_date": pr_records.get(po.name),
}
procurement_record.append(procurement_detail)
return procurement_record
+
def get_mapped_mr_details(conditions):
mr_records = {}
- mr_details = frappe.db.sql("""
+ mr_details = frappe.db.sql(
+ """
SELECT
parent.transaction_date,
parent.per_ordered,
@@ -217,7 +210,11 @@ def get_mapped_mr_details(conditions):
AND parent.name=child.parent
AND parent.docstatus=1
{conditions}
- """.format(conditions=conditions), as_dict=1) #nosec
+ """.format(
+ conditions=conditions
+ ),
+ as_dict=1,
+ ) # nosec
procurement_record_against_mr = []
for record in mr_details:
@@ -236,14 +233,17 @@ def get_mapped_mr_details(conditions):
actual_cost=0,
purchase_order_amt=0,
purchase_order_amt_in_company_currency=0,
- project = record.project,
- cost_center = record.cost_center
+ project=record.project,
+ cost_center=record.cost_center,
)
procurement_record_against_mr.append(procurement_record_details)
return mr_records, procurement_record_against_mr
+
def get_mapped_pi_records():
- return frappe._dict(frappe.db.sql("""
+ return frappe._dict(
+ frappe.db.sql(
+ """
SELECT
pi_item.po_detail,
pi_item.base_amount
@@ -254,10 +254,15 @@ def get_mapped_pi_records():
pi_item.docstatus = 1
AND po.status not in ("Closed","Completed","Cancelled")
AND pi_item.po_detail IS NOT NULL
- """))
+ """
+ )
+ )
+
def get_mapped_pr_records():
- return frappe._dict(frappe.db.sql("""
+ return frappe._dict(
+ frappe.db.sql(
+ """
SELECT
pr_item.purchase_order_item,
pr.posting_date
@@ -267,10 +272,14 @@ def get_mapped_pr_records():
AND pr.name=pr_item.parent
AND pr_item.purchase_order_item IS NOT NULL
AND pr.status not in ("Closed","Completed","Cancelled")
- """))
+ """
+ )
+ )
+
def get_po_entries(conditions):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
child.name,
child.parent,
@@ -297,4 +306,8 @@ def get_po_entries(conditions):
{conditions}
GROUP BY
parent.name, child.item_code
- """.format(conditions=conditions), as_dict=1) #nosec
+ """.format(
+ conditions=conditions
+ ),
+ as_dict=1,
+ ) # nosec
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index 44524527e3..47a66ad46f 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -16,27 +16,28 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
class TestProcurementTracker(FrappeTestCase):
def test_result_for_procurement_tracker(self):
- filters = {
- 'company': '_Test Procurement Company',
- 'cost_center': 'Main - _TPC'
- }
+ filters = {"company": "_Test Procurement Company", "cost_center": "Main - _TPC"}
expected_data = self.generate_expected_data()
report = execute(filters)
length = len(report[1])
- self.assertEqual(expected_data, report[1][length-1])
+ self.assertEqual(expected_data, report[1][length - 1])
def generate_expected_data(self):
if not frappe.db.exists("Company", "_Test Procurement Company"):
- frappe.get_doc(dict(
- doctype="Company",
- company_name="_Test Procurement Company",
- abbr="_TPC",
- default_currency="INR",
- country="Pakistan"
- )).insert()
+ frappe.get_doc(
+ dict(
+ doctype="Company",
+ company_name="_Test Procurement Company",
+ abbr="_TPC",
+ default_currency="INR",
+ country="Pakistan",
+ )
+ ).insert()
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
- mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC")
+ mr = make_material_request(
+ company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC"
+ )
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
po.get("items")[0].cost_center = "Main - _TPC"
@@ -55,7 +56,7 @@ class TestProcurementTracker(FrappeTestCase):
"requesting_site": "_Test Procurement Warehouse - _TPC",
"requestor": "Administrator",
"material_request_no": mr.name,
- "item_code": '_Test Item',
+ "item_code": "_Test Item",
"quantity": 10.0,
"unit_of_measurement": "_Test UOM",
"status": "To Bill",
@@ -67,7 +68,7 @@ class TestProcurementTracker(FrappeTestCase):
"purchase_order_amt": po.net_total,
"purchase_order_amt_in_company_currency": po.base_net_total,
"expected_delivery_date": date_obj,
- "actual_delivery_date": date_obj
+ "actual_delivery_date": date_obj,
}
return expected_data
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
index 9dd912118f..a5c464910d 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -27,6 +27,7 @@ def execute(filters=None):
return columns, data, None, chart_data
+
def validate_filters(filters):
from_date, to_date = filters.get("from_date"), filters.get("to_date")
@@ -35,25 +36,28 @@ def validate_filters(filters):
elif date_diff(to_date, from_date) < 0:
frappe.throw(_("To Date cannot be before From Date."))
+
def get_conditions(filters):
conditions = ""
if filters.get("from_date") and filters.get("to_date"):
conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
- for field in ['company', 'name']:
+ for field in ["company", "name"]:
if filters.get(field):
conditions += f" and po.{field} = %({field})s"
- if filters.get('status'):
+ if filters.get("status"):
conditions += " and po.status in %(status)s"
- if filters.get('project'):
+ if filters.get("project"):
conditions += " and poi.project = %(project)s"
return conditions
+
def get_data(conditions, filters):
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
po.transaction_date as date,
poi.schedule_date as required_date,
@@ -81,13 +85,19 @@ def get_data(conditions, filters):
{0}
GROUP BY poi.name
ORDER BY po.transaction_date ASC
- """.format(conditions), filters, as_dict=1)
+ """.format(
+ conditions
+ ),
+ filters,
+ as_dict=1,
+ )
return data
+
def prepare_data(data, filters):
completed, pending = 0, 0
- pending_field = "pending_amount"
+ pending_field = "pending_amount"
completed_field = "billed_amount"
if filters.get("group_by_po"):
@@ -114,8 +124,17 @@ def prepare_data(data, filters):
po_row["required_date"] = min(getdate(po_row["required_date"]), getdate(row["required_date"]))
# sum numeric columns
- fields = ["qty", "received_qty", "pending_qty", "billed_qty", "qty_to_bill", "amount",
- "received_qty_amount", "billed_amount", "pending_amount"]
+ fields = [
+ "qty",
+ "received_qty",
+ "pending_qty",
+ "billed_qty",
+ "qty_to_bill",
+ "amount",
+ "received_qty_amount",
+ "billed_amount",
+ "pending_amount",
+ ]
for field in fields:
po_row[field] = flt(row[field]) + flt(po_row[field])
@@ -129,152 +148,140 @@ def prepare_data(data, filters):
return data, chart_data
+
def prepare_chart_data(pending, completed):
labels = ["Amount to Bill", "Billed Amount"]
return {
- "data" : {
- "labels": labels,
- "datasets": [
- {"values": [pending, completed]}
- ]
- },
- "type": 'donut',
- "height": 300
+ "data": {"labels": labels, "datasets": [{"values": [pending, completed]}]},
+ "type": "donut",
+ "height": 300,
}
+
def get_columns(filters):
columns = [
- {
- "label":_("Date"),
- "fieldname": "date",
- "fieldtype": "Date",
- "width": 90
- },
- {
- "label":_("Required By"),
- "fieldname": "required_date",
- "fieldtype": "Date",
- "width": 90
- },
+ {"label": _("Date"), "fieldname": "date", "fieldtype": "Date", "width": 90},
+ {"label": _("Required By"), "fieldname": "required_date", "fieldtype": "Date", "width": 90},
{
"label": _("Purchase Order"),
"fieldname": "purchase_order",
"fieldtype": "Link",
"options": "Purchase Order",
- "width": 160
- },
- {
- "label":_("Status"),
- "fieldname": "status",
- "fieldtype": "Data",
- "width": 130
+ "width": 160,
},
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 130},
{
"label": _("Supplier"),
"fieldname": "supplier",
"fieldtype": "Link",
"options": "Supplier",
- "width": 130
- },{
+ "width": 130,
+ },
+ {
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
- "width": 130
- }]
+ "width": 130,
+ },
+ ]
if not filters.get("group_by_po"):
- columns.append({
- "label":_("Item Code"),
- "fieldname": "item_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 100
- })
+ columns.append(
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
+ }
+ )
- columns.extend([
- {
- "label": _("Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 120,
- "convertible": "qty"
- },
- {
- "label": _("Received Qty"),
- "fieldname": "received_qty",
- "fieldtype": "Float",
- "width": 120,
- "convertible": "qty"
- },
- {
- "label": _("Pending Qty"),
- "fieldname": "pending_qty",
- "fieldtype": "Float",
- "width": 80,
- "convertible": "qty"
- },
- {
- "label": _("Billed Qty"),
- "fieldname": "billed_qty",
- "fieldtype": "Float",
- "width": 80,
- "convertible": "qty"
- },
- {
- "label": _("Qty to Bill"),
- "fieldname": "qty_to_bill",
- "fieldtype": "Float",
- "width": 80,
- "convertible": "qty"
- },
- {
- "label": _("Amount"),
- "fieldname": "amount",
- "fieldtype": "Currency",
- "width": 110,
- "options": "Company:company:default_currency",
- "convertible": "rate"
- },
- {
- "label": _("Billed Amount"),
- "fieldname": "billed_amount",
- "fieldtype": "Currency",
- "width": 110,
- "options": "Company:company:default_currency",
- "convertible": "rate"
- },
- {
- "label": _("Pending Amount"),
- "fieldname": "pending_amount",
- "fieldtype": "Currency",
- "width": 130,
- "options": "Company:company:default_currency",
- "convertible": "rate"
- },
- {
- "label": _("Received Qty Amount"),
- "fieldname": "received_qty_amount",
- "fieldtype": "Currency",
- "width": 130,
- "options": "Company:company:default_currency",
- "convertible": "rate"
- },
- {
- "label": _("Warehouse"),
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "options": "Warehouse",
- "width": 100
- },
- {
- "label": _("Company"),
- "fieldname": "company",
- "fieldtype": "Link",
- "options": "Company",
- "width": 100
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Received Qty"),
+ "fieldname": "received_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Pending Qty"),
+ "fieldname": "pending_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Billed Qty"),
+ "fieldname": "billed_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Qty to Bill"),
+ "fieldname": "qty_to_bill",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate",
+ },
+ {
+ "label": _("Billed Amount"),
+ "fieldname": "billed_amount",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate",
+ },
+ {
+ "label": _("Pending Amount"),
+ "fieldname": "pending_amount",
+ "fieldtype": "Currency",
+ "width": 130,
+ "options": "Company:company:default_currency",
+ "convertible": "rate",
+ },
+ {
+ "label": _("Received Qty Amount"),
+ "fieldname": "received_qty_amount",
+ "fieldtype": "Currency",
+ "width": 130,
+ "options": "Company:company:default_currency",
+ "convertible": "rate",
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 100,
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
index 21643a896b..11a74491a4 100644
--- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
+++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
@@ -8,7 +8,8 @@ from erpnext.controllers.trends import get_columns, get_data
def execute(filters=None):
- if not filters: filters ={}
+ if not filters:
+ filters = {}
data = []
conditions = get_columns(filters, "Purchase Order")
data = get_data(filters, conditions)
@@ -16,6 +17,7 @@ def execute(filters=None):
return conditions["columns"], data, None, chart_data
+
def get_chart_data(data, conditions, filters):
if not (data and conditions):
return []
@@ -28,32 +30,27 @@ def get_chart_data(data, conditions, filters):
# fetch only periodic columns as labels
columns = conditions.get("columns")[start:-2][1::2]
- labels = [column.split(':')[0] for column in columns]
+ labels = [column.split(":")[0] for column in columns]
datapoints = [0] * len(labels)
for row in data:
# If group by filter, don't add first row of group (it's already summed)
- if not row[start-1]:
+ if not row[start - 1]:
continue
# Remove None values and compute only periodic data
row = [x if x else 0 for x in row[start:-2]]
- row = row[1::2]
+ row = row[1::2]
for i in range(len(row)):
datapoints[i] += row[i]
return {
- "data" : {
- "labels" : labels,
- "datasets" : [
- {
- "name" : _("{0}").format(filters.get("period")) + _(" Purchase Value"),
- "values" : datapoints
- }
- ]
+ "data": {
+ "labels": labels,
+ "datasets": [
+ {"name": _("{0}").format(filters.get("period")) + _(" Purchase Value"), "values": datapoints}
+ ],
},
- "type" : "line",
- "lineOptions": {
- "regionFill": 1
- }
+ "type": "line",
+ "lineOptions": {"regionFill": 1},
}
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
index 60a8f92cc3..21241e0860 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
@@ -12,7 +12,7 @@ from frappe.utils import date_diff, flt, getdate
def execute(filters=None):
if not filters:
- return [],[]
+ return [], []
validate_filters(filters)
@@ -24,6 +24,7 @@ def execute(filters=None):
return columns, data, None, chart_data
+
def validate_filters(filters):
from_date, to_date = filters.get("from_date"), filters.get("to_date")
@@ -32,33 +33,36 @@ def validate_filters(filters):
elif date_diff(to_date, from_date) < 0:
frappe.throw(_("To Date cannot be before From Date."))
+
def get_data(filters):
mr = frappe.qb.DocType("Material Request")
mr_item = frappe.qb.DocType("Material Request Item")
query = (
frappe.qb.from_(mr)
- .join(mr_item).on(mr_item.parent == mr.name)
+ .join(mr_item)
+ .on(mr_item.parent == mr.name)
.select(
mr.name.as_("material_request"),
mr.transaction_date.as_("date"),
mr_item.schedule_date.as_("required_date"),
mr_item.item_code.as_("item_code"),
Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"),
- Coalesce(mr_item.stock_uom, '').as_("uom"),
+ Coalesce(mr_item.stock_uom, "").as_("uom"),
Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"),
Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
- (
- Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))
- ).as_("qty_to_receive"),
+ (Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))).as_(
+ "qty_to_receive"
+ ),
Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
- (
- Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.ordered_qty, 0))
- ).as_("qty_to_order"),
+ (Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.ordered_qty, 0))).as_(
+ "qty_to_order"
+ ),
mr_item.item_name,
mr_item.description,
- mr.company
- ).where(
+ mr.company,
+ )
+ .where(
(mr.material_request_type == "Purchase")
& (mr.docstatus == 1)
& (mr.status != "Stopped")
@@ -66,25 +70,18 @@ def get_data(filters):
)
)
- query = get_conditions(filters, query, mr, mr_item) # add conditional conditions
+ query = get_conditions(filters, query, mr, mr_item) # add conditional conditions
- query = (
- query.groupby(
- mr.name, mr_item.item_code
- ).orderby(
- mr.transaction_date, mr.schedule_date
- )
- )
+ query = query.groupby(mr.name, mr_item.item_code).orderby(mr.transaction_date, mr.schedule_date)
data = query.run(as_dict=True)
return data
+
def get_conditions(filters, query, mr, mr_item):
if filters.get("from_date") and filters.get("to_date"):
- query = (
- query.where(
- (mr.transaction_date >= filters.get("from_date"))
- & (mr.transaction_date <= filters.get("to_date"))
- )
+ query = query.where(
+ (mr.transaction_date >= filters.get("from_date"))
+ & (mr.transaction_date <= filters.get("to_date"))
)
if filters.get("company"):
query = query.where(mr.company == filters.get("company"))
@@ -97,11 +94,13 @@ def get_conditions(filters, query, mr, mr_item):
return query
+
def update_qty_columns(row_to_update, data_row):
fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]
for field in fields:
row_to_update[field] += flt(data_row[field])
+
def prepare_data(data, filters):
"""Prepare consolidated Report data and Chart data"""
material_request_map, item_qty_map = {}, {}
@@ -110,11 +109,11 @@ def prepare_data(data, filters):
# item wise map for charts
if not row["item_code"] in item_qty_map:
item_qty_map[row["item_code"]] = {
- "qty" : row["qty"],
- "ordered_qty" : row["ordered_qty"],
+ "qty": row["qty"],
+ "ordered_qty": row["ordered_qty"],
"received_qty": row["received_qty"],
"qty_to_receive": row["qty_to_receive"],
- "qty_to_order" : row["qty_to_order"],
+ "qty_to_order": row["qty_to_order"],
}
else:
item_entry = item_qty_map[row["item_code"]]
@@ -130,19 +129,20 @@ def prepare_data(data, filters):
mr_row = material_request_map[row["material_request"]]
mr_row["required_date"] = min(getdate(mr_row["required_date"]), getdate(row["required_date"]))
- #sum numeric columns
+ # sum numeric columns
update_qty_columns(mr_row, row)
chart_data = prepare_chart_data(item_qty_map)
if filters.get("group_by_mr"):
- data =[]
+ data = []
for mr in material_request_map:
data.append(material_request_map[mr])
return data, chart_data
return data, chart_data
+
def prepare_chart_data(item_data):
labels, qty_to_order, ordered_qty, received_qty, qty_to_receive = [], [], [], [], []
@@ -158,35 +158,22 @@ def prepare_chart_data(item_data):
qty_to_receive.append(mr_row["qty_to_receive"])
chart_data = {
- "data" : {
+ "data": {
"labels": labels,
"datasets": [
- {
- 'name': _('Qty to Order'),
- 'values': qty_to_order
- },
- {
- 'name': _('Ordered Qty'),
- 'values': ordered_qty
- },
- {
- 'name': _('Received Qty'),
- 'values': received_qty
- },
- {
- 'name': _('Qty to Receive'),
- 'values': qty_to_receive
- }
- ]
+ {"name": _("Qty to Order"), "values": qty_to_order},
+ {"name": _("Ordered Qty"), "values": ordered_qty},
+ {"name": _("Received Qty"), "values": received_qty},
+ {"name": _("Qty to Receive"), "values": qty_to_receive},
+ ],
},
"type": "bar",
- "barOptions": {
- "stacked": 1
- },
+ "barOptions": {"stacked": 1},
}
return chart_data
+
def get_columns(filters):
columns = [
{
@@ -194,92 +181,78 @@ def get_columns(filters):
"fieldname": "material_request",
"fieldtype": "Link",
"options": "Material Request",
- "width": 150
+ "width": 150,
},
- {
- "label":_("Date"),
- "fieldname": "date",
- "fieldtype": "Date",
- "width": 90
- },
- {
- "label":_("Required By"),
- "fieldname": "required_date",
- "fieldtype": "Date",
- "width": 100
- }
+ {"label": _("Date"), "fieldname": "date", "fieldtype": "Date", "width": 90},
+ {"label": _("Required By"), "fieldname": "required_date", "fieldtype": "Date", "width": 100},
]
if not filters.get("group_by_mr"):
- columns.extend([{
- "label":_("Item Code"),
- "fieldname": "item_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 100
- },
- {
- "label":_("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 200
- },
- {
- "label": _("Stock UOM"),
- "fieldname": "uom",
- "fieldtype": "Data",
- "width": 100,
- }])
+ columns.extend(
+ [
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200},
+ {
+ "label": _("Stock UOM"),
+ "fieldname": "uom",
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ ]
+ )
- columns.extend([
- {
- "label": _("Stock Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 120,
- "convertible": "qty"
- },
- {
- "label": _("Ordered Qty"),
- "fieldname": "ordered_qty",
- "fieldtype": "Float",
- "width": 120,
- "convertible": "qty"
- },
- {
- "label": _("Received Qty"),
- "fieldname": "received_qty",
- "fieldtype": "Float",
- "width": 120,
- "convertible": "qty"
- },
- {
- "label": _("Qty to Receive"),
- "fieldname": "qty_to_receive",
- "fieldtype": "Float",
- "width": 120,
- "convertible": "qty"
- },
- {
- "label": _("Qty to Order"),
- "fieldname": "qty_to_order",
- "fieldtype": "Float",
- "width": 120,
- "convertible": "qty"
- },
- {
- "label": _("Company"),
- "fieldname": "company",
- "fieldtype": "Link",
- "options": "Company",
- "width": 100
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Stock Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Ordered Qty"),
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Received Qty"),
+ "fieldname": "received_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Qty to Receive"),
+ "fieldname": "qty_to_receive",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Qty to Order"),
+ "fieldname": "qty_to_order",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
index a533da00e3..5b84113a9c 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
@@ -16,13 +16,15 @@ from erpnext.stock.doctype.material_request.material_request import make_purchas
class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
def setUp(self) -> None:
create_item("Test MR Report Item")
- self.setup_material_request() # to order and receive
- self.setup_material_request(order=True, days=1) # to receive (ordered)
- self.setup_material_request(order=True, receive=True, days=2) # complete (ordered & received)
+ self.setup_material_request() # to order and receive
+ self.setup_material_request(order=True, days=1) # to receive (ordered)
+ self.setup_material_request(order=True, receive=True, days=2) # complete (ordered & received)
self.filters = frappe._dict(
- company="_Test Company", from_date=today(), to_date=add_days(today(), 30),
- item_code="Test MR Report Item"
+ company="_Test Company",
+ from_date=today(),
+ to_date=add_days(today(), 30),
+ item_code="Test MR Report Item",
)
def tearDown(self) -> None:
@@ -30,10 +32,10 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
def test_date_range(self):
data = get_data(self.filters)
- self.assertEqual(len(data), 2) # MRs today should be fetched
+ self.assertEqual(len(data), 2) # MRs today should be fetched
data = get_data(self.filters.update({"from_date": add_days(today(), 10)}))
- self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is in future
+ self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is in future
def test_ordered_received_material_requests(self):
data = get_data(self.filters)
@@ -45,7 +47,7 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
def setup_material_request(self, order=False, receive=False, days=0):
po = None
- test_records = frappe.get_test_records('Material Request')
+ test_records = frappe.get_test_records("Material Request")
mr = frappe.copy_doc(test_records[0])
mr.transaction_date = add_days(today(), days)
@@ -65,4 +67,3 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
if receive:
pr = make_purchase_receipt(po.name)
pr.submit()
-
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
index 8e5c2f9a30..1b2705a7be 100644
--- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
@@ -13,6 +13,7 @@ def execute(filters=None):
return columns, data
+
def get_data(report_filters):
data = []
orders = get_subcontracted_orders(report_filters)
@@ -24,57 +25,85 @@ def get_data(report_filters):
return data
+
def get_subcontracted_orders(report_filters):
- fields = ['`tabPurchase Order Item`.`parent` as po_id', '`tabPurchase Order Item`.`item_code`',
- '`tabPurchase Order Item`.`item_name`', '`tabPurchase Order Item`.`qty`', '`tabPurchase Order Item`.`name`',
- '`tabPurchase Order Item`.`received_qty`', '`tabPurchase Order`.`status`']
+ fields = [
+ "`tabPurchase Order Item`.`parent` as po_id",
+ "`tabPurchase Order Item`.`item_code`",
+ "`tabPurchase Order Item`.`item_name`",
+ "`tabPurchase Order Item`.`qty`",
+ "`tabPurchase Order Item`.`name`",
+ "`tabPurchase Order Item`.`received_qty`",
+ "`tabPurchase Order`.`status`",
+ ]
filters = get_filters(report_filters)
- return frappe.get_all('Purchase Order', fields = fields, filters=filters) or []
+ return frappe.get_all("Purchase Order", fields=fields, filters=filters) or []
+
def get_filters(report_filters):
- filters = [['Purchase Order', 'docstatus', '=', 1], ['Purchase Order', 'is_subcontracted', '=', 'Yes'],
- ['Purchase Order', 'transaction_date', 'between', (report_filters.from_date, report_filters.to_date)]]
+ filters = [
+ ["Purchase Order", "docstatus", "=", 1],
+ ["Purchase Order", "is_subcontracted", "=", "Yes"],
+ [
+ "Purchase Order",
+ "transaction_date",
+ "between",
+ (report_filters.from_date, report_filters.to_date),
+ ],
+ ]
- for field in ['name', 'company']:
+ for field in ["name", "company"]:
if report_filters.get(field):
- filters.append(['Purchase Order', field, '=', report_filters.get(field)])
+ filters.append(["Purchase Order", field, "=", report_filters.get(field)])
return filters
+
def get_supplied_items(orders, report_filters):
if not orders:
return []
- fields = ['parent', 'main_item_code', 'rm_item_code', 'required_qty',
- 'supplied_qty', 'returned_qty', 'total_supplied_qty', 'consumed_qty', 'reference_name']
+ fields = [
+ "parent",
+ "main_item_code",
+ "rm_item_code",
+ "required_qty",
+ "supplied_qty",
+ "returned_qty",
+ "total_supplied_qty",
+ "consumed_qty",
+ "reference_name",
+ ]
- filters = {'parent': ('in', [d.po_id for d in orders]), 'docstatus': 1}
+ filters = {"parent": ("in", [d.po_id for d in orders]), "docstatus": 1}
supplied_items = {}
- for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters):
+ for row in frappe.get_all("Purchase Order Item Supplied", fields=fields, filters=filters):
new_key = (row.parent, row.reference_name, row.main_item_code)
supplied_items.setdefault(new_key, []).append(row)
return supplied_items
+
def prepare_subcontracted_data(orders, supplied_items):
po_details = {}
for row in orders:
key = (row.po_id, row.name, row.item_code)
if key not in po_details:
- po_details.setdefault(key, frappe._dict({'po_item': row, 'supplied_items': []}))
+ po_details.setdefault(key, frappe._dict({"po_item": row, "supplied_items": []}))
details = po_details[key]
if supplied_items.get(key):
for supplied_item in supplied_items[key]:
- details['supplied_items'].append(supplied_item)
+ details["supplied_items"].append(supplied_item)
return po_details
+
def get_subcontracted_data(po_details, data):
for key, details in po_details.items():
res = details.po_item
@@ -85,6 +114,7 @@ def get_subcontracted_data(po_details, data):
res.update(row)
data.append(res)
+
def get_columns():
return [
{
@@ -92,62 +122,27 @@ def get_columns():
"fieldname": "po_id",
"fieldtype": "Link",
"options": "Purchase Order",
- "width": 100
- },
- {
- "label": _("Status"),
- "fieldname": "status",
- "fieldtype": "Data",
- "width": 80
+ "width": 100,
},
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80},
{
"label": _("Subcontracted Item"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 160
- },
- {
- "label": _("Order Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 90
- },
- {
- "label": _("Received Qty"),
- "fieldname": "received_qty",
- "fieldtype": "Float",
- "width": 110
+ "width": 160,
},
+ {"label": _("Order Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 90},
+ {"label": _("Received Qty"), "fieldname": "received_qty", "fieldtype": "Float", "width": 110},
{
"label": _("Supplied Item"),
"fieldname": "rm_item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 160
+ "width": 160,
},
- {
- "label": _("Required Qty"),
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Supplied Qty"),
- "fieldname": "supplied_qty",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Consumed Qty"),
- "fieldname": "consumed_qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Returned Qty"),
- "fieldname": "returned_qty",
- "fieldtype": "Float",
- "width": 110
- }
+ {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 110},
+ {"label": _("Supplied Qty"), "fieldname": "supplied_qty", "fieldtype": "Float", "width": 110},
+ {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120},
+ {"label": _("Returned Qty"), "fieldname": "returned_qty", "fieldtype": "Float", "width": 110},
]
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py
index 67e275f985..004657b6e8 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py
@@ -12,9 +12,10 @@ def execute(filters=None):
data = []
columns = get_columns()
- get_data(data , filters)
+ get_data(data, filters)
return columns, data
+
def get_columns():
return [
{
@@ -22,54 +23,39 @@ def get_columns():
"fieldtype": "Link",
"fieldname": "purchase_order",
"options": "Purchase Order",
- "width": 150
- },
- {
- "label": _("Date"),
- "fieldtype": "Date",
- "fieldname": "date",
- "hidden": 1,
- "width": 150
+ "width": 150,
},
+ {"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150},
{
"label": _("Supplier"),
"fieldtype": "Link",
"fieldname": "supplier",
"options": "Supplier",
- "width": 150
+ "width": 150,
},
{
"label": _("Finished Good Item Code"),
"fieldtype": "Data",
"fieldname": "fg_item_code",
- "width": 100
- },
- {
- "label": _("Item name"),
- "fieldtype": "Data",
- "fieldname": "item_name",
- "width": 100
+ "width": 100,
},
+ {"label": _("Item name"), "fieldtype": "Data", "fieldname": "item_name", "width": 100},
{
"label": _("Required Quantity"),
"fieldtype": "Float",
"fieldname": "required_qty",
- "width": 100
+ "width": 100,
},
{
"label": _("Received Quantity"),
"fieldtype": "Float",
"fieldname": "received_qty",
- "width": 100
+ "width": 100,
},
- {
- "label": _("Pending Quantity"),
- "fieldtype": "Float",
- "fieldname": "pending_qty",
- "width": 100
- }
+ {"label": _("Pending Quantity"), "fieldtype": "Float", "fieldname": "pending_qty", "width": 100},
]
+
def get_data(data, filters):
po = get_po(filters)
po_name = [v.name for v in po]
@@ -77,29 +63,35 @@ def get_data(data, filters):
for item in sub_items:
for order in po:
if order.name == item.parent and item.received_qty < item.qty:
- row ={
- 'purchase_order': item.parent,
- 'date': order.transaction_date,
- 'supplier': order.supplier,
- 'fg_item_code': item.item_code,
- 'item_name': item.item_name,
- 'required_qty': item.qty,
- 'received_qty':item.received_qty,
- 'pending_qty':item.qty - item.received_qty
+ row = {
+ "purchase_order": item.parent,
+ "date": order.transaction_date,
+ "supplier": order.supplier,
+ "fg_item_code": item.item_code,
+ "item_name": item.item_name,
+ "required_qty": item.qty,
+ "received_qty": item.received_qty,
+ "pending_qty": item.qty - item.received_qty,
}
data.append(row)
+
def get_po(filters):
record_filters = [
- ["is_subcontracted", "=", "Yes"],
- ["supplier", "=", filters.supplier],
- ["transaction_date", "<=", filters.to_date],
- ["transaction_date", ">=", filters.from_date],
- ["docstatus", "=", 1]
- ]
- return frappe.get_all("Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"])
+ ["is_subcontracted", "=", "Yes"],
+ ["supplier", "=", filters.supplier],
+ ["transaction_date", "<=", filters.to_date],
+ ["transaction_date", ">=", filters.from_date],
+ ["docstatus", "=", 1],
+ ]
+ return frappe.get_all(
+ "Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"]
+ )
+
def get_purchase_order_item_supplied(po):
- return frappe.get_all("Purchase Order Item", filters=[
- ('parent', 'IN', po)
- ], fields=["parent", "item_code", "item_name", "qty", "received_qty"])
+ return frappe.get_all(
+ "Purchase Order Item",
+ filters=[("parent", "IN", po)],
+ fields=["parent", "item_code", "item_name", "qty", "received_qty"],
+ )
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
index c2b38d38e1..26e4243eee 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
@@ -16,26 +16,40 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestSubcontractedItemToBeReceived(FrappeTestCase):
-
def test_pending_and_received_qty(self):
- po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
+ po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
transfer_param = []
- make_stock_entry(item_code='_Test Item', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
- make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
+ )
+ make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse 1 - _TC",
+ qty=100,
+ basic_rate=100,
+ )
make_purchase_receipt_against_po(po.name)
po.reload()
- col, data = execute(filters=frappe._dict({'supplier': po.supplier,
- 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
- 'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))}))
- self.assertEqual(data[0]['pending_qty'], 5)
- self.assertEqual(data[0]['received_qty'], 5)
- self.assertEqual(data[0]['purchase_order'], po.name)
- self.assertEqual(data[0]['supplier'], po.supplier)
+ col, data = execute(
+ filters=frappe._dict(
+ {
+ "supplier": po.supplier,
+ "from_date": frappe.utils.get_datetime(
+ frappe.utils.add_to_date(po.transaction_date, days=-10)
+ ),
+ "to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10)),
+ }
+ )
+ )
+ self.assertEqual(data[0]["pending_qty"], 5)
+ self.assertEqual(data[0]["received_qty"], 5)
+ self.assertEqual(data[0]["purchase_order"], po.name)
+ self.assertEqual(data[0]["supplier"], po.supplier)
def make_purchase_receipt_against_po(po, quantity=5):
pr = make_purchase_receipt(po)
pr.items[0].qty = quantity
- pr.supplier_warehouse = '_Test Warehouse 1 - _TC'
+ pr.supplier_warehouse = "_Test Warehouse 1 - _TC"
pr.insert()
pr.submit()
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
index 6b605add4c..98b18da4ac 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
@@ -15,6 +15,7 @@ def execute(filters=None):
return columns, data or []
+
def get_columns():
return [
{
@@ -22,47 +23,28 @@ def get_columns():
"fieldtype": "Link",
"fieldname": "purchase_order",
"options": "Purchase Order",
- "width": 200
- },
- {
- "label": _("Date"),
- "fieldtype": "Date",
- "fieldname": "date",
- "width": 150
+ "width": 200,
},
+ {"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150},
{
"label": _("Supplier"),
"fieldtype": "Link",
"fieldname": "supplier",
"options": "Supplier",
- "width": 150
- },
- {
- "label": _("Item Code"),
- "fieldtype": "Data",
- "fieldname": "rm_item_code",
- "width": 150
- },
- {
- "label": _("Required Quantity"),
- "fieldtype": "Float",
- "fieldname": "reqd_qty",
- "width": 150
+ "width": 150,
},
+ {"label": _("Item Code"), "fieldtype": "Data", "fieldname": "rm_item_code", "width": 150},
+ {"label": _("Required Quantity"), "fieldtype": "Float", "fieldname": "reqd_qty", "width": 150},
{
"label": _("Transferred Quantity"),
"fieldtype": "Float",
"fieldname": "transferred_qty",
- "width": 200
+ "width": 200,
},
- {
- "label": _("Pending Quantity"),
- "fieldtype": "Float",
- "fieldname": "p_qty",
- "width": 150
- }
+ {"label": _("Pending Quantity"), "fieldtype": "Float", "fieldname": "p_qty", "width": 150},
]
+
def get_data(filters):
po_rm_item_details = get_po_items_to_supply(filters)
@@ -76,6 +58,7 @@ def get_data(filters):
return data
+
def get_po_items_to_supply(filters):
return frappe.db.get_all(
"Purchase Order",
@@ -85,14 +68,14 @@ def get_po_items_to_supply(filters):
"supplier as supplier",
"`tabPurchase Order Item Supplied`.rm_item_code as rm_item_code",
"`tabPurchase Order Item Supplied`.required_qty as reqd_qty",
- "`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty"
+ "`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty",
],
- filters = [
+ filters=[
["Purchase Order", "per_received", "<", "100"],
["Purchase Order", "is_subcontracted", "=", "Yes"],
["Purchase Order", "supplier", "=", filters.supplier],
["Purchase Order", "transaction_date", "<=", filters.to_date],
["Purchase Order", "transaction_date", ">=", filters.from_date],
- ["Purchase Order", "docstatus", "=", 1]
- ]
+ ["Purchase Order", "docstatus", "=", 1],
+ ],
)
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
index fc9acabc81..401176d5ce 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
@@ -17,82 +17,87 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestSubcontractedItemToBeTransferred(FrappeTestCase):
-
def test_pending_and_transferred_qty(self):
- po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ item_code="_Test FG Item", is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
# Material Receipt of RMs
- make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
- make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
+ make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", qty=100, basic_rate=100
+ )
se = transfer_subcontracted_raw_materials(po)
- col, data = execute(filters=frappe._dict(
- {
- 'supplier': po.supplier,
- 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
- 'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))
- }
- ))
+ col, data = execute(
+ filters=frappe._dict(
+ {
+ "supplier": po.supplier,
+ "from_date": frappe.utils.get_datetime(
+ frappe.utils.add_to_date(po.transaction_date, days=-10)
+ ),
+ "to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10)),
+ }
+ )
+ )
po.reload()
- po_data = [row for row in data if row.get('purchase_order') == po.name]
+ po_data = [row for row in data if row.get("purchase_order") == po.name]
# Alphabetically sort to be certain of order
- po_data = sorted(po_data, key = lambda i: i['rm_item_code'])
+ po_data = sorted(po_data, key=lambda i: i["rm_item_code"])
self.assertEqual(len(po_data), 2)
- self.assertEqual(po_data[0]['purchase_order'], po.name)
+ self.assertEqual(po_data[0]["purchase_order"], po.name)
- self.assertEqual(po_data[0]['rm_item_code'], '_Test Item')
- self.assertEqual(po_data[0]['p_qty'], 8)
- self.assertEqual(po_data[0]['transferred_qty'], 2)
+ self.assertEqual(po_data[0]["rm_item_code"], "_Test Item")
+ self.assertEqual(po_data[0]["p_qty"], 8)
+ self.assertEqual(po_data[0]["transferred_qty"], 2)
- self.assertEqual(po_data[1]['rm_item_code'], '_Test Item Home Desktop 100')
- self.assertEqual(po_data[1]['p_qty'], 19)
- self.assertEqual(po_data[1]['transferred_qty'], 1)
+ self.assertEqual(po_data[1]["rm_item_code"], "_Test Item Home Desktop 100")
+ self.assertEqual(po_data[1]["p_qty"], 19)
+ self.assertEqual(po_data[1]["transferred_qty"], 1)
se.cancel()
po.cancel()
+
def transfer_subcontracted_raw_materials(po):
# Order of supplied items fetched in PO is flaky
- transfer_qty_map = {
- '_Test Item': 2,
- '_Test Item Home Desktop 100': 1
- }
+ transfer_qty_map = {"_Test Item": 2, "_Test Item Home Desktop 100": 1}
item_1 = po.supplied_items[0].rm_item_code
item_2 = po.supplied_items[1].rm_item_code
rm_item = [
{
- 'name': po.supplied_items[0].name,
- 'item_code': item_1,
- 'rm_item_code': item_1,
- 'item_name': item_1,
- 'qty': transfer_qty_map[item_1],
- 'warehouse': '_Test Warehouse - _TC',
- 'rate': 100,
- 'amount': 100 * transfer_qty_map[item_1],
- 'stock_uom': 'Nos'
+ "name": po.supplied_items[0].name,
+ "item_code": item_1,
+ "rm_item_code": item_1,
+ "item_name": item_1,
+ "qty": transfer_qty_map[item_1],
+ "warehouse": "_Test Warehouse - _TC",
+ "rate": 100,
+ "amount": 100 * transfer_qty_map[item_1],
+ "stock_uom": "Nos",
},
{
- 'name': po.supplied_items[1].name,
- 'item_code': item_2,
- 'rm_item_code': item_2,
- 'item_name': item_2,
- 'qty': transfer_qty_map[item_2],
- 'warehouse': '_Test Warehouse - _TC',
- 'rate': 100,
- 'amount': 100 * transfer_qty_map[item_2],
- 'stock_uom': 'Nos'
- }
+ "name": po.supplied_items[1].name,
+ "item_code": item_2,
+ "rm_item_code": item_2,
+ "item_name": item_2,
+ "qty": transfer_qty_map[item_2],
+ "warehouse": "_Test Warehouse - _TC",
+ "rate": 100,
+ "amount": 100 * transfer_qty_map[item_2],
+ "stock_uom": "Nos",
+ },
]
rm_item_string = json.dumps(rm_item)
se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
- se.from_warehouse = '_Test Warehouse - _TC'
- se.to_warehouse = '_Test Warehouse - _TC'
- se.stock_entry_type = 'Send to Subcontractor'
+ se.from_warehouse = "_Test Warehouse - _TC"
+ se.to_warehouse = "_Test Warehouse - _TC"
+ se.stock_entry_type = "Send to Subcontractor"
se.save()
se.submit()
return se
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
index 65f9ce3c57..3013b6d160 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
@@ -24,6 +24,7 @@ def execute(filters=None):
return columns, data, message, chart_data
+
def get_conditions(filters):
conditions = ""
if filters.get("item_code"):
@@ -43,8 +44,10 @@ def get_conditions(filters):
return conditions
+
def get_data(filters, conditions):
- supplier_quotation_data = frappe.db.sql("""
+ supplier_quotation_data = frappe.db.sql(
+ """
SELECT
sqi.parent, sqi.item_code,
sqi.qty, sqi.stock_qty, sqi.amount,
@@ -60,23 +63,33 @@ def get_data(filters, conditions):
AND sq.company = %(company)s
AND sq.transaction_date between %(from_date)s and %(to_date)s
{0}
- order by sq.transaction_date, sqi.item_code""".format(conditions), filters, as_dict=1)
+ order by sq.transaction_date, sqi.item_code""".format(
+ conditions
+ ),
+ filters,
+ as_dict=1,
+ )
return supplier_quotation_data
+
def prepare_data(supplier_quotation_data, filters):
out, groups, qty_list, suppliers, chart_data = [], [], [], [], []
group_wise_map = defaultdict(list)
supplier_qty_price_map = {}
- group_by_field = "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code"
+ group_by_field = (
+ "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code"
+ )
company_currency = frappe.db.get_default("currency")
float_precision = cint(frappe.db.get_default("float_precision")) or 2
for data in supplier_quotation_data:
- group = data.get(group_by_field) # get item or supplier value for this row
+ group = data.get(group_by_field) # get item or supplier value for this row
- supplier_currency = frappe.db.get_value("Supplier", data.get("supplier_name"), "default_currency")
+ supplier_currency = frappe.db.get_value(
+ "Supplier", data.get("supplier_name"), "default_currency"
+ )
if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
@@ -84,16 +97,18 @@ def prepare_data(supplier_quotation_data, filters):
exchange_rate = 1
row = {
- "item_code": "" if group_by_field=="item_code" else data.get("item_code"), # leave blank if group by field
- "supplier_name": "" if group_by_field=="supplier_name" else data.get("supplier_name"),
+ "item_code": ""
+ if group_by_field == "item_code"
+ else data.get("item_code"), # leave blank if group by field
+ "supplier_name": "" if group_by_field == "supplier_name" else data.get("supplier_name"),
"quotation": data.get("parent"),
"qty": data.get("qty"),
"price": flt(data.get("amount") * exchange_rate, float_precision),
"uom": data.get("uom"),
- "stock_uom": data.get('stock_uom'),
+ "stock_uom": data.get("stock_uom"),
"request_for_quotation": data.get("request_for_quotation"),
- "valid_till": data.get('valid_till'),
- "lead_time_days": data.get('lead_time_days')
+ "valid_till": data.get("valid_till"),
+ "lead_time_days": data.get("lead_time_days"),
}
row["price_per_unit"] = flt(row["price"]) / (flt(data.get("stock_qty")) or 1)
@@ -119,8 +134,8 @@ def prepare_data(supplier_quotation_data, filters):
# final data format for report view
for group in groups:
- group_entries = group_wise_map[group] # all entries pertaining to item/supplier
- group_entries[0].update({group_by_field : group}) # Add item/supplier name in first group row
+ group_entries = group_wise_map[group] # all entries pertaining to item/supplier
+ group_entries[0].update({group_by_field: group}) # Add item/supplier name in first group row
if highlight_min_price:
prices = [group_entry["price_per_unit"] for group_entry in group_entries]
@@ -137,6 +152,7 @@ def prepare_data(supplier_quotation_data, filters):
return out, chart_data
+
def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
data_points_map = {}
qty_list.sort()
@@ -157,107 +173,89 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
for qty in qty_list:
datapoints = {
"name": currency_symbol + " (Qty " + str(qty) + " )",
- "values": data_points_map[qty]
+ "values": data_points_map[qty],
}
dataset.append(datapoints)
- chart_data = {
- "data": {
- "labels": suppliers,
- "datasets": dataset
- },
- "type": "bar"
- }
+ chart_data = {"data": {"labels": suppliers, "datasets": dataset}, "type": "bar"}
return chart_data
+
def get_columns(filters):
group_by_columns = [
- {
- "fieldname": "supplier_name",
- "label": _("Supplier"),
- "fieldtype": "Link",
- "options": "Supplier",
- "width": 150
- },
- {
- "fieldname": "item_code",
- "label": _("Item"),
- "fieldtype": "Link",
- "options": "Item",
- "width": 150
- }]
+ {
+ "fieldname": "supplier_name",
+ "label": _("Supplier"),
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "width": 150,
+ },
+ {
+ "fieldname": "item_code",
+ "label": _("Item"),
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 150,
+ },
+ ]
columns = [
- {
- "fieldname": "uom",
- "label": _("UOM"),
- "fieldtype": "Link",
- "options": "UOM",
- "width": 90
- },
- {
- "fieldname": "qty",
- "label": _("Quantity"),
- "fieldtype": "Float",
- "width": 80
- },
- {
- "fieldname": "price",
- "label": _("Price"),
- "fieldtype": "Currency",
- "options": "Company:company:default_currency",
- "width": 110
- },
- {
- "fieldname": "stock_uom",
- "label": _("Stock UOM"),
- "fieldtype": "Link",
- "options": "UOM",
- "width": 90
- },
- {
- "fieldname": "price_per_unit",
- "label": _("Price per Unit (Stock UOM)"),
- "fieldtype": "Currency",
- "options": "Company:company:default_currency",
- "width": 120
- },
- {
- "fieldname": "quotation",
- "label": _("Supplier Quotation"),
- "fieldtype": "Link",
- "options": "Supplier Quotation",
- "width": 200
- },
- {
- "fieldname": "valid_till",
- "label": _("Valid Till"),
- "fieldtype": "Date",
- "width": 100
- },
- {
- "fieldname": "lead_time_days",
- "label": _("Lead Time (Days)"),
- "fieldtype": "Int",
- "width": 100
- },
- {
- "fieldname": "request_for_quotation",
- "label": _("Request for Quotation"),
- "fieldtype": "Link",
- "options": "Request for Quotation",
- "width": 150
- }]
+ {"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90},
+ {"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80},
+ {
+ "fieldname": "price",
+ "label": _("Price"),
+ "fieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "width": 110,
+ },
+ {
+ "fieldname": "stock_uom",
+ "label": _("Stock UOM"),
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 90,
+ },
+ {
+ "fieldname": "price_per_unit",
+ "label": _("Price per Unit (Stock UOM)"),
+ "fieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "width": 120,
+ },
+ {
+ "fieldname": "quotation",
+ "label": _("Supplier Quotation"),
+ "fieldtype": "Link",
+ "options": "Supplier Quotation",
+ "width": 200,
+ },
+ {"fieldname": "valid_till", "label": _("Valid Till"), "fieldtype": "Date", "width": 100},
+ {
+ "fieldname": "lead_time_days",
+ "label": _("Lead Time (Days)"),
+ "fieldtype": "Int",
+ "width": 100,
+ },
+ {
+ "fieldname": "request_for_quotation",
+ "label": _("Request for Quotation"),
+ "fieldtype": "Link",
+ "options": "Request for Quotation",
+ "width": 150,
+ },
+ ]
if filters.get("group_by") == "Group by Item":
group_by_columns.reverse()
- columns[0:0] = group_by_columns # add positioned group by columns to the report
+ columns[0:0] = group_by_columns # add positioned group by columns to the report
return columns
+
def get_message():
- return """
+ return """
Valid till :
diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py
index 66c60d5637..f97cd5e9dd 100644
--- a/erpnext/buying/utils.py
+++ b/erpnext/buying/utils.py
@@ -14,7 +14,8 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_
def update_last_purchase_rate(doc, is_submit):
"""updates last_purchase_rate in item table for each item"""
import frappe.utils
- this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date'))
+
+ this_purchase_date = frappe.utils.getdate(doc.get("posting_date") or doc.get("transaction_date"))
for d in doc.get("items"):
# get last purchase details
@@ -22,9 +23,10 @@ def update_last_purchase_rate(doc, is_submit):
# compare last purchase date and this transaction's date
last_purchase_rate = None
- if last_purchase_details and \
- (doc.get('docstatus') == 2 or last_purchase_details.purchase_date > this_purchase_date):
- last_purchase_rate = last_purchase_details['base_net_rate']
+ if last_purchase_details and (
+ doc.get("docstatus") == 2 or last_purchase_details.purchase_date > this_purchase_date
+ ):
+ last_purchase_rate = last_purchase_details["base_net_rate"]
elif is_submit == 1:
# even if this transaction is the latest one, it should be submitted
# for it to be considered for latest purchase rate
@@ -36,9 +38,7 @@ def update_last_purchase_rate(doc, is_submit):
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
# update last purchsae rate
- frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
-
-
+ frappe.db.set_value("Item", d.item_code, "last_purchase_rate", flt(last_purchase_rate))
def validate_for_items(doc):
@@ -50,44 +50,65 @@ def validate_for_items(doc):
frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code))
# update with latest quantities
- bin = frappe.db.sql("""select projected_qty from `tabBin` where
- item_code = %s and warehouse = %s""", (d.item_code, d.warehouse), as_dict=1)
+ bin = frappe.db.sql(
+ """select projected_qty from `tabBin` where
+ item_code = %s and warehouse = %s""",
+ (d.item_code, d.warehouse),
+ as_dict=1,
+ )
- f_lst ={'projected_qty': bin and flt(bin[0]['projected_qty']) or 0, 'ordered_qty': 0, 'received_qty' : 0}
- if d.doctype in ('Purchase Receipt Item', 'Purchase Invoice Item'):
- f_lst.pop('received_qty')
- for x in f_lst :
+ f_lst = {
+ "projected_qty": bin and flt(bin[0]["projected_qty"]) or 0,
+ "ordered_qty": 0,
+ "received_qty": 0,
+ }
+ if d.doctype in ("Purchase Receipt Item", "Purchase Invoice Item"):
+ f_lst.pop("received_qty")
+ for x in f_lst:
if d.meta.get_field(x):
d.set(x, f_lst[x])
- item = frappe.db.sql("""select is_stock_item,
+ item = frappe.db.sql(
+ """select is_stock_item,
is_sub_contracted_item, end_of_life, disabled from `tabItem` where name=%s""",
- d.item_code, as_dict=1)[0]
+ d.item_code,
+ as_dict=1,
+ )[0]
validate_end_of_life(d.item_code, item.end_of_life, item.disabled)
# validate stock item
- if item.is_stock_item==1 and d.qty and not d.warehouse and not d.get("delivered_by_supplier"):
- frappe.throw(_("Warehouse is mandatory for stock Item {0} in row {1}").format(d.item_code, d.idx))
+ if item.is_stock_item == 1 and d.qty and not d.warehouse and not d.get("delivered_by_supplier"):
+ frappe.throw(
+ _("Warehouse is mandatory for stock Item {0} in row {1}").format(d.item_code, d.idx)
+ )
items.append(cstr(d.item_code))
- if items and len(items) != len(set(items)) and \
- not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0):
+ if (
+ items
+ and len(items) != len(set(items))
+ and not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0)
+ ):
frappe.throw(_("Same item cannot be entered multiple times."))
+
def check_on_hold_or_closed_status(doctype, docname):
status = frappe.db.get_value(doctype, docname, "status")
if status in ("Closed", "On Hold"):
- frappe.throw(_("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError)
+ frappe.throw(
+ _("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError
+ )
+
@frappe.whitelist()
def get_linked_material_requests(items):
items = json.loads(items)
mr_list = []
for item in items:
- material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name,
+ material_request = frappe.db.sql(
+ """SELECT distinct mr.name AS mr_name,
(mr_item.qty - mr_item.ordered_qty) AS qty,
mr_item.item_code AS item_code,
mr_item.name AS mr_item
@@ -98,7 +119,10 @@ def get_linked_material_requests(items):
AND mr.per_ordered < 99.99
AND mr.docstatus = 1
AND mr.status != 'Stopped'
- ORDER BY mr_item.item_code ASC""",{"item": item}, as_dict=1)
+ ORDER BY mr_item.item_code ASC""",
+ {"item": item},
+ as_dict=1,
+ )
if material_request:
mr_list.append(material_request)
diff --git a/erpnext/commands/__init__.py b/erpnext/commands/__init__.py
index 8e12fad3d7..ddf0acc96a 100644
--- a/erpnext/commands/__init__.py
+++ b/erpnext/commands/__init__.py
@@ -7,4 +7,5 @@ import click
def call_command(cmd, context):
return click.Context(cmd, obj=context).forward(cmd)
+
commands = []
diff --git a/erpnext/config/education.py b/erpnext/config/education.py
index d718a94252..c37cf2b196 100644
--- a/erpnext/config/education.py
+++ b/erpnext/config/education.py
@@ -11,113 +11,61 @@ def get_data():
"name": "Student",
"onboard": 1,
},
- {
- "type": "doctype",
- "name": "Guardian"
- },
- {
- "type": "doctype",
- "name": "Student Log"
- },
- {
- "type": "doctype",
- "name": "Student Group"
- }
- ]
+ {"type": "doctype", "name": "Guardian"},
+ {"type": "doctype", "name": "Student Log"},
+ {"type": "doctype", "name": "Student Group"},
+ ],
},
{
"label": _("Admission"),
"items": [
-
- {
- "type": "doctype",
- "name": "Student Applicant"
- },
- {
- "type": "doctype",
- "name": "Web Academy Applicant"
- },
- {
- "type": "doctype",
- "name": "Student Admission"
- },
- {
- "type": "doctype",
- "name": "Program Enrollment"
- }
- ]
+ {"type": "doctype", "name": "Student Applicant"},
+ {"type": "doctype", "name": "Web Academy Applicant"},
+ {"type": "doctype", "name": "Student Admission"},
+ {"type": "doctype", "name": "Program Enrollment"},
+ ],
},
{
"label": _("Attendance"),
"items": [
- {
- "type": "doctype",
- "name": "Student Attendance"
- },
- {
- "type": "doctype",
- "name": "Student Leave Application"
- },
+ {"type": "doctype", "name": "Student Attendance"},
+ {"type": "doctype", "name": "Student Leave Application"},
{
"type": "report",
"is_query_report": True,
"name": "Absent Student Report",
- "doctype": "Student Attendance"
+ "doctype": "Student Attendance",
},
{
"type": "report",
"is_query_report": True,
"name": "Student Batch-Wise Attendance",
- "doctype": "Student Attendance"
+ "doctype": "Student Attendance",
},
- ]
+ ],
},
{
"label": _("Tools"),
"items": [
- {
- "type": "doctype",
- "name": "Student Attendance Tool"
- },
- {
- "type": "doctype",
- "name": "Assessment Result Tool"
- },
- {
- "type": "doctype",
- "name": "Student Group Creation Tool"
- },
- {
- "type": "doctype",
- "name": "Program Enrollment Tool"
- },
- {
- "type": "doctype",
- "name": "Course Scheduling Tool"
- }
- ]
+ {"type": "doctype", "name": "Student Attendance Tool"},
+ {"type": "doctype", "name": "Assessment Result Tool"},
+ {"type": "doctype", "name": "Student Group Creation Tool"},
+ {"type": "doctype", "name": "Program Enrollment Tool"},
+ {"type": "doctype", "name": "Course Scheduling Tool"},
+ ],
},
{
"label": _("Assessment"),
"items": [
- {
- "type": "doctype",
- "name": "Assessment Plan"
- },
+ {"type": "doctype", "name": "Assessment Plan"},
{
"type": "doctype",
"name": "Assessment Group",
"link": "Tree/Assessment Group",
},
- {
- "type": "doctype",
- "name": "Assessment Result"
- },
- {
- "type": "doctype",
- "name": "Assessment Criteria"
- }
- ]
+ {"type": "doctype", "name": "Assessment Result"},
+ {"type": "doctype", "name": "Assessment Criteria"},
+ ],
},
{
"label": _("Assessment Reports"),
@@ -126,60 +74,38 @@ def get_data():
"type": "report",
"is_query_report": True,
"name": "Course wise Assessment Report",
- "doctype": "Assessment Result"
+ "doctype": "Assessment Result",
},
{
"type": "report",
"is_query_report": True,
"name": "Final Assessment Grades",
- "doctype": "Assessment Result"
+ "doctype": "Assessment Result",
},
{
"type": "report",
"is_query_report": True,
"name": "Assessment Plan Status",
- "doctype": "Assessment Plan"
+ "doctype": "Assessment Plan",
},
- {
- "type": "doctype",
- "name": "Student Report Generation Tool"
- }
- ]
+ {"type": "doctype", "name": "Student Report Generation Tool"},
+ ],
},
{
"label": _("Fees"),
"items": [
- {
- "type": "doctype",
- "name": "Fees"
- },
- {
- "type": "doctype",
- "name": "Fee Schedule"
- },
- {
- "type": "doctype",
- "name": "Fee Structure"
- },
- {
- "type": "doctype",
- "name": "Fee Category"
- }
- ]
+ {"type": "doctype", "name": "Fees"},
+ {"type": "doctype", "name": "Fee Schedule"},
+ {"type": "doctype", "name": "Fee Structure"},
+ {"type": "doctype", "name": "Fee Category"},
+ ],
},
{
"label": _("Schedule"),
"items": [
- {
- "type": "doctype",
- "name": "Course Schedule",
- "route": "/app/List/Course Schedule/Calendar"
- },
- {
- "type": "doctype",
- "name": "Course Scheduling Tool"
- }
- ]
+ {"type": "doctype", "name": "Course Schedule", "route": "/app/List/Course Schedule/Calendar"},
+ {"type": "doctype", "name": "Course Scheduling Tool"},
+ ],
},
{
"label": _("Masters"),
@@ -206,72 +132,39 @@ def get_data():
"type": "doctype",
"name": "Room",
"onboard": 1,
- }
- ]
+ },
+ ],
},
{
"label": _("Content Masters"),
"items": [
- {
- "type": "doctype",
- "name": "Article"
- },
- {
- "type": "doctype",
- "name": "Video"
- },
- {
- "type": "doctype",
- "name": "Quiz"
- }
- ]
+ {"type": "doctype", "name": "Article"},
+ {"type": "doctype", "name": "Video"},
+ {"type": "doctype", "name": "Quiz"},
+ ],
},
{
"label": _("LMS Activity"),
"items": [
- {
- "type": "doctype",
- "name": "Course Enrollment"
- },
- {
- "type": "doctype",
- "name": "Course Activity"
- },
- {
- "type": "doctype",
- "name": "Quiz Activity"
- }
- ]
+ {"type": "doctype", "name": "Course Enrollment"},
+ {"type": "doctype", "name": "Course Activity"},
+ {"type": "doctype", "name": "Quiz Activity"},
+ ],
},
{
"label": _("Settings"),
"items": [
- {
- "type": "doctype",
- "name": "Student Category"
- },
- {
- "type": "doctype",
- "name": "Student Batch Name"
- },
+ {"type": "doctype", "name": "Student Category"},
+ {"type": "doctype", "name": "Student Batch Name"},
{
"type": "doctype",
"name": "Grading Scale",
"onboard": 1,
},
- {
- "type": "doctype",
- "name": "Academic Term"
- },
- {
- "type": "doctype",
- "name": "Academic Year"
- },
- {
- "type": "doctype",
- "name": "Education Settings"
- }
- ]
+ {"type": "doctype", "name": "Academic Term"},
+ {"type": "doctype", "name": "Academic Year"},
+ {"type": "doctype", "name": "Education Settings"},
+ ],
},
{
"label": _("Other Reports"),
@@ -280,20 +173,20 @@ def get_data():
"type": "report",
"is_query_report": True,
"name": "Student and Guardian Contact Details",
- "doctype": "Program Enrollment"
+ "doctype": "Program Enrollment",
},
{
"type": "report",
"is_query_report": True,
"name": "Student Monthly Attendance Sheet",
- "doctype": "Student Attendance"
+ "doctype": "Student Attendance",
},
{
"type": "report",
"name": "Student Fee Collection",
"doctype": "Fees",
- "is_query_report": True
- }
- ]
- }
+ "is_query_report": True,
+ },
+ ],
+ },
]
diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py
index f4675e749a..6186a4e820 100644
--- a/erpnext/config/projects.py
+++ b/erpnext/config/projects.py
@@ -44,7 +44,7 @@ def get_data():
"description": _("Project Update."),
"dependencies": ["Project"],
},
- ]
+ ],
},
{
"label": _("Time Tracking"),
@@ -67,7 +67,7 @@ def get_data():
"description": _("Cost of various activities"),
"dependencies": ["Activity Type"],
},
- ]
+ ],
},
{
"label": _("Reports"),
@@ -95,7 +95,6 @@ def get_data():
"doctype": "Project",
"dependencies": ["Project"],
},
- ]
+ ],
},
-
]
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 34ff45708b..72ac1b37ef 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -56,10 +56,22 @@ from erpnext.stock.get_item_details import (
from erpnext.utilities.transaction_base import TransactionBase
-class AccountMissingError(frappe.ValidationError): pass
+class AccountMissingError(frappe.ValidationError):
+ pass
+
+
+force_item_fields = (
+ "item_group",
+ "brand",
+ "stock_uom",
+ "is_fixed_asset",
+ "item_tax_rate",
+ "pricing_rules",
+ "weight_per_unit",
+ "weight_uom",
+ "total_weight",
+)
-force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate",
- "pricing_rules", "weight_per_unit", "weight_uom", "total_weight")
class AccountsController(TransactionBase):
def __init__(self, *args, **kwargs):
@@ -67,14 +79,14 @@ class AccountsController(TransactionBase):
def get_print_settings(self):
print_setting_fields = []
- items_field = self.meta.get_field('items')
+ items_field = self.meta.get_field("items")
- if items_field and items_field.fieldtype == 'Table':
- print_setting_fields += ['compact_item_print', 'print_uom_after_quantity']
+ if items_field and items_field.fieldtype == "Table":
+ print_setting_fields += ["compact_item_print", "print_uom_after_quantity"]
- taxes_field = self.meta.get_field('taxes')
- if taxes_field and taxes_field.fieldtype == 'Table':
- print_setting_fields += ['print_taxes_with_zero_amount']
+ taxes_field = self.meta.get_field("taxes")
+ if taxes_field and taxes_field.fieldtype == "Table":
+ print_setting_fields += ["print_taxes_with_zero_amount"]
return print_setting_fields
@@ -86,34 +98,44 @@ class AccountsController(TransactionBase):
return self.__company_currency
def onload(self):
- self.set_onload("make_payment_via_journal_entry",
- frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry'))
+ self.set_onload(
+ "make_payment_via_journal_entry",
+ frappe.db.get_single_value("Accounts Settings", "make_payment_via_journal_entry"),
+ )
if self.is_new():
- relevant_docs = ("Quotation", "Purchase Order", "Sales Order",
- "Purchase Invoice", "Sales Invoice")
+ relevant_docs = (
+ "Quotation",
+ "Purchase Order",
+ "Sales Order",
+ "Purchase Invoice",
+ "Sales Invoice",
+ )
if self.doctype in relevant_docs:
self.set_payment_schedule()
def ensure_supplier_is_not_blocked(self):
- is_supplier_payment = self.doctype == 'Payment Entry' and self.party_type == 'Supplier'
- is_buying_invoice = self.doctype in ['Purchase Invoice', 'Purchase Order']
+ is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
+ is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
supplier = None
supplier_name = None
if is_buying_invoice or is_supplier_payment:
supplier_name = self.supplier if is_buying_invoice else self.party
- supplier = frappe.get_doc('Supplier', supplier_name)
+ supplier = frappe.get_doc("Supplier", supplier_name)
if supplier and supplier_name and supplier.on_hold:
- if (is_buying_invoice and supplier.hold_type in ['All', 'Invoices']) or \
- (is_supplier_payment and supplier.hold_type in ['All', 'Payments']):
+ if (is_buying_invoice and supplier.hold_type in ["All", "Invoices"]) or (
+ is_supplier_payment and supplier.hold_type in ["All", "Payments"]
+ ):
if not supplier.release_date or getdate(nowdate()) <= supplier.release_date:
frappe.msgprint(
- _('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1)
+ _("{0} is blocked so this transaction cannot proceed").format(supplier_name),
+ raise_exception=1,
+ )
def validate(self):
- if not self.get('is_return') and not self.get('is_debit_note'):
+ if not self.get("is_return") and not self.get("is_debit_note"):
self.validate_qty_is_not_zero()
if self.get("_action") and self._action != "update_after_submit":
@@ -146,8 +168,8 @@ class AccountsController(TransactionBase):
self.validate_party()
self.validate_currency()
- if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
- pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
+ if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
+ pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
@@ -160,7 +182,7 @@ class AccountsController(TransactionBase):
self.set_inter_company_account()
- if self.doctype == 'Purchase Invoice':
+ if self.doctype == "Purchase Invoice":
self.calculate_paid_amount()
# apply tax withholding only if checked and applicable
self.set_tax_withholding()
@@ -169,7 +191,7 @@ class AccountsController(TransactionBase):
validate_einvoice_fields(self)
- if self.doctype != 'Material Request':
+ if self.doctype != "Material Request":
apply_pricing_rule_on_transaction(self)
def before_cancel(self):
@@ -177,26 +199,37 @@ class AccountsController(TransactionBase):
def on_trash(self):
# delete sl and gl entries on deletion of transaction
- if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
- frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
- frappe.db.sql("delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
+ if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
+ frappe.db.sql(
+ "delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
+ )
+ frappe.db.sql(
+ "delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s",
+ (self.doctype, self.name),
+ )
def validate_deferred_start_and_end_date(self):
for d in self.items:
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
if not (d.service_start_date and d.service_end_date):
- frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
+ frappe.throw(
+ _("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx)
+ )
elif getdate(d.service_start_date) > getdate(d.service_end_date):
- frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
+ frappe.throw(
+ _("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx)
+ )
elif getdate(self.posting_date) > getdate(d.service_end_date):
- frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
+ frappe.throw(
+ _("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx)
+ )
def validate_invoice_documents_schedule(self):
self.validate_payment_schedule_dates()
self.set_due_date()
self.set_payment_schedule()
self.validate_payment_schedule_amount()
- if not self.get('ignore_default_payment_terms_template'):
+ if not self.get("ignore_default_payment_terms_template"):
self.validate_due_date()
self.validate_advance_entries()
@@ -212,8 +245,16 @@ class AccountsController(TransactionBase):
self.validate_non_invoice_documents_schedule()
def before_print(self, settings=None):
- if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
- 'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
+ if self.doctype in [
+ "Purchase Order",
+ "Sales Order",
+ "Sales Invoice",
+ "Purchase Invoice",
+ "Supplier Quotation",
+ "Purchase Receipt",
+ "Delivery Note",
+ "Quotation",
+ ]:
if self.get("group_same_items"):
self.group_similar_items()
@@ -234,7 +275,9 @@ class AccountsController(TransactionBase):
if is_paid:
if not self.cash_bank_account:
# show message that the amount is not paid
- frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
+ frappe.throw(
+ _("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")
+ )
if cint(self.is_return) and self.grand_total > self.paid_amount:
self.paid_amount = flt(flt(self.grand_total), self.precision("paid_amount"))
@@ -242,8 +285,9 @@ class AccountsController(TransactionBase):
elif not flt(self.paid_amount) and flt(self.outstanding_amount) > 0:
self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount"))
- self.base_paid_amount = flt(self.paid_amount * self.conversion_rate,
- self.precision("base_paid_amount"))
+ self.base_paid_amount = flt(
+ self.paid_amount * self.conversion_rate, self.precision("base_paid_amount")
+ )
def set_missing_values(self, for_validate=False):
if frappe.flags.in_test:
@@ -254,13 +298,14 @@ class AccountsController(TransactionBase):
def calculate_taxes_and_totals(self):
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
+
calculate_taxes_and_totals(self)
if self.doctype in (
- 'Sales Order',
- 'Delivery Note',
- 'Sales Invoice',
- 'POS Invoice',
+ "Sales Order",
+ "Delivery Note",
+ "Sales Invoice",
+ "POS Invoice",
):
self.calculate_commission()
self.calculate_contribution()
@@ -274,50 +319,75 @@ class AccountsController(TransactionBase):
date_field = "transaction_date"
if date_field and self.get(date_field):
- validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
- self.meta.get_label(date_field), self)
+ validate_fiscal_year(
+ self.get(date_field), self.fiscal_year, self.company, self.meta.get_label(date_field), self
+ )
def validate_party_accounts(self):
- if self.doctype not in ('Sales Invoice', 'Purchase Invoice'):
+ if self.doctype not in ("Sales Invoice", "Purchase Invoice"):
return
- if self.doctype == 'Sales Invoice':
- party_account_field = 'debit_to'
- item_field = 'income_account'
+ if self.doctype == "Sales Invoice":
+ party_account_field = "debit_to"
+ item_field = "income_account"
else:
- party_account_field = 'credit_to'
- item_field = 'expense_account'
+ party_account_field = "credit_to"
+ item_field = "expense_account"
- for item in self.get('items'):
+ for item in self.get("items"):
if item.get(item_field) == self.get(party_account_field):
- frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx,
- frappe.bold(frappe.unscrub(item_field)), item.get(item_field),
- frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field)))
+ frappe.throw(
+ _("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(
+ item.idx,
+ frappe.bold(frappe.unscrub(item_field)),
+ item.get(item_field),
+ frappe.bold(frappe.unscrub(party_account_field)),
+ self.get(party_account_field),
+ )
+ )
def validate_inter_company_reference(self):
- if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
+ if self.doctype not in ("Purchase Invoice", "Purchase Receipt", "Purchase Order"):
return
if self.is_internal_transfer():
- if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
- or self.get('inter_company_order_reference')):
+ if not (
+ self.get("inter_company_reference")
+ or self.get("inter_company_invoice_reference")
+ or self.get("inter_company_order_reference")
+ ):
msg = _("Internal Sale or Delivery Reference missing.")
msg += _("Please create purchase from internal sale or delivery document itself")
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
def validate_due_date(self):
- if self.get('is_pos'): return
+ if self.get("is_pos"):
+ return
from erpnext.accounts.party import validate_due_date
+
if self.doctype == "Sales Invoice":
if not self.due_date:
frappe.throw(_("Due Date is mandatory"))
- validate_due_date(self.posting_date, self.due_date,
- "Customer", self.customer, self.company, self.payment_terms_template)
+ validate_due_date(
+ self.posting_date,
+ self.due_date,
+ "Customer",
+ self.customer,
+ self.company,
+ self.payment_terms_template,
+ )
elif self.doctype == "Purchase Invoice":
- validate_due_date(self.bill_date or self.posting_date, self.due_date,
- "Supplier", self.supplier, self.company, self.bill_date, self.payment_terms_template)
+ validate_due_date(
+ self.bill_date or self.posting_date,
+ self.due_date,
+ "Supplier",
+ self.supplier,
+ self.company,
+ self.bill_date,
+ self.payment_terms_template,
+ )
def set_price_list_currency(self, buying_or_selling):
if self.meta.get_field("posting_date"):
@@ -335,15 +405,15 @@ class AccountsController(TransactionBase):
args = "for_buying"
if self.meta.get_field(fieldname) and self.get(fieldname):
- self.price_list_currency = frappe.db.get_value("Price List",
- self.get(fieldname), "currency")
+ self.price_list_currency = frappe.db.get_value("Price List", self.get(fieldname), "currency")
if self.price_list_currency == self.company_currency:
self.plc_conversion_rate = 1.0
elif not self.plc_conversion_rate:
- self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
- self.company_currency, transaction_date, args)
+ self.plc_conversion_rate = get_exchange_rate(
+ self.price_list_currency, self.company_currency, transaction_date, args
+ )
# currency
if not self.currency:
@@ -352,8 +422,9 @@ class AccountsController(TransactionBase):
elif self.currency == self.company_currency:
self.conversion_rate = 1.0
elif not self.conversion_rate:
- self.conversion_rate = get_exchange_rate(self.currency,
- self.company_currency, transaction_date, args)
+ self.conversion_rate = get_exchange_rate(
+ self.currency, self.company_currency, transaction_date, args
+ )
def set_missing_item_details(self, for_validate=False):
"""set missing item values"""
@@ -369,7 +440,11 @@ class AccountsController(TransactionBase):
parent_dict.update({"document_type": document_type})
# party_name field used for customer in quotation
- if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
+ if (
+ self.doctype == "Quotation"
+ and self.quotation_to == "Customer"
+ and parent_dict.get("party_name")
+ ):
parent_dict.update({"customer": parent_dict.get("party_name")})
self.pricing_rules = []
@@ -381,7 +456,9 @@ class AccountsController(TransactionBase):
args["doctype"] = self.doctype
args["name"] = self.name
args["child_docname"] = item.name
- args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0
+ args["ignore_pricing_rule"] = (
+ self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0
+ )
if not args.get("transaction_date"):
args["transaction_date"] = args.get("posting_date")
@@ -393,10 +470,10 @@ class AccountsController(TransactionBase):
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
- if (item.get(fieldname) is None or fieldname in force_item_fields):
+ if item.get(fieldname) is None or fieldname in force_item_fields:
item.set(fieldname, value)
- elif fieldname in ['cost_center', 'conversion_factor'] and not item.get(fieldname):
+ elif fieldname in ["cost_center", "conversion_factor"] and not item.get(fieldname):
item.set(fieldname, value)
elif fieldname == "serial_no":
@@ -404,7 +481,7 @@ class AccountsController(TransactionBase):
item_conversion_factor = item.get("conversion_factor") or 1.0
item_qty = abs(item.get("qty")) * item_conversion_factor
- if item_qty != len(get_serial_nos(item.get('serial_no'))):
+ if item_qty != len(get_serial_nos(item.get("serial_no"))):
item.set(fieldname, value)
elif (
@@ -423,13 +500,17 @@ class AccountsController(TransactionBase):
# reset pricing rule fields if pricing_rule_removed
item.set(fieldname, value)
- if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
- item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
+ if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field(
+ "is_fixed_asset"
+ ):
+ item.set("is_fixed_asset", ret.get("is_fixed_asset", 0))
# Double check for cost center
# Items add via promotional scheme may not have cost center set
- if hasattr(item, 'cost_center') and not item.get('cost_center'):
- item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company))
+ if hasattr(item, "cost_center") and not item.get("cost_center"):
+ item.set(
+ "cost_center", self.get("cost_center") or erpnext.get_default_cost_center(self.company)
+ )
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
@@ -441,7 +522,7 @@ class AccountsController(TransactionBase):
def apply_pricing_rule_on_items(self, item, pricing_rule_args):
if not pricing_rule_args.get("validate_applied_rule", 0):
# if user changed the discount percentage then set user's discount percentage ?
- if pricing_rule_args.get("price_or_product_discount") == 'Price':
+ if pricing_rule_args.get("price_or_product_discount") == "Price":
item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
item.set("discount_amount", pricing_rule_args.get("discount_amount"))
@@ -449,39 +530,48 @@ class AccountsController(TransactionBase):
item.set("price_list_rate", pricing_rule_args.get("price_list_rate"))
if item.get("price_list_rate"):
- item.rate = flt(item.price_list_rate *
- (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
+ item.rate = flt(
+ item.price_list_rate * (1.0 - (flt(item.discount_percentage) / 100.0)),
+ item.precision("rate"),
+ )
- if item.get('discount_amount'):
+ if item.get("discount_amount"):
item.rate = item.price_list_rate - item.discount_amount
if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
item.rate = pricing_rule_args.get("rate")
- elif pricing_rule_args.get('free_item_data'):
- apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
+ elif pricing_rule_args.get("free_item_data"):
+ apply_pricing_rule_for_free_items(self, pricing_rule_args.get("free_item_data"))
elif pricing_rule_args.get("validate_applied_rule"):
- for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')):
+ for pricing_rule in get_applied_pricing_rules(item.get("pricing_rules")):
pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
- for field in ['discount_percentage', 'discount_amount', 'rate']:
+ for field in ["discount_percentage", "discount_amount", "rate"]:
if item.get(field) < pricing_rule_doc.get(field):
title = get_link_to_form("Pricing Rule", pricing_rule)
- frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
- .format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
+ frappe.msgprint(
+ _("Row {0}: user has not applied the rule {1} on the item {2}").format(
+ item.idx, frappe.bold(title), frappe.bold(item.item_code)
+ )
+ )
def set_pricing_rule_details(self, item_row, args):
pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
- if not pricing_rules: return
+ if not pricing_rules:
+ return
for pricing_rule in pricing_rules:
- self.append("pricing_rules", {
- "pricing_rule": pricing_rule,
- "item_code": item_row.item_code,
- "child_docname": item_row.name,
- "rule_applied": True
- })
+ self.append(
+ "pricing_rules",
+ {
+ "pricing_rule": pricing_rule,
+ "item_code": item_row.item_code,
+ "child_docname": item_row.name,
+ "rule_applied": True,
+ },
+ )
def set_taxes(self):
if not self.meta.get_field("taxes"):
@@ -492,14 +582,18 @@ class AccountsController(TransactionBase):
if (self.is_new() or self.is_pos_profile_changed()) and not self.get("taxes"):
if self.company and not self.get("taxes_and_charges"):
# get the default tax master
- self.taxes_and_charges = frappe.db.get_value(tax_master_doctype,
- {"is_default": 1, 'company': self.company})
+ self.taxes_and_charges = frappe.db.get_value(
+ tax_master_doctype, {"is_default": 1, "company": self.company}
+ )
self.append_taxes_from_master(tax_master_doctype)
def is_pos_profile_changed(self):
- if (self.doctype == 'Sales Invoice' and self.is_pos and
- self.pos_profile != frappe.db.get_value('Sales Invoice', self.name, 'pos_profile')):
+ if (
+ self.doctype == "Sales Invoice"
+ and self.is_pos
+ and self.pos_profile != frappe.db.get_value("Sales Invoice", self.name, "pos_profile")
+ ):
return True
def append_taxes_from_master(self, tax_master_doctype=None):
@@ -516,44 +610,54 @@ class AccountsController(TransactionBase):
def validate_enabled_taxes_and_charges(self):
taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges")
if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"):
- frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges))
+ frappe.throw(
+ _("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges)
+ )
def validate_tax_account_company(self):
for d in self.get("taxes"):
if d.account_head:
tax_account_company = frappe.db.get_value("Account", d.account_head, "company")
if tax_account_company != self.company:
- frappe.throw(_("Row #{0}: Account {1} does not belong to company {2}")
- .format(d.idx, d.account_head, self.company))
+ frappe.throw(
+ _("Row #{0}: Account {1} does not belong to company {2}").format(
+ d.idx, d.account_head, self.company
+ )
+ )
def get_gl_dict(self, args, account_currency=None, item=None):
"""this method populates the common properties of a gl entry record"""
- posting_date = args.get('posting_date') or self.get('posting_date')
+ posting_date = args.get("posting_date") or self.get("posting_date")
fiscal_years = get_fiscal_years(posting_date, company=self.company)
if len(fiscal_years) > 1:
- frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(
- formatdate(posting_date)))
+ frappe.throw(
+ _("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(
+ formatdate(posting_date)
+ )
+ )
else:
fiscal_year = fiscal_years[0][0]
- gl_dict = frappe._dict({
- 'company': self.company,
- 'posting_date': posting_date,
- 'fiscal_year': fiscal_year,
- 'voucher_type': self.doctype,
- 'voucher_no': self.name,
- 'remarks': self.get("remarks") or self.get("remark"),
- 'debit': 0,
- 'credit': 0,
- 'debit_in_account_currency': 0,
- 'credit_in_account_currency': 0,
- 'is_opening': self.get("is_opening") or "No",
- 'party_type': None,
- 'party': None,
- 'project': self.get("project"),
- 'post_net_value': args.get('post_net_value')
- })
+ gl_dict = frappe._dict(
+ {
+ "company": self.company,
+ "posting_date": posting_date,
+ "fiscal_year": fiscal_year,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "remarks": self.get("remarks") or self.get("remark"),
+ "debit": 0,
+ "credit": 0,
+ "debit_in_account_currency": 0,
+ "credit_in_account_currency": 0,
+ "is_opening": self.get("is_opening") or "No",
+ "party_type": None,
+ "party": None,
+ "project": self.get("project"),
+ "post_net_value": args.get("post_net_value"),
+ }
+ )
accounting_dimensions = get_accounting_dimensions()
dimension_dict = frappe._dict()
@@ -569,13 +673,24 @@ class AccountsController(TransactionBase):
if not account_currency:
account_currency = get_account_currency(gl_dict.account)
- if gl_dict.account and self.doctype not in ["Journal Entry",
- "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
+ if gl_dict.account and self.doctype not in [
+ "Journal Entry",
+ "Period Closing Voucher",
+ "Payment Entry",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Stock Entry",
+ ]:
self.validate_account_currency(gl_dict.account, account_currency)
- if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
- set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
- self.company_currency)
+ if gl_dict.account and self.doctype not in [
+ "Journal Entry",
+ "Period Closing Voucher",
+ "Payment Entry",
+ ]:
+ set_balance_in_account_currency(
+ gl_dict, account_currency, self.get("conversion_rate"), self.company_currency
+ )
return gl_dict
@@ -591,14 +706,21 @@ class AccountsController(TransactionBase):
valid_currency.append(self.currency)
if account_currency not in valid_currency:
- frappe.throw(_("Account {0} is invalid. Account Currency must be {1}")
- .format(account, (' ' + _("or") + ' ').join(valid_currency)))
+ frappe.throw(
+ _("Account {0} is invalid. Account Currency must be {1}").format(
+ account, (" " + _("or") + " ").join(valid_currency)
+ )
+ )
def clear_unallocated_advances(self, childtype, parentfield):
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
- frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
- and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
+ frappe.db.sql(
+ """delete from `tab%s` where parentfield=%s and parent = %s
+ and allocated_amount = 0"""
+ % (childtype, "%s", "%s"),
+ (parentfield, self.name),
+ )
@frappe.whitelist()
def apply_shipping_rule(self):
@@ -608,16 +730,16 @@ class AccountsController(TransactionBase):
self.calculate_taxes_and_totals()
def get_shipping_address(self):
- '''Returns Address object from shipping address fields if present'''
+ """Returns Address object from shipping address fields if present"""
# shipping address fields can be `shipping_address_name` or `shipping_address`
# try getting value from both
- for fieldname in ('shipping_address_name', 'shipping_address'):
+ for fieldname in ("shipping_address_name", "shipping_address"):
shipping_field = self.meta.get_field(fieldname)
- if shipping_field and shipping_field.fieldtype == 'Link':
+ if shipping_field and shipping_field.fieldtype == "Link":
if self.get(fieldname):
- return frappe.get_doc('Address', self.get(fieldname))
+ return frappe.get_doc("Address", self.get(fieldname))
return {}
@@ -633,10 +755,10 @@ class AccountsController(TransactionBase):
if d.against_order:
allocated_amount = flt(d.amount)
else:
- if self.get('party_account_currency') == self.company_currency:
- amount = self.get('base_rounded_total') or self.base_grand_total
+ if self.get("party_account_currency") == self.company_currency:
+ amount = self.get("base_rounded_total") or self.base_grand_total
else:
- amount = self.get('rounded_total') or self.grand_total
+ amount = self.get("rounded_total") or self.grand_total
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount)
@@ -649,7 +771,7 @@ class AccountsController(TransactionBase):
"remarks": d.remarks,
"advance_amount": flt(d.amount),
"allocated_amount": allocated_amount,
- "ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry
+ "ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
}
self.append("advances", advance_row)
@@ -670,21 +792,24 @@ class AccountsController(TransactionBase):
order_field = "purchase_order"
order_doctype = "Purchase Order"
- order_list = list(set(d.get(order_field)
- for d in self.get("items") if d.get(order_field)))
+ order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
- journal_entries = get_advance_journal_entries(party_type, party, party_account,
- amount_field, order_doctype, order_list, include_unallocated)
+ journal_entries = get_advance_journal_entries(
+ party_type, party, party_account, amount_field, order_doctype, order_list, include_unallocated
+ )
- payment_entries = get_advance_payment_entries(party_type, party, party_account,
- order_doctype, order_list, include_unallocated)
+ payment_entries = get_advance_payment_entries(
+ party_type, party, party_account, order_doctype, order_list, include_unallocated
+ )
res = journal_entries + payment_entries
return res
def is_inclusive_tax(self):
- is_inclusive = cint(frappe.db.get_single_value("Accounts Settings", "show_inclusive_tax_in_print"))
+ is_inclusive = cint(
+ frappe.db.get_single_value("Accounts Settings", "show_inclusive_tax_in_print")
+ )
if is_inclusive:
is_inclusive = 0
@@ -695,10 +820,10 @@ class AccountsController(TransactionBase):
def validate_advance_entries(self):
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
- order_list = list(set(d.get(order_field)
- for d in self.get("items") if d.get(order_field)))
+ order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
- if not order_list: return
+ if not order_list:
+ return
advance_entries = self.get_advance_entries(include_unallocated=False)
@@ -706,22 +831,24 @@ class AccountsController(TransactionBase):
advance_entries_against_si = [d.reference_name for d in self.get("advances")]
for d in advance_entries:
if not advance_entries_against_si or d.reference_name not in advance_entries_against_si:
- frappe.msgprint(_(
- "Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.")
- .format(d.reference_name, d.against_order))
+ frappe.msgprint(
+ _(
+ "Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice."
+ ).format(d.reference_name, d.against_order)
+ )
def set_advance_gain_or_loss(self):
- if self.get('conversion_rate') == 1 or not self.get("advances"):
+ if self.get("conversion_rate") == 1 or not self.get("advances"):
return
- is_purchase_invoice = self.doctype == 'Purchase Invoice'
+ is_purchase_invoice = self.doctype == "Purchase Invoice"
party_account = self.credit_to if is_purchase_invoice else self.debit_to
if get_account_currency(party_account) != self.currency:
return
for d in self.get("advances"):
advance_exchange_rate = d.ref_exchange_rate
- if (d.allocated_amount and self.conversion_rate != advance_exchange_rate):
+ if d.allocated_amount and self.conversion_rate != advance_exchange_rate:
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
@@ -730,61 +857,71 @@ class AccountsController(TransactionBase):
d.exchange_gain_loss = difference
def make_exchange_gain_loss_gl_entries(self, gl_entries):
- if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
+ if self.get("doctype") in ["Purchase Invoice", "Sales Invoice"]:
for d in self.get("advances"):
if d.exchange_gain_loss:
- is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
+ is_purchase_invoice = self.get("doctype") == "Purchase Invoice"
party = self.supplier if is_purchase_invoice else self.customer
party_account = self.credit_to if is_purchase_invoice else self.debit_to
party_type = "Supplier" if is_purchase_invoice else "Customer"
- gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
+ gain_loss_account = frappe.db.get_value("Company", self.company, "exchange_gain_loss_account")
if not gain_loss_account:
- frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}")
- .format(self.get('company')))
+ frappe.throw(
+ _("Please set default Exchange Gain/Loss Account in Company {}").format(self.get("company"))
+ )
account_currency = get_account_currency(gain_loss_account)
if account_currency != self.company_currency:
- frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
+ frappe.throw(
+ _("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency)
+ )
# for purchase
- dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
+ dr_or_cr = "debit" if d.exchange_gain_loss > 0 else "credit"
if not is_purchase_invoice:
# just reverse for sales?
- dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
+ dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
gl_entries.append(
- self.get_gl_dict({
- "account": gain_loss_account,
- "account_currency": account_currency,
- "against": party,
- dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
- dr_or_cr: abs(d.exchange_gain_loss),
- "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
- "project": self.project
- }, item=d)
+ self.get_gl_dict(
+ {
+ "account": gain_loss_account,
+ "account_currency": account_currency,
+ "against": party,
+ dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
+ dr_or_cr: abs(d.exchange_gain_loss),
+ "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
+ "project": self.project,
+ },
+ item=d,
+ )
)
- dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
+ dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
gl_entries.append(
- self.get_gl_dict({
- "account": party_account,
- "party_type": party_type,
- "party": party,
- "against": gain_loss_account,
- dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate),
- dr_or_cr: abs(d.exchange_gain_loss),
- "cost_center": self.cost_center,
- "project": self.project
- }, self.party_account_currency, item=self)
+ self.get_gl_dict(
+ {
+ "account": party_account,
+ "party_type": party_type,
+ "party": party,
+ "against": gain_loss_account,
+ dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate),
+ dr_or_cr: abs(d.exchange_gain_loss),
+ "cost_center": self.cost_center,
+ "project": self.project,
+ },
+ self.party_account_currency,
+ item=self,
+ )
)
def update_against_document_in_jv(self):
"""
- Links invoice and advance voucher:
- 1. cancel advance voucher
- 2. split into multiple rows if partially adjusted, assign against voucher
- 3. submit advance voucher
+ Links invoice and advance voucher:
+ 1. cancel advance voucher
+ 2. split into multiple rows if partially adjusted, assign against voucher
+ 3. submit advance voucher
"""
if self.doctype == "Sales Invoice":
@@ -799,45 +936,56 @@ class AccountsController(TransactionBase):
dr_or_cr = "debit_in_account_currency"
lst = []
- for d in self.get('advances'):
+ for d in self.get("advances"):
if flt(d.allocated_amount) > 0:
- args = frappe._dict({
- 'voucher_type': d.reference_type,
- 'voucher_no': d.reference_name,
- 'voucher_detail_no': d.reference_row,
- 'against_voucher_type': self.doctype,
- 'against_voucher': self.name,
- 'account': party_account,
- 'party_type': party_type,
- 'party': party,
- 'is_advance': 'Yes',
- 'dr_or_cr': dr_or_cr,
- 'unadjusted_amount': flt(d.advance_amount),
- 'allocated_amount': flt(d.allocated_amount),
- 'precision': d.precision('advance_amount'),
- 'exchange_rate': (self.conversion_rate
- if self.party_account_currency != self.company_currency else 1),
- 'grand_total': (self.base_grand_total
- if self.party_account_currency == self.company_currency else self.grand_total),
- 'outstanding_amount': self.outstanding_amount,
- 'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'),
- 'exchange_gain_loss': flt(d.get('exchange_gain_loss'))
- })
+ args = frappe._dict(
+ {
+ "voucher_type": d.reference_type,
+ "voucher_no": d.reference_name,
+ "voucher_detail_no": d.reference_row,
+ "against_voucher_type": self.doctype,
+ "against_voucher": self.name,
+ "account": party_account,
+ "party_type": party_type,
+ "party": party,
+ "is_advance": "Yes",
+ "dr_or_cr": dr_or_cr,
+ "unadjusted_amount": flt(d.advance_amount),
+ "allocated_amount": flt(d.allocated_amount),
+ "precision": d.precision("advance_amount"),
+ "exchange_rate": (
+ self.conversion_rate if self.party_account_currency != self.company_currency else 1
+ ),
+ "grand_total": (
+ self.base_grand_total
+ if self.party_account_currency == self.company_currency
+ else self.grand_total
+ ),
+ "outstanding_amount": self.outstanding_amount,
+ "difference_account": frappe.db.get_value(
+ "Company", self.company, "exchange_gain_loss_account"
+ ),
+ "exchange_gain_loss": flt(d.get("exchange_gain_loss")),
+ }
+ )
lst.append(args)
if lst:
from erpnext.accounts.utils import reconcile_against_document
+
reconcile_against_document(lst)
def on_cancel(self):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
- if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
+ if frappe.db.get_single_value("Accounts Settings", "unlink_payment_on_cancellation_of_invoice"):
unlink_ref_doc_from_payment_entries(self)
elif self.doctype in ["Sales Order", "Purchase Order"]:
- if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
+ if frappe.db.get_single_value(
+ "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order"
+ ):
unlink_ref_doc_from_payment_entries(self)
if self.doctype == "Sales Order":
@@ -848,33 +996,32 @@ class AccountsController(TransactionBase):
for item in self.items:
so_items.append(item.name)
- linked_po = list(set(frappe.get_all(
- 'Purchase Order Item',
- filters = {
- 'sales_order': self.name,
- 'sales_order_item': ['in', so_items],
- 'docstatus': ['<', 2]
- },
- pluck='parent'
- )))
+ linked_po = list(
+ set(
+ frappe.get_all(
+ "Purchase Order Item",
+ filters={
+ "sales_order": self.name,
+ "sales_order_item": ["in", so_items],
+ "docstatus": ["<", 2],
+ },
+ pluck="parent",
+ )
+ )
+ )
if linked_po:
frappe.db.set_value(
- 'Purchase Order Item', {
- 'sales_order': self.name,
- 'sales_order_item': ['in', so_items],
- 'docstatus': ['<', 2]
- },{
- 'sales_order': None,
- 'sales_order_item': None
- }
+ "Purchase Order Item",
+ {"sales_order": self.name, "sales_order_item": ["in", so_items], "docstatus": ["<", 2]},
+ {"sales_order": None, "sales_order_item": None},
)
frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
def get_tax_map(self):
tax_map = {}
- for tax in self.get('taxes'):
+ for tax in self.get("taxes"):
tax_map.setdefault(tax.account_head, 0.0)
tax_map[tax.account_head] += tax.tax_amount
@@ -884,7 +1031,11 @@ class AccountsController(TransactionBase):
amount = item.net_amount
base_amount = item.base_net_amount
- if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'):
+ if (
+ enable_discount_accounting
+ and self.get("discount_amount")
+ and self.get("additional_discount_account")
+ ):
amount = item.amount
base_amount = item.base_amount
@@ -894,15 +1045,21 @@ class AccountsController(TransactionBase):
amount = tax.tax_amount_after_discount_amount
base_amount = tax.base_tax_amount_after_discount_amount
- if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \
- and self.get('apply_discount_on') == 'Grand Total':
+ if (
+ enable_discount_accounting
+ and self.get("discount_amount")
+ and self.get("additional_discount_account")
+ and self.get("apply_discount_on") == "Grand Total"
+ ):
amount = tax.tax_amount
base_amount = tax.base_tax_amount
return amount, base_amount
def make_discount_gl_entries(self, gl_entries):
- enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+ enable_discount_accounting = cint(
+ frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+ )
if enable_discount_accounting:
if self.doctype == "Purchase Invoice":
@@ -916,61 +1073,81 @@ class AccountsController(TransactionBase):
supplier_or_customer = self.customer
for item in self.get("items"):
- if item.get('discount_amount') and item.get('discount_account'):
+ if item.get("discount_amount") and item.get("discount_account"):
discount_amount = item.discount_amount * item.qty
if self.doctype == "Purchase Invoice":
- income_or_expense_account = (item.expense_account
+ income_or_expense_account = (
+ item.expense_account
if (not item.enable_deferred_expense or self.is_return)
- else item.deferred_expense_account)
+ else item.deferred_expense_account
+ )
else:
- income_or_expense_account = (item.income_account
+ income_or_expense_account = (
+ item.income_account
if (not item.enable_deferred_revenue or self.is_return)
- else item.deferred_revenue_account)
+ else item.deferred_revenue_account
+ )
account_currency = get_account_currency(item.discount_account)
gl_entries.append(
- self.get_gl_dict({
- "account": item.discount_account,
- "against": supplier_or_customer,
- dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
- dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
- item.precision('discount_amount')),
- "cost_center": item.cost_center,
- "project": item.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": item.discount_account,
+ "against": supplier_or_customer,
+ dr_or_cr: flt(discount_amount, item.precision("discount_amount")),
+ dr_or_cr
+ + "_in_account_currency": flt(
+ discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
+ ),
+ "cost_center": item.cost_center,
+ "project": item.project,
+ },
+ account_currency,
+ item=item,
+ )
)
account_currency = get_account_currency(income_or_expense_account)
gl_entries.append(
- self.get_gl_dict({
- "account": income_or_expense_account,
- "against": supplier_or_customer,
- rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
- rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
- item.precision('discount_amount')),
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item)
+ self.get_gl_dict(
+ {
+ "account": income_or_expense_account,
+ "against": supplier_or_customer,
+ rev_dr_cr: flt(discount_amount, item.precision("discount_amount")),
+ rev_dr_cr
+ + "_in_account_currency": flt(
+ discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
+ ),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project,
+ },
+ account_currency,
+ item=item,
+ )
)
- if self.get('discount_amount') and self.get('additional_discount_account'):
+ if self.get("discount_amount") and self.get("additional_discount_account"):
gl_entries.append(
- self.get_gl_dict({
- "account": self.additional_discount_account,
- "against": supplier_or_customer,
- dr_or_cr: self.discount_amount,
- "cost_center": self.cost_center
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.additional_discount_account,
+ "against": supplier_or_customer,
+ dr_or_cr: self.discount_amount,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
)
-
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_allowance_for
item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None
- role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
+ role_allowed_to_over_bill = frappe.db.get_single_value(
+ "Accounts Settings", "role_allowed_to_over_bill"
+ )
user_roles = frappe.get_roles()
total_overbilled_amt = 0.0
@@ -979,21 +1156,29 @@ class AccountsController(TransactionBase):
if not item.get(item_ref_dn):
continue
- ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
- item.get(item_ref_dn), based_on), self.precision(based_on, item))
+ ref_amt = flt(
+ frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on),
+ self.precision(based_on, item),
+ )
if not ref_amt:
frappe.msgprint(
- _("System will not check overbilling since amount for Item {0} in {1} is zero")
- .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
+ _("System will not check overbilling since amount for Item {0} in {1} is zero").format(
+ item.item_code, ref_dt
+ ),
+ title=_("Warning"),
+ indicator="orange",
+ )
continue
already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
- total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
- self.precision(based_on, item))
+ total_billed_amt = flt(
+ flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)
+ )
- allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
- get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
+ allowance, item_allowance, global_qty_allowance, global_amount_allowance = get_allowance_for(
+ item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount"
+ )
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
@@ -1008,20 +1193,29 @@ class AccountsController(TransactionBase):
if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
if self.doctype != "Purchase Invoice":
self.throw_overbill_exception(item, max_allowed_amt)
- elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
+ elif not cint(
+ frappe.db.get_single_value(
+ "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
+ )
+ ):
self.throw_overbill_exception(item, max_allowed_amt)
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
- frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
- .format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True)
+ frappe.msgprint(
+ _("Overbilling of {} ignored because you have {} role.").format(
+ total_overbilled_amt, role_allowed_to_over_bill
+ ),
+ indicator="orange",
+ alert=True,
+ )
def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
- '''
- Returns Sum of Amount of
- Sales/Purchase Invoice Items
- that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`)
- that are submitted OR not submitted but are under current invoice
- '''
+ """
+ Returns Sum of Amount of
+ Sales/Purchase Invoice Items
+ that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`)
+ that are submitted OR not submitted but are under current invoice
+ """
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Sum
@@ -1033,41 +1227,57 @@ class AccountsController(TransactionBase):
result = (
frappe.qb.from_(item_doctype)
.select(Sum(based_on_field))
+ .where(join_field == item.get(item_ref_dn))
.where(
- join_field == item.get(item_ref_dn)
- ).where(
- Criterion.any([ # select all items from other invoices OR current invoices
- Criterion.all([ # for selecting items from other invoices
- item_doctype.docstatus == 1,
- item_doctype.parent != self.name
- ]),
- Criterion.all([ # for selecting items from current invoice, that are linked to same reference
- item_doctype.docstatus == 0,
- item_doctype.parent == self.name,
- item_doctype.name != item.name
- ])
- ])
+ Criterion.any(
+ [ # select all items from other invoices OR current invoices
+ Criterion.all(
+ [ # for selecting items from other invoices
+ item_doctype.docstatus == 1,
+ item_doctype.parent != self.name,
+ ]
+ ),
+ Criterion.all(
+ [ # for selecting items from current invoice, that are linked to same reference
+ item_doctype.docstatus == 0,
+ item_doctype.parent == self.name,
+ item_doctype.name != item.name,
+ ]
+ ),
+ ]
+ )
)
).run()
return result[0][0] if result else 0
def throw_overbill_exception(self, item, max_allowed_amt):
- frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
- .format(item.item_code, item.idx, max_allowed_amt))
+ frappe.throw(
+ _(
+ "Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings"
+ ).format(item.item_code, item.idx, max_allowed_amt)
+ )
def get_company_default(self, fieldname, ignore_validation=False):
from erpnext.accounts.utils import get_company_default
+
return get_company_default(self.company, fieldname, ignore_validation=ignore_validation)
def get_stock_items(self):
stock_items = []
item_codes = list(set(item.item_code for item in self.get("items")))
if item_codes:
- stock_items = [r[0] for r in frappe.db.sql("""
+ stock_items = [
+ r[0]
+ for r in frappe.db.sql(
+ """
select name from `tabItem`
where name in (%s) and is_stock_item=1
- """ % (", ".join(["%s"] * len(item_codes)),), item_codes)]
+ """
+ % (", ".join(["%s"] * len(item_codes)),),
+ item_codes,
+ )
+ ]
return stock_items
@@ -1081,7 +1291,8 @@ class AccountsController(TransactionBase):
rev_dr_or_cr = "credit_in_account_currency"
party = self.supplier
- advance = frappe.db.sql("""
+ advance = frappe.db.sql(
+ """
select
account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
from
@@ -1089,17 +1300,22 @@ class AccountsController(TransactionBase):
where
against_voucher_type = %s and against_voucher = %s and party=%s
and docstatus = 1
- """.format(dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr), (self.doctype, self.name, party), as_dict=1) #nosec
+ """.format(
+ dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr
+ ),
+ (self.doctype, self.name, party),
+ as_dict=1,
+ ) # nosec
if advance:
advance = advance[0]
advance_paid = flt(advance.amount, self.precision("advance_paid"))
- formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"),
- currency=advance.account_currency)
+ formatted_advance_paid = fmt_money(
+ advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency
+ )
- frappe.db.set_value(self.doctype, self.name, "party_account_currency",
- advance.account_currency)
+ frappe.db.set_value(self.doctype, self.name, "party_account_currency", advance.account_currency)
if advance.account_currency == self.currency:
order_total = self.get("rounded_total") or self.grand_total
@@ -1108,34 +1324,50 @@ class AccountsController(TransactionBase):
order_total = self.get("base_rounded_total") or self.base_grand_total
precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total"
- formatted_order_total = fmt_money(order_total, precision=self.precision(precision),
- currency=advance.account_currency)
+ formatted_order_total = fmt_money(
+ order_total, precision=self.precision(precision), currency=advance.account_currency
+ )
if self.currency == self.company_currency and advance_paid > order_total:
- frappe.throw(_("Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})")
- .format(formatted_advance_paid, self.name, formatted_order_total))
+ frappe.throw(
+ _(
+ "Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})"
+ ).format(formatted_advance_paid, self.name, formatted_order_total)
+ )
frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid)
@property
def company_abbr(self):
if not hasattr(self, "_abbr"):
- self._abbr = frappe.db.get_value('Company', self.company, "abbr")
+ self._abbr = frappe.db.get_value("Company", self.company, "abbr")
return self._abbr
def raise_missing_debit_credit_account_error(self, party_type, party):
"""Raise an error if debit to/credit to account does not exist."""
- db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
+ db_or_cr = (
+ frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
+ )
rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
link_to_party = frappe.utils.get_link_to_form(party_type, party)
link_to_company = frappe.utils.get_link_to_form("Company", self.company)
- message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
+ message = _("{0} Account not found against Customer {1}.").format(
+ db_or_cr, frappe.bold(party) or ""
+ )
message += "
" + _("Please set one of the following:") + "
"
- message += "
"
+ message += (
+ "
"
+ )
frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
@@ -1146,10 +1378,15 @@ class AccountsController(TransactionBase):
def get_party(self):
party_type = None
if self.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
- party_type = 'Customer'
+ party_type = "Customer"
- elif self.doctype in ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"):
- party_type = 'Supplier'
+ elif self.doctype in (
+ "Supplier Quotation",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ ):
+ party_type = "Supplier"
elif self.meta.get_field("customer"):
party_type = "Customer"
@@ -1167,11 +1404,17 @@ class AccountsController(TransactionBase):
if party_type and party:
party_account_currency = get_party_account_currency(party_type, party, self.company)
- if (party_account_currency
- and party_account_currency != self.company_currency
- and self.currency != party_account_currency):
- frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
- .format(party_type, party, party_account_currency), InvalidCurrency)
+ if (
+ party_account_currency
+ and party_account_currency != self.company_currency
+ and self.currency != party_account_currency
+ ):
+ frappe.throw(
+ _("Accounting Entry for {0}: {1} can only be made in currency: {2}").format(
+ party_type, party, party_account_currency
+ ),
+ InvalidCurrency,
+ )
# Note: not validating with gle account because we don't have the account
# at quotation / sales order level and we shouldn't stop someone
@@ -1182,15 +1425,21 @@ class AccountsController(TransactionBase):
for adv in self.advances:
consider_for_total_advance = True
if adv.reference_name == linked_doc_name:
- frappe.db.sql("""delete from `tab{0} Advance`
- where name = %s""".format(self.doctype), adv.name)
+ frappe.db.sql(
+ """delete from `tab{0} Advance`
+ where name = %s""".format(
+ self.doctype
+ ),
+ adv.name,
+ )
consider_for_total_advance = False
if consider_for_total_advance:
total_allocated_amount += flt(adv.allocated_amount, adv.precision("allocated_amount"))
- frappe.db.set_value(self.doctype, self.name, "total_advance",
- total_allocated_amount, update_modified=False)
+ frappe.db.set_value(
+ self.doctype, self.name, "total_advance", total_allocated_amount, update_modified=False
+ )
def group_similar_items(self):
group_item_qty = {}
@@ -1222,11 +1471,11 @@ class AccountsController(TransactionBase):
self.remove(item)
def set_payment_schedule(self):
- if self.doctype == 'Sales Invoice' and self.is_pos:
- self.payment_terms_template = ''
+ if self.doctype == "Sales Invoice" and self.is_pos:
+ self.payment_terms_template = ""
return
- party_account_currency = self.get('party_account_currency')
+ party_account_currency = self.get("party_account_currency")
if not party_account_currency:
party_type, party = self.get_party()
@@ -1244,47 +1493,68 @@ class AccountsController(TransactionBase):
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
po_or_so, doctype, fieldname = self.get_order_details()
- automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
+ automatically_fetch_payment_terms = cint(
+ frappe.db.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
+ )
if self.get("total_advance"):
if party_account_currency == self.company_currency:
base_grand_total -= self.get("total_advance")
- grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
+ grand_total = flt(
+ base_grand_total / self.get("conversion_rate"), self.precision("grand_total")
+ )
else:
grand_total -= self.get("total_advance")
- base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
+ base_grand_total = flt(
+ grand_total * self.get("conversion_rate"), self.precision("base_grand_total")
+ )
if not self.get("payment_schedule"):
- if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \
- and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype):
+ if (
+ self.doctype in ["Sales Invoice", "Purchase Invoice"]
+ and automatically_fetch_payment_terms
+ and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
+ ):
self.fetch_payment_terms_from_order(po_or_so, doctype)
- if self.get('payment_terms_template'):
+ if self.get("payment_terms_template"):
self.ignore_default_payment_terms_template = 1
elif self.get("payment_terms_template"):
- data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
+ data = get_payment_terms(
+ self.payment_terms_template, posting_date, grand_total, base_grand_total
+ )
for item in data:
self.append("payment_schedule", item)
elif self.doctype not in ["Purchase Receipt"]:
- data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
+ data = dict(
+ due_date=due_date,
+ invoice_portion=100,
+ payment_amount=grand_total,
+ base_payment_amount=base_grand_total,
+ )
self.append("payment_schedule", data)
for d in self.get("payment_schedule"):
if d.invoice_portion:
- d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
- d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
+ d.payment_amount = flt(
+ grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount")
+ )
+ d.base_payment_amount = flt(
+ base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount")
+ )
d.outstanding = d.payment_amount
elif not d.invoice_portion:
- d.base_payment_amount = flt(d.payment_amount * self.get("conversion_rate"), d.precision('base_payment_amount'))
-
+ d.base_payment_amount = flt(
+ d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
+ )
def get_order_details(self):
if self.doctype == "Sales Invoice":
- po_or_so = self.get('items')[0].get('sales_order')
+ po_or_so = self.get("items")[0].get("sales_order")
po_or_so_doctype = "Sales Order"
po_or_so_doctype_name = "sales_order"
else:
- po_or_so = self.get('items')[0].get('purchase_order')
+ po_or_so = self.get("items")[0].get("purchase_order")
po_or_so_doctype = "Purchase Order"
po_or_so_doctype_name = "purchase_order"
@@ -1300,21 +1570,21 @@ class AccountsController(TransactionBase):
return False
def all_items_have_same_po_or_so(self, po_or_so, fieldname):
- for item in self.get('items'):
+ for item in self.get("items"):
if item.get(fieldname) != po_or_so:
return False
return True
def linked_order_has_payment_terms_template(self, po_or_so, doctype):
- return frappe.get_value(doctype, po_or_so, 'payment_terms_template')
+ return frappe.get_value(doctype, po_or_so, "payment_terms_template")
def linked_order_has_payment_schedule(self, po_or_so):
- return frappe.get_all('Payment Schedule', filters={'parent': po_or_so})
+ return frappe.get_all("Payment Schedule", filters={"parent": po_or_so})
def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
"""
- Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
+ Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
"""
po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so)
@@ -1323,19 +1593,19 @@ class AccountsController(TransactionBase):
for schedule in po_or_so.payment_schedule:
payment_schedule = {
- 'payment_term': schedule.payment_term,
- 'due_date': schedule.due_date,
- 'invoice_portion': schedule.invoice_portion,
- 'mode_of_payment': schedule.mode_of_payment,
- 'description': schedule.description
+ "payment_term": schedule.payment_term,
+ "due_date": schedule.due_date,
+ "invoice_portion": schedule.invoice_portion,
+ "mode_of_payment": schedule.mode_of_payment,
+ "description": schedule.description,
}
- if schedule.discount_type == 'Percentage':
- payment_schedule['discount_type'] = schedule.discount_type
- payment_schedule['discount'] = schedule.discount
+ if schedule.discount_type == "Percentage":
+ payment_schedule["discount_type"] = schedule.discount_type
+ payment_schedule["discount"] = schedule.discount
if not schedule.invoice_portion:
- payment_schedule['payment_amount'] = schedule.payment_amount
+ payment_schedule["payment_amount"] = schedule.payment_amount
self.append("payment_schedule", payment_schedule)
@@ -1348,23 +1618,29 @@ class AccountsController(TransactionBase):
dates = []
li = []
- if self.doctype == 'Sales Invoice' and self.is_pos: return
+ if self.doctype == "Sales Invoice" and self.is_pos:
+ return
for d in self.get("payment_schedule"):
if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
- frappe.throw(_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(d.idx))
+ frappe.throw(
+ _("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(d.idx)
+ )
elif d.due_date in dates:
li.append(_("{0} in row {1}").format(d.due_date, d.idx))
dates.append(d.due_date)
if li:
- duplicates = '
' + '
'.join(li)
- frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates))
+ duplicates = "
" + "
".join(li)
+ frappe.throw(
+ _("Rows with duplicate due dates in other rows were found: {0}").format(duplicates)
+ )
def validate_payment_schedule_amount(self):
- if self.doctype == 'Sales Invoice' and self.is_pos: return
+ if self.doctype == "Sales Invoice" and self.is_pos:
+ return
- party_account_currency = self.get('party_account_currency')
+ party_account_currency = self.get("party_account_currency")
if not party_account_currency:
party_type, party = self.get_party()
@@ -1388,14 +1664,25 @@ class AccountsController(TransactionBase):
if self.get("total_advance"):
if party_account_currency == self.company_currency:
base_grand_total -= self.get("total_advance")
- grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
+ grand_total = flt(
+ base_grand_total / self.get("conversion_rate"), self.precision("grand_total")
+ )
else:
grand_total -= self.get("total_advance")
- base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
+ base_grand_total = flt(
+ grand_total * self.get("conversion_rate"), self.precision("base_grand_total")
+ )
- if flt(total, self.precision("grand_total")) - flt(grand_total, self.precision("grand_total")) > 0.1 or \
- flt(base_total, self.precision("base_grand_total")) - flt(base_grand_total, self.precision("base_grand_total")) > 0.1:
- frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
+ if (
+ flt(total, self.precision("grand_total")) - flt(grand_total, self.precision("grand_total"))
+ > 0.1
+ or flt(base_total, self.precision("base_grand_total"))
+ - flt(base_grand_total, self.precision("base_grand_total"))
+ > 0.1
+ ):
+ frappe.throw(
+ _("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")
+ )
def is_rounded_total_disabled(self):
if self.meta.get_field("disable_rounded_total"):
@@ -1405,30 +1692,33 @@ class AccountsController(TransactionBase):
def set_inter_company_account(self):
"""
- Set intercompany account for inter warehouse transactions
- This account will be used in case billing company and internal customer's
- representation company is same
+ Set intercompany account for inter warehouse transactions
+ This account will be used in case billing company and internal customer's
+ representation company is same
"""
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
- unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
+ unrealized_profit_loss_account = frappe.db.get_value(
+ "Company", self.company, "unrealized_profit_loss_account"
+ )
if not unrealized_profit_loss_account:
- msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
- frappe.bold(self.company))
+ msg = _(
+ "Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}"
+ ).format(frappe.bold(self.company))
frappe.throw(msg)
self.unrealized_profit_loss_account = unrealized_profit_loss_account
def is_internal_transfer(self):
"""
- It will an internal transfer if its an internal customer and representation
- company is same as billing company
+ It will an internal transfer if its an internal customer and representation
+ company is same as billing company
"""
- if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
- internal_party_field = 'is_internal_customer'
- elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
- internal_party_field = 'is_internal_supplier'
+ if self.doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
+ internal_party_field = "is_internal_customer"
+ elif self.doctype in ("Purchase Invoice", "Purchase Receipt", "Purchase Order"):
+ internal_party_field = "is_internal_supplier"
if self.get(internal_party_field) and (self.represents_company == self.company):
return True
@@ -1436,11 +1726,11 @@ class AccountsController(TransactionBase):
return False
def process_common_party_accounting(self):
- is_invoice = self.doctype in ['Sales Invoice', 'Purchase Invoice']
+ is_invoice = self.doctype in ["Sales Invoice", "Purchase Invoice"]
if not is_invoice:
return
- if frappe.db.get_single_value('Accounts Settings', 'enable_common_party_accounting'):
+ if frappe.db.get_single_value("Accounts Settings", "enable_common_party_accounting"):
party_link = self.get_common_party_link()
if party_link and self.outstanding_amount:
self.create_advance_and_reconcile(party_link)
@@ -1448,10 +1738,10 @@ class AccountsController(TransactionBase):
def get_common_party_link(self):
party_type, party = self.get_party()
return frappe.db.get_value(
- doctype='Party Link',
- filters={'secondary_role': party_type, 'secondary_party': party},
- fieldname=['primary_role', 'primary_party'],
- as_dict=True
+ doctype="Party Link",
+ filters={"secondary_role": party_type, "secondary_party": party},
+ fieldname=["primary_role", "primary_party"],
+ as_dict=True,
)
def create_advance_and_reconcile(self, party_link):
@@ -1461,11 +1751,11 @@ class AccountsController(TransactionBase):
primary_account = get_party_account(primary_party_type, primary_party, self.company)
secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
- jv = frappe.new_doc('Journal Entry')
- jv.voucher_type = 'Journal Entry'
+ jv = frappe.new_doc("Journal Entry")
+ jv.voucher_type = "Journal Entry"
jv.posting_date = self.posting_date
jv.company = self.company
- jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name)
+ jv.remark = "Adjustment for {} {}".format(self.doctype, self.name)
reconcilation_entry = frappe._dict()
advance_entry = frappe._dict()
@@ -1481,21 +1771,22 @@ class AccountsController(TransactionBase):
advance_entry.party_type = primary_party_type
advance_entry.party = primary_party
advance_entry.cost_center = self.cost_center
- advance_entry.is_advance = 'Yes'
+ advance_entry.is_advance = "Yes"
- if self.doctype == 'Sales Invoice':
+ if self.doctype == "Sales Invoice":
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
advance_entry.debit_in_account_currency = self.outstanding_amount
else:
advance_entry.credit_in_account_currency = self.outstanding_amount
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
- jv.append('accounts', reconcilation_entry)
- jv.append('accounts', advance_entry)
+ jv.append("accounts", reconcilation_entry)
+ jv.append("accounts", advance_entry)
jv.save()
jv.submit()
+
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
@@ -1503,7 +1794,8 @@ def get_tax_rate(account_head):
@frappe.whitelist()
def get_default_taxes_and_charges(master_doctype, tax_template=None, company=None):
- if not company: return {}
+ if not company:
+ return {}
if tax_template and company:
tax_template_company = frappe.db.get_value(master_doctype, tax_template, "company")
@@ -1513,8 +1805,8 @@ def get_default_taxes_and_charges(master_doctype, tax_template=None, company=Non
default_tax = frappe.db.get_value(master_doctype, {"is_default": 1, "company": company})
return {
- 'taxes_and_charges': default_tax,
- 'taxes': get_taxes_and_charges(master_doctype, default_tax)
+ "taxes_and_charges": default_tax,
+ "taxes": get_taxes_and_charges(master_doctype, default_tax),
}
@@ -1523,6 +1815,7 @@ def get_taxes_and_charges(master_doctype, master_name):
if not master_name:
return
from frappe.model import default_fields
+
tax_master = frappe.get_doc(master_doctype, master_name)
taxes_and_charges = []
@@ -1541,105 +1834,157 @@ def get_taxes_and_charges(master_doctype, master_name):
def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company):
"""common validation for currency and price list currency"""
- company_currency = frappe.get_cached_value('Company', company, "default_currency")
+ company_currency = frappe.get_cached_value("Company", company, "default_currency")
if not conversion_rate:
throw(
- _("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.")
- .format(conversion_rate_label, currency, company_currency)
+ _("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format(
+ conversion_rate_label, currency, company_currency
+ )
)
def validate_taxes_and_charges(tax):
- if tax.charge_type in ['Actual', 'On Net Total', 'On Paid Amount'] and tax.row_id:
- frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"))
- elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']:
+ if tax.charge_type in ["Actual", "On Net Total", "On Paid Amount"] and tax.row_id:
+ frappe.throw(
+ _("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'")
+ )
+ elif tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
if cint(tax.idx) == 1:
frappe.throw(
- _("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
+ _(
+ "Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"
+ )
+ )
elif not tax.row_id:
- frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}").format(tax.idx, _(tax.doctype)))
+ frappe.throw(
+ _("Please specify a valid Row ID for row {0} in table {1}").format(tax.idx, _(tax.doctype))
+ )
elif tax.row_id and cint(tax.row_id) >= cint(tax.idx):
- frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type"))
+ frappe.throw(
+ _("Cannot refer row number greater than or equal to current row number for this Charge type")
+ )
if tax.charge_type == "Actual":
tax.rate = None
-def validate_account_head(idx, account, company, context=''):
- account_company = frappe.get_cached_value('Account', account, 'company')
+def validate_account_head(idx, account, company, context=""):
+ account_company = frappe.get_cached_value("Account", account, "company")
if account_company != company:
- frappe.throw(_('Row {0}: {3} Account {1} does not belong to Company {2}')
- .format(idx, frappe.bold(account), frappe.bold(company), context), title=_('Invalid Account'))
+ frappe.throw(
+ _("Row {0}: {3} Account {1} does not belong to Company {2}").format(
+ idx, frappe.bold(account), frappe.bold(company), context
+ ),
+ title=_("Invalid Account"),
+ )
def validate_cost_center(tax, doc):
if not tax.cost_center:
return
- company = frappe.get_cached_value('Cost Center',
- tax.cost_center, 'company')
+ company = frappe.get_cached_value("Cost Center", tax.cost_center, "company")
if company != doc.company:
- frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}')
- .format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center'))
+ frappe.throw(
+ _("Row {0}: Cost Center {1} does not belong to Company {2}").format(
+ tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)
+ ),
+ title=_("Invalid Cost Center"),
+ )
def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range):
- throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
+ throw(
+ _("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(
+ tax.idx, row_range
+ )
+ )
if cint(getattr(tax, "included_in_print_rate", None)):
if tax.charge_type == "Actual":
# inclusive tax cannot be of type Actual
- throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
- elif tax.charge_type == "On Previous Row Amount" and \
- not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate):
+ throw(
+ _("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(
+ tax.idx
+ )
+ )
+ elif tax.charge_type == "On Previous Row Amount" and not cint(
+ doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate
+ ):
# referred row should also be inclusive
_on_previous_row_error(tax.row_id)
- elif tax.charge_type == "On Previous Row Total" and \
- not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]):
+ elif tax.charge_type == "On Previous Row Total" and not all(
+ [cint(t.included_in_print_rate) for t in doc.get("taxes")[: cint(tax.row_id) - 1]]
+ ):
# all rows about the referred tax should be inclusive
_on_previous_row_error("1 - %d" % (tax.row_id,))
elif tax.get("category") == "Valuation":
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
-def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):
+def set_balance_in_account_currency(
+ gl_dict, account_currency=None, conversion_rate=None, company_currency=None
+):
if (not conversion_rate) and (account_currency != company_currency):
- frappe.throw(_("Account: {0} with currency: {1} can not be selected")
- .format(gl_dict.account, account_currency))
+ frappe.throw(
+ _("Account: {0} with currency: {1} can not be selected").format(
+ gl_dict.account, account_currency
+ )
+ )
- gl_dict["account_currency"] = company_currency if account_currency == company_currency \
- else account_currency
+ gl_dict["account_currency"] = (
+ company_currency if account_currency == company_currency else account_currency
+ )
# set debit/credit in account currency if not provided
if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):
- gl_dict.debit_in_account_currency = gl_dict.debit if account_currency == company_currency \
+ gl_dict.debit_in_account_currency = (
+ gl_dict.debit
+ if account_currency == company_currency
else flt(gl_dict.debit / conversion_rate, 2)
+ )
if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency):
- gl_dict.credit_in_account_currency = gl_dict.credit if account_currency == company_currency \
+ gl_dict.credit_in_account_currency = (
+ gl_dict.credit
+ if account_currency == company_currency
else flt(gl_dict.credit / conversion_rate, 2)
+ )
-def get_advance_journal_entries(party_type, party, party_account, amount_field,
- order_doctype, order_list, include_unallocated=True):
- dr_or_cr = "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
+def get_advance_journal_entries(
+ party_type,
+ party,
+ party_account,
+ amount_field,
+ order_doctype,
+ order_list,
+ include_unallocated=True,
+):
+ dr_or_cr = (
+ "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
+ )
conditions = []
if include_unallocated:
conditions.append("ifnull(t2.reference_name, '')=''")
if order_list:
- order_condition = ', '.join(['%s'] * len(order_list))
- conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))" \
- .format(order_doctype, order_condition))
+ order_condition = ", ".join(["%s"] * len(order_list))
+ conditions.append(
+ " (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))".format(
+ order_doctype, order_condition
+ )
+ )
reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
- journal_entries = frappe.db.sql("""
+ journal_entries = frappe.db.sql(
+ """
select
"Journal Entry" as reference_type, t1.name as reference_name,
t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
@@ -1651,31 +1996,50 @@ def get_advance_journal_entries(party_type, party, party_account, amount_field,
and t2.party_type = %s and t2.party = %s
and t2.is_advance = 'Yes' and t1.docstatus = 1
and {1} > 0 {2}
- order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition),
- [party_account, party_type, party] + order_list, as_dict=1)
+ order by t1.posting_date""".format(
+ amount_field, dr_or_cr, reference_condition
+ ),
+ [party_account, party_type, party] + order_list,
+ as_dict=1,
+ )
return list(journal_entries)
-def get_advance_payment_entries(party_type, party, party_account, order_doctype,
- order_list=None, include_unallocated=True, against_all_orders=False, limit=None, condition=None):
+def get_advance_payment_entries(
+ party_type,
+ party,
+ party_account,
+ order_doctype,
+ order_list=None,
+ include_unallocated=True,
+ against_all_orders=False,
+ limit=None,
+ condition=None,
+):
party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
- currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
+ currency_field = (
+ "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
+ )
payment_type = "Receive" if party_type == "Customer" else "Pay"
- exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
+ exchange_rate_field = (
+ "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
+ )
payment_entries_against_order, unallocated_payment_entries = [], []
limit_cond = "limit %s" % limit if limit else ""
if order_list or against_all_orders:
if order_list:
- reference_condition = " and t2.reference_name in ({0})" \
- .format(', '.join(['%s'] * len(order_list)))
+ reference_condition = " and t2.reference_name in ({0})".format(
+ ", ".join(["%s"] * len(order_list))
+ )
else:
reference_condition = ""
order_list = []
- payment_entries_against_order = frappe.db.sql("""
+ payment_entries_against_order = frappe.db.sql(
+ """
select
"Payment Entry" as reference_type, t1.name as reference_name,
t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
@@ -1687,12 +2051,16 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
and t2.reference_doctype = %s {2}
order by t1.posting_date {3}
- """.format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field),
- [party_account, payment_type, party_type, party,
- order_doctype] + order_list, as_dict=1)
+ """.format(
+ currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field
+ ),
+ [party_account, payment_type, party_type, party, order_doctype] + order_list,
+ as_dict=1,
+ )
if include_unallocated:
- unallocated_payment_entries = frappe.db.sql("""
+ unallocated_payment_entries = frappe.db.sql(
+ """
select "Payment Entry" as reference_type, name as reference_name, posting_date,
remarks, unallocated_amount as amount, {2} as exchange_rate, {3} as currency
from `tabPayment Entry`
@@ -1700,11 +2068,16 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
{0} = %s and party_type = %s and party = %s and payment_type = %s
and docstatus = 1 and unallocated_amount > 0 {condition}
order by posting_date {1}
- """.format(party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or ""),
- (party_account, party_type, party, payment_type), as_dict=1)
+ """.format(
+ party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or ""
+ ),
+ (party_account, party_type, party, payment_type),
+ as_dict=1,
+ )
return list(payment_entries_against_order) + list(unallocated_payment_entries)
+
def update_invoice_status():
"""Updates status as Overdue for applicable invoices. Runs daily."""
today = getdate()
@@ -1722,10 +2095,7 @@ def update_invoice_status():
payable_amount = (
frappe.qb.from_(payment_schedule)
.select(Sum(payment_amount))
- .where(
- (payment_schedule.parent == invoice.name)
- & (payment_schedule.due_date < today)
- )
+ .where((payment_schedule.parent == invoice.name) & (payment_schedule.due_date < today))
)
total = (
@@ -1740,21 +2110,14 @@ def update_invoice_status():
.else_(invoice.base_rounded_total)
)
- total_amount = (
- frappe.qb.terms.Case()
- .when(consider_base_amount, base_total)
- .else_(total)
- )
+ total_amount = frappe.qb.terms.Case().when(consider_base_amount, base_total).else_(total)
is_overdue = total_amount - invoice.outstanding_amount < payable_amount
conditions = (
(invoice.docstatus == 1)
& (invoice.outstanding_amount > 0)
- & (
- invoice.status.like("Unpaid%")
- | invoice.status.like("Partly Paid%")
- )
+ & (invoice.status.like("Unpaid%") | invoice.status.like("Partly Paid%"))
& (
((invoice.is_pos & invoice.due_date < today) | is_overdue)
if doctype == "Sales Invoice"
@@ -1772,7 +2135,9 @@ def update_invoice_status():
@frappe.whitelist()
-def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
+def get_payment_terms(
+ terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None
+):
if not terms_template:
return
@@ -1780,14 +2145,18 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_
schedule = []
for d in terms_doc.get("terms"):
- term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date)
+ term_details = get_payment_term_details(
+ d, posting_date, grand_total, base_grand_total, bill_date
+ )
schedule.append(term_details)
return schedule
@frappe.whitelist()
-def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
+def get_payment_term_details(
+ term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None
+):
term_details = frappe._dict()
if isinstance(term, str):
term = frappe.get_doc("Payment Term", term)
@@ -1814,6 +2183,7 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, base_gra
return term_details
+
def get_due_date(term, posting_date=None, bill_date=None):
due_date = None
date = bill_date or posting_date
@@ -1825,6 +2195,7 @@ def get_due_date(term, posting_date=None, bill_date=None):
due_date = add_months(get_last_day(date), term.credit_months)
return due_date
+
def get_discount_date(term, posting_date=None, bill_date=None):
discount_validity = None
date = bill_date or posting_date
@@ -1836,64 +2207,73 @@ def get_discount_date(term, posting_date=None, bill_date=None):
discount_validity = add_months(get_last_day(date), term.discount_validity)
return discount_validity
+
def get_supplier_block_status(party_name):
"""
Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of
a `Supplier`
"""
- supplier = frappe.get_doc('Supplier', party_name)
+ supplier = frappe.get_doc("Supplier", party_name)
info = {
- 'on_hold': supplier.on_hold,
- 'release_date': supplier.release_date,
- 'hold_type': supplier.hold_type
+ "on_hold": supplier.on_hold,
+ "release_date": supplier.release_date,
+ "hold_type": supplier.hold_type,
}
return info
+
def set_child_tax_template_and_map(item, child_item, parent_doc):
args = {
- 'item_code': item.item_code,
- 'posting_date': parent_doc.transaction_date,
- 'tax_category': parent_doc.get('tax_category'),
- 'company': parent_doc.get('company')
- }
+ "item_code": item.item_code,
+ "posting_date": parent_doc.transaction_date,
+ "tax_category": parent_doc.get("tax_category"),
+ "company": parent_doc.get("company"),
+ }
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
if child_item.get("item_tax_template"):
- child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
+ child_item.item_tax_rate = get_item_tax_map(
+ parent_doc.get("company"), child_item.item_tax_template, as_json=True
+ )
+
def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
- add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
+ add_taxes_from_item_tax_template = frappe.db.get_single_value(
+ "Accounts Settings", "add_taxes_from_item_tax_template"
+ )
if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
tax_map = json.loads(child_item.get("item_tax_rate"))
for tax_type in tax_map:
tax_rate = flt(tax_map[tax_type])
- taxes = parent_doc.get('taxes') or []
+ taxes = parent_doc.get("taxes") or []
# add new row for tax head only if missing
found = any(tax.account_head == tax_type for tax in taxes)
if not found:
tax_row = parent_doc.append("taxes", {})
- tax_row.update({
- "description" : str(tax_type).split(' - ')[0],
- "charge_type" : "On Net Total",
- "account_head" : tax_type,
- "rate" : tax_rate
- })
+ tax_row.update(
+ {
+ "description": str(tax_type).split(" - ")[0],
+ "charge_type": "On Net Total",
+ "account_head": tax_type,
+ "rate": tax_rate,
+ }
+ )
if parent_doc.doctype == "Purchase Order":
- tax_row.update({
- "category" : "Total",
- "add_deduct_tax" : "Add"
- })
+ tax_row.update({"category": "Total", "add_deduct_tax": "Add"})
if db_insert:
tax_row.db_insert()
-def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
+
+def set_order_defaults(
+ parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item
+):
"""
Returns a Sales/Purchase Order Item child item containing the default values
"""
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
- item = frappe.get_doc("Item", trans_item.get('item_code'))
+ item = frappe.get_doc("Item", trans_item.get("item_code"))
for field in ("item_code", "item_name", "description", "item_group"):
child_item.update({field: item.get(field)})
@@ -1903,8 +2283,10 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child
child_item.stock_uom = item.stock_uom
child_item.uom = trans_item.get("uom") or item.stock_uom
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
- conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
- child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
+ conversion_factor = flt(
+ get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")
+ )
+ child_item.conversion_factor = flt(trans_item.get("conversion_factor")) or conversion_factor
if child_doctype == "Purchase Order Item":
# Initialized value will update in parent validation
@@ -1913,28 +2295,53 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child
if child_doctype == "Sales Order Item":
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
- frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
- .format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
+ frappe.throw(
+ _("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.").format(
+ frappe.bold("default warehouse"), frappe.bold(item.item_code)
+ )
+ )
set_child_tax_template_and_map(item, child_item, p_doc)
add_taxes_from_tax_template(child_item, p_doc)
return child_item
+
def validate_child_on_delete(row, parent):
"""Check if partially transacted item (row) is being deleted."""
if parent.doctype == "Sales Order":
if flt(row.delivered_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code))
+ frappe.throw(
+ _("Row #{0}: Cannot delete item {1} which has already been delivered").format(
+ row.idx, row.item_code
+ )
+ )
if flt(row.work_order_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code))
+ frappe.throw(
+ _("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(
+ row.idx, row.item_code
+ )
+ )
if flt(row.ordered_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code))
+ frappe.throw(
+ _("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(
+ row.idx, row.item_code
+ )
+ )
if parent.doctype == "Purchase Order" and flt(row.received_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code))
+ frappe.throw(
+ _("Row #{0}: Cannot delete item {1} which has already been received").format(
+ row.idx, row.item_code
+ )
+ )
if flt(row.billed_amt):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code))
+ frappe.throw(
+ _("Row #{0}: Cannot delete item {1} which has already been billed.").format(
+ row.idx, row.item_code
+ )
+ )
+
def update_bin_on_delete(row, doctype):
"""Update bin for deleted item (row)."""
@@ -1944,6 +2351,7 @@ def update_bin_on_delete(row, doctype):
get_reserved_qty,
update_bin_qty,
)
+
qty_dict = {}
if doctype == "Sales Order":
@@ -1957,6 +2365,7 @@ def update_bin_on_delete(row, doctype):
if row.warehouse:
update_bin_qty(row.item_code, row.warehouse, qty_dict)
+
def validate_and_delete_children(parent, data):
deleted_children = []
updated_item_names = [d.get("docname") for d in data]
@@ -1979,14 +2388,18 @@ def validate_and_delete_children(parent, data):
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
- def check_doc_permissions(doc, perm_type='create'):
+ def check_doc_permissions(doc, perm_type="create"):
try:
doc.check_permission(perm_type)
except frappe.PermissionError:
- actions = { 'create': 'add', 'write': 'update'}
+ actions = {"create": "add", "write": "update"}
- frappe.throw(_("You do not have permissions to {} items in a {}.")
- .format(actions[perm_type], parent_doctype), title=_("Insufficient Permissions"))
+ frappe.throw(
+ _("You do not have permissions to {} items in a {}.").format(
+ actions[perm_type], parent_doctype
+ ),
+ title=_("Insufficient Permissions"),
+ )
def validate_workflow_conditions(doc):
workflow = get_workflow_name(doc.doctype)
@@ -2006,13 +2419,17 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if not transitions:
frappe.throw(
- _("You are not allowed to update as per the conditions set in {} Workflow.").format(get_link_to_form("Workflow", workflow)),
- title=_("Insufficient Permissions")
+ _("You are not allowed to update as per the conditions set in {} Workflow.").format(
+ get_link_to_form("Workflow", workflow)
+ ),
+ title=_("Insufficient Permissions"),
)
def get_new_child_item(item_row):
child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
- return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
+ return set_order_defaults(
+ parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row
+ )
def validate_quantity(child_item, d):
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
@@ -2023,10 +2440,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
data = json.loads(trans_items)
- sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
+ sales_doctypes = ["Sales Order", "Sales Invoice", "Delivery Note", "Quotation"]
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
- check_doc_permissions(parent, 'write')
+ check_doc_permissions(parent, "write")
validate_and_delete_children(parent, data)
for d in data:
@@ -2038,28 +2455,38 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if not d.get("docname"):
new_child_flag = True
- check_doc_permissions(parent, 'create')
+ check_doc_permissions(parent, "create")
child_item = get_new_child_item(d)
else:
- check_doc_permissions(parent, 'write')
- child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
+ check_doc_permissions(parent, "write")
+ child_item = frappe.get_doc(parent_doctype + " Item", d.get("docname"))
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
- prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
+ prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(
+ d.get("conversion_factor")
+ )
prev_uom, new_uom = child_item.get("uom"), d.get("uom")
- if parent_doctype == 'Sales Order':
+ if parent_doctype == "Sales Order":
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
- elif parent_doctype == 'Purchase Order':
+ elif parent_doctype == "Purchase Order":
prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
rate_unchanged = prev_rate == new_rate
qty_unchanged = prev_qty == new_qty
uom_unchanged = prev_uom == new_uom
conversion_factor_unchanged = prev_con_fac == new_con_fac
- date_unchanged = prev_date == getdate(new_date) if prev_date and new_date else False # in case of delivery note etc
- if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
+ date_unchanged = (
+ prev_date == getdate(new_date) if prev_date and new_date else False
+ ) # in case of delivery note etc
+ if (
+ rate_unchanged
+ and qty_unchanged
+ and conversion_factor_unchanged
+ and uom_unchanged
+ and date_unchanged
+ ):
continue
validate_quantity(child_item, d)
@@ -2069,9 +2496,14 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
conv_fac_precision = child_item.precision("conversion_factor") or 2
qty_precision = child_item.precision("qty") or 2
- if flt(child_item.billed_amt, rate_precision) > flt(flt(d.get("rate"), rate_precision) * flt(d.get("qty"), qty_precision), rate_precision):
- frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
- .format(child_item.idx, child_item.item_code))
+ if flt(child_item.billed_amt, rate_precision) > flt(
+ flt(d.get("rate"), rate_precision) * flt(d.get("qty"), qty_precision), rate_precision
+ ):
+ frappe.throw(
+ _("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.").format(
+ child_item.idx, child_item.item_code
+ )
+ )
else:
child_item.rate = flt(d.get("rate"), rate_precision)
@@ -2079,18 +2511,22 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if child_item.stock_uom == child_item.uom:
child_item.conversion_factor = 1
else:
- child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision)
+ child_item.conversion_factor = flt(d.get("conversion_factor"), conv_fac_precision)
if d.get("uom"):
child_item.uom = d.get("uom")
- conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
- child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) or conversion_factor
+ conversion_factor = flt(
+ get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")
+ )
+ child_item.conversion_factor = (
+ flt(d.get("conversion_factor"), conv_fac_precision) or conversion_factor
+ )
- if d.get("delivery_date") and parent_doctype == 'Sales Order':
- child_item.delivery_date = d.get('delivery_date')
+ if d.get("delivery_date") and parent_doctype == "Sales Order":
+ child_item.delivery_date = d.get("delivery_date")
- if d.get("schedule_date") and parent_doctype == 'Purchase Order':
- child_item.schedule_date = d.get('schedule_date')
+ if d.get("schedule_date") and parent_doctype == "Purchase Order":
+ child_item.schedule_date = d.get("schedule_date")
if flt(child_item.price_list_rate):
if flt(child_item.rate) > flt(child_item.price_list_rate):
@@ -2100,14 +2536,16 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if parent_doctype in sales_doctypes:
child_item.margin_type = "Amount"
- child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
- child_item.precision("margin_rate_or_amount"))
+ child_item.margin_rate_or_amount = flt(
+ child_item.rate - child_item.price_list_rate, child_item.precision("margin_rate_or_amount")
+ )
child_item.rate_with_margin = child_item.rate
else:
- child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
- child_item.precision("discount_percentage"))
- child_item.discount_amount = flt(
- child_item.price_list_rate) - flt(child_item.rate)
+ child_item.discount_percentage = flt(
+ (1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
+ child_item.precision("discount_percentage"),
+ )
+ child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
if parent_doctype in sales_doctypes:
child_item.margin_type = ""
@@ -2130,11 +2568,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if parent_doctype == "Sales Order":
make_packing_list(parent)
parent.set_gross_profit()
- frappe.get_doc('Authorization Control').validate_approving_authority(parent.doctype,
- parent.company, parent.base_grand_total)
+ frappe.get_doc("Authorization Control").validate_approving_authority(
+ parent.doctype, parent.company, parent.base_grand_total
+ )
parent.set_payment_schedule()
- if parent_doctype == 'Purchase Order':
+ if parent_doctype == "Purchase Order":
parent.validate_minimum_order_qty()
parent.validate_budget()
if parent.is_against_so():
@@ -2148,8 +2587,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
parent.save()
- if parent_doctype == 'Purchase Order':
- update_last_purchase_rate(parent, is_submit = 1)
+ if parent_doctype == "Purchase Order":
+ update_last_purchase_rate(parent, is_submit=1)
parent.update_prevdoc_status()
parent.update_requested_qty()
parent.update_ordered_qty()
@@ -2162,7 +2601,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
else:
parent.update_reserved_qty()
parent.update_project()
- parent.update_prevdoc_status('submit')
+ parent.update_prevdoc_status("submit")
parent.update_delivery_status()
parent.reload()
@@ -2172,10 +2611,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
parent.update_billing_percentage()
parent.set_status()
+
@erpnext.allow_regional
def validate_regional(doc):
pass
+
@erpnext.allow_regional
def validate_einvoice_fields(doc):
pass
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index b740476481..47892073f3 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -20,12 +20,11 @@ from erpnext.stock.utils import get_incoming_rate
class QtyMismatchError(ValidationError):
pass
-class BuyingController(StockController, Subcontracting):
+class BuyingController(StockController, Subcontracting):
def get_feed(self):
if self.get("supplier_name"):
- return _("From {0} | {1} {2}").format(self.supplier_name, self.currency,
- self.grand_total)
+ return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
def validate(self):
super(BuyingController, self).validate()
@@ -40,16 +39,18 @@ class BuyingController(StockController, Subcontracting):
self.set_supplier_address()
self.validate_asset_return()
- if self.doctype=="Purchase Invoice":
+ if self.doctype == "Purchase Invoice":
self.validate_purchase_receipt_if_update_stock()
- if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and self.update_stock):
+ if self.doctype == "Purchase Receipt" or (
+ self.doctype == "Purchase Invoice" and self.update_stock
+ ):
# self.validate_purchase_return()
self.validate_rejected_warehouse()
self.validate_accepted_rejected_qty()
validate_for_items(self)
- #sub-contracting
+ # sub-contracting
self.validate_for_subcontracting()
self.create_raw_materials_supplied("supplied_items")
self.set_landed_cost_voucher_amount()
@@ -59,8 +60,12 @@ class BuyingController(StockController, Subcontracting):
def onload(self):
super(BuyingController, self).onload()
- self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings',
- 'backflush_raw_materials_of_subcontract_based_on'))
+ self.set_onload(
+ "backflush_based_on",
+ frappe.db.get_single_value(
+ "Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
+ ),
+ )
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@@ -77,9 +82,9 @@ class BuyingController(StockController, Subcontracting):
doctype=self.doctype,
company=self.company,
party_address=self.get("supplier_address"),
- shipping_address=self.get('shipping_address'),
- fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'),
- ignore_permissions=self.flags.ignore_permissions
+ shipping_address=self.get("shipping_address"),
+ fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
+ ignore_permissions=self.flags.ignore_permissions,
)
)
@@ -88,14 +93,16 @@ class BuyingController(StockController, Subcontracting):
def set_supplier_from_item_default(self):
if self.meta.get_field("supplier") and not self.supplier:
for d in self.get("items"):
- supplier = frappe.db.get_value("Item Default",
- {"parent": d.item_code, "company": self.company}, "default_supplier")
+ supplier = frappe.db.get_value(
+ "Item Default", {"parent": d.item_code, "company": self.company}, "default_supplier"
+ )
if supplier:
self.supplier = supplier
else:
item_group = frappe.db.get_value("Item", d.item_code, "item_group")
- supplier = frappe.db.get_value("Item Default",
- {"parent": item_group, "company": self.company}, "default_supplier")
+ supplier = frappe.db.get_value(
+ "Item Default", {"parent": item_group, "company": self.company}, "default_supplier"
+ )
if supplier:
self.supplier = supplier
break
@@ -106,55 +113,71 @@ class BuyingController(StockController, Subcontracting):
self.update_tax_category(msg)
def update_tax_category(self, msg):
- tax_for_valuation = [d for d in self.get("taxes")
- if d.category in ["Valuation", "Valuation and Total"]]
+ tax_for_valuation = [
+ d for d in self.get("taxes") if d.category in ["Valuation", "Valuation and Total"]
+ ]
if tax_for_valuation:
for d in tax_for_valuation:
- d.category = 'Total'
+ d.category = "Total"
msgprint(msg)
def validate_asset_return(self):
- if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
+ if self.doctype not in ["Purchase Receipt", "Purchase Invoice"] or not self.is_return:
return
- purchase_doc_field = 'purchase_receipt' if self.doctype == 'Purchase Receipt' else 'purchase_invoice'
- not_cancelled_asset = [d.name for d in frappe.db.get_all("Asset", {
- purchase_doc_field: self.return_against,
- "docstatus": 1
- })]
+ purchase_doc_field = (
+ "purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice"
+ )
+ not_cancelled_asset = [
+ d.name
+ for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1})
+ ]
if self.is_return and len(not_cancelled_asset):
- frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.")
- .format(self.return_against), title=_("Not Allowed"))
+ frappe.throw(
+ _(
+ "{} has submitted assets linked to it. You need to cancel the assets to create purchase return."
+ ).format(self.return_against),
+ title=_("Not Allowed"),
+ )
def get_asset_items(self):
- if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
+ if self.doctype not in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]:
return []
return [d.item_code for d in self.items if d.is_fixed_asset]
def set_landed_cost_voucher_amount(self):
for d in self.get("items"):
- lc_voucher_data = frappe.db.sql("""select sum(applicable_charges), cost_center
+ lc_voucher_data = frappe.db.sql(
+ """select sum(applicable_charges), cost_center
from `tabLanded Cost Item`
- where docstatus = 1 and purchase_receipt_item = %s""", d.name)
+ where docstatus = 1 and purchase_receipt_item = %s""",
+ d.name,
+ )
d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
- d.db_set('cost_center', lc_voucher_data[0][1])
+ d.db_set("cost_center", lc_voucher_data[0][1])
def validate_from_warehouse(self):
- for item in self.get('items'):
- if item.get('from_warehouse') and (item.get('from_warehouse') == item.get('warehouse')):
- frappe.throw(_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx))
+ for item in self.get("items"):
+ if item.get("from_warehouse") and (item.get("from_warehouse") == item.get("warehouse")):
+ frappe.throw(
+ _("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx)
+ )
- if item.get('from_warehouse') and self.get('is_subcontracted') == 'Yes':
- frappe.throw(_("Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor").format(item.idx))
+ if item.get("from_warehouse") and self.get("is_subcontracted") == "Yes":
+ frappe.throw(
+ _(
+ "Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor"
+ ).format(item.idx)
+ )
def set_supplier_address(self):
address_dict = {
- 'supplier_address': 'address_display',
- 'shipping_address': 'shipping_address_display'
+ "supplier_address": "address_display",
+ "shipping_address": "shipping_address_display",
}
for address_field, address_display_field in address_dict.items():
@@ -163,6 +186,7 @@ class BuyingController(StockController, Subcontracting):
def set_total_in_words(self):
from frappe.utils import money_in_words
+
if self.meta.get_field("base_in_words"):
if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled():
amount = self.base_rounded_total
@@ -181,10 +205,10 @@ class BuyingController(StockController, Subcontracting):
# update valuation rate
def update_valuation_rate(self, reset_outgoing_rate=True):
"""
- item_tax_amount is the total tax amount applied on that item
- stored for valuation
+ item_tax_amount is the total tax amount applied on that item
+ stored for valuation
- TODO: rename item_tax_amount to valuation_tax_amount
+ TODO: rename item_tax_amount to valuation_tax_amount
"""
stock_and_asset_items = []
stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
@@ -192,36 +216,50 @@ class BuyingController(StockController, Subcontracting):
stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
last_item_idx = 1
for d in self.get("items"):
- if (d.item_code and d.item_code in stock_and_asset_items):
+ if d.item_code and d.item_code in stock_and_asset_items:
stock_and_asset_items_qty += flt(d.qty)
stock_and_asset_items_amount += flt(d.base_net_amount)
last_item_idx = d.idx
- total_valuation_amount = sum(flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
- if d.category in ["Valuation", "Valuation and Total"])
+ total_valuation_amount = sum(
+ flt(d.base_tax_amount_after_discount_amount)
+ for d in self.get("taxes")
+ if d.category in ["Valuation", "Valuation and Total"]
+ )
valuation_amount_adjustment = total_valuation_amount
for i, item in enumerate(self.get("items")):
if item.item_code and item.qty and item.item_code in stock_and_asset_items:
- item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
+ item_proportion = (
+ flt(item.base_net_amount) / stock_and_asset_items_amount
+ if stock_and_asset_items_amount
else flt(item.qty) / stock_and_asset_items_qty
+ )
if i == (last_item_idx - 1):
- item.item_tax_amount = flt(valuation_amount_adjustment,
- self.precision("item_tax_amount", item))
+ item.item_tax_amount = flt(
+ valuation_amount_adjustment, self.precision("item_tax_amount", item)
+ )
else:
- item.item_tax_amount = flt(item_proportion * total_valuation_amount,
- self.precision("item_tax_amount", item))
+ item.item_tax_amount = flt(
+ item_proportion * total_valuation_amount, self.precision("item_tax_amount", item)
+ )
valuation_amount_adjustment -= item.item_tax_amount
self.round_floats_in(item)
- if flt(item.conversion_factor)==0.0:
- item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
+ if flt(item.conversion_factor) == 0.0:
+ item.conversion_factor = (
+ get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
+ )
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
- item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + item.rm_supp_cost
- + flt(item.landed_cost_voucher_amount)) / qty_in_stock_uom)
+ item.valuation_rate = (
+ item.base_net_amount
+ + item.item_tax_amount
+ + item.rm_supp_cost
+ + flt(item.landed_cost_voucher_amount)
+ ) / qty_in_stock_uom
else:
item.valuation_rate = 0.0
@@ -242,46 +280,55 @@ class BuyingController(StockController, Subcontracting):
# Get outgoing rate based on original item cost based on valuation method
if not d.get(frappe.scrub(ref_doctype)):
- outgoing_rate = get_incoming_rate({
- "item_code": d.item_code,
- "warehouse": d.get('from_warehouse'),
- "posting_date": self.get('posting_date') or self.get('transation_date'),
- "posting_time": self.get('posting_time'),
- "qty": -1 * flt(d.get('stock_qty')),
- "serial_no": d.get('serial_no'),
- "batch_no": d.get("batch_no"),
- "company": self.company,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "allow_zero_valuation": d.get("allow_zero_valuation")
- }, raise_error_if_no_rate=False)
+ outgoing_rate = get_incoming_rate(
+ {
+ "item_code": d.item_code,
+ "warehouse": d.get("from_warehouse"),
+ "posting_date": self.get("posting_date") or self.get("transation_date"),
+ "posting_time": self.get("posting_time"),
+ "qty": -1 * flt(d.get("stock_qty")),
+ "serial_no": d.get("serial_no"),
+ "batch_no": d.get("batch_no"),
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "allow_zero_valuation": d.get("allow_zero_valuation"),
+ },
+ raise_error_if_no_rate=False,
+ )
- rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
+ rate = flt(outgoing_rate * d.conversion_factor, d.precision("rate"))
else:
- rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
+ rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
if self.is_internal_transfer():
if rate != d.rate:
d.rate = rate
d.discount_percentage = 0
d.discount_amount = 0
- frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
- .format(d.idx), alert=1)
+ frappe.msgprint(
+ _(
+ "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
+ ).format(d.idx),
+ alert=1,
+ )
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
supplied_items_cost = 0.0
for d in self.get("supplied_items"):
if d.reference_name == item_row_id:
- if reset_outgoing_rate and frappe.get_cached_value('Item', d.rm_item_code, 'is_stock_item'):
- rate = get_incoming_rate({
- "item_code": d.rm_item_code,
- "warehouse": self.supplier_warehouse,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- "qty": -1 * d.consumed_qty,
- "serial_no": d.serial_no,
- "batch_no": d.batch_no,
- })
+ if reset_outgoing_rate and frappe.get_cached_value("Item", d.rm_item_code, "is_stock_item"):
+ rate = get_incoming_rate(
+ {
+ "item_code": d.rm_item_code,
+ "warehouse": self.supplier_warehouse,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "qty": -1 * d.consumed_qty,
+ "serial_no": d.serial_no,
+ "batch_no": d.batch_no,
+ }
+ )
if rate > 0:
d.rate = rate
@@ -316,7 +363,7 @@ class BuyingController(StockController, Subcontracting):
item.bom = None
def create_raw_materials_supplied(self, raw_material_table):
- if self.is_subcontracted=="Yes":
+ if self.is_subcontracted == "Yes":
self.set_materials_for_subcontracted_items(raw_material_table)
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@@ -324,19 +371,17 @@ class BuyingController(StockController, Subcontracting):
item.rm_supp_cost = 0.0
if self.is_subcontracted == "No" and self.get("supplied_items"):
- self.set('supplied_items', [])
+ self.set("supplied_items", [])
@property
def sub_contracted_items(self):
if not hasattr(self, "_sub_contracted_items"):
self._sub_contracted_items = []
- item_codes = list(set(item.item_code for item in
- self.get("items")))
+ item_codes = list(set(item.item_code for item in self.get("items")))
if item_codes:
- items = frappe.get_all('Item', filters={
- 'name': ['in', item_codes],
- 'is_sub_contracted_item': 1
- })
+ items = frappe.get_all(
+ "Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1}
+ )
self._sub_contracted_items = [item.name for item in items]
return self._sub_contracted_items
@@ -350,9 +395,11 @@ class BuyingController(StockController, Subcontracting):
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
- if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
+ if self.doctype == "Purchase Receipt" and d.meta.get_field("received_stock_qty"):
# Set Received Qty in Stock UOM
- d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
+ d.received_stock_qty = flt(d.received_qty) * flt(
+ d.conversion_factor, d.precision("conversion_factor")
+ )
def validate_purchase_return(self):
for d in self.get("items"):
@@ -368,20 +415,26 @@ class BuyingController(StockController, Subcontracting):
d.rejected_warehouse = self.rejected_warehouse
if not d.rejected_warehouse:
- frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code))
+ frappe.throw(
+ _("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(
+ d.idx, d.item_code
+ )
+ )
# validate accepted and rejected qty
def validate_accepted_rejected_qty(self):
for d in self.get("items"):
- self.validate_negative_quantity(d, ["received_qty","qty", "rejected_qty"])
+ self.validate_negative_quantity(d, ["received_qty", "qty", "rejected_qty"])
if not flt(d.received_qty) and (flt(d.qty) or flt(d.rejected_qty)):
d.received_qty = flt(d.qty) + flt(d.rejected_qty)
# Check Received Qty = Accepted Qty + Rejected Qty
val = flt(d.qty) + flt(d.rejected_qty)
- if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))):
- message = _("Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}").format(d.idx, d.item_code)
+ if flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty")):
+ message = _(
+ "Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}"
+ ).format(d.idx, d.item_code)
frappe.throw(msg=message, title=_("Mismatch"), exc=QtyMismatchError)
def validate_negative_quantity(self, item_row, field_list):
@@ -391,15 +444,20 @@ class BuyingController(StockController, Subcontracting):
item_row = item_row.as_dict()
for fieldname in field_list:
if flt(item_row[fieldname]) < 0:
- frappe.throw(_("Row #{0}: {1} can not be negative for item {2}").format(item_row['idx'],
- frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))
+ frappe.throw(
+ _("Row #{0}: {1} can not be negative for item {2}").format(
+ item_row["idx"],
+ frappe.get_meta(item_row.doctype).get_label(fieldname),
+ item_row["item_code"],
+ )
+ )
def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname):
for d in self.get("items"):
if d.get(ref_fieldname):
status = frappe.db.get_value(ref_doctype, d.get(ref_fieldname), "status")
if status in ("Closed", "On Hold"):
- frappe.throw(_("{0} {1} is {2}").format(ref_doctype,d.get(ref_fieldname), status))
+ frappe.throw(_("{0} {1} is {2}").format(ref_doctype, d.get(ref_fieldname), status))
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
self.update_ordered_and_reserved_qty()
@@ -407,76 +465,88 @@ class BuyingController(StockController, Subcontracting):
sl_entries = []
stock_items = self.get_stock_items()
- for d in self.get('items'):
+ for d in self.get("items"):
if d.item_code in stock_items and d.warehouse:
pr_qty = flt(d.qty) * flt(d.conversion_factor)
if pr_qty:
- if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==1)
- or (cint(self.is_return) and self.docstatus==2)):
- from_warehouse_sle = self.get_sl_entries(d, {
- "actual_qty": -1 * pr_qty,
- "warehouse": d.from_warehouse,
- "outgoing_rate": d.rate,
- "recalculate_rate": 1,
- "dependant_sle_voucher_detail_no": d.name
- })
+ if d.from_warehouse and (
+ (not cint(self.is_return) and self.docstatus == 1)
+ or (cint(self.is_return) and self.docstatus == 2)
+ ):
+ from_warehouse_sle = self.get_sl_entries(
+ d,
+ {
+ "actual_qty": -1 * pr_qty,
+ "warehouse": d.from_warehouse,
+ "outgoing_rate": d.rate,
+ "recalculate_rate": 1,
+ "dependant_sle_voucher_detail_no": d.name,
+ },
+ )
sl_entries.append(from_warehouse_sle)
- sle = self.get_sl_entries(d, {
- "actual_qty": flt(pr_qty),
- "serial_no": cstr(d.serial_no).strip()
- })
+ sle = self.get_sl_entries(
+ d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
+ )
if self.is_return:
- outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
+ outgoing_rate = get_rate_for_return(
+ self.doctype, self.name, d.item_code, self.return_against, item_row=d
+ )
- sle.update({
- "outgoing_rate": outgoing_rate,
- "recalculate_rate": 1
- })
+ sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1})
if d.from_warehouse:
sle.dependant_sle_voucher_detail_no = d.name
else:
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
- sle.update({
- "incoming_rate": incoming_rate,
- "recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0
- })
+ sle.update(
+ {
+ "incoming_rate": incoming_rate,
+ "recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0,
+ }
+ )
sl_entries.append(sle)
- if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==2)
- or (cint(self.is_return) and self.docstatus==1)):
- from_warehouse_sle = self.get_sl_entries(d, {
- "actual_qty": -1 * pr_qty,
- "warehouse": d.from_warehouse,
- "recalculate_rate": 1
- })
+ if d.from_warehouse and (
+ (not cint(self.is_return) and self.docstatus == 2)
+ or (cint(self.is_return) and self.docstatus == 1)
+ ):
+ from_warehouse_sle = self.get_sl_entries(
+ d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1}
+ )
sl_entries.append(from_warehouse_sle)
if flt(d.rejected_qty) != 0:
- sl_entries.append(self.get_sl_entries(d, {
- "warehouse": d.rejected_warehouse,
- "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
- "serial_no": cstr(d.rejected_serial_no).strip(),
- "incoming_rate": 0.0
- }))
+ sl_entries.append(
+ self.get_sl_entries(
+ d,
+ {
+ "warehouse": d.rejected_warehouse,
+ "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
+ "serial_no": cstr(d.rejected_serial_no).strip(),
+ "incoming_rate": 0.0,
+ },
+ )
+ )
self.make_sl_entries_for_supplier_warehouse(sl_entries)
- self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
- via_landed_cost_voucher=via_landed_cost_voucher)
+ self.make_sl_entries(
+ sl_entries,
+ allow_negative_stock=allow_negative_stock,
+ via_landed_cost_voucher=via_landed_cost_voucher,
+ )
def update_ordered_and_reserved_qty(self):
po_map = {}
for d in self.get("items"):
- if self.doctype=="Purchase Receipt" \
- and d.purchase_order:
- po_map.setdefault(d.purchase_order, []).append(d.purchase_order_item)
+ if self.doctype == "Purchase Receipt" and d.purchase_order:
+ po_map.setdefault(d.purchase_order, []).append(d.purchase_order_item)
- elif self.doctype=="Purchase Invoice" and d.purchase_order and d.po_detail:
+ elif self.doctype == "Purchase Invoice" and d.purchase_order and d.po_detail:
po_map.setdefault(d.purchase_order, []).append(d.po_detail)
for po, po_item_rows in po_map.items():
@@ -484,68 +554,78 @@ class BuyingController(StockController, Subcontracting):
po_obj = frappe.get_doc("Purchase Order", po)
if po_obj.status in ["Closed", "Cancelled"]:
- frappe.throw(_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
- frappe.InvalidStatusError)
+ frappe.throw(
+ _("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
+ frappe.InvalidStatusError,
+ )
po_obj.update_ordered_qty(po_item_rows)
if self.is_subcontracted:
po_obj.update_reserved_qty_for_subcontract()
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
- if hasattr(self, 'supplied_items'):
- for d in self.get('supplied_items'):
+ if hasattr(self, "supplied_items"):
+ for d in self.get("supplied_items"):
# negative quantity is passed, as raw material qty has to be decreased
# when PR is submitted and it has to be increased when PR is cancelled
- sl_entries.append(self.get_sl_entries(d, {
- "item_code": d.rm_item_code,
- "warehouse": self.supplier_warehouse,
- "actual_qty": -1*flt(d.consumed_qty),
- "dependant_sle_voucher_detail_no": d.reference_name
- }))
+ sl_entries.append(
+ self.get_sl_entries(
+ d,
+ {
+ "item_code": d.rm_item_code,
+ "warehouse": self.supplier_warehouse,
+ "actual_qty": -1 * flt(d.consumed_qty),
+ "dependant_sle_voucher_detail_no": d.reference_name,
+ },
+ )
+ )
def on_submit(self):
- if self.get('is_return'):
+ if self.get("is_return"):
return
- if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
- field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
+ if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
+ field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt"
self.process_fixed_asset()
self.update_fixed_asset(field)
- if self.doctype in ['Purchase Order', 'Purchase Receipt']:
- update_last_purchase_rate(self, is_submit = 1)
+ if self.doctype in ["Purchase Order", "Purchase Receipt"]:
+ update_last_purchase_rate(self, is_submit=1)
def on_cancel(self):
super(BuyingController, self).on_cancel()
- if self.get('is_return'):
+ if self.get("is_return"):
return
- if self.doctype in ['Purchase Order', 'Purchase Receipt']:
- update_last_purchase_rate(self, is_submit = 0)
+ if self.doctype in ["Purchase Order", "Purchase Receipt"]:
+ update_last_purchase_rate(self, is_submit=0)
- if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
- field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
+ if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
+ field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt"
self.delete_linked_asset()
self.update_fixed_asset(field, delete_asset=True)
def validate_budget(self):
if self.docstatus == 1:
- for data in self.get('items'):
+ for data in self.get("items"):
args = data.as_dict()
- args.update({
- 'doctype': self.doctype,
- 'company': self.company,
- 'posting_date': (self.schedule_date
- if self.doctype == 'Material Request' else self.transaction_date)
- })
+ args.update(
+ {
+ "doctype": self.doctype,
+ "company": self.company,
+ "posting_date": (
+ self.schedule_date if self.doctype == "Material Request" else self.transaction_date
+ ),
+ }
+ )
validate_expense_against_budget(args)
def process_fixed_asset(self):
- if self.doctype == 'Purchase Invoice' and not self.update_stock:
+ if self.doctype == "Purchase Invoice" and not self.update_stock:
return
asset_items = self.get_asset_items()
@@ -560,12 +640,12 @@ class BuyingController(StockController, Subcontracting):
if d.is_fixed_asset:
item_data = items_data.get(d.item_code)
- if item_data.get('auto_create_assets'):
+ if item_data.get("auto_create_assets"):
# If asset has to be auto created
# Check for asset naming series
- if item_data.get('asset_naming_series'):
+ if item_data.get("asset_naming_series"):
created_assets = []
- if item_data.get('is_grouped_asset'):
+ if item_data.get("is_grouped_asset"):
asset = self.make_asset(d, is_grouped_asset=True)
created_assets.append(asset)
else:
@@ -575,21 +655,31 @@ class BuyingController(StockController, Subcontracting):
if len(created_assets) > 5:
# dont show asset form links if more than 5 assets are created
- messages.append(_('{} Assets created for {}').format(len(created_assets), frappe.bold(d.item_code)))
- else:
- assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets))
- assets_link = frappe.bold(','.join(assets_link))
-
- is_plural = 's' if len(created_assets) != 1 else ''
messages.append(
- _('Asset{} {assets_link} created for {}').format(is_plural, frappe.bold(d.item_code), assets_link=assets_link)
+ _("{} Assets created for {}").format(len(created_assets), frappe.bold(d.item_code))
+ )
+ else:
+ assets_link = list(map(lambda d: frappe.utils.get_link_to_form("Asset", d), created_assets))
+ assets_link = frappe.bold(",".join(assets_link))
+
+ is_plural = "s" if len(created_assets) != 1 else ""
+ messages.append(
+ _("Asset{} {assets_link} created for {}").format(
+ is_plural, frappe.bold(d.item_code), assets_link=assets_link
+ )
)
else:
- frappe.throw(_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}")
- .format(d.idx, frappe.bold(d.item_code)))
+ frappe.throw(
+ _("Row {}: Asset Naming Series is mandatory for the auto creation for item {}").format(
+ d.idx, frappe.bold(d.item_code)
+ )
+ )
else:
- messages.append(_("Assets not created for {0}. You will have to create asset manually.")
- .format(frappe.bold(d.item_code)))
+ messages.append(
+ _("Assets not created for {0}. You will have to create asset manually.").format(
+ frappe.bold(d.item_code)
+ )
+ )
for message in messages:
frappe.msgprint(message, title="Success", indicator="green")
@@ -598,31 +688,34 @@ class BuyingController(StockController, Subcontracting):
if not row.asset_location:
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
- item_data = frappe.db.get_value('Item',
- row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1)
+ item_data = frappe.db.get_value(
+ "Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
+ )
if is_grouped_asset:
purchase_amount = flt(row.base_amount + row.item_tax_amount)
else:
purchase_amount = flt(row.base_rate + row.item_tax_amount)
- asset = frappe.get_doc({
- 'doctype': 'Asset',
- 'item_code': row.item_code,
- 'asset_name': row.item_name,
- 'naming_series': item_data.get('asset_naming_series') or 'AST',
- 'asset_category': item_data.get('asset_category'),
- 'location': row.asset_location,
- 'company': self.company,
- 'supplier': self.supplier,
- 'purchase_date': self.posting_date,
- 'calculate_depreciation': 1,
- 'purchase_receipt_amount': purchase_amount,
- 'gross_purchase_amount': purchase_amount,
- 'asset_quantity': row.qty if is_grouped_asset else 0,
- 'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None,
- 'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None
- })
+ asset = frappe.get_doc(
+ {
+ "doctype": "Asset",
+ "item_code": row.item_code,
+ "asset_name": row.item_name,
+ "naming_series": item_data.get("asset_naming_series") or "AST",
+ "asset_category": item_data.get("asset_category"),
+ "location": row.asset_location,
+ "company": self.company,
+ "supplier": self.supplier,
+ "purchase_date": self.posting_date,
+ "calculate_depreciation": 1,
+ "purchase_receipt_amount": purchase_amount,
+ "gross_purchase_amount": purchase_amount,
+ "asset_quantity": row.qty if is_grouped_asset else 0,
+ "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
+ "purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
+ }
+ )
asset.flags.ignore_validate = True
asset.flags.ignore_mandatory = True
@@ -631,22 +724,25 @@ class BuyingController(StockController, Subcontracting):
return asset.name
- def update_fixed_asset(self, field, delete_asset = False):
+ def update_fixed_asset(self, field, delete_asset=False):
for d in self.get("items"):
if d.is_fixed_asset:
- is_auto_create_enabled = frappe.db.get_value('Item', d.item_code, 'auto_create_assets')
- assets = frappe.db.get_all('Asset', filters={ field : self.name, 'item_code' : d.item_code })
+ is_auto_create_enabled = frappe.db.get_value("Item", d.item_code, "auto_create_assets")
+ assets = frappe.db.get_all("Asset", filters={field: self.name, "item_code": d.item_code})
for asset in assets:
- asset = frappe.get_doc('Asset', asset.name)
+ asset = frappe.get_doc("Asset", asset.name)
if delete_asset and is_auto_create_enabled:
# need to delete movements to delete assets otherwise throws link exists error
movements = frappe.db.sql(
"""SELECT asm.name
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
- WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1)
+ WHERE asm_item.parent=asm.name and asm_item.asset=%s""",
+ asset.name,
+ as_dict=1,
+ )
for movement in movements:
- frappe.delete_doc('Asset Movement', movement.name, force=1)
+ frappe.delete_doc("Asset Movement", movement.name, force=1)
frappe.delete_doc("Asset", asset.name, force=1)
continue
@@ -659,8 +755,11 @@ class BuyingController(StockController, Subcontracting):
asset.set(field, None)
asset.supplier = None
if asset.docstatus == 1 and delete_asset:
- frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue.')
- .format(frappe.utils.get_link_to_form('Asset', asset.name)))
+ frappe.throw(
+ _(
+ "Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue."
+ ).format(frappe.utils.get_link_to_form("Asset", asset.name))
+ )
asset.flags.ignore_validate_update_after_submit = True
asset.flags.ignore_mandatory = True
@@ -670,7 +769,7 @@ class BuyingController(StockController, Subcontracting):
asset.save()
def delete_linked_asset(self):
- if self.doctype == 'Purchase Invoice' and not self.get('update_stock'):
+ if self.doctype == "Purchase Invoice" and not self.get("update_stock"):
return
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
@@ -681,37 +780,47 @@ class BuyingController(StockController, Subcontracting):
if any(d.schedule_date for d in self.get("items")):
# Select earliest schedule_date.
- self.schedule_date = min(d.schedule_date for d in self.get("items")
- if d.schedule_date is not None)
+ self.schedule_date = min(
+ d.schedule_date for d in self.get("items") if d.schedule_date is not None
+ )
if self.schedule_date:
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.schedule_date:
d.schedule_date = self.schedule_date
- if (d.schedule_date and self.transaction_date and
- getdate(d.schedule_date) < getdate(self.transaction_date)):
+ if (
+ d.schedule_date
+ and self.transaction_date
+ and getdate(d.schedule_date) < getdate(self.transaction_date)
+ ):
frappe.throw(_("Row #{0}: Reqd by Date cannot be before Transaction Date").format(d.idx))
else:
frappe.throw(_("Please enter Reqd by Date"))
def validate_items(self):
# validate items to see if they have is_purchase_item or is_subcontracted_item enabled
- if self.doctype=="Material Request": return
+ if self.doctype == "Material Request":
+ return
- if hasattr(self, "is_subcontracted") and self.is_subcontracted == 'Yes':
+ if hasattr(self, "is_subcontracted") and self.is_subcontracted == "Yes":
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
else:
validate_item_type(self, "is_purchase_item", "purchase")
+
def get_asset_item_details(asset_items):
asset_items_data = {}
- for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series", "is_grouped_asset"],
- filters = {'name': ('in', asset_items)}):
+ for d in frappe.get_all(
+ "Item",
+ fields=["name", "auto_create_assets", "asset_naming_series", "is_grouped_asset"],
+ filters={"name": ("in", asset_items)},
+ ):
asset_items_data.setdefault(d.name, d)
return asset_items_data
+
def validate_item_type(doc, fieldname, message):
# iterate through items and check if they are valid sales or purchase items
items = [d.item_code for d in doc.items if d.item_code]
@@ -722,16 +831,28 @@ def validate_item_type(doc, fieldname, message):
item_list = ", ".join(["%s" % frappe.db.escape(d) for d in items])
- invalid_items = [d[0] for d in frappe.db.sql("""
+ invalid_items = [
+ d[0]
+ for d in frappe.db.sql(
+ """
select item_code from tabItem where name in ({0}) and {1}=0
- """.format(item_list, fieldname), as_list=True)]
+ """.format(
+ item_list, fieldname
+ ),
+ as_list=True,
+ )
+ ]
if invalid_items:
items = ", ".join([d for d in invalid_items])
if len(invalid_items) > 1:
- error_message = _("Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
+ error_message = _(
+ "Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master"
+ ).format(items, message)
else:
- error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
+ error_message = _(
+ "Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master"
+ ).format(items, message)
frappe.throw(error_message)
diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py
index dd02ce1748..8ff397dbfc 100644
--- a/erpnext/controllers/employee_boarding_controller.py
+++ b/erpnext/controllers/employee_boarding_controller.py
@@ -12,34 +12,39 @@ from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
class EmployeeBoardingController(Document):
- '''
- Create the project and the task for the boarding process
- Assign to the concerned person and roles as per the onboarding/separation template
- '''
+ """
+ Create the project and the task for the boarding process
+ Assign to the concerned person and roles as per the onboarding/separation template
+ """
+
def validate(self):
# remove the task if linked before submitting the form
if self.amended_from:
for activity in self.activities:
- activity.task = ''
+ activity.task = ""
def on_submit(self):
# create the project for the given employee onboarding
- project_name = _(self.doctype) + ' : '
- if self.doctype == 'Employee Onboarding':
+ project_name = _(self.doctype) + " : "
+ if self.doctype == "Employee Onboarding":
project_name += self.job_applicant
else:
project_name += self.employee
- project = frappe.get_doc({
- 'doctype': 'Project',
- 'project_name': project_name,
- 'expected_start_date': self.date_of_joining if self.doctype == 'Employee Onboarding' else self.resignation_letter_date,
- 'department': self.department,
- 'company': self.company
- }).insert(ignore_permissions=True, ignore_mandatory=True)
+ project = frappe.get_doc(
+ {
+ "doctype": "Project",
+ "project_name": project_name,
+ "expected_start_date": self.date_of_joining
+ if self.doctype == "Employee Onboarding"
+ else self.resignation_letter_date,
+ "department": self.department,
+ "company": self.company,
+ }
+ ).insert(ignore_permissions=True, ignore_mandatory=True)
- self.db_set('project', project.name)
- self.db_set('boarding_status', 'Pending')
+ self.db_set("project", project.name)
+ self.db_set("boarding_status", "Pending")
self.reload()
self.create_task_and_notify_user()
@@ -53,22 +58,25 @@ class EmployeeBoardingController(Document):
dates = self.get_task_dates(activity, holiday_list)
- task = frappe.get_doc({
- 'doctype': 'Task',
- 'project': self.project,
- 'subject': activity.activity_name + ' : ' + self.employee_name,
- 'description': activity.description,
- 'department': self.department,
- 'company': self.company,
- 'task_weight': activity.task_weight,
- 'exp_start_date': dates[0],
- 'exp_end_date': dates[1]
- }).insert(ignore_permissions=True)
- activity.db_set('task', task.name)
+ task = frappe.get_doc(
+ {
+ "doctype": "Task",
+ "project": self.project,
+ "subject": activity.activity_name + " : " + self.employee_name,
+ "description": activity.description,
+ "department": self.department,
+ "company": self.company,
+ "task_weight": activity.task_weight,
+ "exp_start_date": dates[0],
+ "exp_end_date": dates[1],
+ }
+ ).insert(ignore_permissions=True)
+ activity.db_set("task", task.name)
users = [activity.user] if activity.user else []
if activity.role:
- user_list = frappe.db.sql_list('''
+ user_list = frappe.db.sql_list(
+ """
SELECT
DISTINCT(has_role.parent)
FROM
@@ -79,25 +87,27 @@ class EmployeeBoardingController(Document):
has_role.parenttype = 'User'
AND user.enabled = 1
AND has_role.role = %s
- ''', activity.role)
+ """,
+ activity.role,
+ )
users = unique(users + user_list)
- if 'Administrator' in users:
- users.remove('Administrator')
+ if "Administrator" in users:
+ users.remove("Administrator")
# assign the task the users
if users:
self.assign_task_to_users(task, users)
def get_holiday_list(self):
- if self.doctype == 'Employee Separation':
+ if self.doctype == "Employee Separation":
return get_holiday_list_for_employee(self.employee)
else:
if self.employee:
return get_holiday_list_for_employee(self.employee)
else:
if not self.holiday_list:
- frappe.throw(_('Please set the Holiday List.'), frappe.MandatoryError)
+ frappe.throw(_("Please set the Holiday List."), frappe.MandatoryError)
else:
return self.holiday_list
@@ -122,51 +132,62 @@ class EmployeeBoardingController(Document):
def assign_task_to_users(self, task, users):
for user in users:
args = {
- 'assign_to': [user],
- 'doctype': task.doctype,
- 'name': task.name,
- 'description': task.description or task.subject,
- 'notify': self.notify_users_by_email
+ "assign_to": [user],
+ "doctype": task.doctype,
+ "name": task.name,
+ "description": task.description or task.subject,
+ "notify": self.notify_users_by_email,
}
assign_to.add(args)
def on_cancel(self):
# delete task project
project = self.project
- for task in frappe.get_all('Task', filters={'project': project}):
- frappe.delete_doc('Task', task.name, force=1)
- frappe.delete_doc('Project', project, force=1)
- self.db_set('project', '')
+ for task in frappe.get_all("Task", filters={"project": project}):
+ frappe.delete_doc("Task", task.name, force=1)
+ frappe.delete_doc("Project", project, force=1)
+ self.db_set("project", "")
for activity in self.activities:
- activity.db_set('task', '')
+ activity.db_set("task", "")
- frappe.msgprint(_('Linked Project {} and Tasks deleted.').format(
- project), alert=True, indicator='blue')
+ frappe.msgprint(
+ _("Linked Project {} and Tasks deleted.").format(project), alert=True, indicator="blue"
+ )
@frappe.whitelist()
def get_onboarding_details(parent, parenttype):
- return frappe.get_all('Employee Boarding Activity',
- fields=['activity_name', 'role', 'user', 'required_for_employee_creation',
- 'description', 'task_weight', 'begin_on', 'duration'],
- filters={'parent': parent, 'parenttype': parenttype},
- order_by= 'idx')
+ return frappe.get_all(
+ "Employee Boarding Activity",
+ fields=[
+ "activity_name",
+ "role",
+ "user",
+ "required_for_employee_creation",
+ "description",
+ "task_weight",
+ "begin_on",
+ "duration",
+ ],
+ filters={"parent": parent, "parenttype": parenttype},
+ order_by="idx",
+ )
def update_employee_boarding_status(project):
- employee_onboarding = frappe.db.exists('Employee Onboarding', {'project': project.name})
- employee_separation = frappe.db.exists('Employee Separation', {'project': project.name})
+ employee_onboarding = frappe.db.exists("Employee Onboarding", {"project": project.name})
+ employee_separation = frappe.db.exists("Employee Separation", {"project": project.name})
if not (employee_onboarding or employee_separation):
return
- status = 'Pending'
+ status = "Pending"
if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0:
- status = 'In Process'
+ status = "In Process"
elif flt(project.percent_complete) == 100.0:
- status = 'Completed'
+ status = "Completed"
if employee_onboarding:
- frappe.db.set_value('Employee Onboarding', employee_onboarding, 'boarding_status', status)
+ frappe.db.set_value("Employee Onboarding", employee_onboarding, "boarding_status", status)
elif employee_separation:
- frappe.db.set_value('Employee Separation', employee_separation, 'boarding_status', status)
+ frappe.db.set_value("Employee Separation", employee_separation, "boarding_status", status)
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 68ad702b97..e68ee909d9 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -10,24 +10,30 @@ from frappe import _
from frappe.utils import cstr, flt
-class ItemVariantExistsError(frappe.ValidationError): pass
-class InvalidItemAttributeValueError(frappe.ValidationError): pass
-class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
+class ItemVariantExistsError(frappe.ValidationError):
+ pass
+
+
+class InvalidItemAttributeValueError(frappe.ValidationError):
+ pass
+
+
+class ItemTemplateCannotHaveStock(frappe.ValidationError):
+ pass
+
@frappe.whitelist()
-def get_variant(template, args=None, variant=None, manufacturer=None,
- manufacturer_part_no=None):
+def get_variant(template, args=None, variant=None, manufacturer=None, manufacturer_part_no=None):
"""Validates Attributes and their Values, then looks for an exactly
- matching Item Variant
+ matching Item Variant
- :param item: Template Item
- :param args: A dictionary with "Attribute" as key and "Attribute Value" as value
+ :param item: Template Item
+ :param args: A dictionary with "Attribute" as key and "Attribute Value" as value
"""
- item_template = frappe.get_doc('Item', template)
+ item_template = frappe.get_doc("Item", template)
- if item_template.variant_based_on=='Manufacturer' and manufacturer:
- return make_variant_based_on_manufacturer(item_template, manufacturer,
- manufacturer_part_no)
+ if item_template.variant_based_on == "Manufacturer" and manufacturer:
+ return make_variant_based_on_manufacturer(item_template, manufacturer, manufacturer_part_no)
else:
if isinstance(args, str):
args = json.loads(args)
@@ -36,28 +42,30 @@ def get_variant(template, args=None, variant=None, manufacturer=None,
frappe.throw(_("Please specify at least one attribute in the Attributes table"))
return find_variant(template, args, variant)
+
def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part_no):
- '''Make and return a new variant based on manufacturer and
- manufacturer part no'''
+ """Make and return a new variant based on manufacturer and
+ manufacturer part no"""
from frappe.model.naming import append_number_if_name_exists
- variant = frappe.new_doc('Item')
+ variant = frappe.new_doc("Item")
copy_attributes_to_variant(template, variant)
variant.manufacturer = manufacturer
variant.manufacturer_part_no = manufacturer_part_no
- variant.item_code = append_number_if_name_exists('Item', template.name)
+ variant.item_code = append_number_if_name_exists("Item", template.name)
return variant
+
def validate_item_variant_attributes(item, args=None):
if isinstance(item, str):
- item = frappe.get_doc('Item', item)
+ item = frappe.get_doc("Item", item)
if not args:
- args = {d.attribute.lower():d.attribute_value for d in item.attributes}
+ args = {d.attribute.lower(): d.attribute_value for d in item.attributes}
attribute_values, numeric_values = get_attribute_values(item)
@@ -73,6 +81,7 @@ def validate_item_variant_attributes(item, args=None):
attributes_list = attribute_values.get(attribute.lower(), [])
validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
+
def validate_is_incremental(numeric_attribute, attribute, value, item):
from_range = numeric_attribute.from_range
to_range = numeric_attribute.to_range
@@ -84,30 +93,48 @@ def validate_is_incremental(numeric_attribute, attribute, value, item):
is_in_range = from_range <= flt(value) <= to_range
precision = max(len(cstr(v).split(".")[-1].rstrip("0")) for v in (value, increment))
- #avoid precision error by rounding the remainder
+ # avoid precision error by rounding the remainder
remainder = flt((flt(value) - from_range) % increment, precision)
- is_incremental = remainder==0 or remainder==increment
+ is_incremental = remainder == 0 or remainder == increment
if not (is_in_range and is_incremental):
- frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3} for Item {4}")\
- .format(attribute, from_range, to_range, increment, item),
- InvalidItemAttributeValueError, title=_('Invalid Attribute'))
+ frappe.throw(
+ _(
+ "Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3} for Item {4}"
+ ).format(attribute, from_range, to_range, increment, item),
+ InvalidItemAttributeValueError,
+ title=_("Invalid Attribute"),
+ )
-def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
- allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
+
+def validate_item_attribute_value(
+ attributes_list, attribute, attribute_value, item, from_variant=True
+):
+ allow_rename_attribute_value = frappe.db.get_single_value(
+ "Item Variant Settings", "allow_rename_attribute_value"
+ )
if allow_rename_attribute_value:
pass
elif attribute_value not in attributes_list:
if from_variant:
- frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
- frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
+ frappe.throw(
+ _("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
+ frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)
+ ),
+ InvalidItemAttributeValueError,
+ title=_("Invalid Value"),
+ )
else:
msg = _("The value {0} is already assigned to an existing Item {1}.").format(
- frappe.bold(attribute_value), frappe.bold(item))
- msg += "
" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
+ frappe.bold(attribute_value), frappe.bold(item)
+ )
+ msg += "
" + _(
+ "To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings."
+ ).format(frappe.bold("Allow Rename Attribute Value"))
+
+ frappe.throw(msg, InvalidItemAttributeValueError, title=_("Edit Not Allowed"))
- frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
def get_attribute_values(item):
if not frappe.flags.attribute_values:
@@ -116,9 +143,11 @@ def get_attribute_values(item):
for t in frappe.get_all("Item Attribute Value", fields=["parent", "attribute_value"]):
attribute_values.setdefault(t.parent.lower(), []).append(t.attribute_value)
- for t in frappe.get_all('Item Variant Attribute',
+ for t in frappe.get_all(
+ "Item Variant Attribute",
fields=["attribute", "from_range", "to_range", "increment"],
- filters={'numeric_values': 1, 'parent': item.variant_of}):
+ filters={"numeric_values": 1, "parent": item.variant_of},
+ ):
numeric_values[t.attribute.lower()] = t
frappe.flags.attribute_values = attribute_values
@@ -126,14 +155,22 @@ def get_attribute_values(item):
return frappe.flags.attribute_values, frappe.flags.numeric_values
+
def find_variant(template, args, variant_item_code=None):
- conditions = ["""(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})"""\
- .format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
+ conditions = [
+ """(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})""".format(
+ frappe.db.escape(key), frappe.db.escape(cstr(value))
+ )
+ for key, value in args.items()
+ ]
conditions = " or ".join(conditions)
from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes
- possible_variants = [i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code]
+
+ possible_variants = [
+ i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code
+ ]
for variant in possible_variants:
variant = frappe.get_doc("Item", variant)
@@ -145,7 +182,7 @@ def find_variant(template, args, variant_item_code=None):
for attribute, value in args.items():
for row in variant.attributes:
- if row.attribute==attribute and row.attribute_value== cstr(value):
+ if row.attribute == attribute and row.attribute_value == cstr(value):
# this row matches
match_count += 1
break
@@ -153,6 +190,7 @@ def find_variant(template, args, variant_item_code=None):
if match_count == len(args.keys()):
return variant.name
+
@frappe.whitelist()
def create_variant(item, args):
if isinstance(args, str):
@@ -160,14 +198,11 @@ def create_variant(item, args):
template = frappe.get_doc("Item", item)
variant = frappe.new_doc("Item")
- variant.variant_based_on = 'Item Attribute'
+ variant.variant_based_on = "Item Attribute"
variant_attributes = []
for d in template.attributes:
- variant_attributes.append({
- "attribute": d.attribute,
- "attribute_value": args.get(d.attribute)
- })
+ variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)})
variant.set("attributes", variant_attributes)
copy_attributes_to_variant(template, variant)
@@ -175,6 +210,7 @@ def create_variant(item, args):
return variant
+
@frappe.whitelist()
def enqueue_multiple_variant_creation(item, args):
# There can be innumerable attribute combinations, enqueue
@@ -189,9 +225,14 @@ def enqueue_multiple_variant_creation(item, args):
if total_variants < 10:
return create_multiple_variants(item, args)
else:
- frappe.enqueue("erpnext.controllers.item_variant.create_multiple_variants",
- item=item, args=args, now=frappe.flags.in_test);
- return 'queued'
+ frappe.enqueue(
+ "erpnext.controllers.item_variant.create_multiple_variants",
+ item=item,
+ args=args,
+ now=frappe.flags.in_test,
+ )
+ return "queued"
+
def create_multiple_variants(item, args):
count = 0
@@ -204,26 +245,27 @@ def create_multiple_variants(item, args):
if not get_variant(item, args=attribute_values):
variant = create_variant(item, attribute_values)
variant.save()
- count +=1
+ count += 1
return count
+
def generate_keyed_value_combinations(args):
"""
From this:
- args = {"attr1": ["a", "b", "c"], "attr2": ["1", "2"], "attr3": ["A"]}
+ args = {"attr1": ["a", "b", "c"], "attr2": ["1", "2"], "attr3": ["A"]}
To this:
- [
- {u'attr1': u'a', u'attr2': u'1', u'attr3': u'A'},
- {u'attr1': u'b', u'attr2': u'1', u'attr3': u'A'},
- {u'attr1': u'c', u'attr2': u'1', u'attr3': u'A'},
- {u'attr1': u'a', u'attr2': u'2', u'attr3': u'A'},
- {u'attr1': u'b', u'attr2': u'2', u'attr3': u'A'},
- {u'attr1': u'c', u'attr2': u'2', u'attr3': u'A'}
- ]
+ [
+ {u'attr1': u'a', u'attr2': u'1', u'attr3': u'A'},
+ {u'attr1': u'b', u'attr2': u'1', u'attr3': u'A'},
+ {u'attr1': u'c', u'attr2': u'1', u'attr3': u'A'},
+ {u'attr1': u'a', u'attr2': u'2', u'attr3': u'A'},
+ {u'attr1': u'b', u'attr2': u'2', u'attr3': u'A'},
+ {u'attr1': u'c', u'attr2': u'2', u'attr3': u'A'}
+ ]
"""
# Return empty list if empty
@@ -259,17 +301,25 @@ def generate_keyed_value_combinations(args):
return results
+
def copy_attributes_to_variant(item, variant):
# copy non no-copy fields
- exclude_fields = ["naming_series", "item_code", "item_name", "published_in_website",
- "opening_stock", "variant_of", "valuation_rate"]
+ exclude_fields = [
+ "naming_series",
+ "item_code",
+ "item_name",
+ "published_in_website",
+ "opening_stock",
+ "variant_of",
+ "valuation_rate",
+ ]
- if item.variant_based_on=='Manufacturer':
+ if item.variant_based_on == "Manufacturer":
# don't copy manufacturer values if based on part no
- exclude_fields += ['manufacturer', 'manufacturer_part_no']
+ exclude_fields += ["manufacturer", "manufacturer_part_no"]
- allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields = ['field_name'])]
+ allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields=["field_name"])]
if "variant_based_on" not in allow_fields:
allow_fields.append("variant_based_on")
for field in item.meta.fields:
@@ -288,11 +338,11 @@ def copy_attributes_to_variant(item, variant):
variant.variant_of = item.name
- if 'description' not in allow_fields:
+ if "description" not in allow_fields:
if not variant.description:
- variant.description = ""
+ variant.description = ""
else:
- if item.variant_based_on=='Item Attribute':
+ if item.variant_based_on == "Item Attribute":
if variant.attributes:
attributes_description = item.description + " "
for d in variant.attributes:
@@ -301,6 +351,7 @@ def copy_attributes_to_variant(item, variant):
if attributes_description not in variant.description:
variant.description = attributes_description
+
def make_variant_item_code(template_item_code, template_item_name, variant):
"""Uses template's item code and abbreviations to make variant's item code"""
if variant.item_code:
@@ -308,13 +359,14 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
abbreviations = []
for attr in variant.attributes:
- item_attribute = frappe.db.sql("""select i.numeric_values, v.abbr
+ item_attribute = frappe.db.sql(
+ """select i.numeric_values, v.abbr
from `tabItem Attribute` i left join `tabItem Attribute Value` v
on (i.name=v.parent)
- where i.name=%(attribute)s and (v.attribute_value=%(attribute_value)s or i.numeric_values = 1)""", {
- "attribute": attr.attribute,
- "attribute_value": attr.attribute_value
- }, as_dict=True)
+ where i.name=%(attribute)s and (v.attribute_value=%(attribute_value)s or i.numeric_values = 1)""",
+ {"attribute": attr.attribute, "attribute_value": attr.attribute_value},
+ as_dict=True,
+ )
if not item_attribute:
continue
@@ -322,13 +374,16 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
# frappe.bold(attr.attribute_value)), title=_('Invalid Attribute'),
# exc=InvalidItemAttributeValueError)
- abbr_or_value = cstr(attr.attribute_value) if item_attribute[0].numeric_values else item_attribute[0].abbr
+ abbr_or_value = (
+ cstr(attr.attribute_value) if item_attribute[0].numeric_values else item_attribute[0].abbr
+ )
abbreviations.append(abbr_or_value)
if abbreviations:
variant.item_code = "{0}-{1}".format(template_item_code, "-".join(abbreviations))
variant.item_name = "{0}-{1}".format(template_item_name, "-".join(abbreviations))
+
@frappe.whitelist()
def create_variant_doc_for_quick_entry(template, args):
variant_based_on = frappe.db.get_value("Item", template, "variant_based_on")
diff --git a/erpnext/controllers/print_settings.py b/erpnext/controllers/print_settings.py
index cf9de52d4c..d2c80961a3 100644
--- a/erpnext/controllers/print_settings.py
+++ b/erpnext/controllers/print_settings.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
def set_print_templates_for_item_table(doc, settings):
doc.print_templates = {
"items": "templates/print_formats/includes/items.html",
@@ -20,16 +19,21 @@ def set_print_templates_for_item_table(doc, settings):
doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"]
if settings.compact_item_print:
- doc.child_print_templates["items"]["description"] =\
- "templates/print_formats/includes/item_table_description.html"
+ doc.child_print_templates["items"][
+ "description"
+ ] = "templates/print_formats/includes/item_table_description.html"
doc.flags.format_columns = format_columns
+
def set_print_templates_for_taxes(doc, settings):
doc.flags.show_inclusive_tax_in_print = doc.is_inclusive_tax()
- doc.print_templates.update({
- "total": "templates/print_formats/includes/total.html",
- "taxes": "templates/print_formats/includes/taxes.html"
- })
+ doc.print_templates.update(
+ {
+ "total": "templates/print_formats/includes/total.html",
+ "taxes": "templates/print_formats/includes/taxes.html",
+ }
+ )
+
def format_columns(display_columns, compact_fields):
compact_fields = compact_fields + ["image", "item_code", "item_name"]
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index d870823ad1..abe9977c84 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -21,7 +21,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
conditions = []
fields = get_fields("Employee", ["name", "employee_name"])
- return frappe.db.sql("""select {fields} from `tabEmployee`
+ return frappe.db.sql(
+ """select {fields} from `tabEmployee`
where status in ('Active', 'Suspended')
and docstatus < 2
and ({key} like %(txt)s
@@ -32,17 +33,16 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
idx desc,
name, employee_name
- limit %(start)s, %(page_len)s""".format(**{
- 'fields': ", ".join(fields),
- 'key': searchfield,
- 'fcond': get_filters_cond(doctype, filters, conditions),
- 'mcond': get_match_cond(doctype)
- }), {
- 'txt': "%%%s%%" % txt,
- '_txt': txt.replace("%", ""),
- 'start': start,
- 'page_len': page_len
- })
+ limit %(start)s, %(page_len)s""".format(
+ **{
+ "fields": ", ".join(fields),
+ "key": searchfield,
+ "fcond": get_filters_cond(doctype, filters, conditions),
+ "mcond": get_match_cond(doctype),
+ }
+ ),
+ {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
+ )
# searches for leads which are not converted
@@ -51,7 +51,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
def lead_query(doctype, txt, searchfield, start, page_len, filters):
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
- return frappe.db.sql("""select {fields} from `tabLead`
+ return frappe.db.sql(
+ """select {fields} from `tabLead`
where docstatus < 2
and ifnull(status, '') != 'Converted'
and ({key} like %(txt)s
@@ -64,19 +65,15 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, company_name), locate(%(_txt)s, company_name), 99999),
idx desc,
name, lead_name
- limit %(start)s, %(page_len)s""".format(**{
- 'fields': ", ".join(fields),
- 'key': searchfield,
- 'mcond':get_match_cond(doctype)
- }), {
- 'txt': "%%%s%%" % txt,
- '_txt': txt.replace("%", ""),
- 'start': start,
- 'page_len': page_len
- })
+ limit %(start)s, %(page_len)s""".format(
+ **{"fields": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
+ ),
+ {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
+ )
+
+ # searches for customer
- # searches for customer
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def customer_query(doctype, txt, searchfield, start, page_len, filters):
@@ -93,7 +90,8 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
searchfields = frappe.get_meta("Customer").get_search_fields()
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
- return frappe.db.sql("""select {fields} from `tabCustomer`
+ return frappe.db.sql(
+ """select {fields} from `tabCustomer`
where docstatus < 2
and ({scond}) and disabled=0
{fcond} {mcond}
@@ -102,17 +100,16 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, customer_name), locate(%(_txt)s, customer_name), 99999),
idx desc,
name, customer_name
- limit %(start)s, %(page_len)s""".format(**{
- "fields": ", ".join(fields),
- "scond": searchfields,
- "mcond": get_match_cond(doctype),
- "fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
- }), {
- 'txt': "%%%s%%" % txt,
- '_txt': txt.replace("%", ""),
- 'start': start,
- 'page_len': page_len
- })
+ limit %(start)s, %(page_len)s""".format(
+ **{
+ "fields": ", ".join(fields),
+ "scond": searchfields,
+ "mcond": get_match_cond(doctype),
+ "fcond": get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
+ }
+ ),
+ {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
+ )
# searches for supplier
@@ -128,7 +125,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
fields = get_fields("Supplier", fields)
- return frappe.db.sql("""select {field} from `tabSupplier`
+ return frappe.db.sql(
+ """select {field} from `tabSupplier`
where docstatus < 2
and ({key} like %(txt)s
or supplier_name like %(txt)s) and disabled=0
@@ -139,29 +137,25 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, supplier_name), locate(%(_txt)s, supplier_name), 99999),
idx desc,
name, supplier_name
- limit %(start)s, %(page_len)s """.format(**{
- 'field': ', '.join(fields),
- 'key': searchfield,
- 'mcond':get_match_cond(doctype)
- }), {
- 'txt': "%%%s%%" % txt,
- '_txt': txt.replace("%", ""),
- 'start': start,
- 'page_len': page_len
- })
+ limit %(start)s, %(page_len)s """.format(
+ **{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
+ ),
+ {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
+ )
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
- company_currency = erpnext.get_company_currency(filters.get('company'))
+ company_currency = erpnext.get_company_currency(filters.get("company"))
def get_accounts(with_account_type_filter):
- account_type_condition = ''
+ account_type_condition = ""
if with_account_type_filter:
account_type_condition = "AND account_type in %(account_types)s"
- accounts = frappe.db.sql("""
+ accounts = frappe.db.sql(
+ """
SELECT name, parent_account
FROM `tabAccount`
WHERE `tabAccount`.docstatus!=2
@@ -176,7 +170,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
""".format(
account_type_condition=account_type_condition,
searchfield=searchfield,
- mcond=get_match_cond(doctype)
+ mcond=get_match_cond(doctype),
),
dict(
account_types=filters.get("account_type"),
@@ -184,8 +178,8 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
currency=company_currency,
txt="%{}%".format(txt),
offset=start,
- limit=page_len
- )
+ limit=page_len,
+ ),
)
return accounts
@@ -206,7 +200,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if isinstance(filters, str):
filters = json.loads(filters)
- #Get searchfields from meta and use in Item Link field query
+ # Get searchfields from meta and use in Item Link field query
meta = frappe.get_meta("Item", cached=True)
searchfields = meta.get_search_fields()
@@ -216,49 +210,56 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if ignored_field in searchfields:
searchfields.remove(ignored_field)
- columns = ''
- extra_searchfields = [field for field in searchfields
- if not field in ["name", "item_group", "description", "item_name"]]
+ columns = ""
+ extra_searchfields = [
+ field
+ for field in searchfields
+ if not field in ["name", "item_group", "description", "item_name"]
+ ]
if extra_searchfields:
columns = ", " + ", ".join(extra_searchfields)
- searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"]
- if not field in searchfields]
+ searchfields = searchfields + [
+ field
+ for field in [searchfield or "name", "item_code", "item_group", "item_name"]
+ if not field in searchfields
+ ]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
if filters and isinstance(filters, dict):
- if filters.get('customer') or filters.get('supplier'):
- party = filters.get('customer') or filters.get('supplier')
- item_rules_list = frappe.get_all('Party Specific Item',
- filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
+ if filters.get("customer") or filters.get("supplier"):
+ party = filters.get("customer") or filters.get("supplier")
+ item_rules_list = frappe.get_all(
+ "Party Specific Item", filters={"party": party}, fields=["restrict_based_on", "based_on_value"]
+ )
filters_dict = {}
for rule in item_rules_list:
- if rule['restrict_based_on'] == 'Item':
- rule['restrict_based_on'] = 'name'
+ if rule["restrict_based_on"] == "Item":
+ rule["restrict_based_on"] = "name"
filters_dict[rule.restrict_based_on] = []
for rule in item_rules_list:
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
for filter in filters_dict:
- filters[scrub(filter)] = ['in', filters_dict[filter]]
+ filters[scrub(filter)] = ["in", filters_dict[filter]]
- if filters.get('customer'):
- del filters['customer']
+ if filters.get("customer"):
+ del filters["customer"]
else:
- del filters['supplier']
+ del filters["supplier"]
else:
- filters.pop('customer', None)
- filters.pop('supplier', None)
+ filters.pop("customer", None)
+ filters.pop("supplier", None)
-
- description_cond = ''
- if frappe.db.count('Item', cache=True) < 50000:
+ description_cond = ""
+ if frappe.db.count("Item", cache=True) < 50000:
# scan description only if items are less than 50000
- description_cond = 'or tabItem.description LIKE %(txt)s'
- return frappe.db.sql("""select
+ description_cond = "or tabItem.description LIKE %(txt)s"
+ return frappe.db.sql(
+ """select
tabItem.name, tabItem.item_name, tabItem.item_group,
if(length(tabItem.description) > 40, \
concat(substr(tabItem.description, 1, 40), "..."), description) as description
@@ -279,16 +280,19 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
limit %(start)s, %(page_len)s """.format(
columns=columns,
scond=searchfields,
- fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
- mcond=get_match_cond(doctype).replace('%', '%%'),
- description_cond = description_cond),
- {
- "today": nowdate(),
- "txt": "%%%s%%" % txt,
- "_txt": txt.replace("%", ""),
- "start": start,
- "page_len": page_len
- }, as_dict=as_dict)
+ fcond=get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
+ mcond=get_match_cond(doctype).replace("%", "%%"),
+ description_cond=description_cond,
+ ),
+ {
+ "today": nowdate(),
+ "txt": "%%%s%%" % txt,
+ "_txt": txt.replace("%", ""),
+ "start": start,
+ "page_len": page_len,
+ },
+ as_dict=as_dict,
+ )
@frappe.whitelist()
@@ -297,7 +301,8 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
conditions = []
fields = get_fields("BOM", ["name", "item"])
- return frappe.db.sql("""select {fields}
+ return frappe.db.sql(
+ """select {fields}
from tabBOM
where tabBOM.docstatus=1
and tabBOM.is_active=1
@@ -308,30 +313,35 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
idx desc, name
limit %(start)s, %(page_len)s """.format(
fields=", ".join(fields),
- fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
- mcond=get_match_cond(doctype).replace('%', '%%'),
- key=searchfield),
+ fcond=get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
+ mcond=get_match_cond(doctype).replace("%", "%%"),
+ key=searchfield,
+ ),
{
- 'txt': '%' + txt + '%',
- '_txt': txt.replace("%", ""),
- 'start': start or 0,
- 'page_len': page_len or 20
- })
+ "txt": "%" + txt + "%",
+ "_txt": txt.replace("%", ""),
+ "start": start or 0,
+ "page_len": page_len or 20,
+ },
+ )
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
- cond = ''
- if filters and filters.get('customer'):
+ cond = ""
+ if filters and filters.get("customer"):
cond = """(`tabProject`.customer = %s or
- ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
+ ifnull(`tabProject`.customer,"")="") and""" % (
+ frappe.db.escape(filters.get("customer"))
+ )
fields = get_fields("Project", ["name", "project_name"])
searchfields = frappe.get_meta("Project").get_search_fields()
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
- return frappe.db.sql("""select {fields} from `tabProject`
+ return frappe.db.sql(
+ """select {fields} from `tabProject`
where
`tabProject`.status not in ("Completed", "Cancelled")
and {cond} {scond} {match_cond}
@@ -340,15 +350,15 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
idx desc,
`tabProject`.name asc
limit {start}, {page_len}""".format(
- fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
+ fields=", ".join(["`tabProject`.{0}".format(f) for f in fields]),
cond=cond,
scond=searchfields,
match_cond=get_match_cond(doctype),
start=start,
- page_len=page_len), {
- "txt": "%{0}%".format(txt),
- "_txt": txt.replace('%', '')
- })
+ page_len=page_len,
+ ),
+ {"txt": "%{0}%".format(txt), "_txt": txt.replace("%", "")},
+ )
@frappe.whitelist()
@@ -356,7 +366,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select %(fields)s
from `tabDelivery Note`
where `tabDelivery Note`.`%(key)s` like %(txt)s and
@@ -371,15 +382,19 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
)
)
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
- """ % {
- "fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
- "key": searchfield,
- "fcond": get_filters_cond(doctype, filters, []),
- "mcond": get_match_cond(doctype),
- "start": start,
- "page_len": page_len,
- "txt": "%(txt)s"
- }, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict)
+ """
+ % {
+ "fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
+ "key": searchfield,
+ "fcond": get_filters_cond(doctype, filters, []),
+ "mcond": get_match_cond(doctype),
+ "start": start,
+ "page_len": page_len,
+ "txt": "%(txt)s",
+ },
+ {"txt": ("%%%s%%" % txt)},
+ as_dict=as_dict,
+ )
@frappe.whitelist()
@@ -391,12 +406,12 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
batch_nos = None
args = {
- 'item_code': filters.get("item_code"),
- 'warehouse': filters.get("warehouse"),
- 'posting_date': filters.get('posting_date'),
- 'txt': "%{0}%".format(txt),
+ "item_code": filters.get("item_code"),
+ "warehouse": filters.get("warehouse"),
+ "posting_date": filters.get("posting_date"),
+ "txt": "%{0}%".format(txt),
"start": start,
- "page_len": page_len
+ "page_len": page_len,
}
having_clause = "having sum(sle.actual_qty) > 0"
@@ -406,20 +421,21 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
meta = frappe.get_meta("Batch", cached=True)
searchfields = meta.get_search_fields()
- search_columns = ''
- search_cond = ''
+ search_columns = ""
+ search_cond = ""
if searchfields:
search_columns = ", " + ", ".join(searchfields)
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
- if args.get('warehouse'):
- searchfields = ['batch.' + field for field in searchfields]
+ if args.get("warehouse"):
+ searchfields = ["batch." + field for field in searchfields]
if searchfields:
search_columns = ", " + ", ".join(searchfields)
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
- batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
+ batch_nos = frappe.db.sql(
+ """select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
{search_columns}
from `tabStock Ledger Entry` sle
@@ -439,16 +455,19 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
group by batch_no {having_clause}
order by batch.expiry_date, sle.batch_no desc
limit %(start)s, %(page_len)s""".format(
- search_columns = search_columns,
+ search_columns=search_columns,
cond=cond,
match_conditions=get_match_cond(doctype),
- having_clause = having_clause,
- search_cond = search_cond
- ), args)
+ having_clause=having_clause,
+ search_cond=search_cond,
+ ),
+ args,
+ )
return batch_nos
else:
- return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
+ return frappe.db.sql(
+ """select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
{search_columns}
from `tabBatch` batch
where batch.disabled = 0
@@ -462,8 +481,14 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
{match_conditions}
order by expiry_date, name desc
- limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns,
- search_cond = search_cond, match_conditions=get_match_cond(doctype)), args)
+ limit %(start)s, %(page_len)s""".format(
+ cond,
+ search_columns=search_columns,
+ search_cond=search_cond,
+ match_conditions=get_match_cond(doctype),
+ ),
+ args,
+ )
@frappe.whitelist()
@@ -486,25 +511,33 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
if searchfield and txt:
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
- return frappe.desk.reportview.execute("Account", filters = filter_list,
- fields = ["name", "parent_account"],
- limit_start=start, limit_page_length=page_len, as_list=True)
+ return frappe.desk.reportview.execute(
+ "Account",
+ filters=filter_list,
+ fields=["name", "parent_account"],
+ limit_start=start,
+ limit_page_length=page_len,
+ as_list=True,
+ )
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
+ return frappe.db.sql(
+ """select distinct bo.name, bo.blanket_order_type, bo.to_date
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
where
boi.parent = bo.name
and boi.item_code = {item_code}
and bo.blanket_order_type = '{blanket_order_type}'
and bo.company = {company}
- and bo.docstatus = 1"""
- .format(item_code = frappe.db.escape(filters.get("item")),
- blanket_order_type = filters.get("blanket_order_type"),
- company = frappe.db.escape(filters.get("company"))
- ))
+ and bo.docstatus = 1""".format(
+ item_code=frappe.db.escape(filters.get("item")),
+ blanket_order_type=filters.get("blanket_order_type"),
+ company=frappe.db.escape(filters.get("company")),
+ )
+ )
@frappe.whitelist()
@@ -515,23 +548,26 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
# income account can be any Credit account,
# but can also be a Asset account with account_type='Income Account' in special circumstances.
# Hence the first condition is an "OR"
- if not filters: filters = {}
+ if not filters:
+ filters = {}
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
- return frappe.db.sql("""select tabAccount.name from `tabAccount`
+ return frappe.db.sql(
+ """select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Income Account", "Temporary"))
and tabAccount.is_group=0
and tabAccount.`{key}` LIKE %(txt)s
{condition} {match_condition}
- order by idx desc, name"""
- .format(condition=condition, match_condition=get_match_cond(doctype), key=searchfield), {
- 'txt': '%' + txt + '%',
- 'company': filters.get("company", "")
- })
+ order by idx desc, name""".format(
+ condition=condition, match_condition=get_match_cond(doctype), key=searchfield
+ ),
+ {"txt": "%" + txt + "%", "company": filters.get("company", "")},
+ )
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -539,68 +575,73 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
+
dimension_filters = get_dimension_filter_map()
- dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
+ dimension_filters = dimension_filters.get((filters.get("dimension"), filters.get("account")))
query_filters = []
or_filters = []
- fields = ['name']
+ fields = ["name"]
searchfields = frappe.get_meta(doctype).get_search_fields()
meta = frappe.get_meta(doctype)
if meta.is_tree:
- query_filters.append(['is_group', '=', 0])
+ query_filters.append(["is_group", "=", 0])
- if meta.has_field('disabled'):
- query_filters.append(['disabled', '!=', 1])
+ if meta.has_field("disabled"):
+ query_filters.append(["disabled", "!=", 1])
- if meta.has_field('company'):
- query_filters.append(['company', '=', filters.get('company')])
+ if meta.has_field("company"):
+ query_filters.append(["company", "=", filters.get("company")])
for field in searchfields:
- or_filters.append([field, 'LIKE', "%%%s%%" % txt])
+ or_filters.append([field, "LIKE", "%%%s%%" % txt])
fields.append(field)
if dimension_filters:
- if dimension_filters['allow_or_restrict'] == 'Allow':
- query_selector = 'in'
+ if dimension_filters["allow_or_restrict"] == "Allow":
+ query_selector = "in"
else:
- query_selector = 'not in'
+ query_selector = "not in"
- if len(dimension_filters['allowed_dimensions']) == 1:
- dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
+ if len(dimension_filters["allowed_dimensions"]) == 1:
+ dimensions = tuple(dimension_filters["allowed_dimensions"] * 2)
else:
- dimensions = tuple(dimension_filters['allowed_dimensions'])
+ dimensions = tuple(dimension_filters["allowed_dimensions"])
- query_filters.append(['name', query_selector, dimensions])
+ query_filters.append(["name", query_selector, dimensions])
- output = frappe.get_list(doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1)
+ output = frappe.get_list(
+ doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1
+ )
return [tuple(d) for d in set(output)]
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
- if not filters: filters = {}
+ if not filters:
+ filters = {}
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
- return frappe.db.sql("""select tabAccount.name from `tabAccount`
+ return frappe.db.sql(
+ """select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
and tabAccount.is_group=0
and tabAccount.docstatus!=2
and tabAccount.{key} LIKE %(txt)s
- {condition} {match_condition}"""
- .format(condition=condition, key=searchfield,
- match_condition=get_match_cond(doctype)), {
- 'company': filters.get("company", ""),
- 'txt': '%' + txt + '%'
- })
+ {condition} {match_condition}""".format(
+ condition=condition, key=searchfield, match_condition=get_match_cond(doctype)
+ ),
+ {"company": filters.get("company", ""), "txt": "%" + txt + "%"},
+ )
@frappe.whitelist()
@@ -621,14 +662,16 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
limit
{start}, {page_len}
""".format(
- bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True),
- key=searchfield,
- fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
- mcond=get_match_cond(doctype),
- start=start,
- page_len=page_len,
- txt=frappe.db.escape('%{0}%'.format(txt))
- )
+ bin_conditions=get_filters_cond(
+ doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
+ ),
+ key=searchfield,
+ fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
+ mcond=get_match_cond(doctype),
+ start=start,
+ page_len=page_len,
+ txt=frappe.db.escape("%{0}%".format(txt)),
+ )
return frappe.db.sql(query)
@@ -647,10 +690,12 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
query = """select batch_id from `tabBatch`
where disabled = 0
and (expiry_date >= CURDATE() or expiry_date IS NULL)
- and name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
+ and name like {txt}""".format(
+ txt=frappe.db.escape("%{0}%".format(txt))
+ )
- if filters and filters.get('item'):
- query += " and item = {item}".format(item = frappe.db.escape(filters.get('item')))
+ if filters and filters.get("item"):
+ query += " and item = {item}".format(item=frappe.db.escape(filters.get("item")))
return frappe.db.sql(query, filters)
@@ -659,8 +704,8 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
@frappe.validate_and_sanitize_search_inputs
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
item_filters = [
- ['manufacturer', 'like', '%' + txt + '%'],
- ['item_code', '=', filters.get("item_code")]
+ ["manufacturer", "like", "%" + txt + "%"],
+ ["item_code", "=", filters.get("item_code")],
]
item_manufacturers = frappe.get_all(
@@ -669,7 +714,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
filters=item_filters,
limit_start=start,
limit_page_length=page_len,
- as_list=1
+ as_list=1,
)
return item_manufacturers
@@ -681,10 +726,14 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
select pr.name
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem
where pr.docstatus = 1 and pritem.parent = pr.name
- and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
+ and pr.name like {txt}""".format(
+ txt=frappe.db.escape("%{0}%".format(txt))
+ )
- if filters and filters.get('item_code'):
- query += " and pritem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
+ if filters and filters.get("item_code"):
+ query += " and pritem.item_code = {item_code}".format(
+ item_code=frappe.db.escape(filters.get("item_code"))
+ )
return frappe.db.sql(query, filters)
@@ -696,10 +745,14 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
select pi.name
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem
where pi.docstatus = 1 and piitem.parent = pi.name
- and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
+ and pi.name like {txt}""".format(
+ txt=frappe.db.escape("%{0}%".format(txt))
+ )
- if filters and filters.get('item_code'):
- query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
+ if filters and filters.get("item_code"):
+ query += " and piitem.item_code = {item_code}".format(
+ item_code=frappe.db.escape(filters.get("item_code"))
+ )
return frappe.db.sql(query, filters)
@@ -708,27 +761,29 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
@frappe.validate_and_sanitize_search_inputs
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
- item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
- item_group = filters.get('item_group')
- company = filters.get('company')
+ item_doc = frappe.get_cached_doc("Item", filters.get("item_code"))
+ item_group = filters.get("item_group")
+ company = filters.get("company")
taxes = item_doc.taxes or []
while item_group:
- item_group_doc = frappe.get_cached_doc('Item Group', item_group)
+ item_group_doc = frappe.get_cached_doc("Item Group", item_group)
taxes += item_group_doc.taxes or []
item_group = item_group_doc.parent_item_group
if not taxes:
- return frappe.get_all('Item Tax Template', filters={'disabled': 0, 'company': company}, as_list=True)
+ return frappe.get_all(
+ "Item Tax Template", filters={"disabled": 0, "company": company}, as_list=True
+ )
else:
- valid_from = filters.get('valid_from')
+ valid_from = filters.get("valid_from")
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
args = {
- 'item_code': filters.get('item_code'),
- 'posting_date': valid_from,
- 'tax_category': filters.get('tax_category'),
- 'company': company
+ "item_code": filters.get("item_code"),
+ "posting_date": valid_from,
+ "tax_category": filters.get("tax_category"),
+ "company": company,
}
taxes = _get_item_tax_template(args, taxes, for_validate=True)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index d6296eb589..bdde3a1fd8 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -11,7 +11,9 @@ import erpnext
from erpnext.stock.utils import get_incoming_rate
-class StockOverReturnError(frappe.ValidationError): pass
+class StockOverReturnError(frappe.ValidationError):
+ pass
+
def validate_return(doc):
if not doc.meta.get_field("is_return") or not doc.is_return:
@@ -21,32 +23,50 @@ def validate_return(doc):
validate_return_against(doc)
validate_returned_items(doc)
+
def validate_return_against(doc):
if not frappe.db.exists(doc.doctype, doc.return_against):
- frappe.throw(_("Invalid {0}: {1}")
- .format(doc.meta.get_label("return_against"), doc.return_against))
+ frappe.throw(
+ _("Invalid {0}: {1}").format(doc.meta.get_label("return_against"), doc.return_against)
+ )
else:
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
- if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
+ if (
+ ref_doc.company == doc.company
+ and ref_doc.get(party_type) == doc.get(party_type)
+ and ref_doc.docstatus == 1
+ ):
# validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
- ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
+ ref_posting_datetime = "%s %s" % (
+ ref_doc.posting_date,
+ ref_doc.get("posting_time") or "00:00:00",
+ )
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
- frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
+ frappe.throw(
+ _("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime))
+ )
# validate same exchange rate
if doc.conversion_rate != ref_doc.conversion_rate:
- frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
- .format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
+ frappe.throw(
+ _("Exchange Rate must be same as {0} {1} ({2})").format(
+ doc.doctype, doc.return_against, ref_doc.conversion_rate
+ )
+ )
# validate update stock
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
- frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
- .format(doc.return_against))
+ frappe.throw(
+ _("'Update Stock' can not be checked because items are not delivered via {0}").format(
+ doc.return_against
+ )
+ )
+
def validate_returned_items(doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -54,43 +74,61 @@ def validate_returned_items(doc):
valid_items = frappe._dict()
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
- if doc.doctype != 'Purchase Invoice':
+ if doc.doctype != "Purchase Invoice":
select_fields += ",serial_no, batch_no"
- if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
+ if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
select_fields += ",rejected_qty, received_qty"
- for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s"""
- .format(select_fields, doc.doctype), doc.return_against, as_dict=1):
- valid_items = get_ref_item_dict(valid_items, d)
+ for d in frappe.db.sql(
+ """select {0} from `tab{1} Item` where parent = %s""".format(select_fields, doc.doctype),
+ doc.return_against,
+ as_dict=1,
+ ):
+ valid_items = get_ref_item_dict(valid_items, d)
if doc.doctype in ("Delivery Note", "Sales Invoice"):
- for d in frappe.db.sql("""select item_code, qty, serial_no, batch_no from `tabPacked Item`
- where parent = %s""", doc.return_against, as_dict=1):
- valid_items = get_ref_item_dict(valid_items, d)
+ for d in frappe.db.sql(
+ """select item_code, qty, serial_no, batch_no from `tabPacked Item`
+ where parent = %s""",
+ doc.return_against,
+ as_dict=1,
+ ):
+ valid_items = get_ref_item_dict(valid_items, d)
already_returned_items = get_already_returned_items(doc)
# ( not mandatory when it is Purchase Invoice or a Sales Invoice without Update Stock )
- warehouse_mandatory = not ((doc.doctype=="Purchase Invoice" or doc.doctype=="Sales Invoice") and not doc.update_stock)
+ warehouse_mandatory = not (
+ (doc.doctype == "Purchase Invoice" or doc.doctype == "Sales Invoice") and not doc.update_stock
+ )
items_returned = False
for d in doc.get("items"):
- if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
+ if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0):
if d.item_code not in valid_items:
- frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}")
- .format(d.idx, d.item_code, doc.doctype, doc.return_against))
+ frappe.throw(
+ _("Row # {0}: Returned Item {1} does not exist in {2} {3}").format(
+ d.idx, d.item_code, doc.doctype, doc.return_against
+ )
+ )
else:
ref = valid_items.get(d.item_code, frappe._dict())
validate_quantity(doc, d, ref, valid_items, already_returned_items)
if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt(d.rate) > ref.rate:
- frappe.throw(_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}")
- .format(d.idx, doc.doctype, doc.return_against))
+ frappe.throw(
+ _("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
+ d.idx, doc.doctype, doc.return_against
+ )
+ )
elif ref.batch_no and d.batch_no not in ref.batch_no:
- frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
- .format(d.idx, doc.doctype, doc.return_against))
+ frappe.throw(
+ _("Row # {0}: Batch No must be same as {1} {2}").format(
+ d.idx, doc.doctype, doc.return_against
+ )
+ )
elif ref.serial_no:
if not d.serial_no:
@@ -99,11 +137,16 @@ def validate_returned_items(doc):
serial_nos = get_serial_nos(d.serial_no)
for s in serial_nos:
if s not in ref.serial_no:
- frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}")
- .format(d.idx, s, doc.doctype, doc.return_against))
+ frappe.throw(
+ _("Row # {0}: Serial No {1} does not match with {2} {3}").format(
+ d.idx, s, doc.doctype, doc.return_against
+ )
+ )
- if (warehouse_mandatory and not d.get("warehouse") and
- frappe.db.get_value("Item", d.item_code, "is_stock_item")
+ if (
+ warehouse_mandatory
+ and not d.get("warehouse")
+ and frappe.db.get_value("Item", d.item_code, "is_stock_item")
):
frappe.throw(_("Warehouse is mandatory"))
@@ -115,21 +158,23 @@ def validate_returned_items(doc):
if not items_returned:
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
+
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
- fields = ['stock_qty']
- if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']:
- fields.extend(['received_qty', 'rejected_qty'])
+ fields = ["stock_qty"]
+ if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]:
+ fields.extend(["received_qty", "rejected_qty"])
already_returned_data = already_returned_items.get(args.item_code) or {}
company_currency = erpnext.get_company_currency(doc.company)
- stock_qty_precision = get_field_precision(frappe.get_meta(doc.doctype + " Item")
- .get_field("stock_qty"), company_currency)
+ stock_qty_precision = get_field_precision(
+ frappe.get_meta(doc.doctype + " Item").get_field("stock_qty"), company_currency
+ )
for column in fields:
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
- if column == 'stock_qty':
+ if column == "stock_qty":
reference_qty = ref.get(column)
current_stock_qty = args.get(column)
else:
@@ -137,38 +182,49 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
max_returnable_qty = flt(reference_qty, stock_qty_precision) - returned_qty
- label = column.replace('_', ' ').title()
+ label = column.replace("_", " ").title()
if reference_qty:
if flt(args.get(column)) > 0:
frappe.throw(_("{0} must be negative in return document").format(label))
elif returned_qty >= reference_qty and args.get(column):
- frappe.throw(_("Item {0} has already been returned")
- .format(args.item_code), StockOverReturnError)
+ frappe.throw(
+ _("Item {0} has already been returned").format(args.item_code), StockOverReturnError
+ )
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
- frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
- .format(args.idx, max_returnable_qty, args.item_code), StockOverReturnError)
+ frappe.throw(
+ _("Row # {0}: Cannot return more than {1} for Item {2}").format(
+ args.idx, max_returnable_qty, args.item_code
+ ),
+ StockOverReturnError,
+ )
+
def get_ref_item_dict(valid_items, ref_item_row):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
- valid_items.setdefault(ref_item_row.item_code, frappe._dict({
- "qty": 0,
- "rate": 0,
- "stock_qty": 0,
- "rejected_qty": 0,
- "received_qty": 0,
- "serial_no": [],
- "conversion_factor": ref_item_row.get("conversion_factor", 1),
- "batch_no": []
- }))
+ valid_items.setdefault(
+ ref_item_row.item_code,
+ frappe._dict(
+ {
+ "qty": 0,
+ "rate": 0,
+ "stock_qty": 0,
+ "rejected_qty": 0,
+ "received_qty": 0,
+ "serial_no": [],
+ "conversion_factor": ref_item_row.get("conversion_factor", 1),
+ "batch_no": [],
+ }
+ ),
+ )
item_dict = valid_items[ref_item_row.item_code]
item_dict["qty"] += ref_item_row.qty
- item_dict["stock_qty"] += ref_item_row.get('stock_qty', 0)
+ item_dict["stock_qty"] += ref_item_row.get("stock_qty", 0)
if ref_item_row.get("rate", 0) > item_dict["rate"]:
item_dict["rate"] = ref_item_row.get("rate", 0)
- if ref_item_row.parenttype in ['Purchase Invoice', 'Purchase Receipt']:
+ if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt"]:
item_dict["received_qty"] += ref_item_row.received_qty
item_dict["rejected_qty"] += ref_item_row.rejected_qty
@@ -180,13 +236,15 @@ def get_ref_item_dict(valid_items, ref_item_row):
return valid_items
+
def get_already_returned_items(doc):
- column = 'child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty'
- if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
+ column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty"
+ if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
select {0}
from
`tab{1} Item` child, `tab{2}` par
@@ -194,62 +252,79 @@ def get_already_returned_items(doc):
child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s
group by item_code
- """.format(column, doc.doctype, doc.doctype), doc.return_against, as_dict=1)
+ """.format(
+ column, doc.doctype, doc.doctype
+ ),
+ doc.return_against,
+ as_dict=1,
+ )
items = {}
for d in data:
- items.setdefault(d.item_code, frappe._dict({
- "qty": d.get("qty"),
- "stock_qty": d.get("stock_qty"),
- "received_qty": d.get("received_qty"),
- "rejected_qty": d.get("rejected_qty")
- }))
+ items.setdefault(
+ d.item_code,
+ frappe._dict(
+ {
+ "qty": d.get("qty"),
+ "stock_qty": d.get("stock_qty"),
+ "received_qty": d.get("received_qty"),
+ "rejected_qty": d.get("rejected_qty"),
+ }
+ ),
+ )
return items
+
def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
child_doctype = doctype + " Item"
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
- if doctype in ('Purchase Receipt', 'Purchase Invoice'):
- party_type = 'supplier'
+ if doctype in ("Purchase Receipt", "Purchase Invoice"):
+ party_type = "supplier"
else:
- party_type = 'customer'
+ party_type = "customer"
fields = [
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
- "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
+ "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype),
]
if doctype in ("Purchase Receipt", "Purchase Invoice"):
fields += [
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
- "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype)
+ "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
]
if doctype == "Purchase Receipt":
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
# Used retrun against and supplier and is_retrun because there is an index added for it
- data = frappe.db.get_list(doctype,
- fields = fields,
- filters = [
+ data = frappe.db.get_list(
+ doctype,
+ fields=fields,
+ filters=[
[doctype, "return_against", "=", return_against],
[doctype, party_type, "=", party],
[doctype, "docstatus", "=", 1],
[doctype, "is_return", "=", 1],
- [child_doctype, reference_field, "=", row_name]
- ])
+ [child_doctype, reference_field, "=", row_name],
+ ],
+ )
return data[0]
+
def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
company = frappe.db.get_value("Delivery Note", source_name, "company")
- default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
+ default_warehouse_for_sales_return = frappe.db.get_value(
+ "Company", company, "default_warehouse_for_sales_return"
+ )
def set_missing_values(source, target):
doc = frappe.get_doc(target)
@@ -273,29 +348,34 @@ def make_return_doc(doctype, source_name, target_doc=None):
tax.tax_amount = -1 * tax.tax_amount
if doc.get("is_return"):
- if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
+ if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
doc.consolidated_invoice = ""
- doc.set('payments', [])
+ doc.set("payments", [])
for data in source.payments:
paid_amount = 0.00
base_paid_amount = 0.00
- data.base_amount = flt(data.amount*source.conversion_rate, source.precision("base_paid_amount"))
+ data.base_amount = flt(
+ data.amount * source.conversion_rate, source.precision("base_paid_amount")
+ )
paid_amount += data.amount
base_paid_amount += data.base_amount
- doc.append('payments', {
- 'mode_of_payment': data.mode_of_payment,
- 'type': data.type,
- 'amount': -1 * paid_amount,
- 'base_amount': -1 * base_paid_amount,
- 'account': data.account,
- 'default': data.default
- })
+ doc.append(
+ "payments",
+ {
+ "mode_of_payment": data.mode_of_payment,
+ "type": data.type,
+ "amount": -1 * paid_amount,
+ "base_amount": -1 * base_paid_amount,
+ "account": data.account,
+ "default": data.default,
+ },
+ )
if doc.is_pos:
doc.paid_amount = -1 * source.paid_amount
- elif doc.doctype == 'Purchase Invoice':
+ elif doc.doctype == "Purchase Invoice":
doc.paid_amount = -1 * source.paid_amount
doc.base_paid_amount = -1 * source.base_paid_amount
- doc.payment_terms_template = ''
+ doc.payment_terms_template = ""
doc.payment_schedule = []
if doc.get("is_return") and hasattr(doc, "packed_items"):
@@ -312,16 +392,24 @@ def make_return_doc(doctype, source_name, target_doc=None):
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
if serial_nos:
- target_doc.serial_no = '\n'.join(serial_nos)
+ target_doc.serial_no = "\n".join(serial_nos)
if doctype == "Purchase Receipt":
- returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
- target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
- target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
- target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+ returned_qty_map = get_returned_qty_map_for_row(
+ source_parent.name, source_parent.supplier, source_doc.name, doctype
+ )
+ target_doc.received_qty = -1 * flt(
+ source_doc.received_qty - (returned_qty_map.get("received_qty") or 0)
+ )
+ target_doc.rejected_qty = -1 * flt(
+ source_doc.rejected_qty - (returned_qty_map.get("rejected_qty") or 0)
+ )
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
- target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
- target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
+ target_doc.received_stock_qty = -1 * flt(
+ source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0)
+ )
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_order_item = source_doc.purchase_order_item
@@ -329,12 +417,18 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice":
- returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
- target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
- target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
- target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+ returned_qty_map = get_returned_qty_map_for_row(
+ source_parent.name, source_parent.supplier, source_doc.name, doctype
+ )
+ target_doc.received_qty = -1 * flt(
+ source_doc.received_qty - (returned_qty_map.get("received_qty") or 0)
+ )
+ target_doc.rejected_qty = -1 * flt(
+ source_doc.rejected_qty - (returned_qty_map.get("rejected_qty") or 0)
+ )
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
- target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_receipt = source_doc.purchase_receipt
target_doc.rejected_warehouse = source_doc.rejected_warehouse
@@ -343,9 +437,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note":
- returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
- target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
- target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+ returned_qty_map = get_returned_qty_map_for_row(
+ source_parent.name, source_parent.customer, source_doc.name, doctype
+ )
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice
@@ -356,9 +452,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice" or doctype == "POS Invoice":
- returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
- target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
- target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+ returned_qty_map = get_returned_qty_map_for_row(
+ source_parent.name, source_parent.customer, source_doc.name, doctype
+ )
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note
@@ -377,41 +475,56 @@ def make_return_doc(doctype, source_name, target_doc=None):
def update_terms(source_doc, target_doc, source_parent):
target_doc.payment_amount = -source_doc.payment_amount
- doclist = get_mapped_doc(doctype, source_name, {
- doctype: {
- "doctype": doctype,
-
- "validation": {
- "docstatus": ["=", 1],
- }
- },
- doctype +" Item": {
- "doctype": doctype + " Item",
- "field_map": {
- "serial_no": "serial_no",
- "batch_no": "batch_no"
+ doclist = get_mapped_doc(
+ doctype,
+ source_name,
+ {
+ doctype: {
+ "doctype": doctype,
+ "validation": {
+ "docstatus": ["=", 1],
+ },
},
- "postprocess": update_item
+ doctype
+ + " Item": {
+ "doctype": doctype + " Item",
+ "field_map": {"serial_no": "serial_no", "batch_no": "batch_no"},
+ "postprocess": update_item,
+ },
+ "Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},
},
- "Payment Schedule": {
- "doctype": "Payment Schedule",
- "postprocess": update_terms
- }
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
- doclist.set_onload('ignore_price_list', True)
+ doclist.set_onload("ignore_price_list", True)
return doclist
-def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
- item_row=None, voucher_detail_no=None, sle=None):
+
+def get_rate_for_return(
+ voucher_type,
+ voucher_no,
+ item_code,
+ return_against=None,
+ item_row=None,
+ voucher_detail_no=None,
+ sle=None,
+):
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
return_against_item_field = get_return_against_item_fields(voucher_type)
- filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
- return_against, item_code, return_against_item_field, item_row)
+ filters = get_filters(
+ voucher_type,
+ voucher_no,
+ voucher_detail_no,
+ return_against,
+ item_code,
+ return_against_item_field,
+ item_row,
+ )
if voucher_type in ("Purchase Receipt", "Purchase Invoice"):
select_field = "incoming_rate"
@@ -419,53 +532,66 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
select_field = "abs(stock_value_difference / actual_qty)"
rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
- if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
- rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
+ if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]:
+ rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate")
if not rate and sle:
- rate = get_incoming_rate({
- "item_code": sle.item_code,
- "warehouse": sle.warehouse,
- "posting_date": sle.get('posting_date'),
- "posting_time": sle.get('posting_time'),
- "qty": sle.actual_qty,
- "serial_no": sle.get('serial_no'),
- "batch_no": sle.get("batch_no"),
- "company": sle.company,
- "voucher_type": sle.voucher_type,
- "voucher_no": sle.voucher_no
- }, raise_error_if_no_rate=False)
+ rate = get_incoming_rate(
+ {
+ "item_code": sle.item_code,
+ "warehouse": sle.warehouse,
+ "posting_date": sle.get("posting_date"),
+ "posting_time": sle.get("posting_time"),
+ "qty": sle.actual_qty,
+ "serial_no": sle.get("serial_no"),
+ "batch_no": sle.get("batch_no"),
+ "company": sle.company,
+ "voucher_type": sle.voucher_type,
+ "voucher_no": sle.voucher_no,
+ },
+ raise_error_if_no_rate=False,
+ )
return rate
+
def get_return_against_item_fields(voucher_type):
return_against_item_fields = {
"Purchase Receipt": "purchase_receipt_item",
"Purchase Invoice": "purchase_invoice_item",
"Delivery Note": "dn_detail",
- "Sales Invoice": "sales_invoice_item"
+ "Sales Invoice": "sales_invoice_item",
}
return return_against_item_fields[voucher_type]
-def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, item_code, return_against_item_field, item_row):
- filters = {
- "voucher_type": voucher_type,
- "voucher_no": return_against,
- "item_code": item_code
- }
+
+def get_filters(
+ voucher_type,
+ voucher_no,
+ voucher_detail_no,
+ return_against,
+ item_code,
+ return_against_item_field,
+ item_row,
+):
+ filters = {"voucher_type": voucher_type, "voucher_no": return_against, "item_code": item_code}
if item_row:
reference_voucher_detail_no = item_row.get(return_against_item_field)
else:
- reference_voucher_detail_no = frappe.db.get_value(voucher_type + " Item", voucher_detail_no, return_against_item_field)
+ reference_voucher_detail_no = frappe.db.get_value(
+ voucher_type + " Item", voucher_detail_no, return_against_item_field
+ )
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
return filters
+
def get_returned_serial_nos(child_doc, parent_doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
return_ref_field = frappe.scrub(child_doc.doctype)
if child_doc.doctype == "Delivery Note Item":
return_ref_field = "dn_detail"
@@ -474,10 +600,14 @@ def get_returned_serial_nos(child_doc, parent_doc):
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
- filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1],
- [child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]]
+ filters = [
+ [parent_doc.doctype, "return_against", "=", parent_doc.name],
+ [parent_doc.doctype, "is_return", "=", 1],
+ [child_doc.doctype, return_ref_field, "=", child_doc.name],
+ [parent_doc.doctype, "docstatus", "=", 1],
+ ]
- for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters):
+ for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
serial_nos.extend(get_serial_nos(row.serial_no))
return serial_nos
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index e918cde7c4..7877827ac7 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -17,8 +17,7 @@ from erpnext.stock.utils import get_incoming_rate
class SellingController(StockController):
def get_feed(self):
- return _("To {0} | {1} {2}").format(self.customer_name, self.currency,
- self.grand_total)
+ return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total)
def onload(self):
super(SellingController, self).onload()
@@ -64,32 +63,43 @@ class SellingController(StockController):
if customer:
from erpnext.accounts.party import _get_party_details
+
fetch_payment_terms_template = False
- if (self.get("__islocal") or
- self.company != frappe.db.get_value(self.doctype, self.name, 'company')):
+ if self.get("__islocal") or self.company != frappe.db.get_value(
+ self.doctype, self.name, "company"
+ ):
fetch_payment_terms_template = True
- party_details = _get_party_details(customer,
+ party_details = _get_party_details(
+ customer,
ignore_permissions=self.flags.ignore_permissions,
- doctype=self.doctype, company=self.company,
- posting_date=self.get('posting_date'),
+ doctype=self.doctype,
+ company=self.company,
+ posting_date=self.get("posting_date"),
fetch_payment_terms_template=fetch_payment_terms_template,
- party_address=self.customer_address, shipping_address=self.shipping_address_name,
- company_address=self.get('company_address'))
+ party_address=self.customer_address,
+ shipping_address=self.shipping_address_name,
+ company_address=self.get("company_address"),
+ )
if not self.meta.get_field("sales_team"):
party_details.pop("sales_team")
self.update_if_missing(party_details)
elif lead:
from erpnext.crm.doctype.lead.lead import get_lead_details
- self.update_if_missing(get_lead_details(lead,
- posting_date=self.get('transaction_date') or self.get('posting_date'),
- company=self.company))
- if self.get('taxes_and_charges') and not self.get('taxes') and not for_validate:
- taxes = get_taxes_and_charges('Sales Taxes and Charges Template', self.taxes_and_charges)
+ self.update_if_missing(
+ get_lead_details(
+ lead,
+ posting_date=self.get("transaction_date") or self.get("posting_date"),
+ company=self.company,
+ )
+ )
+
+ if self.get("taxes_and_charges") and not self.get("taxes") and not for_validate:
+ taxes = get_taxes_and_charges("Sales Taxes and Charges Template", self.taxes_and_charges)
for tax in taxes:
- self.append('taxes', tax)
+ self.append("taxes", tax)
def set_price_list_and_item_details(self, for_validate=False):
self.set_price_list_currency("Selling")
@@ -98,12 +108,15 @@ class SellingController(StockController):
def remove_shipping_charge(self):
if self.shipping_rule:
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
- existing_shipping_charge = self.get("taxes", {
- "doctype": "Sales Taxes and Charges",
- "charge_type": "Actual",
- "account_head": shipping_rule.account,
- "cost_center": shipping_rule.cost_center
- })
+ existing_shipping_charge = self.get(
+ "taxes",
+ {
+ "doctype": "Sales Taxes and Charges",
+ "charge_type": "Actual",
+ "account_head": shipping_rule.account,
+ "cost_center": shipping_rule.cost_center,
+ },
+ )
if existing_shipping_charge:
self.get("taxes").remove(existing_shipping_charge[-1])
self.calculate_taxes_and_totals()
@@ -112,8 +125,9 @@ class SellingController(StockController):
from frappe.utils import money_in_words
if self.meta.get_field("base_in_words"):
- base_amount = abs(self.base_grand_total
- if self.is_rounded_total_disabled() else self.base_rounded_total)
+ base_amount = abs(
+ self.base_grand_total if self.is_rounded_total_disabled() else self.base_rounded_total
+ )
self.base_in_words = money_in_words(base_amount, self.company_currency)
if self.meta.get_field("in_words"):
@@ -124,15 +138,15 @@ class SellingController(StockController):
if not self.meta.get_field("commission_rate"):
return
- self.round_floats_in(
- self, ("amount_eligible_for_commission", "commission_rate")
- )
+ self.round_floats_in(self, ("amount_eligible_for_commission", "commission_rate"))
if not (0 <= self.commission_rate <= 100.0):
- throw("{} {}".format(
- _(self.meta.get_label("commission_rate")),
- _("must be between 0 and 100"),
- ))
+ throw(
+ "{} {}".format(
+ _(self.meta.get_label("commission_rate")),
+ _("must be between 0 and 100"),
+ )
+ )
self.amount_eligible_for_commission = sum(
item.base_net_amount for item in self.items if item.grant_commission
@@ -140,7 +154,7 @@ class SellingController(StockController):
self.total_commission = flt(
self.amount_eligible_for_commission * self.commission_rate / 100.0,
- self.precision("total_commission")
+ self.precision("total_commission"),
)
def calculate_contribution(self):
@@ -154,12 +168,14 @@ class SellingController(StockController):
sales_person.allocated_amount = flt(
self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0,
- self.precision("allocated_amount", sales_person))
+ self.precision("allocated_amount", sales_person),
+ )
if sales_person.commission_rate:
sales_person.incentives = flt(
sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
- self.precision("incentives", sales_person))
+ self.precision("incentives", sales_person),
+ )
total += sales_person.allocated_percentage
@@ -183,25 +199,29 @@ class SellingController(StockController):
def validate_selling_price(self):
def throw_message(idx, item_name, rate, ref_rate_field):
- throw(_("""Row #{0}: Selling rate for item {1} is lower than its {2}.
+ throw(
+ _(
+ """Row #{0}: Selling rate for item {1} is lower than its {2}.
Selling {3} should be atleast {4}.
Alternatively,
you can disable selling price validation in {5} to bypass
- this validation.""").format(
- idx,
- bold(item_name),
- bold(ref_rate_field),
- bold("net rate"),
- bold(rate),
- get_link_to_form("Selling Settings", "Selling Settings"),
- ), title=_("Invalid Selling Price"))
+ this validation."""
+ ).format(
+ idx,
+ bold(item_name),
+ bold(ref_rate_field),
+ bold("net rate"),
+ bold(rate),
+ get_link_to_form("Selling Settings", "Selling Settings"),
+ ),
+ title=_("Invalid Selling Price"),
+ )
- if (
- self.get("is_return")
- or not frappe.db.get_single_value("Selling Settings", "validate_selling_price")
+ if self.get("is_return") or not frappe.db.get_single_value(
+ "Selling Settings", "validate_selling_price"
):
return
- is_internal_customer = self.get('is_internal_customer')
+ is_internal_customer = self.get("is_internal_customer")
valuation_rate_map = {}
for item in self.items:
@@ -212,17 +232,10 @@ class SellingController(StockController):
"Item", item.item_code, ("last_purchase_rate", "is_stock_item")
)
- last_purchase_rate_in_sales_uom = (
- last_purchase_rate * (item.conversion_factor or 1)
- )
+ last_purchase_rate_in_sales_uom = last_purchase_rate * (item.conversion_factor or 1)
if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
- throw_message(
- item.idx,
- item.item_name,
- last_purchase_rate_in_sales_uom,
- "last purchase rate"
- )
+ throw_message(item.idx, item.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")
if is_internal_customer or not is_stock_item:
continue
@@ -238,7 +251,8 @@ class SellingController(StockController):
for valuation_rate in valuation_rate_map
)
- valuation_rates = frappe.db.sql(f"""
+ valuation_rates = frappe.db.sql(
+ f"""
select
item_code, warehouse, valuation_rate
from
@@ -246,7 +260,9 @@ class SellingController(StockController):
where
({" or ".join(or_conditions)})
and valuation_rate > 0
- """, as_dict=True)
+ """,
+ as_dict=True,
+ )
for rate in valuation_rates:
valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
@@ -255,24 +271,15 @@ class SellingController(StockController):
if not item.item_code or item.is_free_item:
continue
- last_valuation_rate = valuation_rate_map.get(
- (item.item_code, item.warehouse)
- )
+ last_valuation_rate = valuation_rate_map.get((item.item_code, item.warehouse))
if not last_valuation_rate:
continue
- last_valuation_rate_in_sales_uom = (
- last_valuation_rate * (item.conversion_factor or 1)
- )
+ last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
- throw_message(
- item.idx,
- item.item_name,
- last_valuation_rate_in_sales_uom,
- "valuation rate"
- )
+ throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate")
def get_item_list(self):
il = []
@@ -284,68 +291,90 @@ class SellingController(StockController):
for p in self.get("packed_items"):
if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
# the packing details table's qty is already multiplied with parent's qty
- il.append(frappe._dict({
- 'warehouse': p.warehouse or d.warehouse,
- 'item_code': p.item_code,
- 'qty': flt(p.qty),
- 'uom': p.uom,
- 'batch_no': cstr(p.batch_no).strip(),
- 'serial_no': cstr(p.serial_no).strip(),
- 'name': d.name,
- 'target_warehouse': p.target_warehouse,
- 'company': self.company,
- 'voucher_type': self.doctype,
- 'allow_zero_valuation': d.allow_zero_valuation_rate,
- 'sales_invoice_item': d.get("sales_invoice_item"),
- 'dn_detail': d.get("dn_detail"),
- 'incoming_rate': p.get("incoming_rate")
- }))
+ il.append(
+ frappe._dict(
+ {
+ "warehouse": p.warehouse or d.warehouse,
+ "item_code": p.item_code,
+ "qty": flt(p.qty),
+ "uom": p.uom,
+ "batch_no": cstr(p.batch_no).strip(),
+ "serial_no": cstr(p.serial_no).strip(),
+ "name": d.name,
+ "target_warehouse": p.target_warehouse,
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "allow_zero_valuation": d.allow_zero_valuation_rate,
+ "sales_invoice_item": d.get("sales_invoice_item"),
+ "dn_detail": d.get("dn_detail"),
+ "incoming_rate": p.get("incoming_rate"),
+ }
+ )
+ )
else:
- il.append(frappe._dict({
- 'warehouse': d.warehouse,
- 'item_code': d.item_code,
- 'qty': d.stock_qty,
- 'uom': d.uom,
- 'stock_uom': d.stock_uom,
- 'conversion_factor': d.conversion_factor,
- 'batch_no': cstr(d.get("batch_no")).strip(),
- 'serial_no': cstr(d.get("serial_no")).strip(),
- 'name': d.name,
- 'target_warehouse': d.target_warehouse,
- 'company': self.company,
- 'voucher_type': self.doctype,
- 'allow_zero_valuation': d.allow_zero_valuation_rate,
- 'sales_invoice_item': d.get("sales_invoice_item"),
- 'dn_detail': d.get("dn_detail"),
- 'incoming_rate': d.get("incoming_rate")
- }))
+ il.append(
+ frappe._dict(
+ {
+ "warehouse": d.warehouse,
+ "item_code": d.item_code,
+ "qty": d.stock_qty,
+ "uom": d.uom,
+ "stock_uom": d.stock_uom,
+ "conversion_factor": d.conversion_factor,
+ "batch_no": cstr(d.get("batch_no")).strip(),
+ "serial_no": cstr(d.get("serial_no")).strip(),
+ "name": d.name,
+ "target_warehouse": d.target_warehouse,
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "allow_zero_valuation": d.allow_zero_valuation_rate,
+ "sales_invoice_item": d.get("sales_invoice_item"),
+ "dn_detail": d.get("dn_detail"),
+ "incoming_rate": d.get("incoming_rate"),
+ }
+ )
+ )
return il
def has_product_bundle(self, item_code):
- return frappe.db.sql("""select name from `tabProduct Bundle`
- where new_item_code=%s and docstatus != 2""", item_code)
+ return frappe.db.sql(
+ """select name from `tabProduct Bundle`
+ where new_item_code=%s and docstatus != 2""",
+ item_code,
+ )
def get_already_delivered_qty(self, current_docname, so, so_detail):
- delivered_via_dn = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item`
+ delivered_via_dn = frappe.db.sql(
+ """select sum(qty) from `tabDelivery Note Item`
where so_detail = %s and docstatus = 1
and against_sales_order = %s
- and parent != %s""", (so_detail, so, current_docname))
+ and parent != %s""",
+ (so_detail, so, current_docname),
+ )
- delivered_via_si = frappe.db.sql("""select sum(si_item.qty)
+ delivered_via_si = frappe.db.sql(
+ """select sum(si_item.qty)
from `tabSales Invoice Item` si_item, `tabSales Invoice` si
where si_item.parent = si.name and si.update_stock = 1
and si_item.so_detail = %s and si.docstatus = 1
and si_item.sales_order = %s
- and si.name != %s""", (so_detail, so, current_docname))
+ and si.name != %s""",
+ (so_detail, so, current_docname),
+ )
- total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) \
- + (flt(delivered_via_si[0][0]) if delivered_via_si else 0)
+ total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) + (
+ flt(delivered_via_si[0][0]) if delivered_via_si else 0
+ )
return total_delivered_qty
def get_so_qty_and_warehouse(self, so_detail):
- so_item = frappe.db.sql("""select qty, warehouse from `tabSales Order Item`
- where name = %s and docstatus = 1""", so_detail, as_dict=1)
+ so_item = frappe.db.sql(
+ """select qty, warehouse from `tabSales Order Item`
+ where name = %s and docstatus = 1""",
+ so_detail,
+ as_dict=1,
+ )
so_qty = so_item and flt(so_item[0]["qty"]) or 0.0
so_warehouse = so_item and so_item[0]["warehouse"] or ""
return so_qty, so_warehouse
@@ -371,8 +400,9 @@ class SellingController(StockController):
sales_order = frappe.get_doc("Sales Order", so)
if sales_order.status in ["Closed", "Cancelled"]:
- frappe.throw(_("{0} {1} is cancelled or closed").format(_("Sales Order"), so),
- frappe.InvalidStatusError)
+ frappe.throw(
+ _("{0} {1} is cancelled or closed").format(_("Sales Order"), so), frappe.InvalidStatusError
+ )
sales_order.update_reserved_qty(so_item_rows)
@@ -384,43 +414,52 @@ class SellingController(StockController):
for d in items:
if not self.get("return_against"):
# Get incoming rate based on original item cost based on valuation method
- qty = flt(d.get('stock_qty') or d.get('actual_qty'))
+ qty = flt(d.get("stock_qty") or d.get("actual_qty"))
if not (self.get("is_return") and d.incoming_rate):
- d.incoming_rate = get_incoming_rate({
- "item_code": d.item_code,
- "warehouse": d.warehouse,
- "posting_date": self.get('posting_date') or self.get('transaction_date'),
- "posting_time": self.get('posting_time') or nowtime(),
- "qty": qty if cint(self.get("is_return")) else (-1 * qty),
- "serial_no": d.get('serial_no'),
- "batch_no": d.get("batch_no"),
- "company": self.company,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "allow_zero_valuation": d.get("allow_zero_valuation")
- }, raise_error_if_no_rate=False)
+ d.incoming_rate = get_incoming_rate(
+ {
+ "item_code": d.item_code,
+ "warehouse": d.warehouse,
+ "posting_date": self.get("posting_date") or self.get("transaction_date"),
+ "posting_time": self.get("posting_time") or nowtime(),
+ "qty": qty if cint(self.get("is_return")) else (-1 * qty),
+ "serial_no": d.get("serial_no"),
+ "batch_no": d.get("batch_no"),
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "allow_zero_valuation": d.get("allow_zero_valuation"),
+ },
+ raise_error_if_no_rate=False,
+ )
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
if d.doctype == "Packed Item":
- incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision('incoming_rate'))
+ incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
- rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
+ rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
if d.rate != rate:
d.rate = rate
d.discount_percentage = 0
d.discount_amount = 0
- frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
- .format(d.idx), alert=1)
+ frappe.msgprint(
+ _(
+ "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
+ ).format(d.idx),
+ alert=1,
+ )
elif self.get("return_against"):
# Get incoming rate of return entry from reference document
# based on original item cost as per valuation method
- d.incoming_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
+ d.incoming_rate = get_rate_for_return(
+ self.doctype, self.name, d.item_code, self.return_against, item_row=d
+ )
def update_stock_ledger(self):
self.update_reserved_qty()
@@ -429,63 +468,66 @@ class SellingController(StockController):
# Loop over items and packed items table
for d in self.get_item_list():
if frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty):
- if flt(d.conversion_factor)==0.0:
- d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
+ if flt(d.conversion_factor) == 0.0:
+ d.conversion_factor = (
+ get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
+ )
# On cancellation or return entry submission, make stock ledger entry for
# target warehouse first, to update serial no values properly
- if d.warehouse and ((not cint(self.is_return) and self.docstatus==1)
- or (cint(self.is_return) and self.docstatus==2)):
- sl_entries.append(self.get_sle_for_source_warehouse(d))
+ if d.warehouse and (
+ (not cint(self.is_return) and self.docstatus == 1)
+ or (cint(self.is_return) and self.docstatus == 2)
+ ):
+ sl_entries.append(self.get_sle_for_source_warehouse(d))
if d.target_warehouse:
sl_entries.append(self.get_sle_for_target_warehouse(d))
- if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
- or (cint(self.is_return) and self.docstatus==1)):
- sl_entries.append(self.get_sle_for_source_warehouse(d))
+ if d.warehouse and (
+ (not cint(self.is_return) and self.docstatus == 2)
+ or (cint(self.is_return) and self.docstatus == 1)
+ ):
+ sl_entries.append(self.get_sle_for_source_warehouse(d))
self.make_sl_entries(sl_entries)
def get_sle_for_source_warehouse(self, item_row):
- sle = self.get_sl_entries(item_row, {
- "actual_qty": -1*flt(item_row.qty),
- "incoming_rate": item_row.incoming_rate,
- "recalculate_rate": cint(self.is_return)
- })
+ sle = self.get_sl_entries(
+ item_row,
+ {
+ "actual_qty": -1 * flt(item_row.qty),
+ "incoming_rate": item_row.incoming_rate,
+ "recalculate_rate": cint(self.is_return),
+ },
+ )
if item_row.target_warehouse and not cint(self.is_return):
sle.dependant_sle_voucher_detail_no = item_row.name
return sle
def get_sle_for_target_warehouse(self, item_row):
- sle = self.get_sl_entries(item_row, {
- "actual_qty": flt(item_row.qty),
- "warehouse": item_row.target_warehouse
- })
+ sle = self.get_sl_entries(
+ item_row, {"actual_qty": flt(item_row.qty), "warehouse": item_row.target_warehouse}
+ )
if self.docstatus == 1:
if not cint(self.is_return):
- sle.update({
- "incoming_rate": item_row.incoming_rate,
- "recalculate_rate": 1
- })
+ sle.update({"incoming_rate": item_row.incoming_rate, "recalculate_rate": 1})
else:
- sle.update({
- "outgoing_rate": item_row.incoming_rate
- })
+ sle.update({"outgoing_rate": item_row.incoming_rate})
if item_row.warehouse:
sle.dependant_sle_voucher_detail_no = item_row.name
return sle
def set_po_nos(self, for_validate=False):
- if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
+ if self.doctype == "Sales Invoice" and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_sales_invoice()
- if self.doctype == 'Delivery Note' and hasattr(self, "items"):
+ if self.doctype == "Delivery Note" and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_delivery_note()
@@ -494,34 +536,39 @@ class SellingController(StockController):
po_nos = []
if self.po_no:
po_nos.append(self.po_no)
- self.get_po_nos('Sales Order', 'sales_order', po_nos)
- self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
- self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
+ self.get_po_nos("Sales Order", "sales_order", po_nos)
+ self.get_po_nos("Delivery Note", "delivery_note", po_nos)
+ self.po_no = ", ".join(list(set(x.strip() for x in ",".join(po_nos).split(","))))
def set_pos_for_delivery_note(self):
po_nos = []
if self.po_no:
po_nos.append(self.po_no)
- self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
- self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
- self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
+ self.get_po_nos("Sales Order", "against_sales_order", po_nos)
+ self.get_po_nos("Sales Invoice", "against_sales_invoice", po_nos)
+ self.po_no = ", ".join(list(set(x.strip() for x in ",".join(po_nos).split(","))))
def get_po_nos(self, ref_doctype, ref_fieldname, po_nos):
doc_list = list(set(d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)))
if doc_list:
- po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')]
+ po_nos += [
+ d.po_no
+ for d in frappe.get_all(ref_doctype, "po_no", filters={"name": ("in", doc_list)})
+ if d.get("po_no")
+ ]
def set_gross_profit(self):
if self.doctype in ["Sales Order", "Quotation"]:
for item in self.items:
- item.gross_profit = flt(((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item))
-
+ item.gross_profit = flt(
+ ((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
+ )
def set_customer_address(self):
address_dict = {
- 'customer_address': 'address_display',
- 'shipping_address_name': 'shipping_address',
- 'company_address': 'company_address_display'
+ "customer_address": "address_display",
+ "shipping_address_name": "shipping_address",
+ "company_address": "company_address_display",
}
for address_field, address_display_field in address_dict.items():
@@ -537,15 +584,31 @@ class SellingController(StockController):
if self.doctype == "POS Invoice":
return
- for d in self.get('items'):
+ for d in self.get("items"):
if self.doctype == "Sales Invoice":
- stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
+ stock_items = [
+ d.item_code,
+ d.description,
+ d.warehouse,
+ d.sales_order or d.delivery_note,
+ d.batch_no or "",
+ ]
non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
elif self.doctype == "Delivery Note":
- stock_items = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
- non_stock_items = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
+ stock_items = [
+ d.item_code,
+ d.description,
+ d.warehouse,
+ d.against_sales_order or d.against_sales_invoice,
+ d.batch_no or "",
+ ]
+ non_stock_items = [
+ d.item_code,
+ d.description,
+ d.against_sales_order or d.against_sales_invoice,
+ ]
elif self.doctype in ["Sales Order", "Quotation"]:
- stock_items = [d.item_code, d.description, d.warehouse, '']
+ stock_items = [d.item_code, d.description, d.warehouse, ""]
non_stock_items = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
@@ -553,7 +616,7 @@ class SellingController(StockController):
duplicate_items_msg += "
"
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
- get_link_to_form("Selling Settings", "Selling Settings")
+ get_link_to_form("Selling Settings", "Selling Settings"),
)
if stock_items in check_list:
frappe.throw(duplicate_items_msg)
@@ -571,22 +634,26 @@ class SellingController(StockController):
for d in items:
if d.get("target_warehouse") and d.get("warehouse") == d.get("target_warehouse"):
warehouse = frappe.bold(d.get("target_warehouse"))
- frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same")
- .format(d.idx, warehouse, warehouse))
+ frappe.throw(
+ _("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same").format(
+ d.idx, warehouse, warehouse
+ )
+ )
if not self.get("is_internal_customer") and any(d.get("target_warehouse") for d in items):
msg = _("Target Warehouse is set for some items but the customer is not an internal customer.")
msg += " " + _("This {} will be treated as material transfer.").format(_(self.doctype))
frappe.msgprint(msg, title="Internal Transfer", alert=True)
-
def validate_items(self):
# validate items to see if they have is_sales_item enabled
from erpnext.controllers.buying_controller import validate_item_type
+
validate_item_type(self, "is_sales_item", "sales")
+
def set_default_income_account_for_item(obj):
for d in obj.get("items"):
if d.item_code:
if getattr(d, "income_account", None):
- set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
+ set_item_default(d.item_code, obj.company, "income_account", d.income_account)
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index affde4aa8a..3c0a10e086 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -8,12 +8,15 @@ from frappe.model.document import Document
from frappe.utils import comma_or, flt, getdate, now, nowdate
-class OverAllowanceError(frappe.ValidationError): pass
+class OverAllowanceError(frappe.ValidationError):
+ pass
+
def validate_status(status, options):
if status not in options:
frappe.throw(_("Status must be one of {0}").format(comma_or(options)))
+
status_map = {
"Lead": [
["Lost Quotation", "has_lost_quotation"],
@@ -26,7 +29,7 @@ status_map = {
["Lost", "has_lost_quotation"],
["Quotation", "has_active_quotation"],
["Converted", "has_ordered_quotation"],
- ["Closed", "eval:self.status=='Closed'"]
+ ["Closed", "eval:self.status=='Closed'"],
],
"Quotation": [
["Draft", None],
@@ -37,20 +40,41 @@ status_map = {
],
"Sales Order": [
["Draft", None],
- ["To Deliver and Bill", "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1"],
- ["To Bill", "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1"],
- ["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note"],
- ["Completed", "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1"],
+ [
+ "To Deliver and Bill",
+ "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1",
+ ],
+ [
+ "To Bill",
+ "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
+ ],
+ [
+ "To Deliver",
+ "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note",
+ ],
+ [
+ "Completed",
+ "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
+ ],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["On Hold", "eval:self.status=='On Hold'"],
],
"Purchase Order": [
["Draft", None],
- ["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
+ [
+ "To Receive and Bill",
+ "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1",
+ ],
["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
- ["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
- ["Completed", "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1"],
+ [
+ "To Receive",
+ "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1",
+ ],
+ [
+ "Completed",
+ "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1",
+ ],
["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"],
@@ -77,18 +101,39 @@ status_map = {
["Stopped", "eval:self.status == 'Stopped'"],
["Cancelled", "eval:self.docstatus == 2"],
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
- ["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
- ["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
- ["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
- ["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
- ["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
- ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
- ["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
+ [
+ "Ordered",
+ "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
+ ],
+ [
+ "Transferred",
+ "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'",
+ ],
+ [
+ "Issued",
+ "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'",
+ ],
+ [
+ "Received",
+ "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
+ ],
+ [
+ "Partially Received",
+ "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
+ ],
+ [
+ "Partially Ordered",
+ "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1",
+ ],
+ [
+ "Manufactured",
+ "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
+ ],
],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"],
- ["Cancelled", "eval:self.docstatus == 2"]
+ ["Cancelled", "eval:self.docstatus == 2"],
],
"POS Opening Entry": [
["Draft", None],
@@ -106,15 +151,16 @@ status_map = {
"Transaction Deletion Record": [
["Draft", None],
["Completed", "eval:self.docstatus == 1"],
- ]
+ ],
}
+
class StatusUpdater(Document):
"""
- Updates the status of the calling records
- Delivery Note: Update Delivered Qty, Update Percent and Validate over delivery
- Sales Invoice: Update Billed Amt, Update Percent and Validate over billing
- Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
+ Updates the status of the calling records
+ Delivery Note: Update Delivered Qty, Update Percent and Validate over delivery
+ Sales Invoice: Update Billed Amt, Update Percent and Validate over billing
+ Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
"""
def update_prevdoc_status(self):
@@ -123,8 +169,8 @@ class StatusUpdater(Document):
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
- if self.get('amended_from'):
- self.status = 'Draft'
+ if self.get("amended_from"):
+ self.status = "Draft"
return
if self.doctype in status_map:
@@ -139,20 +185,33 @@ class StatusUpdater(Document):
self.status = s[0]
break
elif s[1].startswith("eval:"):
- if frappe.safe_eval(s[1][5:], None, { "self": self.as_dict(), "getdate": getdate,
- "nowdate": nowdate, "get_value": frappe.db.get_value }):
+ if frappe.safe_eval(
+ s[1][5:],
+ None,
+ {
+ "self": self.as_dict(),
+ "getdate": getdate,
+ "nowdate": nowdate,
+ "get_value": frappe.db.get_value,
+ },
+ ):
self.status = s[0]
break
elif getattr(self, s[1])():
self.status = s[0]
break
- if self.status != _status and self.status not in ("Cancelled", "Partially Ordered",
- "Ordered", "Issued", "Transferred"):
+ if self.status != _status and self.status not in (
+ "Cancelled",
+ "Partially Ordered",
+ "Ordered",
+ "Issued",
+ "Transferred",
+ ):
self.add_comment("Label", _(self.status))
if update:
- self.db_set('status', self.status, update_modified = update_modified)
+ self.db_set("status", self.status, update_modified=update_modified)
def validate_qty(self):
"""Validates qty at row level"""
@@ -167,57 +226,78 @@ class StatusUpdater(Document):
# get unique transactions to update
for d in self.get_all_children():
- if hasattr(d, 'qty') and d.qty < 0 and not self.get('is_return'):
+ if hasattr(d, "qty") and d.qty < 0 and not self.get("is_return"):
frappe.throw(_("For an item {0}, quantity must be positive number").format(d.item_code))
- if hasattr(d, 'qty') and d.qty > 0 and self.get('is_return'):
+ if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"):
frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code))
- if d.doctype == args['source_dt'] and d.get(args["join_field"]):
- args['name'] = d.get(args['join_field'])
+ if d.doctype == args["source_dt"] and d.get(args["join_field"]):
+ args["name"] = d.get(args["join_field"])
# get all qty where qty > target_field
- item = frappe.db.sql("""select item_code, `{target_ref_field}`,
+ item = frappe.db.sql(
+ """select item_code, `{target_ref_field}`,
`{target_field}`, parenttype, parent from `tab{target_dt}`
where `{target_ref_field}` < `{target_field}`
- and name=%s and docstatus=1""".format(**args),
- args['name'], as_dict=1)
+ and name=%s and docstatus=1""".format(
+ **args
+ ),
+ args["name"],
+ as_dict=1,
+ )
if item:
item = item[0]
- item['idx'] = d.idx
- item['target_ref_field'] = args['target_ref_field'].replace('_', ' ')
+ item["idx"] = d.idx
+ item["target_ref_field"] = args["target_ref_field"].replace("_", " ")
# if not item[args['target_ref_field']]:
# msgprint(_("Note: System will not check over-delivery and over-booking for Item {0} as quantity or amount is 0").format(item.item_code))
- if args.get('no_allowance'):
- item['reduce_by'] = item[args['target_field']] - item[args['target_ref_field']]
- if item['reduce_by'] > .01:
+ if args.get("no_allowance"):
+ item["reduce_by"] = item[args["target_field"]] - item[args["target_ref_field"]]
+ if item["reduce_by"] > 0.01:
self.limits_crossed_error(args, item, "qty")
- elif item[args['target_ref_field']]:
+ elif item[args["target_ref_field"]]:
self.check_overflow_with_allowance(item, args)
def check_overflow_with_allowance(self, item, args):
"""
- Checks if there is overflow condering a relaxation allowance
+ Checks if there is overflow condering a relaxation allowance
"""
- qty_or_amount = "qty" if "qty" in args['target_ref_field'] else "amount"
+ qty_or_amount = "qty" if "qty" in args["target_ref_field"] else "amount"
# check if overflow is within allowance
- allowance, self.item_allowance, self.global_qty_allowance, self.global_amount_allowance = \
- get_allowance_for(item['item_code'], self.item_allowance,
- self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
+ (
+ allowance,
+ self.item_allowance,
+ self.global_qty_allowance,
+ self.global_amount_allowance,
+ ) = get_allowance_for(
+ item["item_code"],
+ self.item_allowance,
+ self.global_qty_allowance,
+ self.global_amount_allowance,
+ qty_or_amount,
+ )
- role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
- role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
- role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
+ role_allowed_to_over_deliver_receive = frappe.db.get_single_value(
+ "Stock Settings", "role_allowed_to_over_deliver_receive"
+ )
+ role_allowed_to_over_bill = frappe.db.get_single_value(
+ "Accounts Settings", "role_allowed_to_over_bill"
+ )
+ role = (
+ role_allowed_to_over_deliver_receive if qty_or_amount == "qty" else role_allowed_to_over_bill
+ )
- overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
- item[args['target_ref_field']]) * 100
+ overflow_percent = (
+ (item[args["target_field"]] - item[args["target_ref_field"]]) / item[args["target_ref_field"]]
+ ) * 100
if overflow_percent - allowance > 0.01:
- item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
- item['reduce_by'] = item[args['target_field']] - item['max_allowed']
+ item["max_allowed"] = flt(item[args["target_ref_field"]] * (100 + allowance) / 100)
+ item["reduce_by"] = item[args["target_field"]] - item["max_allowed"]
if role not in frappe.get_roles():
self.limits_crossed_error(args, item, qty_or_amount)
@@ -225,45 +305,55 @@ class StatusUpdater(Document):
self.warn_about_bypassing_with_role(item, qty_or_amount, role)
def limits_crossed_error(self, args, item, qty_or_amount):
- '''Raise exception for limits crossed'''
+ """Raise exception for limits crossed"""
if qty_or_amount == "qty":
- action_msg = _('To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.')
+ action_msg = _(
+ 'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
+ )
else:
- action_msg = _('To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.')
+ action_msg = _(
+ 'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'
+ )
- frappe.throw(_('This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?')
- .format(
+ frappe.throw(
+ _(
+ "This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?"
+ ).format(
frappe.bold(_(item["target_ref_field"].title())),
frappe.bold(item["reduce_by"]),
- frappe.bold(_(args.get('target_dt'))),
+ frappe.bold(_(args.get("target_dt"))),
frappe.bold(_(self.doctype)),
- frappe.bold(item.get('item_code'))
- ) + '
' + action_msg, OverAllowanceError, title = _('Limit Crossed'))
+ frappe.bold(item.get("item_code")),
+ )
+ + "
"
+ + action_msg,
+ OverAllowanceError,
+ title=_("Limit Crossed"),
+ )
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
- msg = (_("{} of {} {} ignored for item {} because you have {} role.")
- .format(
- action,
- _(item["target_ref_field"].title()),
- frappe.bold(item["reduce_by"]),
- frappe.bold(item.get('item_code')),
- role)
- )
+ msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
+ action,
+ _(item["target_ref_field"].title()),
+ frappe.bold(item["reduce_by"]),
+ frappe.bold(item.get("item_code")),
+ role,
+ )
frappe.msgprint(msg, indicator="orange", alert=True)
def update_qty(self, update_modified=True):
"""Updates qty or amount at row level
- :param update_modified: If true, updates `modified` and `modified_by` for target parent doc
+ :param update_modified: If true, updates `modified` and `modified_by` for target parent doc
"""
for args in self.status_updater:
# condition to include current record (if submit or no if cancel)
if self.docstatus == 1:
- args['cond'] = ' or parent="%s"' % self.name.replace('"', '\"')
+ args["cond"] = ' or parent="%s"' % self.name.replace('"', '"')
else:
- args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
+ args["cond"] = ' and parent!="%s"' % self.name.replace('"', '"')
self._update_children(args, update_modified)
@@ -273,56 +363,73 @@ class StatusUpdater(Document):
def _update_children(self, args, update_modified):
"""Update quantities or amount in child table"""
for d in self.get_all_children():
- if d.doctype != args['source_dt']:
+ if d.doctype != args["source_dt"]:
continue
self._update_modified(args, update_modified)
# updates qty in the child table
- args['detail_id'] = d.get(args['join_field'])
+ args["detail_id"] = d.get(args["join_field"])
- args['second_source_condition'] = ""
- if args.get('second_source_dt') and args.get('second_source_field') \
- and args.get('second_join_field'):
+ args["second_source_condition"] = ""
+ if (
+ args.get("second_source_dt")
+ and args.get("second_source_field")
+ and args.get("second_join_field")
+ ):
if not args.get("second_source_extra_cond"):
args["second_source_extra_cond"] = ""
- args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
+ args["second_source_condition"] = frappe.db.sql(
+ """ select ifnull((select sum(%(second_source_field)s)
from `tab%(second_source_dt)s`
where `%(second_join_field)s`="%(detail_id)s"
and (`tab%(second_source_dt)s`.docstatus=1)
- %(second_source_extra_cond)s), 0) """ % args)[0][0]
+ %(second_source_extra_cond)s), 0) """
+ % args
+ )[0][0]
- if args['detail_id']:
- if not args.get("extra_cond"): args["extra_cond"] = ""
+ if args["detail_id"]:
+ if not args.get("extra_cond"):
+ args["extra_cond"] = ""
- args["source_dt_value"] = frappe.db.sql("""
+ args["source_dt_value"] = (
+ frappe.db.sql(
+ """
(select ifnull(sum(%(source_field)s), 0)
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
and (docstatus=1 %(cond)s) %(extra_cond)s)
- """ % args)[0][0] or 0.0
+ """
+ % args
+ )[0][0]
+ or 0.0
+ )
- if args['second_source_condition']:
- args["source_dt_value"] += flt(args['second_source_condition'])
+ if args["second_source_condition"]:
+ args["source_dt_value"] += flt(args["second_source_condition"])
- frappe.db.sql("""update `tab%(target_dt)s`
+ frappe.db.sql(
+ """update `tab%(target_dt)s`
set %(target_field)s = %(source_dt_value)s %(update_modified)s
- where name='%(detail_id)s'""" % args)
+ where name='%(detail_id)s'"""
+ % args
+ )
def _update_percent_field_in_targets(self, args, update_modified=True):
"""Update percent field in parent transaction"""
- if args.get('percent_join_field_parent'):
+ if args.get("percent_join_field_parent"):
# if reference to target doc where % is to be updated, is
# in source doc's parent form, consider percent_join_field_parent
- args['name'] = self.get(args['percent_join_field_parent'])
+ args["name"] = self.get(args["percent_join_field_parent"])
self._update_percent_field(args, update_modified)
else:
- distinct_transactions = set(d.get(args['percent_join_field'])
- for d in self.get_all_children(args['source_dt']))
+ distinct_transactions = set(
+ d.get(args["percent_join_field"]) for d in self.get_all_children(args["source_dt"])
+ )
for name in distinct_transactions:
if name:
- args['name'] = name
+ args["name"] = name
self._update_percent_field(args, update_modified)
def _update_percent_field(self, args, update_modified=True):
@@ -330,23 +437,29 @@ class StatusUpdater(Document):
self._update_modified(args, update_modified)
- if args.get('target_parent_field'):
- frappe.db.sql("""update `tab%(target_parent_dt)s`
+ if args.get("target_parent_field"):
+ frappe.db.sql(
+ """update `tab%(target_parent_dt)s`
set %(target_parent_field)s = round(
ifnull((select
ifnull(sum(if(abs(%(target_ref_field)s) > abs(%(target_field)s), abs(%(target_field)s), abs(%(target_ref_field)s))), 0)
/ sum(abs(%(target_ref_field)s)) * 100
from `tab%(target_dt)s` where parent="%(name)s" having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
%(update_modified)s
- where name='%(name)s'""" % args)
+ where name='%(name)s'"""
+ % args
+ )
# update field
- if args.get('status_field'):
- frappe.db.sql("""update `tab%(target_parent_dt)s`
+ if args.get("status_field"):
+ frappe.db.sql(
+ """update `tab%(target_parent_dt)s`
set %(status_field)s = if(%(target_parent_field)s<0.001,
'Not %(keyword)s', if(%(target_parent_field)s>=99.999999,
'Fully %(keyword)s', 'Partly %(keyword)s'))
- where name='%(name)s'""" % args)
+ where name='%(name)s'"""
+ % args
+ )
if update_modified:
target = frappe.get_doc(args["target_parent_dt"], args["name"])
@@ -355,22 +468,24 @@ class StatusUpdater(Document):
def _update_modified(self, args, update_modified):
if not update_modified:
- args['update_modified'] = ''
+ args["update_modified"] = ""
return
- args['update_modified'] = ', modified = {0}, modified_by = {1}'.format(
- frappe.db.escape(now()),
- frappe.db.escape(frappe.session.user)
+ args["update_modified"] = ", modified = {0}, modified_by = {1}".format(
+ frappe.db.escape(now()), frappe.db.escape(frappe.session.user)
)
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
ref_fieldname = frappe.scrub(ref_dt)
- ref_docs = [item.get(ref_fieldname) for item in (self.get('items') or []) if item.get(ref_fieldname)]
+ ref_docs = [
+ item.get(ref_fieldname) for item in (self.get("items") or []) if item.get(ref_fieldname)
+ ]
if not ref_docs:
return
- zero_amount_refdocs = frappe.db.sql_list("""
+ zero_amount_refdocs = frappe.db.sql_list(
+ """
SELECT
name
from
@@ -379,21 +494,34 @@ class StatusUpdater(Document):
docstatus = 1
and base_net_total = 0
and name in %(ref_docs)s
- """.format(ref_dt=ref_dt), {
- 'ref_docs': ref_docs
- })
+ """.format(
+ ref_dt=ref_dt
+ ),
+ {"ref_docs": ref_docs},
+ )
if zero_amount_refdocs:
self.update_billing_status(zero_amount_refdocs, ref_dt, ref_fieldname)
def update_billing_status(self, zero_amount_refdoc, ref_dt, ref_fieldname):
for ref_dn in zero_amount_refdoc:
- ref_doc_qty = flt(frappe.db.sql("""select ifnull(sum(qty), 0) from `tab%s Item`
- where parent=%s""" % (ref_dt, '%s'), (ref_dn))[0][0])
+ ref_doc_qty = flt(
+ frappe.db.sql(
+ """select ifnull(sum(qty), 0) from `tab%s Item`
+ where parent=%s"""
+ % (ref_dt, "%s"),
+ (ref_dn),
+ )[0][0]
+ )
- billed_qty = flt(frappe.db.sql("""select ifnull(sum(qty), 0)
- from `tab%s Item` where %s=%s and docstatus=1""" %
- (self.doctype, ref_fieldname, '%s'), (ref_dn))[0][0])
+ billed_qty = flt(
+ frappe.db.sql(
+ """select ifnull(sum(qty), 0)
+ from `tab%s Item` where %s=%s and docstatus=1"""
+ % (self.doctype, ref_fieldname, "%s"),
+ (ref_dn),
+ )[0][0]
+ )
per_billed = (min(ref_doc_qty, billed_qty) / ref_doc_qty) * 100
@@ -402,7 +530,7 @@ class StatusUpdater(Document):
ref_doc.db_set("per_billed", per_billed)
# set billling status
- if hasattr(ref_doc, 'billing_status'):
+ if hasattr(ref_doc, "billing_status"):
if ref_doc.per_billed < 0.001:
ref_doc.db_set("billing_status", "Not Billed")
elif ref_doc.per_billed > 99.999999:
@@ -412,29 +540,51 @@ class StatusUpdater(Document):
ref_doc.set_status(update=True)
-def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
+
+def get_allowance_for(
+ item_code,
+ item_allowance=None,
+ global_qty_allowance=None,
+ global_amount_allowance=None,
+ qty_or_amount="qty",
+):
"""
- Returns the allowance for the item, if not set, returns global allowance
+ Returns the allowance for the item, if not set, returns global allowance
"""
if item_allowance is None:
item_allowance = {}
if qty_or_amount == "qty":
if item_allowance.get(item_code, frappe._dict()).get("qty"):
- return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance
+ return (
+ item_allowance[item_code].qty,
+ item_allowance,
+ global_qty_allowance,
+ global_amount_allowance,
+ )
else:
if item_allowance.get(item_code, frappe._dict()).get("amount"):
- return item_allowance[item_code].amount, item_allowance, global_qty_allowance, global_amount_allowance
+ return (
+ item_allowance[item_code].amount,
+ item_allowance,
+ global_qty_allowance,
+ global_amount_allowance,
+ )
- qty_allowance, over_billing_allowance = \
- frappe.db.get_value('Item', item_code, ['over_delivery_receipt_allowance', 'over_billing_allowance'])
+ qty_allowance, over_billing_allowance = frappe.db.get_value(
+ "Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"]
+ )
if qty_or_amount == "qty" and not qty_allowance:
if global_qty_allowance == None:
- global_qty_allowance = flt(frappe.db.get_single_value('Stock Settings', 'over_delivery_receipt_allowance'))
+ global_qty_allowance = flt(
+ frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
+ )
qty_allowance = global_qty_allowance
elif qty_or_amount == "amount" and not over_billing_allowance:
if global_amount_allowance == None:
- global_amount_allowance = flt(frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance'))
+ global_amount_allowance = flt(
+ frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
+ )
over_billing_allowance = global_amount_allowance
if qty_or_amount == "qty":
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 8972c32879..feec42f43a 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -21,14 +21,22 @@ from erpnext.stock import get_warehouse_account_map
from erpnext.stock.stock_ledger import get_items_to_be_repost
-class QualityInspectionRequiredError(frappe.ValidationError): pass
-class QualityInspectionRejectedError(frappe.ValidationError): pass
-class QualityInspectionNotSubmittedError(frappe.ValidationError): pass
+class QualityInspectionRequiredError(frappe.ValidationError):
+ pass
+
+
+class QualityInspectionRejectedError(frappe.ValidationError):
+ pass
+
+
+class QualityInspectionNotSubmittedError(frappe.ValidationError):
+ pass
+
class StockController(AccountsController):
def validate(self):
super(StockController, self).validate()
- if not self.get('is_return'):
+ if not self.get("is_return"):
self.validate_inspection()
self.validate_serialized_batch()
self.clean_serial_nos()
@@ -41,44 +49,56 @@ class StockController(AccountsController):
if self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
- provisional_accounting_for_non_stock_items = \
- cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
+ provisional_accounting_for_non_stock_items = cint(
+ frappe.db.get_value(
+ "Company", self.company, "enable_provisional_accounting_for_non_stock_items"
+ )
+ )
- if cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items:
+ if (
+ cint(erpnext.is_perpetual_inventory_enabled(self.company))
+ or provisional_accounting_for_non_stock_items
+ ):
warehouse_account = get_warehouse_account_map(self.company)
- if self.docstatus==1:
+ if self.docstatus == 1:
if not gl_entries:
gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries, from_repost=from_repost)
- elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
+ elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
for d in self.get("items"):
- if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
- serial_nos = frappe.get_all("Serial No",
+ if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
+ serial_nos = frappe.get_all(
+ "Serial No",
fields=["batch_no", "name", "warehouse"],
- filters={
- "name": ("in", get_serial_nos(d.serial_no))
- }
+ filters={"name": ("in", get_serial_nos(d.serial_no))},
)
for row in serial_nos:
if row.warehouse and row.batch_no != d.batch_no:
- frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
- .format(d.idx, row.name, d.batch_no))
+ frappe.throw(
+ _("Row #{0}: Serial No {1} does not belong to Batch {2}").format(
+ d.idx, row.name, d.batch_no
+ )
+ )
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):
- frappe.throw(_("Row #{0}: The batch {1} has already expired.")
- .format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
+ frappe.throw(
+ _("Row #{0}: The batch {1} has already expired.").format(
+ d.idx, get_link_to_form("Batch", d.get("batch_no"))
+ )
+ )
def clean_serial_nos(self):
from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string
@@ -88,13 +108,14 @@ class StockController(AccountsController):
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
- for row in self.get('packed_items') or []:
+ for row in self.get("packed_items") or []:
if hasattr(row, "serial_no") and row.serial_no:
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
- def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
- default_cost_center=None):
+ def get_gl_entries(
+ self, warehouse_account=None, default_expense_account=None, default_cost_center=None
+ ):
if not warehouse_account:
warehouse_account = get_warehouse_account_map(self.company)
@@ -116,44 +137,61 @@ class StockController(AccountsController):
self.check_expense_account(item_row)
# expense account/ target_warehouse / source_warehouse
- if item_row.get('target_warehouse'):
- warehouse = item_row.get('target_warehouse')
+ if item_row.get("target_warehouse"):
+ warehouse = item_row.get("target_warehouse")
expense_account = warehouse_account[warehouse]["account"]
else:
expense_account = item_row.expense_account
- gl_list.append(self.get_gl_dict({
- "account": warehouse_account[sle.warehouse]["account"],
- "against": expense_account,
- "cost_center": item_row.cost_center,
- "project": item_row.project or self.get('project'),
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": flt(sle.stock_value_difference, precision),
- "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
- }, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
+ gl_list.append(
+ self.get_gl_dict(
+ {
+ "account": warehouse_account[sle.warehouse]["account"],
+ "against": expense_account,
+ "cost_center": item_row.cost_center,
+ "project": item_row.project or self.get("project"),
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": flt(sle.stock_value_difference, precision),
+ "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
+ },
+ warehouse_account[sle.warehouse]["account_currency"],
+ item=item_row,
+ )
+ )
- gl_list.append(self.get_gl_dict({
- "account": expense_account,
- "against": warehouse_account[sle.warehouse]["account"],
- "cost_center": item_row.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(sle.stock_value_difference, precision),
- "project": item_row.get("project") or self.get("project"),
- "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
- }, item=item_row))
+ gl_list.append(
+ self.get_gl_dict(
+ {
+ "account": expense_account,
+ "against": warehouse_account[sle.warehouse]["account"],
+ "cost_center": item_row.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(sle.stock_value_difference, precision),
+ "project": item_row.get("project") or self.get("project"),
+ "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
+ },
+ item=item_row,
+ )
+ )
elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse)
if warehouse_with_no_account:
for wh in warehouse_with_no_account:
if frappe.db.get_value("Warehouse", wh, "company"):
- frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
+ frappe.throw(
+ _(
+ "Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}."
+ ).format(wh, self.company)
+ )
return process_gl_map(gl_list, precision=precision)
def get_debit_field_precision(self):
if not frappe.flags.debit_field_precision:
- frappe.flags.debit_field_precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
+ frappe.flags.debit_field_precision = frappe.get_precision(
+ "GL Entry", "debit_in_account_currency"
+ )
return frappe.flags.debit_field_precision
@@ -163,12 +201,16 @@ class StockController(AccountsController):
is_opening = "Yes" if reconciliation_purpose == "Opening Stock" else "No"
details = []
for voucher_detail_no in sle_map:
- details.append(frappe._dict({
- "name": voucher_detail_no,
- "expense_account": default_expense_account,
- "cost_center": default_cost_center,
- "is_opening": is_opening
- }))
+ details.append(
+ frappe._dict(
+ {
+ "name": voucher_detail_no,
+ "expense_account": default_expense_account,
+ "cost_center": default_cost_center,
+ "is_opening": is_opening,
+ }
+ )
+ )
return details
else:
details = self.get("items")
@@ -207,7 +249,8 @@ class StockController(AccountsController):
def get_stock_ledger_details(self):
stock_ledger = {}
- stock_ledger_entries = frappe.db.sql("""
+ stock_ledger_entries = frappe.db.sql(
+ """
select
name, warehouse, stock_value_difference, valuation_rate,
voucher_detail_no, item_code, posting_date, posting_time,
@@ -216,110 +259,154 @@ class StockController(AccountsController):
`tabStock Ledger Entry`
where
voucher_type=%s and voucher_no=%s and is_cancelled = 0
- """, (self.doctype, self.name), as_dict=True)
+ """,
+ (self.doctype, self.name),
+ as_dict=True,
+ )
for sle in stock_ledger_entries:
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
return stock_ledger
def make_batches(self, warehouse_field):
- '''Create batches if required. Called before submit'''
+ """Create batches if required. Called before submit"""
for d in self.items:
if d.get(warehouse_field) and not d.batch_no:
- has_batch_no, create_new_batch = frappe.db.get_value('Item', d.item_code, ['has_batch_no', 'create_new_batch'])
+ has_batch_no, create_new_batch = frappe.db.get_value(
+ "Item", d.item_code, ["has_batch_no", "create_new_batch"]
+ )
if has_batch_no and create_new_batch:
- d.batch_no = frappe.get_doc(dict(
- doctype='Batch',
- item=d.item_code,
- supplier=getattr(self, 'supplier', None),
- reference_doctype=self.doctype,
- reference_name=self.name)).insert().name
+ d.batch_no = (
+ frappe.get_doc(
+ dict(
+ doctype="Batch",
+ item=d.item_code,
+ supplier=getattr(self, "supplier", None),
+ reference_doctype=self.doctype,
+ reference_name=self.name,
+ )
+ )
+ .insert()
+ .name
+ )
def check_expense_account(self, item):
if not item.get("expense_account"):
msg = _("Please set an Expense Account in the Items table")
- frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
- .format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
+ frappe.throw(
+ _("Row #{0}: Expense Account not set for the Item {1}. {2}").format(
+ item.idx, frappe.bold(item.item_code), msg
+ ),
+ title=_("Expense Account Missing"),
+ )
else:
- is_expense_account = frappe.get_cached_value("Account",
- item.get("expense_account"), "report_type")=="Profit and Loss"
- if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account:
- frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account")
- .format(item.get("expense_account")))
+ is_expense_account = (
+ frappe.get_cached_value("Account", item.get("expense_account"), "report_type")
+ == "Profit and Loss"
+ )
+ if (
+ self.doctype
+ not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry")
+ and not is_expense_account
+ ):
+ frappe.throw(
+ _("Expense / Difference account ({0}) must be a 'Profit or Loss' account").format(
+ item.get("expense_account")
+ )
+ )
if is_expense_account and not item.get("cost_center"):
- frappe.throw(_("{0} {1}: Cost Center is mandatory for Item {2}").format(
- _(self.doctype), self.name, item.get("item_code")))
+ frappe.throw(
+ _("{0} {1}: Cost Center is mandatory for Item {2}").format(
+ _(self.doctype), self.name, item.get("item_code")
+ )
+ )
def delete_auto_created_batches(self):
for d in self.items:
- if not d.batch_no: continue
+ if not d.batch_no:
+ continue
- frappe.db.set_value("Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None)
+ frappe.db.set_value(
+ "Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None
+ )
d.batch_no = None
d.db_set("batch_no", None)
- for data in frappe.get_all("Batch",
- {'reference_name': self.name, 'reference_doctype': self.doctype}):
+ for data in frappe.get_all(
+ "Batch", {"reference_name": self.name, "reference_doctype": self.doctype}
+ ):
frappe.delete_doc("Batch", data.name)
def get_sl_entries(self, d, args):
- sl_dict = frappe._dict({
- "item_code": d.get("item_code", None),
- "warehouse": d.get("warehouse", None),
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- 'fiscal_year': get_fiscal_year(self.posting_date, company=self.company)[0],
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "voucher_detail_no": d.name,
- "actual_qty": (self.docstatus==1 and 1 or -1)*flt(d.get("stock_qty")),
- "stock_uom": frappe.db.get_value("Item", args.get("item_code") or d.get("item_code"), "stock_uom"),
- "incoming_rate": 0,
- "company": self.company,
- "batch_no": cstr(d.get("batch_no")).strip(),
- "serial_no": d.get("serial_no"),
- "project": d.get("project") or self.get('project'),
- "is_cancelled": 1 if self.docstatus==2 else 0
- })
+ sl_dict = frappe._dict(
+ {
+ "item_code": d.get("item_code", None),
+ "warehouse": d.get("warehouse", None),
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "fiscal_year": get_fiscal_year(self.posting_date, company=self.company)[0],
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "voucher_detail_no": d.name,
+ "actual_qty": (self.docstatus == 1 and 1 or -1) * flt(d.get("stock_qty")),
+ "stock_uom": frappe.db.get_value(
+ "Item", args.get("item_code") or d.get("item_code"), "stock_uom"
+ ),
+ "incoming_rate": 0,
+ "company": self.company,
+ "batch_no": cstr(d.get("batch_no")).strip(),
+ "serial_no": d.get("serial_no"),
+ "project": d.get("project") or self.get("project"),
+ "is_cancelled": 1 if self.docstatus == 2 else 0,
+ }
+ )
sl_dict.update(args)
return sl_dict
- def make_sl_entries(self, sl_entries, allow_negative_stock=False,
- via_landed_cost_voucher=False):
+ def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries
+
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
def make_gl_entries_on_cancel(self):
- if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s
- and voucher_no=%s""", (self.doctype, self.name)):
- self.make_gl_entries()
+ if frappe.db.sql(
+ """select name from `tabGL Entry` where voucher_type=%s
+ and voucher_no=%s""",
+ (self.doctype, self.name),
+ ):
+ self.make_gl_entries()
def get_serialized_items(self):
serialized_items = []
item_codes = list(set(d.item_code for d in self.get("items")))
if item_codes:
- serialized_items = frappe.db.sql_list("""select name from `tabItem`
- where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
- tuple(item_codes))
+ serialized_items = frappe.db.sql_list(
+ """select name from `tabItem`
+ where has_serial_no=1 and name in ({})""".format(
+ ", ".join(["%s"] * len(item_codes))
+ ),
+ tuple(item_codes),
+ )
return serialized_items
def validate_warehouse(self):
from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
- warehouses = list(set(d.warehouse for d in
- self.get("items") if getattr(d, "warehouse", None)))
+ warehouses = list(set(d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)))
- target_warehouses = list(set([d.target_warehouse for d in
- self.get("items") if getattr(d, "target_warehouse", None)]))
+ target_warehouses = list(
+ set([d.target_warehouse for d in self.get("items") if getattr(d, "target_warehouse", None)])
+ )
warehouses.extend(target_warehouses)
- from_warehouse = list(set([d.from_warehouse for d in
- self.get("items") if getattr(d, "from_warehouse", None)]))
+ from_warehouse = list(
+ set([d.from_warehouse for d in self.get("items") if getattr(d, "from_warehouse", None)])
+ )
warehouses.extend(from_warehouse)
@@ -332,14 +419,17 @@ class StockController(AccountsController):
if self.doctype == "Delivery Note":
target_ref_field = "amount - (returned_qty * rate)"
- self._update_percent_field({
- "target_dt": self.doctype + " Item",
- "target_parent_dt": self.doctype,
- "target_parent_field": "per_billed",
- "target_ref_field": target_ref_field,
- "target_field": "billed_amt",
- "name": self.name,
- }, update_modified)
+ self._update_percent_field(
+ {
+ "target_dt": self.doctype + " Item",
+ "target_parent_dt": self.doctype,
+ "target_parent_field": "per_billed",
+ "target_ref_field": target_ref_field,
+ "target_field": "billed_amt",
+ "name": self.name,
+ },
+ update_modified,
+ )
def validate_inspection(self):
"""Checks if quality inspection is set/ is valid for Items that require inspection."""
@@ -347,24 +437,28 @@ class StockController(AccountsController):
"Purchase Receipt": "inspection_required_before_purchase",
"Purchase Invoice": "inspection_required_before_purchase",
"Sales Invoice": "inspection_required_before_delivery",
- "Delivery Note": "inspection_required_before_delivery"
+ "Delivery Note": "inspection_required_before_delivery",
}
inspection_required_fieldname = inspection_fieldname_map.get(self.doctype)
# return if inspection is not required on document level
- if ((not inspection_required_fieldname and self.doctype != "Stock Entry") or
- (self.doctype == "Stock Entry" and not self.inspection_required) or
- (self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)):
- return
+ if (
+ (not inspection_required_fieldname and self.doctype != "Stock Entry")
+ or (self.doctype == "Stock Entry" and not self.inspection_required)
+ or (self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)
+ ):
+ return
- for row in self.get('items'):
+ for row in self.get("items"):
qi_required = False
- if (inspection_required_fieldname and frappe.db.get_value("Item", row.item_code, inspection_required_fieldname)):
+ if inspection_required_fieldname and frappe.db.get_value(
+ "Item", row.item_code, inspection_required_fieldname
+ ):
qi_required = True
elif self.doctype == "Stock Entry" and row.t_warehouse:
- qi_required = True # inward stock needs inspection
+ qi_required = True # inward stock needs inspection
- if qi_required: # validate row only if inspection is required on item level
+ if qi_required: # validate row only if inspection is required on item level
self.validate_qi_presence(row)
if self.docstatus == 1:
self.validate_qi_submission(row)
@@ -381,12 +475,16 @@ class StockController(AccountsController):
def validate_qi_submission(self, row):
"""Check if QI is submitted on row level, during submission"""
- action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted")
+ action = frappe.db.get_single_value(
+ "Stock Settings", "action_if_quality_inspection_is_not_submitted"
+ )
qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
if not qa_docstatus == 1:
- link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
- msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
+ link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
+ msg = (
+ f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
+ )
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
else:
@@ -398,7 +496,7 @@ class StockController(AccountsController):
qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status")
if qa_status == "Rejected":
- link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
+ link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}"
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
@@ -411,48 +509,71 @@ class StockController(AccountsController):
frappe.get_doc("Blanket Order", blanket_order).update_ordered_qty()
def validate_customer_provided_item(self):
- for d in self.get('items'):
+ for d in self.get("items"):
# Customer Provided parts will have zero valuation rate
- if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
+ if frappe.db.get_value("Item", d.item_code, "is_customer_provided_item"):
d.allow_zero_valuation_rate = 1
def set_rate_of_stock_uom(self):
- if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
+ if self.doctype in [
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Purchase Order",
+ "Sales Invoice",
+ "Sales Order",
+ "Delivery Note",
+ "Quotation",
+ ]:
for d in self.get("items"):
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
def validate_internal_transfer(self):
- if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
- and self.is_internal_transfer():
+ if (
+ self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
+ and self.is_internal_transfer()
+ ):
self.validate_in_transit_warehouses()
self.validate_multi_currency()
self.validate_packed_items()
def validate_in_transit_warehouses(self):
- if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
- for item in self.get('items'):
+ if (
+ self.doctype == "Sales Invoice" and self.get("update_stock")
+ ) or self.doctype == "Delivery Note":
+ for item in self.get("items"):
if not item.target_warehouse:
- frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
+ frappe.throw(
+ _("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx)
+ )
- if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
- for item in self.get('items'):
+ if (
+ self.doctype == "Purchase Invoice" and self.get("update_stock")
+ ) or self.doctype == "Purchase Receipt":
+ for item in self.get("items"):
if not item.from_warehouse:
- frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
+ frappe.throw(
+ _("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx)
+ )
def validate_multi_currency(self):
if self.currency != self.company_currency:
frappe.throw(_("Internal transfers can only be done in company's default currency"))
def validate_packed_items(self):
- if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
+ if self.doctype in ("Sales Invoice", "Delivery Note Item") and self.get("packed_items"):
frappe.throw(_("Packed Items cannot be transferred internally"))
def validate_putaway_capacity(self):
# if over receipt is attempted while 'apply putaway rule' is disabled
# and if rule was applied on the transaction, validate it.
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
- valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
- "Stock Reconciliation")
+
+ valid_doctype = self.doctype in (
+ "Purchase Receipt",
+ "Stock Entry",
+ "Purchase Invoice",
+ "Stock Reconciliation",
+ )
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
valid_doctype = False
@@ -461,14 +582,15 @@ class StockController(AccountsController):
rule_map = defaultdict(dict)
for item in self.get("items"):
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
- rule = frappe.db.get_value("Putaway Rule",
- {
- "item_code": item.get("item_code"),
- "warehouse": item.get(warehouse_field)
- },
- ["name", "disable"], as_dict=True)
+ rule = frappe.db.get_value(
+ "Putaway Rule",
+ {"item_code": item.get("item_code"), "warehouse": item.get(warehouse_field)},
+ ["name", "disable"],
+ as_dict=True,
+ )
if rule:
- if rule.get("disabled"): continue # dont validate for disabled rule
+ if rule.get("disabled"):
+ continue # dont validate for disabled rule
if self.doctype == "Stock Reconciliation":
stock_qty = flt(item.qty)
@@ -489,46 +611,55 @@ class StockController(AccountsController):
frappe.throw(msg=message, title=_("Over Receipt"))
def prepare_over_receipt_message(self, rule, values):
- message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
- .format(
- frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
- frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
- )
+ message = _(
+ "{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}."
+ ).format(
+ frappe.bold(values["qty_put"]),
+ frappe.bold(values["item"]),
+ frappe.bold(values["warehouse"]),
+ frappe.bold(values["capacity"]),
+ )
message += "
"
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
return message
def repost_future_sle_and_gle(self):
- args = frappe._dict({
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "company": self.company
- })
+ args = frappe._dict(
+ {
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "company": self.company,
+ }
+ )
if future_sle_exists(args) or repost_required_for_queue(self):
- item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"))
+ item_based_reposting = cint(
+ frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
+ )
if item_based_reposting:
create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name)
else:
create_repost_item_valuation_entry(args)
+
def repost_required_for_queue(doc: StockController) -> bool:
"""check if stock document contains repeated item-warehouse with queue based valuation.
if queue exists for repeated items then SLEs need to reprocessed in background again.
"""
- consuming_sles = frappe.db.get_all("Stock Ledger Entry",
+ consuming_sles = frappe.db.get_all(
+ "Stock Ledger Entry",
filters={
"voucher_type": doc.doctype,
"voucher_no": doc.name,
"actual_qty": ("<", 0),
- "is_cancelled": 0
+ "is_cancelled": 0,
},
- fields=["item_code", "warehouse", "stock_queue"]
+ fields=["item_code", "warehouse", "stock_queue"],
)
item_warehouses = [(sle.item_code, sle.warehouse) for sle in consuming_sles]
@@ -551,32 +682,41 @@ def make_quality_inspections(doctype, docname, items):
inspections = []
for item in items:
if flt(item.get("sample_size")) > flt(item.get("qty")):
- frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format(
- item_name=item.get("item_name"),
- sample_size=item.get("sample_size"),
- accepted_quantity=item.get("qty")
- ))
+ frappe.throw(
+ _(
+ "{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})"
+ ).format(
+ item_name=item.get("item_name"),
+ sample_size=item.get("sample_size"),
+ accepted_quantity=item.get("qty"),
+ )
+ )
- quality_inspection = frappe.get_doc({
- "doctype": "Quality Inspection",
- "inspection_type": "Incoming",
- "inspected_by": frappe.session.user,
- "reference_type": doctype,
- "reference_name": docname,
- "item_code": item.get("item_code"),
- "description": item.get("description"),
- "sample_size": flt(item.get("sample_size")),
- "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
- "batch_no": item.get("batch_no")
- }).insert()
+ quality_inspection = frappe.get_doc(
+ {
+ "doctype": "Quality Inspection",
+ "inspection_type": "Incoming",
+ "inspected_by": frappe.session.user,
+ "reference_type": doctype,
+ "reference_name": docname,
+ "item_code": item.get("item_code"),
+ "description": item.get("description"),
+ "sample_size": flt(item.get("sample_size")),
+ "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
+ "batch_no": item.get("batch_no"),
+ }
+ ).insert()
quality_inspection.save()
inspections.append(quality_inspection.name)
return inspections
+
def is_reposting_pending():
- return frappe.db.exists("Repost Item Valuation",
- {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
+ return frappe.db.exists(
+ "Repost Item Valuation", {"docstatus": 1, "status": ["in", ["Queued", "In Progress"]]}
+ )
+
def future_sle_exists(args, sl_entries=None):
key = (args.voucher_type, args.voucher_no)
@@ -593,7 +733,8 @@ def future_sle_exists(args, sl_entries=None):
or_conditions = get_conditions_to_validate_future_sle(sl_entries)
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
select item_code, warehouse, count(name) as total_row
from `tabStock Ledger Entry` force index (item_warehouse)
where
@@ -604,43 +745,55 @@ def future_sle_exists(args, sl_entries=None):
and is_cancelled = 0
GROUP BY
item_code, warehouse
- """.format(" or ".join(or_conditions)), args, as_dict=1)
+ """.format(
+ " or ".join(or_conditions)
+ ),
+ args,
+ as_dict=1,
+ )
for d in data:
frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
return len(data)
-def validate_future_sle_not_exists(args, key, sl_entries=None):
- item_key = ''
- if args.get('item_code'):
- item_key = (args.get('item_code'), args.get('warehouse'))
- if not sl_entries and hasattr(frappe.local, 'future_sle'):
- if (not frappe.local.future_sle.get(key) or
- (item_key and item_key not in frappe.local.future_sle.get(key))):
+def validate_future_sle_not_exists(args, key, sl_entries=None):
+ item_key = ""
+ if args.get("item_code"):
+ item_key = (args.get("item_code"), args.get("warehouse"))
+
+ if not sl_entries and hasattr(frappe.local, "future_sle"):
+ if not frappe.local.future_sle.get(key) or (
+ item_key and item_key not in frappe.local.future_sle.get(key)
+ ):
return True
+
def get_cached_data(args, key):
- if not hasattr(frappe.local, 'future_sle'):
+ if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}
if key not in frappe.local.future_sle:
frappe.local.future_sle[key] = frappe._dict({})
- if args.get('item_code'):
- item_key = (args.get('item_code'), args.get('warehouse'))
+ if args.get("item_code"):
+ item_key = (args.get("item_code"), args.get("warehouse"))
count = frappe.local.future_sle[key].get(item_key)
return True if (count or count == 0) else False
else:
return frappe.local.future_sle[key]
+
def get_sle_entries_against_voucher(args):
- return frappe.get_all("Stock Ledger Entry",
+ return frappe.get_all(
+ "Stock Ledger Entry",
filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
fields=["item_code", "warehouse"],
- order_by="creation asc")
+ order_by="creation asc",
+ )
+
def get_conditions_to_validate_future_sle(sl_entries):
warehouse_items_map = {}
@@ -654,16 +807,18 @@ def get_conditions_to_validate_future_sle(sl_entries):
for warehouse, items in warehouse_items_map.items():
or_conditions.append(
f"""warehouse = {frappe.db.escape(warehouse)}
- and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""")
+ and item_code in ({', '.join(frappe.db.escape(item) for item in items)})"""
+ )
return or_conditions
+
def create_repost_item_valuation_entry(args):
args = frappe._dict(args)
repost_entry = frappe.new_doc("Repost Item Valuation")
repost_entry.based_on = args.based_on
if not args.based_on:
- repost_entry.based_on = 'Transaction' if args.voucher_no else "Item and Warehouse"
+ repost_entry.based_on = "Transaction" if args.voucher_no else "Item and Warehouse"
repost_entry.voucher_type = args.voucher_type
repost_entry.voucher_no = args.voucher_no
repost_entry.item_code = args.item_code
diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py
index c52c688b73..70830882ef 100644
--- a/erpnext/controllers/subcontracting.py
+++ b/erpnext/controllers/subcontracting.py
@@ -8,9 +8,9 @@ from frappe.utils import cint, flt, get_link_to_form
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
-class Subcontracting():
+class Subcontracting:
def set_materials_for_subcontracted_items(self, raw_material_table):
- if self.doctype == 'Purchase Invoice' and not self.update_stock:
+ if self.doctype == "Purchase Invoice" and not self.update_stock:
return
self.raw_material_table = raw_material_table
@@ -33,13 +33,14 @@ class Subcontracting():
self.__get_backflush_based_on()
def __get_backflush_based_on(self):
- self.backflush_based_on = frappe.db.get_single_value("Buying Settings",
- "backflush_raw_materials_of_subcontract_based_on")
+ self.backflush_based_on = frappe.db.get_single_value(
+ "Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
+ )
def __get_purchase_orders(self):
self.purchase_orders = []
- if self.doctype == 'Purchase Order':
+ if self.doctype == "Purchase Order":
return
self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order]
@@ -48,7 +49,7 @@ class Subcontracting():
self.__changed_name = []
self.__reference_name = []
- if self.doctype == 'Purchase Order' or self.is_new():
+ if self.doctype == "Purchase Order" or self.is_new():
self.set(self.raw_material_table, [])
return
@@ -68,20 +69,20 @@ class Subcontracting():
def __get_data_before_save(self):
item_dict = {}
- if self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self._doc_before_save:
- for row in self._doc_before_save.get('items'):
+ if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self._doc_before_save:
+ for row in self._doc_before_save.get("items"):
item_dict[row.name] = (row.item_code, row.qty)
return item_dict
def get_available_materials(self):
- ''' Get the available raw materials which has been transferred to the supplier.
- available_materials = {
- (item_code, subcontracted_item, purchase_order): {
- 'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
- }
- }
- '''
+ """Get the available raw materials which has been transferred to the supplier.
+ available_materials = {
+ (item_code, subcontracted_item, purchase_order): {
+ 'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
+ }
+ }
+ """
if not self.purchase_orders:
return
@@ -89,8 +90,17 @@ class Subcontracting():
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
if key not in self.available_materials:
- self.available_materials.setdefault(key, frappe._dict({'qty': 0, 'serial_no': [],
- 'batch_no': defaultdict(float), 'item_details': row, 'po_details': []})
+ self.available_materials.setdefault(
+ key,
+ frappe._dict(
+ {
+ "qty": 0,
+ "serial_no": [],
+ "batch_no": defaultdict(float),
+ "item_details": row,
+ "po_details": [],
+ }
+ ),
)
details = self.available_materials[key]
@@ -106,17 +116,17 @@ class Subcontracting():
self.__set_alternative_item_details(row)
self.__transferred_items = copy.deepcopy(self.available_materials)
- for doctype in ['Purchase Receipt', 'Purchase Invoice']:
+ for doctype in ["Purchase Receipt", "Purchase Invoice"]:
self.__update_consumed_materials(doctype)
def __update_consumed_materials(self, doctype, return_consumed_items=False):
- '''Deduct the consumed materials from the available materials.'''
+ """Deduct the consumed materials from the available materials."""
pr_items = self.__get_received_items(doctype)
if not pr_items:
return ([], {}) if return_consumed_items else None
- pr_items = {d.name: d.get(self.get('po_field') or 'purchase_order') for d in pr_items}
+ pr_items = {d.name: d.get(self.get("po_field") or "purchase_order") for d in pr_items}
consumed_materials = self.__get_consumed_items(doctype, pr_items.keys())
if return_consumed_items:
@@ -127,97 +137,153 @@ class Subcontracting():
if not self.available_materials.get(key):
continue
- self.available_materials[key]['qty'] -= row.consumed_qty
+ self.available_materials[key]["qty"] -= row.consumed_qty
if row.serial_no:
- self.available_materials[key]['serial_no'] = list(
- set(self.available_materials[key]['serial_no']) - set(get_serial_nos(row.serial_no))
+ self.available_materials[key]["serial_no"] = list(
+ set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
)
if row.batch_no:
- self.available_materials[key]['batch_no'][row.batch_no] -= row.consumed_qty
+ self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
def __get_transferred_items(self):
- fields = ['`tabStock Entry`.`purchase_order`']
- alias_dict = {'item_code': 'rm_item_code', 'subcontracted_item': 'main_item_code', 'basic_rate': 'rate'}
+ fields = ["`tabStock Entry`.`purchase_order`"]
+ alias_dict = {
+ "item_code": "rm_item_code",
+ "subcontracted_item": "main_item_code",
+ "basic_rate": "rate",
+ }
- child_table_fields = ['item_code', 'item_name', 'description', 'qty', 'basic_rate', 'amount',
- 'serial_no', 'uom', 'subcontracted_item', 'stock_uom', 'batch_no', 'conversion_factor',
- 's_warehouse', 't_warehouse', 'item_group', 'po_detail']
+ child_table_fields = [
+ "item_code",
+ "item_name",
+ "description",
+ "qty",
+ "basic_rate",
+ "amount",
+ "serial_no",
+ "uom",
+ "subcontracted_item",
+ "stock_uom",
+ "batch_no",
+ "conversion_factor",
+ "s_warehouse",
+ "t_warehouse",
+ "item_group",
+ "po_detail",
+ ]
- if self.backflush_based_on == 'BOM':
- child_table_fields.append('original_item')
+ if self.backflush_based_on == "BOM":
+ child_table_fields.append("original_item")
for field in child_table_fields:
- fields.append(f'`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}')
+ fields.append(f"`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}")
- filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purpose', '=', 'Send to Subcontractor'],
- ['Stock Entry', 'purchase_order', 'in', self.purchase_orders]]
+ filters = [
+ ["Stock Entry", "docstatus", "=", 1],
+ ["Stock Entry", "purpose", "=", "Send to Subcontractor"],
+ ["Stock Entry", "purchase_order", "in", self.purchase_orders],
+ ]
- return frappe.get_all('Stock Entry', fields = fields, filters=filters)
+ return frappe.get_all("Stock Entry", fields=fields, filters=filters)
def __get_received_items(self, doctype):
fields = []
- self.po_field = 'purchase_order'
+ self.po_field = "purchase_order"
- for field in ['name', self.po_field, 'parent']:
- fields.append(f'`tab{doctype} Item`.`{field}`')
+ for field in ["name", self.po_field, "parent"]:
+ fields.append(f"`tab{doctype} Item`.`{field}`")
- filters = [[doctype, 'docstatus', '=', 1], [f'{doctype} Item', self.po_field, 'in', self.purchase_orders]]
- if doctype == 'Purchase Invoice':
- filters.append(['Purchase Invoice', 'update_stock', "=", 1])
+ filters = [
+ [doctype, "docstatus", "=", 1],
+ [f"{doctype} Item", self.po_field, "in", self.purchase_orders],
+ ]
+ if doctype == "Purchase Invoice":
+ filters.append(["Purchase Invoice", "update_stock", "=", 1])
- return frappe.get_all(f'{doctype}', fields = fields, filters = filters)
+ return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
def __get_consumed_items(self, doctype, pr_items):
- return frappe.get_all('Purchase Receipt Item Supplied',
- fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'],
- filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items)), 'parenttype': doctype})
+ return frappe.get_all(
+ "Purchase Receipt Item Supplied",
+ fields=[
+ "serial_no",
+ "rm_item_code",
+ "reference_name",
+ "batch_no",
+ "consumed_qty",
+ "main_item_code",
+ ],
+ filters={"docstatus": 1, "reference_name": ("in", list(pr_items)), "parenttype": doctype},
+ )
def __set_alternative_item_details(self, row):
- if row.get('original_item'):
- self.alternative_item_details[row.get('original_item')] = row
+ if row.get("original_item"):
+ self.alternative_item_details[row.get("original_item")] = row
def __get_pending_qty_to_receive(self):
- '''Get qty to be received against the purchase order.'''
+ """Get qty to be received against the purchase order."""
self.qty_to_be_received = defaultdict(float)
- if self.doctype != 'Purchase Order' and self.backflush_based_on != 'BOM' and self.purchase_orders:
- for row in frappe.get_all('Purchase Order Item',
- fields = ['item_code', '(qty - received_qty) as qty', 'parent', 'name'],
- filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}):
+ if (
+ self.doctype != "Purchase Order" and self.backflush_based_on != "BOM" and self.purchase_orders
+ ):
+ for row in frappe.get_all(
+ "Purchase Order Item",
+ fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
+ filters={"docstatus": 1, "parent": ("in", self.purchase_orders)},
+ ):
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
- doctype = 'BOM Item' if not exploded_item else 'BOM Explosion Item'
- fields = [f'`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit']
+ doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
+ fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
- alias_dict = {'item_code': 'rm_item_code', 'name': 'bom_detail_no', 'source_warehouse': 'reserve_warehouse'}
- for field in ['item_code', 'name', 'rate', 'stock_uom',
- 'source_warehouse', 'description', 'item_name', 'stock_uom']:
- fields.append(f'`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}')
+ alias_dict = {
+ "item_code": "rm_item_code",
+ "name": "bom_detail_no",
+ "source_warehouse": "reserve_warehouse",
+ }
+ for field in [
+ "item_code",
+ "name",
+ "rate",
+ "stock_uom",
+ "source_warehouse",
+ "description",
+ "item_name",
+ "stock_uom",
+ ]:
+ fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}")
- filters = [[doctype, 'parent', '=', bom_no], [doctype, 'docstatus', '=', 1],
- ['BOM', 'item', '=', item_code], [doctype, 'sourced_by_supplier', '=', 0]]
+ filters = [
+ [doctype, "parent", "=", bom_no],
+ [doctype, "docstatus", "=", 1],
+ ["BOM", "item", "=", item_code],
+ [doctype, "sourced_by_supplier", "=", 0],
+ ]
- return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or []
+ return (
+ frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
+ )
def __remove_changed_rows(self):
if not self.__changed_name:
return
- i=1
+ i = 1
self.set(self.raw_material_table, [])
for d in self._doc_before_save.supplied_items:
if d.reference_name in self.__changed_name:
continue
- if (d.reference_name not in self.__reference_name):
+ if d.reference_name not in self.__reference_name:
continue
d.idx = i
- self.append('supplied_items', d)
+ self.append("supplied_items", d)
i += 1
@@ -226,31 +292,35 @@ class Subcontracting():
has_supplied_items = True if self.get(self.raw_material_table) else False
for row in self.items:
- if (self.doctype != 'Purchase Order' and ((self.__changed_name and row.name not in self.__changed_name)
- or (has_supplied_items and not self.__changed_name))):
+ if self.doctype != "Purchase Order" and (
+ (self.__changed_name and row.name not in self.__changed_name)
+ or (has_supplied_items and not self.__changed_name)
+ ):
continue
- if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM':
- for bom_item in self.__get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')):
- qty = (flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor)
+ if self.doctype == "Purchase Order" or self.backflush_based_on == "BOM":
+ for bom_item in self.__get_materials_from_bom(
+ row.item_code, row.bom, row.get("include_exploded_items")
+ ):
+ qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor
bom_item.main_item_code = row.item_code
self.__update_reserve_warehouse(bom_item, row)
self.__set_alternative_item(bom_item)
self.__add_supplied_item(row, bom_item, qty)
- elif self.backflush_based_on != 'BOM':
+ elif self.backflush_based_on != "BOM":
for key, transfer_item in self.available_materials.items():
if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0:
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
transfer_item.qty -= qty
- self.__add_supplied_item(row, transfer_item.get('item_details'), qty)
+ self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
if self.qty_to_be_received:
self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty
def __update_reserve_warehouse(self, row, item):
- if self.doctype == 'Purchase Order':
- row.reserve_warehouse = (self.set_reserve_warehouse or item.warehouse)
+ if self.doctype == "Purchase Order":
+ row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
key = (item_row.item_code, item_row.purchase_order)
@@ -262,8 +332,9 @@ class Subcontracting():
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
transfer_item.item_details.required_qty = transfer_item.qty
- if (transfer_item.serial_no or frappe.get_cached_value('UOM',
- transfer_item.item_details.stock_uom, 'must_be_whole_number')):
+ if transfer_item.serial_no or frappe.get_cached_value(
+ "UOM", transfer_item.item_details.stock_uom, "must_be_whole_number"
+ ):
return frappe.utils.ceil(qty)
return qty
@@ -277,7 +348,7 @@ class Subcontracting():
rm_obj = self.append(self.raw_material_table, bom_item)
rm_obj.reference_name = item_row.name
- if self.doctype == 'Purchase Order':
+ if self.doctype == "Purchase Order":
rm_obj.required_qty = qty
else:
rm_obj.consumed_qty = 0
@@ -287,12 +358,12 @@ class Subcontracting():
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
- if (self.available_materials.get(key) and self.available_materials[key]['batch_no']):
+ if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
new_rm_obj = None
- for batch_no, batch_qty in self.available_materials[key]['batch_no'].items():
+ for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
if batch_qty >= qty:
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
- self.available_materials[key]['batch_no'][batch_no] -= qty
+ self.available_materials[key]["batch_no"][batch_no] -= qty
return
elif qty > 0 and batch_qty > 0:
@@ -300,7 +371,7 @@ class Subcontracting():
new_rm_obj = self.append(self.raw_material_table, bom_item)
new_rm_obj.reference_name = item_row.name
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
- self.available_materials[key]['batch_no'][batch_no] = 0
+ self.available_materials[key]["batch_no"][batch_no] = 0
if abs(qty) > 0 and not new_rm_obj:
self.__set_consumed_qty(rm_obj, qty)
@@ -313,29 +384,35 @@ class Subcontracting():
rm_obj.consumed_qty = consumed_qty
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
- rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no,
- 'required_qty': qty, 'purchase_order': item_row.purchase_order})
+ rm_obj.update(
+ {
+ "consumed_qty": qty,
+ "batch_no": batch_no,
+ "required_qty": qty,
+ "purchase_order": item_row.purchase_order,
+ }
+ )
self.__set_serial_nos(item_row, rm_obj)
def __set_serial_nos(self, item_row, rm_obj):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
- if (self.available_materials.get(key) and self.available_materials[key]['serial_no']):
- used_serial_nos = self.available_materials[key]['serial_no'][0: cint(rm_obj.consumed_qty)]
- rm_obj.serial_no = '\n'.join(used_serial_nos)
+ if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
+ used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
+ rm_obj.serial_no = "\n".join(used_serial_nos)
# Removed the used serial nos from the list
for sn in used_serial_nos:
- self.available_materials[key]['serial_no'].remove(sn)
+ self.available_materials[key]["serial_no"].remove(sn)
def set_consumed_qty_in_po(self):
# Update consumed qty back in the purchase order
- if self.is_subcontracted != 'Yes':
+ if self.is_subcontracted != "Yes":
return
self.__get_purchase_orders()
itemwise_consumed_qty = defaultdict(float)
- for doctype in ['Purchase Receipt', 'Purchase Invoice']:
+ for doctype in ["Purchase Receipt", "Purchase Invoice"]:
consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True)
for row in consumed_items:
@@ -345,10 +422,12 @@ class Subcontracting():
self.__update_consumed_qty_in_po(itemwise_consumed_qty)
def __update_consumed_qty_in_po(self, itemwise_consumed_qty):
- fields = ['main_item_code', 'rm_item_code', 'parent', 'supplied_qty', 'name']
- filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}
+ fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
+ filters = {"docstatus": 1, "parent": ("in", self.purchase_orders)}
- for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters, order_by='idx'):
+ for row in frappe.get_all(
+ "Purchase Order Item Supplied", fields=fields, filters=filters, order_by="idx"
+ ):
key = (row.rm_item_code, row.main_item_code, row.parent)
consumed_qty = itemwise_consumed_qty.get(key, 0)
@@ -356,10 +435,10 @@ class Subcontracting():
consumed_qty = row.supplied_qty
itemwise_consumed_qty[key] -= consumed_qty
- frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty)
+ frappe.db.set_value("Purchase Order Item Supplied", row.name, "consumed_qty", consumed_qty)
def __validate_supplied_items(self):
- if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
+ if self.doctype not in ["Purchase Invoice", "Purchase Receipt"]:
return
for row in self.get(self.raw_material_table):
@@ -371,18 +450,20 @@ class Subcontracting():
self.__validate_serial_no(row, key)
def __validate_batch_no(self, row, key):
- if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
- link = get_link_to_form('Purchase Order', row.purchase_order)
+ if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
+ "batch_no"
+ ):
+ link = get_link_to_form("Purchase Order", row.purchase_order)
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
def __validate_serial_no(self, row, key):
- if row.get('serial_no'):
- serial_nos = get_serial_nos(row.get('serial_no'))
- incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no'))
+ if row.get("serial_no"):
+ serial_nos = get_serial_nos(row.get("serial_no"))
+ incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get("serial_no"))
if incorrect_sn:
incorrect_sn = "\n".join(incorrect_sn)
- link = get_link_to_form('Purchase Order', row.purchase_order)
- msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
+ link = get_link_to_form("Purchase Order", row.purchase_order)
+ msg = f"The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}"
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 29da5f11ab..ce58d5b4ab 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -59,23 +59,23 @@ class calculate_taxes_and_totals(object):
self.calculate_total_net_weight()
def validate_item_tax_template(self):
- for item in self.doc.get('items'):
- if item.item_code and item.get('item_tax_template'):
+ for item in self.doc.get("items"):
+ if item.item_code and item.get("item_tax_template"):
item_doc = frappe.get_cached_doc("Item", item.item_code)
args = {
- 'net_rate': item.net_rate or item.rate,
- 'tax_category': self.doc.get('tax_category'),
- 'posting_date': self.doc.get('posting_date'),
- 'bill_date': self.doc.get('bill_date'),
- 'transaction_date': self.doc.get('transaction_date'),
- 'company': self.doc.get('company')
+ "net_rate": item.net_rate or item.rate,
+ "tax_category": self.doc.get("tax_category"),
+ "posting_date": self.doc.get("posting_date"),
+ "bill_date": self.doc.get("bill_date"),
+ "transaction_date": self.doc.get("transaction_date"),
+ "company": self.doc.get("company"),
}
item_group = item_doc.item_group
item_group_taxes = []
while item_group:
- item_group_doc = frappe.get_cached_doc('Item Group', item_group)
+ item_group_doc = frappe.get_cached_doc("Item Group", item_group)
item_group_taxes += item_group_doc.taxes or []
item_group = item_group_doc.parent_item_group
@@ -90,9 +90,11 @@ class calculate_taxes_and_totals(object):
if taxes:
if item.item_tax_template not in taxes:
item.item_tax_template = taxes[0]
- frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format(
- item.idx, frappe.bold(item.item_code)
- ))
+ frappe.msgprint(
+ _("Row {0}: Item Tax template updated as per validity and rate applied").format(
+ item.idx, frappe.bold(item.item_code)
+ )
+ )
def validate_conversion_rate(self):
# validate conversion rate
@@ -101,13 +103,17 @@ class calculate_taxes_and_totals(object):
self.doc.currency = company_currency
self.doc.conversion_rate = 1.0
else:
- validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
- self.doc.meta.get_label("conversion_rate"), self.doc.company)
+ validate_conversion_rate(
+ self.doc.currency,
+ self.doc.conversion_rate,
+ self.doc.meta.get_label("conversion_rate"),
+ self.doc.company,
+ )
self.doc.conversion_rate = flt(self.doc.conversion_rate)
def calculate_item_values(self):
- if self.doc.get('is_consolidated'):
+ if self.doc.get("is_consolidated"):
return
if not self.discount_amount_applied:
@@ -118,19 +124,30 @@ class calculate_taxes_and_totals(object):
item.rate = 0.0
elif item.price_list_rate:
if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
- item.rate = flt(item.price_list_rate *
- (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
+ item.rate = flt(
+ item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
+ )
item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
elif item.discount_amount and item.pricing_rules:
- item.rate = item.price_list_rate - item.discount_amount
+ item.rate = item.price_list_rate - item.discount_amount
- if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
- 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
+ if item.doctype in [
+ "Quotation Item",
+ "Sales Order Item",
+ "Delivery Note Item",
+ "Sales Invoice Item",
+ "POS Invoice Item",
+ "Purchase Invoice Item",
+ "Purchase Order Item",
+ "Purchase Receipt Item",
+ ]:
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0:
- item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
+ item.rate = flt(
+ item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
+ )
if item.discount_amount and not item.discount_percentage:
item.rate = item.rate_with_margin - item.discount_amount
@@ -149,18 +166,22 @@ class calculate_taxes_and_totals(object):
elif not item.qty and self.doc.get("is_debit_note"):
item.amount = flt(item.rate, item.precision("amount"))
else:
- item.amount = flt(item.rate * item.qty, item.precision("amount"))
+ item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.net_amount = item.amount
- self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
+ self._set_in_company_currency(
+ item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"]
+ )
item.item_tax_amount = 0.0
def _set_in_company_currency(self, doc, fields):
"""set values in base currency"""
for f in fields:
- val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
+ val = flt(
+ flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f)
+ )
doc.set("base_" + f, val)
def initialize_taxes(self):
@@ -169,16 +190,22 @@ class calculate_taxes_and_totals(object):
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, self.doc)
- if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
+ if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
tax.item_wise_tax_detail = {}
- tax_fields = ["total", "tax_amount_after_discount_amount",
- "tax_amount_for_current_item", "grand_total_for_current_item",
- "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
+ tax_fields = [
+ "total",
+ "tax_amount_after_discount_amount",
+ "tax_amount_for_current_item",
+ "grand_total_for_current_item",
+ "tax_fraction_for_current_item",
+ "grand_total_fraction_for_current_item",
+ ]
- if tax.charge_type != "Actual" and \
- not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
- tax_fields.append("tax_amount")
+ if tax.charge_type != "Actual" and not (
+ self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
+ ):
+ tax_fields.append("tax_amount")
for fieldname in tax_fields:
tax.set(fieldname, 0.0)
@@ -194,25 +221,32 @@ class calculate_taxes_and_totals(object):
cumulated_tax_fraction = 0
total_inclusive_tax_amount_per_qty = 0
for i, tax in enumerate(self.doc.get("taxes")):
- tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
+ (
+ tax.tax_fraction_for_current_item,
+ inclusive_tax_amount_per_qty,
+ ) = self.get_current_tax_fraction(tax, item_tax_map)
- if i==0:
+ if i == 0:
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
else:
- tax.grand_total_fraction_for_current_item = \
- self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
+ tax.grand_total_fraction_for_current_item = (
+ self.doc.get("taxes")[i - 1].grand_total_fraction_for_current_item
+ tax.tax_fraction_for_current_item
+ )
cumulated_tax_fraction += tax.tax_fraction_for_current_item
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
- if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
+ if (
+ not self.discount_amount_applied
+ and item.qty
+ and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty)
+ ):
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
- item.discount_percentage = flt(item.discount_percentage,
- item.precision("discount_percentage"))
+ item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
self._set_in_company_currency(item, ["net_rate", "net_amount"])
@@ -221,8 +255,8 @@ class calculate_taxes_and_totals(object):
def get_current_tax_fraction(self, tax, item_tax_map):
"""
- Get tax fraction for calculating tax exclusive amount
- from tax inclusive amount
+ Get tax fraction for calculating tax exclusive amount
+ from tax inclusive amount
"""
current_tax_fraction = 0
inclusive_tax_amount_per_qty = 0
@@ -234,12 +268,14 @@ class calculate_taxes_and_totals(object):
current_tax_fraction = tax_rate / 100.0
elif tax.charge_type == "On Previous Row Amount":
- current_tax_fraction = (tax_rate / 100.0) * \
- self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
+ current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
+ cint(tax.row_id) - 1
+ ].tax_fraction_for_current_item
elif tax.charge_type == "On Previous Row Total":
- current_tax_fraction = (tax_rate / 100.0) * \
- self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
+ current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
+ cint(tax.row_id) - 1
+ ].grand_total_fraction_for_current_item
elif tax.charge_type == "On Item Quantity":
inclusive_tax_amount_per_qty = flt(tax_rate)
@@ -257,7 +293,9 @@ class calculate_taxes_and_totals(object):
return tax.rate
def calculate_net_total(self):
- self.doc.total_qty = self.doc.total = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
+ self.doc.total_qty = (
+ self.doc.total
+ ) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
for item in self.doc.get("items"):
self.doc.total += item.amount
@@ -276,13 +314,20 @@ class calculate_taxes_and_totals(object):
self._calculate()
def calculate_taxes(self):
- rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
+ rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get(
+ "rounding_adjustment"
+ )
if not rounding_adjustment_computed:
self.doc.rounding_adjustment = 0
# maintain actual tax rate based on idx
- actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
- for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
+ actual_tax_dict = dict(
+ [
+ [tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
+ for tax in self.doc.get("taxes")
+ if tax.charge_type == "Actual"
+ ]
+ )
for n, item in enumerate(self.doc.get("items")):
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
@@ -297,9 +342,10 @@ class calculate_taxes_and_totals(object):
current_tax_amount += actual_tax_dict[tax.idx]
# accumulate tax amount into tax.tax_amount
- if tax.charge_type != "Actual" and \
- not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
- tax.tax_amount += current_tax_amount
+ if tax.charge_type != "Actual" and not (
+ self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
+ ):
+ tax.tax_amount += current_tax_amount
# store tax_amount for current item as it will be used for
# charge type = 'On Previous Row Amount'
@@ -312,17 +358,17 @@ class calculate_taxes_and_totals(object):
# note: grand_total_for_current_item contains the contribution of
# item's amount, previously applied tax and the current tax on that item
- if i==0:
+ if i == 0:
tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
else:
- tax.grand_total_for_current_item = \
- flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
+ tax.grand_total_for_current_item = flt(
+ self.doc.get("taxes")[i - 1].grand_total_for_current_item + current_tax_amount
+ )
# set precision in the last item iteration
if n == len(self.doc.get("items")) - 1:
self.round_off_totals(tax)
- self._set_in_company_currency(tax,
- ["tax_amount", "tax_amount_after_discount_amount"])
+ self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
self.round_off_base_values(tax)
self.set_cumulative_total(i, tax)
@@ -330,20 +376,29 @@ class calculate_taxes_and_totals(object):
self._set_in_company_currency(tax, ["total"])
# adjust Discount Amount loss in last tax iteration
- if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
- and self.doc.discount_amount \
- and self.doc.apply_discount_on == "Grand Total" \
- and not rounding_adjustment_computed:
- self.doc.rounding_adjustment = flt(self.doc.grand_total
- - flt(self.doc.discount_amount) - tax.total,
- self.doc.precision("rounding_adjustment"))
+ if (
+ i == (len(self.doc.get("taxes")) - 1)
+ and self.discount_amount_applied
+ and self.doc.discount_amount
+ and self.doc.apply_discount_on == "Grand Total"
+ and not rounding_adjustment_computed
+ ):
+ self.doc.rounding_adjustment = flt(
+ self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
+ self.doc.precision("rounding_adjustment"),
+ )
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
# if just for valuation, do not add the tax amount in total
# if tax/charges is for deduction, multiply by -1
if getattr(tax, "category", None):
tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
- if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
+ if self.doc.doctype in [
+ "Purchase Order",
+ "Purchase Invoice",
+ "Purchase Receipt",
+ "Supplier Quotation",
+ ]:
tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
return tax_amount
@@ -354,7 +409,7 @@ class calculate_taxes_and_totals(object):
if row_idx == 0:
tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
else:
- tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
+ tax.total = flt(self.doc.get("taxes")[row_idx - 1].total + tax_amount, tax.precision("total"))
def get_current_tax_amount(self, item, tax, item_tax_map):
tax_rate = self._get_tax_rate(tax, item_tax_map)
@@ -363,16 +418,20 @@ class calculate_taxes_and_totals(object):
if tax.charge_type == "Actual":
# distribute the tax amount proportionally to each item row
actual = flt(tax.tax_amount, tax.precision("tax_amount"))
- current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
+ current_tax_amount = (
+ item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
+ )
elif tax.charge_type == "On Net Total":
current_tax_amount = (tax_rate / 100.0) * item.net_amount
elif tax.charge_type == "On Previous Row Amount":
- current_tax_amount = (tax_rate / 100.0) * \
- self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
+ current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
+ cint(tax.row_id) - 1
+ ].tax_amount_for_current_item
elif tax.charge_type == "On Previous Row Total":
- current_tax_amount = (tax_rate / 100.0) * \
- self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
+ current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
+ cint(tax.row_id) - 1
+ ].grand_total_for_current_item
elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.qty
@@ -384,11 +443,11 @@ class calculate_taxes_and_totals(object):
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
# store tax breakup for each item
key = item.item_code or item.item_name
- item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
+ item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate
if tax.item_wise_tax_detail.get(key):
item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
- tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
+ tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
def round_off_totals(self, tax):
if tax.account_head in frappe.flags.round_off_applicable_accounts:
@@ -396,8 +455,9 @@ class calculate_taxes_and_totals(object):
tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
- tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
- tax.precision("tax_amount"))
+ tax.tax_amount_after_discount_amount = flt(
+ tax.tax_amount_after_discount_amount, tax.precision("tax_amount")
+ )
def round_off_base_values(self, tax):
# Round off to nearest integer based on regional settings
@@ -409,11 +469,15 @@ class calculate_taxes_and_totals(object):
# if fully inclusive taxes and diff
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
last_tax = self.doc.get("taxes")[-1]
- non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
- for d in self.doc.get("taxes") if not d.included_in_print_rate)
+ non_inclusive_tax_amount = sum(
+ flt(d.tax_amount_after_discount_amount)
+ for d in self.doc.get("taxes")
+ if not d.included_in_print_rate
+ )
- diff = self.doc.total + non_inclusive_tax_amount \
- - flt(last_tax.total, last_tax.precision("total"))
+ diff = (
+ self.doc.total + non_inclusive_tax_amount - flt(last_tax.total, last_tax.precision("total"))
+ )
# If discount amount applied, deduct the discount amount
# because self.doc.total is always without discount, but last_tax.total is after discount
@@ -422,7 +486,7 @@ class calculate_taxes_and_totals(object):
diff = flt(diff, self.doc.precision("rounding_adjustment"))
- if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
+ if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
self.doc.rounding_adjustment = diff
def calculate_totals(self):
@@ -432,16 +496,27 @@ class calculate_taxes_and_totals(object):
self.doc.grand_total = flt(self.doc.net_total)
if self.doc.get("taxes"):
- self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
- - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
+ self.doc.total_taxes_and_charges = flt(
+ self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
+ self.doc.precision("total_taxes_and_charges"),
+ )
else:
self.doc.total_taxes_and_charges = 0.0
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
- if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
- self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
- if self.doc.total_taxes_and_charges else self.doc.base_net_total
+ if self.doc.doctype in [
+ "Quotation",
+ "Sales Order",
+ "Delivery Note",
+ "Sales Invoice",
+ "POS Invoice",
+ ]:
+ self.doc.base_grand_total = (
+ flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total"))
+ if self.doc.total_taxes_and_charges
+ else self.doc.base_net_total
+ )
else:
self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
for tax in self.doc.get("taxes"):
@@ -453,26 +528,29 @@ class calculate_taxes_and_totals(object):
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
- self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
- if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
+ self.doc.base_grand_total = (
+ flt(self.doc.grand_total * self.doc.conversion_rate)
+ if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted)
else self.doc.base_net_total
+ )
- self._set_in_company_currency(self.doc,
- ["taxes_and_charges_added", "taxes_and_charges_deducted"])
+ self._set_in_company_currency(
+ self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]
+ )
self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
self.set_rounded_total()
def calculate_total_net_weight(self):
- if self.doc.meta.get_field('total_net_weight'):
+ if self.doc.meta.get_field("total_net_weight"):
self.doc.total_net_weight = 0.0
for d in self.doc.items:
if d.total_weight:
self.doc.total_net_weight += d.total_weight
def set_rounded_total(self):
- if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'):
+ if self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment"):
return
if self.doc.meta.get_field("rounded_total"):
@@ -480,33 +558,40 @@ class calculate_taxes_and_totals(object):
self.doc.rounded_total = self.doc.base_rounded_total = 0
return
- self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
- self.doc.currency, self.doc.precision("rounded_total"))
+ self.doc.rounded_total = round_based_on_smallest_currency_fraction(
+ self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
+ )
- #if print_in_rate is set, we would have already calculated rounding adjustment
- self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
- self.doc.precision("rounding_adjustment"))
+ # if print_in_rate is set, we would have already calculated rounding adjustment
+ self.doc.rounding_adjustment += flt(
+ self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
+ )
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
def _cleanup(self):
- if not self.doc.get('is_consolidated'):
+ if not self.doc.get("is_consolidated"):
for tax in self.doc.get("taxes"):
if not tax.get("dont_recompute_tax"):
- tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
+ tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(",", ":"))
def set_discount_amount(self):
if self.doc.additional_discount_percentage:
- self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
- * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
+ self.doc.discount_amount = flt(
+ flt(self.doc.get(scrub(self.doc.apply_discount_on)))
+ * self.doc.additional_discount_percentage
+ / 100,
+ self.doc.precision("discount_amount"),
+ )
def apply_discount_amount(self):
if self.doc.discount_amount:
if not self.doc.apply_discount_on:
frappe.throw(_("Please select Apply Discount On"))
- self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
- self.doc.precision("base_discount_amount"))
+ self.doc.base_discount_amount = flt(
+ self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
+ )
total_for_discount_amount = self.get_total_for_discount_amount()
taxes = self.doc.get("taxes")
@@ -515,20 +600,24 @@ class calculate_taxes_and_totals(object):
if total_for_discount_amount:
# calculate item amount after Discount Amount
for i, item in enumerate(self.doc.get("items")):
- distributed_amount = flt(self.doc.discount_amount) * \
- item.net_amount / total_for_discount_amount
+ distributed_amount = (
+ flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
+ )
item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
net_total += item.net_amount
# discount amount rounding loss adjustment if no taxes
- if (self.doc.apply_discount_on == "Net Total" or not taxes or total_for_discount_amount==self.doc.net_total) \
- and i == len(self.doc.get("items")) - 1:
- discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
- self.doc.precision("net_total"))
+ if (
+ self.doc.apply_discount_on == "Net Total"
+ or not taxes
+ or total_for_discount_amount == self.doc.net_total
+ ) and i == len(self.doc.get("items")) - 1:
+ discount_amount_loss = flt(
+ self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
+ )
- item.net_amount = flt(item.net_amount + discount_amount_loss,
- item.precision("net_amount"))
+ item.net_amount = flt(item.net_amount + discount_amount_loss, item.precision("net_amount"))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) if item.qty else 0
@@ -553,42 +642,52 @@ class calculate_taxes_and_totals(object):
actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
- return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
- self.doc.precision("grand_total"))
-
+ return flt(
+ self.doc.grand_total - sum(actual_taxes_dict.values()), self.doc.precision("grand_total")
+ )
def calculate_total_advance(self):
if self.doc.docstatus < 2:
- total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
- for adv in self.doc.get("advances"))
+ total_allocated_amount = sum(
+ flt(adv.allocated_amount, adv.precision("allocated_amount"))
+ for adv in self.doc.get("advances")
+ )
self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
grand_total = self.doc.rounded_total or self.doc.grand_total
if self.doc.party_account_currency == self.doc.currency:
- invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
- self.doc.precision("grand_total"))
+ invoice_total = flt(
+ grand_total - flt(self.doc.write_off_amount), self.doc.precision("grand_total")
+ )
else:
- base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
- self.doc.precision("base_write_off_amount"))
- invoice_total = flt(grand_total * self.doc.conversion_rate,
- self.doc.precision("grand_total")) - base_write_off_amount
+ base_write_off_amount = flt(
+ flt(self.doc.write_off_amount) * self.doc.conversion_rate,
+ self.doc.precision("base_write_off_amount"),
+ )
+ invoice_total = (
+ flt(grand_total * self.doc.conversion_rate, self.doc.precision("grand_total"))
+ - base_write_off_amount
+ )
if invoice_total > 0 and self.doc.total_advance > invoice_total:
- frappe.throw(_("Advance amount cannot be greater than {0} {1}")
- .format(self.doc.party_account_currency, invoice_total))
+ frappe.throw(
+ _("Advance amount cannot be greater than {0} {1}").format(
+ self.doc.party_account_currency, invoice_total
+ )
+ )
if self.doc.docstatus == 0:
self.calculate_outstanding_amount()
def is_internal_invoice(self):
"""
- Checks if its an internal transfer invoice
- and decides if to calculate any out standing amount or not
+ Checks if its an internal transfer invoice
+ and decides if to calculate any out standing amount or not
"""
- if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
+ if self.doc.doctype in ("Sales Invoice", "Purchase Invoice") and self.doc.is_internal_transfer():
return True
return False
@@ -600,43 +699,62 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount()
- if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
- self.is_internal_invoice(): return
+ if (
+ self.doc.is_return
+ and self.doc.return_against
+ and not self.doc.get("is_pos")
+ or self.is_internal_invoice()
+ ):
+ return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
- self._set_in_company_currency(self.doc, ['write_off_amount'])
+ self._set_in_company_currency(self.doc, ["write_off_amount"])
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
grand_total = self.doc.rounded_total or self.doc.grand_total
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
if self.doc.party_account_currency == self.doc.currency:
- total_amount_to_pay = flt(grand_total - self.doc.total_advance
- - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
+ total_amount_to_pay = flt(
+ grand_total - self.doc.total_advance - flt(self.doc.write_off_amount),
+ self.doc.precision("grand_total"),
+ )
else:
- total_amount_to_pay = flt(flt(base_grand_total, self.doc.precision("base_grand_total")) - self.doc.total_advance
- - flt(self.doc.base_write_off_amount), self.doc.precision("base_grand_total"))
+ total_amount_to_pay = flt(
+ flt(base_grand_total, self.doc.precision("base_grand_total"))
+ - self.doc.total_advance
+ - flt(self.doc.base_write_off_amount),
+ self.doc.precision("base_grand_total"),
+ )
self.doc.round_floats_in(self.doc, ["paid_amount"])
change_amount = 0
- if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
+ if self.doc.doctype == "Sales Invoice" and not self.doc.get("is_return"):
self.calculate_write_off_amount()
self.calculate_change_amount()
- change_amount = self.doc.change_amount \
- if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
+ change_amount = (
+ self.doc.change_amount
+ if self.doc.party_account_currency == self.doc.currency
+ else self.doc.base_change_amount
+ )
- paid_amount = self.doc.paid_amount \
- if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
+ paid_amount = (
+ self.doc.paid_amount
+ if self.doc.party_account_currency == self.doc.currency
+ else self.doc.base_paid_amount
+ )
- self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
- self.doc.precision("outstanding_amount"))
+ self.doc.outstanding_amount = flt(
+ total_amount_to_pay - flt(paid_amount) + flt(change_amount),
+ self.doc.precision("outstanding_amount"),
+ )
if (
- self.doc.doctype == 'Sales Invoice'
- and self.doc.get('is_pos')
- and self.doc.get('is_return')
- and not self.doc.get('is_consolidated')
+ self.doc.doctype == "Sales Invoice"
+ and self.doc.get("is_pos")
+ and self.doc.get("is_return")
+ and not self.doc.get("is_consolidated")
):
self.set_total_amount_to_default_mop(total_amount_to_pay)
self.calculate_paid_amount()
@@ -646,17 +764,17 @@ class calculate_taxes_and_totals(object):
paid_amount = base_paid_amount = 0.0
if self.doc.is_pos:
- for payment in self.doc.get('payments'):
+ for payment in self.doc.get("payments"):
payment.amount = flt(payment.amount)
payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
paid_amount += payment.amount
base_paid_amount += payment.base_amount
elif not self.doc.is_return:
- self.doc.set('payments', [])
+ self.doc.set("payments", [])
if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
base_paid_amount += self.doc.loyalty_amount
- paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
+ paid_amount += self.doc.loyalty_amount / flt(self.doc.conversion_rate)
self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
@@ -667,22 +785,33 @@ class calculate_taxes_and_totals(object):
grand_total = self.doc.rounded_total or self.doc.grand_total
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
- if self.doc.doctype == "Sales Invoice" \
- and self.doc.paid_amount > grand_total and not self.doc.is_return \
- and any(d.type == "Cash" for d in self.doc.payments):
+ if (
+ self.doc.doctype == "Sales Invoice"
+ and self.doc.paid_amount > grand_total
+ and not self.doc.is_return
+ and any(d.type == "Cash" for d in self.doc.payments)
+ ):
- self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
- self.doc.write_off_amount, self.doc.precision("change_amount"))
+ self.doc.change_amount = flt(
+ self.doc.paid_amount - grand_total + self.doc.write_off_amount,
+ self.doc.precision("change_amount"),
+ )
- self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
- self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
+ self.doc.base_change_amount = flt(
+ self.doc.base_paid_amount - base_grand_total + self.doc.base_write_off_amount,
+ self.doc.precision("base_change_amount"),
+ )
def calculate_write_off_amount(self):
if flt(self.doc.change_amount) > 0:
- self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
- + self.doc.change_amount, self.doc.precision("write_off_amount"))
- self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
- self.doc.precision("base_write_off_amount"))
+ self.doc.write_off_amount = flt(
+ self.doc.grand_total - self.doc.paid_amount + self.doc.change_amount,
+ self.doc.precision("write_off_amount"),
+ )
+ self.doc.base_write_off_amount = flt(
+ self.doc.write_off_amount * self.doc.conversion_rate,
+ self.doc.precision("base_write_off_amount"),
+ )
def calculate_margin(self, item):
rate_with_margin = 0.0
@@ -691,10 +820,15 @@ class calculate_taxes_and_totals(object):
if item.pricing_rules and not self.doc.ignore_pricing_rule:
has_margin = False
for d in get_applied_pricing_rules(item.pricing_rules):
- pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
+ pricing_rule = frappe.get_cached_doc("Pricing Rule", d)
- if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
- pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
+ if pricing_rule.margin_rate_or_amount and (
+ (
+ pricing_rule.currency == self.doc.currency
+ and pricing_rule.margin_type in ["Amount", "Percentage"]
+ )
+ or pricing_rule.margin_type == "Percentage"
+ ):
item.margin_type = pricing_rule.margin_type
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
has_margin = True
@@ -705,12 +839,17 @@ class calculate_taxes_and_totals(object):
if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
item.margin_type = "Amount"
- item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
- item.precision("margin_rate_or_amount"))
+ item.margin_rate_or_amount = flt(
+ item.rate - item.price_list_rate, item.precision("margin_rate_or_amount")
+ )
item.rate_with_margin = item.rate
elif item.margin_type and item.margin_rate_or_amount:
- margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
+ margin_value = (
+ item.margin_rate_or_amount
+ if item.margin_type == "Amount"
+ else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
+ )
rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
@@ -720,16 +859,24 @@ class calculate_taxes_and_totals(object):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def set_total_amount_to_default_mop(self, total_amount_to_pay):
- default_mode_of_payment = frappe.db.get_value('POS Payment Method',
- {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
+ default_mode_of_payment = frappe.db.get_value(
+ "POS Payment Method",
+ {"parent": self.doc.pos_profile, "default": 1},
+ ["mode_of_payment"],
+ as_dict=1,
+ )
if default_mode_of_payment:
self.doc.payments = []
- self.doc.append('payments', {
- 'mode_of_payment': default_mode_of_payment.mode_of_payment,
- 'amount': total_amount_to_pay,
- 'default': 1
- })
+ self.doc.append(
+ "payments",
+ {
+ "mode_of_payment": default_mode_of_payment.mode_of_payment,
+ "amount": total_amount_to_pay,
+ "default": 1,
+ },
+ )
+
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
@@ -739,7 +886,7 @@ def get_itemised_tax_breakup_html(doc):
# get headers
tax_accounts = []
for tax in doc.taxes:
- if getattr(tax, "category", None) and tax.category=="Valuation":
+ if getattr(tax, "category", None) and tax.category == "Valuation":
continue
if tax.description not in tax_accounts:
tax_accounts.append(tax.description)
@@ -755,34 +902,40 @@ def get_itemised_tax_breakup_html(doc):
frappe.flags.company = None
return frappe.render_template(
- "templates/includes/itemised_tax_breakup.html", dict(
+ "templates/includes/itemised_tax_breakup.html",
+ dict(
headers=headers,
itemised_tax=itemised_tax,
itemised_taxable_amount=itemised_taxable_amount,
tax_accounts=tax_accounts,
- doc=doc
- )
+ doc=doc,
+ ),
)
+
@frappe.whitelist()
def get_round_off_applicable_accounts(company, account_list):
account_list = get_regional_round_off_accounts(company, account_list)
return account_list
+
@erpnext.allow_regional
def get_regional_round_off_accounts(company, account_list):
pass
+
@erpnext.allow_regional
def update_itemised_tax_data(doc):
- #Don't delete this method, used for localization
+ # Don't delete this method, used for localization
pass
+
@erpnext.allow_regional
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
return [_("Item"), _("Taxable Amount")] + tax_accounts
+
@erpnext.allow_regional
def get_itemised_tax_breakup_data(doc):
itemised_tax = get_itemised_tax(doc.taxes)
@@ -791,10 +944,11 @@ def get_itemised_tax_breakup_data(doc):
return itemised_tax, itemised_taxable_amount
+
def get_itemised_tax(taxes, with_tax_account=False):
itemised_tax = {}
for tax in taxes:
- if getattr(tax, "category", None) and tax.category=="Valuation":
+ if getattr(tax, "category", None) and tax.category == "Valuation":
continue
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
@@ -811,16 +965,16 @@ def get_itemised_tax(taxes, with_tax_account=False):
else:
tax_rate = flt(tax_data)
- itemised_tax[item_code][tax.description] = frappe._dict(dict(
- tax_rate = tax_rate,
- tax_amount = tax_amount
- ))
+ itemised_tax[item_code][tax.description] = frappe._dict(
+ dict(tax_rate=tax_rate, tax_amount=tax_amount)
+ )
if with_tax_account:
itemised_tax[item_code][tax.description].tax_account = tax.account_head
return itemised_tax
+
def get_itemised_taxable_amount(items):
itemised_taxable_amount = frappe._dict()
for item in items:
@@ -830,16 +984,18 @@ def get_itemised_taxable_amount(items):
return itemised_taxable_amount
+
def get_rounded_tax_amount(itemised_tax, precision):
# Rounding based on tax_amount precision
for taxes in itemised_tax.values():
for tax_account in taxes:
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
+
class init_landed_taxes_and_totals(object):
def __init__(self, doc):
self.doc = doc
- self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
+ self.tax_field = "taxes" if self.doc.doctype == "Landed Cost Voucher" else "additional_costs"
self.set_account_currency()
self.set_exchange_rate()
self.set_amounts_in_company_currency()
@@ -848,7 +1004,7 @@ class init_landed_taxes_and_totals(object):
company_currency = erpnext.get_company_currency(self.doc.company)
for d in self.doc.get(self.tax_field):
if not d.account_currency:
- account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
+ account_currency = frappe.db.get_value("Account", d.expense_account, "account_currency")
d.account_currency = account_currency or company_currency
def set_exchange_rate(self):
@@ -857,8 +1013,12 @@ class init_landed_taxes_and_totals(object):
if d.account_currency == company_currency:
d.exchange_rate = 1
elif not d.exchange_rate:
- d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
- account_currency=d.account_currency, company=self.doc.company)
+ d.exchange_rate = get_exchange_rate(
+ self.doc.posting_date,
+ account=d.expense_account,
+ account_currency=d.account_currency,
+ company=self.doc.company,
+ )
if not d.exchange_rate:
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
diff --git a/erpnext/controllers/tests/test_item_variant.py b/erpnext/controllers/tests/test_item_variant.py
index 5c6e06a7d7..68c8d2cd2c 100644
--- a/erpnext/controllers/tests/test_item_variant.py
+++ b/erpnext/controllers/tests/test_item_variant.py
@@ -12,11 +12,12 @@ from erpnext.stock.doctype.quality_inspection.test_quality_inspection import (
class TestItemVariant(unittest.TestCase):
def test_tables_in_template_copied_to_variant(self):
- fields = [{'field_name': 'quality_inspection_template'}]
+ fields = [{"field_name": "quality_inspection_template"}]
set_item_variant_settings(fields)
variant = make_item_variant()
self.assertEqual(variant.get("quality_inspection_template"), "_Test QC Template")
+
def create_variant_with_tables(item, args):
if isinstance(args, str):
args = json.loads(args)
@@ -27,14 +28,11 @@ def create_variant_with_tables(item, args):
template.save()
variant = frappe.new_doc("Item")
- variant.variant_based_on = 'Item Attribute'
+ variant.variant_based_on = "Item Attribute"
variant_attributes = []
for d in template.attributes:
- variant_attributes.append({
- "attribute": d.attribute,
- "attribute_value": args.get(d.attribute)
- })
+ variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)})
variant.set("attributes", variant_attributes)
copy_attributes_to_variant(template, variant)
@@ -42,6 +40,7 @@ def create_variant_with_tables(item, args):
return variant
+
def make_item_variant():
frappe.delete_doc_if_exists("Item", "_Test Variant Item-XSL", force=1)
variant = create_variant_with_tables("_Test Variant Item", '{"Test Size": "Extra Small"}')
@@ -50,6 +49,7 @@ def make_item_variant():
variant.save()
return variant
+
def make_quality_inspection_template():
qc_template = "_Test QC Template"
if frappe.db.exists("Quality Inspection Template", qc_template):
@@ -59,10 +59,13 @@ def make_quality_inspection_template():
qc.quality_inspection_template_name = qc_template
create_quality_inspection_parameter("Moisture")
- qc.append('item_quality_inspection_parameter', {
- "specification": "Moisture",
- "value": "< 5%",
- })
+ qc.append(
+ "item_quality_inspection_parameter",
+ {
+ "specification": "Moisture",
+ "value": "< 5%",
+ },
+ )
qc.insert()
return qc.name
diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py
index e75587607e..919bcdab66 100644
--- a/erpnext/controllers/tests/test_mapper.py
+++ b/erpnext/controllers/tests/test_mapper.py
@@ -10,14 +10,14 @@ from frappe.utils import add_months, nowdate
class TestMapper(unittest.TestCase):
def test_map_docs(self):
- '''Test mapping of multiple source docs on a single target doc'''
+ """Test mapping of multiple source docs on a single target doc"""
make_test_records("Item")
- items = ['_Test Item', '_Test Item 2', '_Test FG Item']
+ items = ["_Test Item", "_Test Item 2", "_Test FG Item"]
# Make source docs (quotations) and a target doc (sales order)
- qtn1, item_list_1 = self.make_quotation(items, '_Test Customer')
- qtn2, item_list_2 = self.make_quotation(items, '_Test Customer')
+ qtn1, item_list_1 = self.make_quotation(items, "_Test Customer")
+ qtn2, item_list_2 = self.make_quotation(items, "_Test Customer")
so, item_list_3 = self.make_sales_order()
# Map source docs to target with corresponding mapper method
@@ -26,20 +26,20 @@ class TestMapper(unittest.TestCase):
# Assert that all inserted items are present in updated sales order
src_items = item_list_1 + item_list_2 + item_list_3
- self.assertEqual(set(d for d in src_items),
- set(d.item_code for d in updated_so.items))
-
+ self.assertEqual(set(d for d in src_items), set(d.item_code for d in updated_so.items))
def make_quotation(self, item_list, customer):
- qtn = frappe.get_doc({
- "doctype": "Quotation",
- "quotation_to": "Customer",
- "party_name": customer,
- "order_type": "Sales",
- "transaction_date" : nowdate(),
- "valid_till" : add_months(nowdate(), 1)
- })
+ qtn = frappe.get_doc(
+ {
+ "doctype": "Quotation",
+ "quotation_to": "Customer",
+ "party_name": customer,
+ "order_type": "Sales",
+ "transaction_date": nowdate(),
+ "valid_till": add_months(nowdate(), 1),
+ }
+ )
for item in item_list:
qtn.append("items", {"qty": "2", "item_code": item})
@@ -47,21 +47,23 @@ class TestMapper(unittest.TestCase):
return qtn, item_list
def make_sales_order(self):
- item = frappe.get_doc({
- "base_amount": 1000.0,
- "base_rate": 100.0,
- "description": "CPU",
- "doctype": "Sales Order Item",
- "item_code": "_Test Item",
- "item_name": "CPU",
- "parentfield": "items",
- "qty": 10.0,
- "rate": 100.0,
- "warehouse": "_Test Warehouse - _TC",
- "stock_uom": "_Test UOM",
- "conversion_factor": 1.0,
- "uom": "_Test UOM"
- })
- so = frappe.get_doc(frappe.get_test_records('Sales Order')[0])
+ item = frappe.get_doc(
+ {
+ "base_amount": 1000.0,
+ "base_rate": 100.0,
+ "description": "CPU",
+ "doctype": "Sales Order Item",
+ "item_code": "_Test Item",
+ "item_name": "CPU",
+ "parentfield": "items",
+ "qty": 10.0,
+ "rate": 100.0,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "_Test UOM",
+ "conversion_factor": 1.0,
+ "uom": "_Test UOM",
+ }
+ )
+ so = frappe.get_doc(frappe.get_test_records("Sales Order")[0])
so.insert(ignore_permissions=True)
return so, [item.item_code]
diff --git a/erpnext/controllers/tests/test_qty_based_taxes.py b/erpnext/controllers/tests/test_qty_based_taxes.py
index 49b844bf7e..2e9dfd2faa 100644
--- a/erpnext/controllers/tests/test_qty_based_taxes.py
+++ b/erpnext/controllers/tests/test_qty_based_taxes.py
@@ -5,104 +5,131 @@ import frappe
def uuid4():
- return str(_uuid4())
+ return str(_uuid4())
+
class TestTaxes(unittest.TestCase):
- def setUp(self):
- self.company = frappe.get_doc({
- 'doctype': 'Company',
- 'company_name': uuid4(),
- 'abbr': ''.join(s[0] for s in uuid4().split('-')),
- 'default_currency': 'USD',
- 'country': 'United States',
- }).insert()
- self.account = frappe.get_doc({
- 'doctype': 'Account',
- 'account_name': uuid4(),
- 'account_type': 'Tax',
- 'company': self.company.name,
- 'parent_account': 'Duties and Taxes - {self.company.abbr}'.format(self=self)
- }).insert()
- self.item_group = frappe.get_doc({
- 'doctype': 'Item Group',
- 'item_group_name': uuid4(),
- 'parent_item_group': 'All Item Groups',
- }).insert()
- self.item_tax_template = frappe.get_doc({
- 'doctype': 'Item Tax Template',
- 'title': uuid4(),
- 'company': self.company.name,
- 'taxes': [
- {
- 'tax_type': self.account.name,
- 'tax_rate': 2,
- }
- ]
- }).insert()
- self.item = frappe.get_doc({
- 'doctype': 'Item',
- 'item_code': uuid4(),
- 'item_group': self.item_group.name,
- 'is_stock_item': 0,
- 'taxes': [
- {
- 'item_tax_template': self.item_tax_template.name,
- 'tax_category': '',
- }
- ],
- }).insert()
- self.customer = frappe.get_doc({
- 'doctype': 'Customer',
- 'customer_name': uuid4(),
- 'customer_group': 'All Customer Groups',
- }).insert()
- self.supplier = frappe.get_doc({
- 'doctype': 'Supplier',
- 'supplier_name': uuid4(),
- 'supplier_group': 'All Supplier Groups',
- }).insert()
+ def setUp(self):
+ self.company = frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": uuid4(),
+ "abbr": "".join(s[0] for s in uuid4().split("-")),
+ "default_currency": "USD",
+ "country": "United States",
+ }
+ ).insert()
+ self.account = frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": uuid4(),
+ "account_type": "Tax",
+ "company": self.company.name,
+ "parent_account": "Duties and Taxes - {self.company.abbr}".format(self=self),
+ }
+ ).insert()
+ self.item_group = frappe.get_doc(
+ {
+ "doctype": "Item Group",
+ "item_group_name": uuid4(),
+ "parent_item_group": "All Item Groups",
+ }
+ ).insert()
+ self.item_tax_template = frappe.get_doc(
+ {
+ "doctype": "Item Tax Template",
+ "title": uuid4(),
+ "company": self.company.name,
+ "taxes": [
+ {
+ "tax_type": self.account.name,
+ "tax_rate": 2,
+ }
+ ],
+ }
+ ).insert()
+ self.item = frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": uuid4(),
+ "item_group": self.item_group.name,
+ "is_stock_item": 0,
+ "taxes": [
+ {
+ "item_tax_template": self.item_tax_template.name,
+ "tax_category": "",
+ }
+ ],
+ }
+ ).insert()
+ self.customer = frappe.get_doc(
+ {
+ "doctype": "Customer",
+ "customer_name": uuid4(),
+ "customer_group": "All Customer Groups",
+ }
+ ).insert()
+ self.supplier = frappe.get_doc(
+ {
+ "doctype": "Supplier",
+ "supplier_name": uuid4(),
+ "supplier_group": "All Supplier Groups",
+ }
+ ).insert()
- def test_taxes(self):
- self.created_docs = []
- for dt in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice',
- 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']:
- doc = frappe.get_doc({
- 'doctype': dt,
- 'company': self.company.name,
- 'supplier': self.supplier.name,
- 'currency': "USD",
- 'schedule_date': frappe.utils.nowdate(),
- 'delivery_date': frappe.utils.nowdate(),
- 'customer': self.customer.name,
- 'buying_price_list' if dt.startswith('Purchase') else 'selling_price_list'
- : 'Standard Buying' if dt.startswith('Purchase') else 'Standard Selling',
- 'items': [
- {
- 'item_code': self.item.name,
- 'qty': 300,
- 'rate': 100,
- }
- ],
- 'taxes': [
- {
- 'charge_type': 'On Item Quantity',
- 'account_head': self.account.name,
- 'description': 'N/A',
- 'rate': 0,
- },
- ],
- })
- doc.run_method('set_missing_values')
- doc.run_method('calculate_taxes_and_totals')
- doc.insert()
- self.assertEqual(doc.taxes[0].tax_amount, 600)
- self.created_docs.append(doc)
+ def test_taxes(self):
+ self.created_docs = []
+ for dt in [
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Quotation",
+ "Sales Order",
+ "Delivery Note",
+ "Sales Invoice",
+ ]:
+ doc = frappe.get_doc(
+ {
+ "doctype": dt,
+ "company": self.company.name,
+ "supplier": self.supplier.name,
+ "currency": "USD",
+ "schedule_date": frappe.utils.nowdate(),
+ "delivery_date": frappe.utils.nowdate(),
+ "customer": self.customer.name,
+ "buying_price_list"
+ if dt.startswith("Purchase")
+ else "selling_price_list": "Standard Buying"
+ if dt.startswith("Purchase")
+ else "Standard Selling",
+ "items": [
+ {
+ "item_code": self.item.name,
+ "qty": 300,
+ "rate": 100,
+ }
+ ],
+ "taxes": [
+ {
+ "charge_type": "On Item Quantity",
+ "account_head": self.account.name,
+ "description": "N/A",
+ "rate": 0,
+ },
+ ],
+ }
+ )
+ doc.run_method("set_missing_values")
+ doc.run_method("calculate_taxes_and_totals")
+ doc.insert()
+ self.assertEqual(doc.taxes[0].tax_amount, 600)
+ self.created_docs.append(doc)
- def tearDown(self):
- for doc in self.created_docs:
- doc.delete()
- self.item.delete()
- self.item_group.delete()
- self.item_tax_template.delete()
- self.account.delete()
- self.company.delete()
+ def tearDown(self):
+ for doc in self.created_docs:
+ doc.delete()
+ self.item.delete()
+ self.item_group.delete()
+ self.item_tax_template.delete()
+ self.account.delete()
+ self.company.delete()
diff --git a/erpnext/controllers/tests/test_transaction_base.py b/erpnext/controllers/tests/test_transaction_base.py
index f4d3f97ef0..1471543f1b 100644
--- a/erpnext/controllers/tests/test_transaction_base.py
+++ b/erpnext/controllers/tests/test_transaction_base.py
@@ -5,18 +5,28 @@ import frappe
class TestUtils(unittest.TestCase):
def test_reset_default_field_value(self):
- doc = frappe.get_doc({
- "doctype": "Purchase Receipt",
- "set_warehouse": "Warehouse 1",
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Purchase Receipt",
+ "set_warehouse": "Warehouse 1",
+ }
+ )
# Same values
- doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
+ doc.items = [
+ {"warehouse": "Warehouse 1"},
+ {"warehouse": "Warehouse 1"},
+ {"warehouse": "Warehouse 1"},
+ ]
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
self.assertEqual(doc.set_warehouse, "Warehouse 1")
# Mixed values
- doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
+ doc.items = [
+ {"warehouse": "Warehouse 1"},
+ {"warehouse": "Warehouse 2"},
+ {"warehouse": "Warehouse 1"},
+ ]
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
self.assertEqual(doc.set_warehouse, None)
@@ -30,9 +40,13 @@ class TestUtils(unittest.TestCase):
from_warehouse="_Test Warehouse - _TC",
to_warehouse="_Test Warehouse 1 - _TC",
items=[
- frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
- frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1)
- ]
+ frappe._dict(
+ item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"
+ ),
+ frappe._dict(
+ item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1
+ ),
+ ],
)
se.save()
@@ -43,18 +57,20 @@ class TestUtils(unittest.TestCase):
se.delete()
def test_reset_default_field_value_in_transfer_stock_entry(self):
- doc = frappe.get_doc({
- "doctype": "Stock Entry",
- "purpose": "Material Receipt",
- "from_warehouse": "Warehouse 1",
- "to_warehouse": "Warehouse 2",
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Stock Entry",
+ "purpose": "Material Receipt",
+ "from_warehouse": "Warehouse 1",
+ "to_warehouse": "Warehouse 2",
+ }
+ )
# Same values
doc.items = [
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
- {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
+ {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
]
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
@@ -66,10 +82,10 @@ class TestUtils(unittest.TestCase):
doc.items = [
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
{"s_warehouse": "Warehouse 3", "t_warehouse": "Warehouse 2"},
- {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
+ {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
]
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
self.assertEqual(doc.from_warehouse, None)
- self.assertEqual(doc.to_warehouse, "Warehouse 2")
\ No newline at end of file
+ self.assertEqual(doc.to_warehouse, "Warehouse 2")
diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py
index 1cb101f214..1d6c5dc0be 100644
--- a/erpnext/controllers/trends.py
+++ b/erpnext/controllers/trends.py
@@ -17,17 +17,33 @@ def get_columns(filters, trans):
# get conditions for grouping filter cond
group_by_cols = group_wise_column(filters.get("group_by"))
- columns = based_on_details["based_on_cols"] + period_cols + [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
+ columns = (
+ based_on_details["based_on_cols"]
+ + period_cols
+ + [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
+ )
if group_by_cols:
- columns = based_on_details["based_on_cols"] + group_by_cols + period_cols + \
- [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
+ columns = (
+ based_on_details["based_on_cols"]
+ + group_by_cols
+ + period_cols
+ + [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
+ )
- conditions = {"based_on_select": based_on_details["based_on_select"], "period_wise_select": period_select,
- "columns": columns, "group_by": based_on_details["based_on_group_by"], "grbc": group_by_cols, "trans": trans,
- "addl_tables": based_on_details["addl_tables"], "addl_tables_relational_cond": based_on_details.get("addl_tables_relational_cond", "")}
+ conditions = {
+ "based_on_select": based_on_details["based_on_select"],
+ "period_wise_select": period_select,
+ "columns": columns,
+ "group_by": based_on_details["based_on_group_by"],
+ "grbc": group_by_cols,
+ "trans": trans,
+ "addl_tables": based_on_details["addl_tables"],
+ "addl_tables_relational_cond": based_on_details.get("addl_tables_relational_cond", ""),
+ }
return conditions
+
def validate_filters(filters):
for f in ["Fiscal Year", "Based On", "Period", "Company"]:
if not filters.get(f.lower().replace(" ", "_")):
@@ -39,153 +55,231 @@ def validate_filters(filters):
if filters.get("based_on") == filters.get("group_by"):
frappe.throw(_("'Based On' and 'Group By' can not be same"))
+
def get_data(filters, conditions):
data = []
- inc, cond= '',''
- query_details = conditions["based_on_select"] + conditions["period_wise_select"]
+ inc, cond = "", ""
+ query_details = conditions["based_on_select"] + conditions["period_wise_select"]
- posting_date = 't1.transaction_date'
- if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']:
- posting_date = 't1.posting_date'
+ posting_date = "t1.transaction_date"
+ if conditions.get("trans") in [
+ "Sales Invoice",
+ "Purchase Invoice",
+ "Purchase Receipt",
+ "Delivery Note",
+ ]:
+ posting_date = "t1.posting_date"
if filters.period_based_on:
- posting_date = 't1.'+filters.period_based_on
+ posting_date = "t1." + filters.period_based_on
if conditions["based_on_select"] in ["t1.project,", "t2.project,"]:
- cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
- if conditions.get('trans') in ['Sales Order', 'Purchase Order']:
+ cond = " and " + conditions["based_on_select"][:-1] + " IS Not NULL"
+ if conditions.get("trans") in ["Sales Order", "Purchase Order"]:
cond += " and t1.status != 'Closed'"
- if conditions.get('trans') == 'Quotation' and filters.get("group_by") == 'Customer':
+ if conditions.get("trans") == "Quotation" and filters.get("group_by") == "Customer":
cond += " and t1.quotation_to = 'Customer'"
- year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
- filters.get('fiscal_year'), ["year_start_date", "year_end_date"])
+ year_start_date, year_end_date = frappe.db.get_value(
+ "Fiscal Year", filters.get("fiscal_year"), ["year_start_date", "year_end_date"]
+ )
if filters.get("group_by"):
- sel_col = ''
+ sel_col = ""
ind = conditions["columns"].index(conditions["grbc"][0])
- if filters.get("group_by") == 'Item':
- sel_col = 't2.item_code'
- elif filters.get("group_by") == 'Customer':
- sel_col = 't1.party_name' if conditions.get('trans') == 'Quotation' else 't1.customer'
- elif filters.get("group_by") == 'Supplier':
- sel_col = 't1.supplier'
+ if filters.get("group_by") == "Item":
+ sel_col = "t2.item_code"
+ elif filters.get("group_by") == "Customer":
+ sel_col = "t1.party_name" if conditions.get("trans") == "Quotation" else "t1.customer"
+ elif filters.get("group_by") == "Supplier":
+ sel_col = "t1.supplier"
- if filters.get('based_on') in ['Item','Customer','Supplier']:
+ if filters.get("based_on") in ["Item", "Customer", "Supplier"]:
inc = 2
- else :
+ else:
inc = 1
- data1 = frappe.db.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
+ data1 = frappe.db.sql(
+ """ select %s from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s and
t1.docstatus = 1 %s %s
group by %s
- """ % (query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"], "%s",
- posting_date, "%s", "%s", conditions.get("addl_tables_relational_cond"), cond, conditions["group_by"]), (filters.get("company"),
- year_start_date, year_end_date),as_list=1)
+ """
+ % (
+ query_details,
+ conditions["trans"],
+ conditions["trans"],
+ conditions["addl_tables"],
+ "%s",
+ posting_date,
+ "%s",
+ "%s",
+ conditions.get("addl_tables_relational_cond"),
+ cond,
+ conditions["group_by"],
+ ),
+ (filters.get("company"), year_start_date, year_end_date),
+ as_list=1,
+ )
for d in range(len(data1)):
- #to add blanck column
+ # to add blanck column
dt = data1[d]
- dt.insert(ind,'')
+ dt.insert(ind, "")
data.append(dt)
- #to get distinct value of col specified by group_by in filter
- row = frappe.db.sql("""select DISTINCT(%s) from `tab%s` t1, `tab%s Item` t2 %s
+ # to get distinct value of col specified by group_by in filter
+ row = frappe.db.sql(
+ """select DISTINCT(%s) from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s
and t1.docstatus = 1 and %s = %s %s %s
- """ %
- (sel_col, conditions["trans"], conditions["trans"], conditions["addl_tables"],
- "%s", posting_date, "%s", "%s", conditions["group_by"], "%s", conditions.get("addl_tables_relational_cond"), cond),
- (filters.get("company"), year_start_date, year_end_date, data1[d][0]), as_list=1)
+ """
+ % (
+ sel_col,
+ conditions["trans"],
+ conditions["trans"],
+ conditions["addl_tables"],
+ "%s",
+ posting_date,
+ "%s",
+ "%s",
+ conditions["group_by"],
+ "%s",
+ conditions.get("addl_tables_relational_cond"),
+ cond,
+ ),
+ (filters.get("company"), year_start_date, year_end_date, data1[d][0]),
+ as_list=1,
+ )
for i in range(len(row)):
- des = ['' for q in range(len(conditions["columns"]))]
+ des = ["" for q in range(len(conditions["columns"]))]
- #get data for group_by filter
- row1 = frappe.db.sql(""" select %s , %s from `tab%s` t1, `tab%s Item` t2 %s
+ # get data for group_by filter
+ row1 = frappe.db.sql(
+ """ select %s , %s from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s
and t1.docstatus = 1 and %s = %s and %s = %s %s %s
- """ %
- (sel_col, conditions["period_wise_select"], conditions["trans"],
- conditions["trans"], conditions["addl_tables"], "%s", posting_date, "%s","%s", sel_col,
- "%s", conditions["group_by"], "%s", conditions.get("addl_tables_relational_cond"), cond),
- (filters.get("company"), year_start_date, year_end_date, row[i][0],
- data1[d][0]), as_list=1)
+ """
+ % (
+ sel_col,
+ conditions["period_wise_select"],
+ conditions["trans"],
+ conditions["trans"],
+ conditions["addl_tables"],
+ "%s",
+ posting_date,
+ "%s",
+ "%s",
+ sel_col,
+ "%s",
+ conditions["group_by"],
+ "%s",
+ conditions.get("addl_tables_relational_cond"),
+ cond,
+ ),
+ (filters.get("company"), year_start_date, year_end_date, row[i][0], data1[d][0]),
+ as_list=1,
+ )
des[ind] = row[i][0]
- for j in range(1,len(conditions["columns"])-inc):
- des[j+inc] = row1[0][j]
+ for j in range(1, len(conditions["columns"]) - inc):
+ des[j + inc] = row1[0][j]
data.append(des)
else:
- data = frappe.db.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
+ data = frappe.db.sql(
+ """ select %s from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s and
t1.docstatus = 1 %s %s
group by %s
- """ %
- (query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"],
- "%s", posting_date, "%s", "%s", cond, conditions.get("addl_tables_relational_cond", ""), conditions["group_by"]),
- (filters.get("company"), year_start_date, year_end_date), as_list=1)
+ """
+ % (
+ query_details,
+ conditions["trans"],
+ conditions["trans"],
+ conditions["addl_tables"],
+ "%s",
+ posting_date,
+ "%s",
+ "%s",
+ cond,
+ conditions.get("addl_tables_relational_cond", ""),
+ conditions["group_by"],
+ ),
+ (filters.get("company"), year_start_date, year_end_date),
+ as_list=1,
+ )
return data
+
def get_mon(dt):
return getdate(dt).strftime("%b")
+
def period_wise_columns_query(filters, trans):
- query_details = ''
+ query_details = ""
pwc = []
bet_dates = get_period_date_ranges(filters.get("period"), filters.get("fiscal_year"))
- if trans in ['Purchase Receipt', 'Delivery Note', 'Purchase Invoice', 'Sales Invoice']:
- trans_date = 'posting_date'
+ if trans in ["Purchase Receipt", "Delivery Note", "Purchase Invoice", "Sales Invoice"]:
+ trans_date = "posting_date"
if filters.period_based_on:
trans_date = filters.period_based_on
else:
- trans_date = 'transaction_date'
+ trans_date = "transaction_date"
- if filters.get("period") != 'Yearly':
+ if filters.get("period") != "Yearly":
for dt in bet_dates:
get_period_wise_columns(dt, filters.get("period"), pwc)
query_details = get_period_wise_query(dt, trans_date, query_details)
else:
- pwc = [_(filters.get("fiscal_year")) + " ("+_("Qty") + "):Float:120",
- _(filters.get("fiscal_year")) + " ("+ _("Amt") + "):Currency:120"]
+ pwc = [
+ _(filters.get("fiscal_year")) + " (" + _("Qty") + "):Float:120",
+ _(filters.get("fiscal_year")) + " (" + _("Amt") + "):Currency:120",
+ ]
query_details = " SUM(t2.stock_qty), SUM(t2.base_net_amount),"
- query_details += 'SUM(t2.stock_qty), SUM(t2.base_net_amount)'
+ query_details += "SUM(t2.stock_qty), SUM(t2.base_net_amount)"
return pwc, query_details
+
def get_period_wise_columns(bet_dates, period, pwc):
- if period == 'Monthly':
- pwc += [_(get_mon(bet_dates[0])) + " (" + _("Qty") + "):Float:120",
- _(get_mon(bet_dates[0])) + " (" + _("Amt") + "):Currency:120"]
+ if period == "Monthly":
+ pwc += [
+ _(get_mon(bet_dates[0])) + " (" + _("Qty") + "):Float:120",
+ _(get_mon(bet_dates[0])) + " (" + _("Amt") + "):Currency:120",
+ ]
else:
- pwc += [_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Qty") + "):Float:120",
- _(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Amt") + "):Currency:120"]
+ pwc += [
+ _(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Qty") + "):Float:120",
+ _(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Amt") + "):Currency:120",
+ ]
+
def get_period_wise_query(bet_dates, trans_date, query_details):
query_details += """SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t2.stock_qty, NULL)),
SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t2.base_net_amount, NULL)),
- """ % {"trans_date": trans_date, "sd": bet_dates[0],"ed": bet_dates[1]}
+ """ % {
+ "trans_date": trans_date,
+ "sd": bet_dates[0],
+ "ed": bet_dates[1],
+ }
return query_details
+
@frappe.whitelist(allow_guest=True)
def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
from dateutil.relativedelta import relativedelta
if not year_start_date:
- year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
- fiscal_year, ["year_start_date", "year_end_date"])
+ year_start_date, year_end_date = frappe.db.get_value(
+ "Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
+ )
- increment = {
- "Monthly": 1,
- "Quarterly": 3,
- "Half-Yearly": 6,
- "Yearly": 12
- }.get(period)
+ increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get(period)
period_date_ranges = []
for i in range(1, 13, increment):
@@ -199,8 +293,10 @@ def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
return period_date_ranges
+
def get_period_month_ranges(period, fiscal_year):
from dateutil.relativedelta import relativedelta
+
period_month_ranges = []
for start_date, end_date in get_period_date_ranges(period, fiscal_year):
@@ -212,6 +308,7 @@ def get_period_month_ranges(period, fiscal_year):
return period_month_ranges
+
def based_wise_columns_query(based_on, trans):
based_on_details = {}
@@ -219,65 +316,74 @@ def based_wise_columns_query(based_on, trans):
if based_on == "Item":
based_on_details["based_on_cols"] = ["Item:Link/Item:120", "Item Name:Data:120"]
based_on_details["based_on_select"] = "t2.item_code, t2.item_name,"
- based_on_details["based_on_group_by"] = 't2.item_code'
- based_on_details["addl_tables"] = ''
+ based_on_details["based_on_group_by"] = "t2.item_code"
+ based_on_details["addl_tables"] = ""
elif based_on == "Item Group":
based_on_details["based_on_cols"] = ["Item Group:Link/Item Group:120"]
based_on_details["based_on_select"] = "t2.item_group,"
- based_on_details["based_on_group_by"] = 't2.item_group'
- based_on_details["addl_tables"] = ''
+ based_on_details["based_on_group_by"] = "t2.item_group"
+ based_on_details["addl_tables"] = ""
elif based_on == "Customer":
- based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"]
+ based_on_details["based_on_cols"] = [
+ "Customer:Link/Customer:120",
+ "Territory:Link/Territory:120",
+ ]
based_on_details["based_on_select"] = "t1.customer_name, t1.territory, "
- based_on_details["based_on_group_by"] = 't1.party_name' if trans == 'Quotation' else 't1.customer'
- based_on_details["addl_tables"] = ''
+ based_on_details["based_on_group_by"] = (
+ "t1.party_name" if trans == "Quotation" else "t1.customer"
+ )
+ based_on_details["addl_tables"] = ""
elif based_on == "Customer Group":
based_on_details["based_on_cols"] = ["Customer Group:Link/Customer Group"]
based_on_details["based_on_select"] = "t1.customer_group,"
- based_on_details["based_on_group_by"] = 't1.customer_group'
- based_on_details["addl_tables"] = ''
+ based_on_details["based_on_group_by"] = "t1.customer_group"
+ based_on_details["addl_tables"] = ""
- elif based_on == 'Supplier':
- based_on_details["based_on_cols"] = ["Supplier:Link/Supplier:120", "Supplier Group:Link/Supplier Group:140"]
+ elif based_on == "Supplier":
+ based_on_details["based_on_cols"] = [
+ "Supplier:Link/Supplier:120",
+ "Supplier Group:Link/Supplier Group:140",
+ ]
based_on_details["based_on_select"] = "t1.supplier, t3.supplier_group,"
- based_on_details["based_on_group_by"] = 't1.supplier'
- based_on_details["addl_tables"] = ',`tabSupplier` t3'
+ based_on_details["based_on_group_by"] = "t1.supplier"
+ based_on_details["addl_tables"] = ",`tabSupplier` t3"
based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name"
- elif based_on == 'Supplier Group':
+ elif based_on == "Supplier Group":
based_on_details["based_on_cols"] = ["Supplier Group:Link/Supplier Group:140"]
based_on_details["based_on_select"] = "t3.supplier_group,"
- based_on_details["based_on_group_by"] = 't3.supplier_group'
- based_on_details["addl_tables"] = ',`tabSupplier` t3'
+ based_on_details["based_on_group_by"] = "t3.supplier_group"
+ based_on_details["addl_tables"] = ",`tabSupplier` t3"
based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name"
elif based_on == "Territory":
based_on_details["based_on_cols"] = ["Territory:Link/Territory:120"]
based_on_details["based_on_select"] = "t1.territory,"
- based_on_details["based_on_group_by"] = 't1.territory'
- based_on_details["addl_tables"] = ''
+ based_on_details["based_on_group_by"] = "t1.territory"
+ based_on_details["addl_tables"] = ""
elif based_on == "Project":
- if trans in ['Sales Invoice', 'Delivery Note', 'Sales Order']:
+ if trans in ["Sales Invoice", "Delivery Note", "Sales Order"]:
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
based_on_details["based_on_select"] = "t1.project,"
- based_on_details["based_on_group_by"] = 't1.project'
- based_on_details["addl_tables"] = ''
- elif trans in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
+ based_on_details["based_on_group_by"] = "t1.project"
+ based_on_details["addl_tables"] = ""
+ elif trans in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]:
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
based_on_details["based_on_select"] = "t2.project,"
- based_on_details["based_on_group_by"] = 't2.project'
- based_on_details["addl_tables"] = ''
+ based_on_details["based_on_group_by"] = "t2.project"
+ based_on_details["addl_tables"] = ""
else:
frappe.throw(_("Project-wise data is not available for Quotation"))
return based_on_details
+
def group_wise_column(group_by):
if group_by:
- return [group_by+":Link/"+group_by+":120"]
+ return [group_by + ":Link/" + group_by + ":120"]
else:
return []
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index 23463abe0a..467323035e 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -15,21 +15,29 @@ def get_list_context(context=None):
return {
"global_number_format": frappe.db.get_default("number_format") or "#,###.##",
"currency": frappe.db.get_default("currency"),
- "currency_symbols": json.dumps(dict(frappe.db.sql("""select name, symbol
- from tabCurrency where enabled=1"""))),
+ "currency_symbols": json.dumps(
+ dict(
+ frappe.db.sql(
+ """select name, symbol
+ from tabCurrency where enabled=1"""
+ )
+ )
+ ),
"row_template": "templates/includes/transaction_row.html",
- "get_list": get_transaction_list
+ "get_list": get_transaction_list,
}
+
def get_webform_list_context(module):
- if get_module_app(module) != 'erpnext':
+ if get_module_app(module) != "erpnext":
return
- return {
- "get_list": get_webform_transaction_list
- }
+ return {"get_list": get_webform_transaction_list}
-def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
- """ Get List of transactions for custom doctypes """
+
+def get_webform_transaction_list(
+ doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"
+):
+ """Get List of transactions for custom doctypes"""
from frappe.www.list import get_list
if not filters:
@@ -38,42 +46,62 @@ def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0,
meta = frappe.get_meta(doctype)
for d in meta.fields:
- if d.fieldtype == 'Link' and d.fieldname != 'amended_from':
+ if d.fieldtype == "Link" and d.fieldname != "amended_from":
allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)]
- allowed_docs.append('')
- filters.append((d.fieldname, 'in', allowed_docs))
+ allowed_docs.append("")
+ filters.append((d.fieldname, "in", allowed_docs))
- return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False,
- fields=None, order_by="modified")
+ return get_list(
+ doctype,
+ txt,
+ filters,
+ limit_start,
+ limit_page_length,
+ ignore_permissions=False,
+ fields=None,
+ order_by="modified",
+ )
-def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False):
+
+def get_transaction_list(
+ doctype,
+ txt=None,
+ filters=None,
+ limit_start=0,
+ limit_page_length=20,
+ order_by="modified",
+ custom=False,
+):
user = frappe.session.user
ignore_permissions = False
- if not filters: filters = []
+ if not filters:
+ filters = []
- if doctype in ['Supplier Quotation', 'Purchase Invoice']:
- filters.append((doctype, 'docstatus', '<', 2))
+ if doctype in ["Supplier Quotation", "Purchase Invoice"]:
+ filters.append((doctype, "docstatus", "<", 2))
else:
- filters.append((doctype, 'docstatus', '=', 1))
+ filters.append((doctype, "docstatus", "=", 1))
- if (user != 'Guest' and is_website_user()) or doctype == 'Request for Quotation':
- parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
+ if (user != "Guest" and is_website_user()) or doctype == "Request for Quotation":
+ parties_doctype = (
+ "Request for Quotation Supplier" if doctype == "Request for Quotation" else doctype
+ )
# find party for this contact
customers, suppliers = get_customers_suppliers(parties_doctype, user)
if customers:
- if doctype == 'Quotation':
- filters.append(('quotation_to', '=', 'Customer'))
- filters.append(('party_name', 'in', customers))
+ if doctype == "Quotation":
+ filters.append(("quotation_to", "=", "Customer"))
+ filters.append(("party_name", "in", customers))
else:
- filters.append(('customer', 'in', customers))
+ filters.append(("customer", "in", customers))
elif suppliers:
- filters.append(('supplier', 'in', suppliers))
+ filters.append(("supplier", "in", suppliers))
elif not custom:
return []
- if doctype == 'Request for Quotation':
+ if doctype == "Request for Quotation":
parties = customers or suppliers
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
@@ -84,49 +112,88 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
ignore_permissions = False
filters = []
- transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
- fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
+ transactions = get_list_for_transactions(
+ doctype,
+ txt,
+ filters,
+ limit_start,
+ limit_page_length,
+ fields="name",
+ ignore_permissions=ignore_permissions,
+ order_by="modified desc",
+ )
if custom:
return transactions
return post_process(doctype, transactions)
-def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
- ignore_permissions=False, fields=None, order_by=None):
- """ Get List of transactions like Invoices, Orders """
+
+def get_list_for_transactions(
+ doctype,
+ txt,
+ filters,
+ limit_start,
+ limit_page_length=20,
+ ignore_permissions=False,
+ fields=None,
+ order_by=None,
+):
+ """Get List of transactions like Invoices, Orders"""
from frappe.www.list import get_list
+
meta = frappe.get_meta(doctype)
data = []
or_filters = []
- for d in get_list(doctype, txt, filters=filters, fields="name", limit_start=limit_start,
- limit_page_length=limit_page_length, ignore_permissions=ignore_permissions, order_by="modified desc"):
+ for d in get_list(
+ doctype,
+ txt,
+ filters=filters,
+ fields="name",
+ limit_start=limit_start,
+ limit_page_length=limit_page_length,
+ ignore_permissions=ignore_permissions,
+ order_by="modified desc",
+ ):
data.append(d)
if txt:
- if meta.get_field('items'):
- if meta.get_field('items').options:
- child_doctype = meta.get_field('items').options
- for item in frappe.get_all(child_doctype, {"item_name": ['like', "%" + txt + "%"]}):
+ if meta.get_field("items"):
+ if meta.get_field("items").options:
+ child_doctype = meta.get_field("items").options
+ for item in frappe.get_all(child_doctype, {"item_name": ["like", "%" + txt + "%"]}):
child = frappe.get_doc(child_doctype, item.name)
or_filters.append([doctype, "name", "=", child.parent])
if or_filters:
- for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters,
- limit_start=limit_start, limit_page_length=limit_page_length,
- ignore_permissions=ignore_permissions, order_by=order_by):
+ for r in frappe.get_list(
+ doctype,
+ fields=fields,
+ filters=filters,
+ or_filters=or_filters,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length,
+ ignore_permissions=ignore_permissions,
+ order_by=order_by,
+ ):
data.append(r)
return data
+
def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length):
- data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}`
- where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".
- format(doctype=parties_doctype, supplier=parties[0], start=limit_start, len = limit_page_length), as_dict=1)
+ data = frappe.db.sql(
+ """select distinct parent as name, supplier from `tab{doctype}`
+ where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".format(
+ doctype=parties_doctype, supplier=parties[0], start=limit_start, len=limit_page_length
+ ),
+ as_dict=1,
+ )
return post_process(doctype, data)
+
def post_process(doctype, data):
result = []
for d in data:
@@ -137,11 +204,15 @@ def post_process(doctype, data):
if doc.get("per_billed"):
doc.status_percent += flt(doc.per_billed)
- doc.status_display.append(_("Billed") if doc.per_billed==100 else _("{0}% Billed").format(doc.per_billed))
+ doc.status_display.append(
+ _("Billed") if doc.per_billed == 100 else _("{0}% Billed").format(doc.per_billed)
+ )
if doc.get("per_delivered"):
doc.status_percent += flt(doc.per_delivered)
- doc.status_display.append(_("Delivered") if doc.per_delivered==100 else _("{0}% Delivered").format(doc.per_delivered))
+ doc.status_display.append(
+ _("Delivered") if doc.per_delivered == 100 else _("{0}% Delivered").format(doc.per_delivered)
+ )
if hasattr(doc, "set_indicator"):
doc.set_indicator()
@@ -152,6 +223,7 @@ def post_process(doctype, data):
return result
+
def get_customers_suppliers(doctype, user):
customers = []
suppliers = []
@@ -160,10 +232,11 @@ def get_customers_suppliers(doctype, user):
customer_field_name = get_customer_field_name(doctype)
has_customer_field = meta.has_field(customer_field_name)
- has_supplier_field = meta.has_field('supplier')
+ has_supplier_field = meta.has_field("supplier")
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
- contacts = frappe.db.sql("""
+ contacts = frappe.db.sql(
+ """
select
`tabContact`.email_id,
`tabDynamic Link`.link_doctype,
@@ -172,15 +245,18 @@ def get_customers_suppliers(doctype, user):
`tabContact`, `tabDynamic Link`
where
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
- """, user, as_dict=1)
- customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
- suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
- elif frappe.has_permission(doctype, 'read', user=user):
+ """,
+ user,
+ as_dict=1,
+ )
+ customers = [c.link_name for c in contacts if c.link_doctype == "Customer"]
+ suppliers = [c.link_name for c in contacts if c.link_doctype == "Supplier"]
+ elif frappe.has_permission(doctype, "read", user=user):
customer_list = frappe.get_list("Customer")
customers = suppliers = [customer.name for customer in customer_list]
- return customers if has_customer_field else None, \
- suppliers if has_supplier_field else None
+ return customers if has_customer_field else None, suppliers if has_supplier_field else None
+
def has_website_permission(doc, ptype, user, verbose=False):
doctype = doc.doctype
@@ -188,25 +264,24 @@ def has_website_permission(doc, ptype, user, verbose=False):
if customers:
return frappe.db.exists(doctype, get_customer_filter(doc, customers))
elif suppliers:
- fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
- return frappe.db.exists(doctype, {
- 'name': doc.name,
- fieldname: ["in", suppliers]
- })
+ fieldname = "suppliers" if doctype == "Request for Quotation" else "supplier"
+ return frappe.db.exists(doctype, {"name": doc.name, fieldname: ["in", suppliers]})
else:
return False
+
def get_customer_filter(doc, customers):
doctype = doc.doctype
filters = frappe._dict()
filters.name = doc.name
- filters[get_customer_field_name(doctype)] = ['in', customers]
- if doctype == 'Quotation':
- filters.quotation_to = 'Customer'
+ filters[get_customer_field_name(doctype)] = ["in", customers]
+ if doctype == "Quotation":
+ filters.quotation_to = "Customer"
return filters
+
def get_customer_field_name(doctype):
- if doctype == 'Quotation':
- return 'party_name'
+ if doctype == "Quotation":
+ return "party_name"
else:
- return 'customer'
+ return "customer"
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 88a9c10131..5f5923dc89 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -12,17 +12,17 @@ from frappe.utils.verified_command import get_signed_params
class Appointment(Document):
-
def find_lead_by_email(self):
lead_list = frappe.get_list(
- 'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True)
+ "Lead", filters={"email_id": self.customer_email}, ignore_permissions=True
+ )
if lead_list:
return lead_list[0].name
return None
def find_customer_by_email(self):
customer_list = frappe.get_list(
- 'Customer', filters={'email_id': self.customer_email}, ignore_permissions=True
+ "Customer", filters={"email_id": self.customer_email}, ignore_permissions=True
)
if customer_list:
return customer_list[0].name
@@ -30,11 +30,12 @@ class Appointment(Document):
def before_insert(self):
number_of_appointments_in_same_slot = frappe.db.count(
- 'Appointment', filters={'scheduled_time': self.scheduled_time})
- number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
+ "Appointment", filters={"scheduled_time": self.scheduled_time}
+ )
+ number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents")
if not number_of_agents == 0:
- if (number_of_appointments_in_same_slot >= number_of_agents):
- frappe.throw(_('Time slot is not available'))
+ if number_of_appointments_in_same_slot >= number_of_agents:
+ frappe.throw(_("Time slot is not available"))
# Link lead
if not self.party:
lead = self.find_lead_by_email()
@@ -53,45 +54,46 @@ class Appointment(Document):
self.create_calendar_event()
else:
# Set status to unverified
- self.status = 'Unverified'
+ self.status = "Unverified"
# Send email to confirm
self.send_confirmation_email()
def send_confirmation_email(self):
verify_url = self._get_verify_url()
- template = 'confirm_appointment'
+ template = "confirm_appointment"
args = {
- "link":verify_url,
- "site_url":frappe.utils.get_url(),
- "full_name":self.customer_name,
+ "link": verify_url,
+ "site_url": frappe.utils.get_url(),
+ "full_name": self.customer_name,
}
- frappe.sendmail(recipients=[self.customer_email],
- template=template,
- args=args,
- subject=_('Appointment Confirmation'))
+ frappe.sendmail(
+ recipients=[self.customer_email],
+ template=template,
+ args=args,
+ subject=_("Appointment Confirmation"),
+ )
if frappe.session.user == "Guest":
+ frappe.msgprint(_("Please check your email to confirm the appointment"))
+ else:
frappe.msgprint(
- _('Please check your email to confirm the appointment'))
- else :
- frappe.msgprint(
- _('Appointment was created. But no lead was found. Please check the email to confirm'))
+ _("Appointment was created. But no lead was found. Please check the email to confirm")
+ )
def on_change(self):
# Sync Calendar
if not self.calendar_event:
return
- cal_event = frappe.get_doc('Event', self.calendar_event)
+ cal_event = frappe.get_doc("Event", self.calendar_event)
cal_event.starts_on = self.scheduled_time
cal_event.save(ignore_permissions=True)
-
def set_verified(self, email):
if not email == self.customer_email:
- frappe.throw(_('Email verification failed.'))
+ frappe.throw(_("Email verification failed."))
# Create new lead
self.create_lead_and_link()
# Remove unverified status
- self.status = 'Open'
+ self.status = "Open"
# Create calender event
self.auto_assign()
self.create_calendar_event()
@@ -102,58 +104,53 @@ class Appointment(Document):
# Return if already linked
if self.party:
return
- lead = frappe.get_doc({
- 'doctype': 'Lead',
- 'lead_name': self.customer_name,
- 'email_id': self.customer_email,
- 'notes': self.customer_details,
- 'phone': self.customer_phone_number,
- })
+ lead = frappe.get_doc(
+ {
+ "doctype": "Lead",
+ "lead_name": self.customer_name,
+ "email_id": self.customer_email,
+ "notes": self.customer_details,
+ "phone": self.customer_phone_number,
+ }
+ )
lead.insert(ignore_permissions=True)
# Link lead
self.party = lead.name
def auto_assign(self):
from frappe.desk.form.assign_to import add as add_assignemnt
+
existing_assignee = self.get_assignee_from_latest_opportunity()
if existing_assignee:
# If the latest opportunity is assigned to someone
# Assign the appointment to the same
- add_assignemnt({
- 'doctype': self.doctype,
- 'name': self.name,
- 'assign_to': [existing_assignee]
- })
+ add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [existing_assignee]})
return
if self._assign:
return
- available_agents = _get_agents_sorted_by_asc_workload(
- getdate(self.scheduled_time))
+ available_agents = _get_agents_sorted_by_asc_workload(getdate(self.scheduled_time))
for agent in available_agents:
- if(_check_agent_availability(agent, self.scheduled_time)):
+ if _check_agent_availability(agent, self.scheduled_time):
agent = agent[0]
- add_assignemnt({
- 'doctype': self.doctype,
- 'name': self.name,
- 'assign_to': [agent]
- })
+ add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [agent]})
break
def get_assignee_from_latest_opportunity(self):
if not self.party:
return None
- if not frappe.db.exists('Lead', self.party):
+ if not frappe.db.exists("Lead", self.party):
return None
opporutnities = frappe.get_list(
- 'Opportunity',
+ "Opportunity",
filters={
- 'party_name': self.party,
+ "party_name": self.party,
},
ignore_permissions=True,
- order_by='creation desc')
+ order_by="creation desc",
+ )
if not opporutnities:
return None
- latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name )
+ latest_opportunity = frappe.get_doc("Opportunity", opporutnities[0].name)
assignee = latest_opportunity._assign
if not assignee:
return None
@@ -163,35 +160,36 @@ class Appointment(Document):
def create_calendar_event(self):
if self.calendar_event:
return
- appointment_event = frappe.get_doc({
- 'doctype': 'Event',
- 'subject': ' '.join(['Appointment with', self.customer_name]),
- 'starts_on': self.scheduled_time,
- 'status': 'Open',
- 'type': 'Public',
- 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'),
- 'event_participants': [dict(reference_doctype=self.appointment_with, reference_docname=self.party)]
- })
+ appointment_event = frappe.get_doc(
+ {
+ "doctype": "Event",
+ "subject": " ".join(["Appointment with", self.customer_name]),
+ "starts_on": self.scheduled_time,
+ "status": "Open",
+ "type": "Public",
+ "send_reminder": frappe.db.get_single_value("Appointment Booking Settings", "email_reminders"),
+ "event_participants": [
+ dict(reference_doctype=self.appointment_with, reference_docname=self.party)
+ ],
+ }
+ )
employee = _get_employee_from_user(self._assign)
if employee:
- appointment_event.append('event_participants', dict(
- reference_doctype='Employee',
- reference_docname=employee.name))
+ appointment_event.append(
+ "event_participants", dict(reference_doctype="Employee", reference_docname=employee.name)
+ )
appointment_event.insert(ignore_permissions=True)
self.calendar_event = appointment_event.name
self.save(ignore_permissions=True)
def _get_verify_url(self):
- verify_route = '/book_appointment/verify'
- params = {
- 'email': self.customer_email,
- 'appointment': self.name
- }
- return get_url(verify_route + '?' + get_signed_params(params))
+ verify_route = "/book_appointment/verify"
+ params = {"email": self.customer_email, "appointment": self.name}
+ return get_url(verify_route + "?" + get_signed_params(params))
def _get_agents_sorted_by_asc_workload(date):
- appointments = frappe.db.get_list('Appointment', fields='*')
+ appointments = frappe.db.get_list("Appointment", fields="*")
agent_list = _get_agent_list_as_strings()
if not appointments:
return agent_list
@@ -209,7 +207,7 @@ def _get_agents_sorted_by_asc_workload(date):
def _get_agent_list_as_strings():
agent_list_as_strings = []
- agent_list = frappe.get_doc('Appointment Booking Settings').agent_list
+ agent_list = frappe.get_doc("Appointment Booking Settings").agent_list
for agent in agent_list:
agent_list_as_strings.append(agent.user)
return agent_list_as_strings
@@ -217,7 +215,8 @@ def _get_agent_list_as_strings():
def _check_agent_availability(agent_email, scheduled_time):
appointemnts_at_scheduled_time = frappe.get_list(
- 'Appointment', filters={'scheduled_time': scheduled_time})
+ "Appointment", filters={"scheduled_time": scheduled_time}
+ )
for appointment in appointemnts_at_scheduled_time:
if appointment._assign == agent_email:
return False
@@ -225,7 +224,7 @@ def _check_agent_availability(agent_email, scheduled_time):
def _get_employee_from_user(user):
- employee_docname = frappe.db.get_value('Employee', {'user_id': user})
+ employee_docname = frappe.db.get_value("Employee", {"user_id": user})
if employee_docname:
- return frappe.get_doc('Employee', employee_docname)
+ return frappe.get_doc("Employee", employee_docname)
return None
diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py
index 1431b03a2e..e43f4601e9 100644
--- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py
+++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py
@@ -10,8 +10,8 @@ from frappe.model.document import Document
class AppointmentBookingSettings(Document):
- agent_list = [] #Hack
- min_date = '01/01/1970 '
+ agent_list = [] # Hack
+ min_date = "01/01/1970 "
format_string = "%d/%m/%Y %H:%M:%S"
def validate(self):
@@ -23,21 +23,22 @@ class AppointmentBookingSettings(Document):
def validate_availability_of_slots(self):
for record in self.availability_of_slots:
- from_time = datetime.datetime.strptime(
- self.min_date+record.from_time, self.format_string)
- to_time = datetime.datetime.strptime(
- self.min_date+record.to_time, self.format_string)
- timedelta = to_time-from_time
+ from_time = datetime.datetime.strptime(self.min_date + record.from_time, self.format_string)
+ to_time = datetime.datetime.strptime(self.min_date + record.to_time, self.format_string)
+ timedelta = to_time - from_time
self.validate_from_and_to_time(from_time, to_time, record)
self.duration_is_divisible(from_time, to_time)
def validate_from_and_to_time(self, from_time, to_time, record):
if from_time > to_time:
- err_msg = _('From Time cannot be later than To Time for {0}').format(record.day_of_week)
+ err_msg = _("From Time cannot be later than To Time for {0}").format(
+ record.day_of_week
+ )
frappe.throw(_(err_msg))
def duration_is_divisible(self, from_time, to_time):
timedelta = to_time - from_time
if timedelta.total_seconds() % (self.appointment_duration * 60):
frappe.throw(
- _('The difference between from time and To Time must be a multiple of Appointment'))
+ _("The difference between from time and To Time must be a multiple of Appointment")
+ )
diff --git a/erpnext/crm/doctype/campaign/campaign.py b/erpnext/crm/doctype/campaign/campaign.py
index 8b62800409..5d06075bdf 100644
--- a/erpnext/crm/doctype/campaign/campaign.py
+++ b/erpnext/crm/doctype/campaign/campaign.py
@@ -8,7 +8,7 @@ from frappe.model.naming import set_name_by_naming_series
class Campaign(Document):
def autoname(self):
- if frappe.defaults.get_global_default('campaign_naming_by') != 'Naming Series':
+ if frappe.defaults.get_global_default("campaign_naming_by") != "Naming Series":
self.name = self.campaign_name
else:
set_name_by_naming_series(self)
diff --git a/erpnext/crm/doctype/contract/contract.py b/erpnext/crm/doctype/contract/contract.py
index e21f46a383..1c2470b6e4 100644
--- a/erpnext/crm/doctype/contract/contract.py
+++ b/erpnext/crm/doctype/contract/contract.py
@@ -75,11 +75,11 @@ def get_status(start_date, end_date):
Get a Contract's status based on the start, current and end dates
Args:
- start_date (str): The start date of the contract
- end_date (str): The end date of the contract
+ start_date (str): The start date of the contract
+ end_date (str): The end date of the contract
Returns:
- str: 'Active' if within range, otherwise 'Inactive'
+ str: 'Active' if within range, otherwise 'Inactive'
"""
if not end_date:
@@ -98,13 +98,13 @@ def update_status_for_contracts():
and submitted Contracts
"""
- contracts = frappe.get_all("Contract",
- filters={"is_signed": True,
- "docstatus": 1},
- fields=["name", "start_date", "end_date"])
+ contracts = frappe.get_all(
+ "Contract",
+ filters={"is_signed": True, "docstatus": 1},
+ fields=["name", "start_date", "end_date"],
+ )
for contract in contracts:
- status = get_status(contract.get("start_date"),
- contract.get("end_date"))
+ status = get_status(contract.get("start_date"), contract.get("end_date"))
frappe.db.set_value("Contract", contract.get("name"), "status", status)
diff --git a/erpnext/crm/doctype/contract/test_contract.py b/erpnext/crm/doctype/contract/test_contract.py
index e685362a49..13901683de 100644
--- a/erpnext/crm/doctype/contract/test_contract.py
+++ b/erpnext/crm/doctype/contract/test_contract.py
@@ -8,7 +8,6 @@ from frappe.utils import add_days, nowdate
class TestContract(unittest.TestCase):
-
def setUp(self):
frappe.db.sql("delete from `tabContract`")
self.contract_doc = get_contract()
@@ -65,10 +64,7 @@ class TestContract(unittest.TestCase):
# Mark all the terms as fulfilled
self.contract_doc.requires_fulfilment = 1
fulfilment_terms = []
- fulfilment_terms.append({
- "requirement": "This is a test requirement.",
- "fulfilled": 0
- })
+ fulfilment_terms.append({"requirement": "This is a test requirement.", "fulfilled": 0})
self.contract_doc.set("fulfilment_terms", fulfilment_terms)
for term in self.contract_doc.fulfilment_terms:
@@ -85,14 +81,8 @@ class TestContract(unittest.TestCase):
# Mark only the first term as fulfilled
self.contract_doc.save()
fulfilment_terms = []
- fulfilment_terms.append({
- "requirement": "This is a test requirement.",
- "fulfilled": 0
- })
- fulfilment_terms.append({
- "requirement": "This is another test requirement.",
- "fulfilled": 0
- })
+ fulfilment_terms.append({"requirement": "This is a test requirement.", "fulfilled": 0})
+ fulfilment_terms.append({"requirement": "This is another test requirement.", "fulfilled": 0})
self.contract_doc.set("fulfilment_terms", fulfilment_terms)
self.contract_doc.fulfilment_terms[0].fulfilled = 1
@@ -110,6 +100,7 @@ class TestContract(unittest.TestCase):
self.assertEqual(self.contract_doc.fulfilment_status, "Lapsed")
+
def get_contract():
doc = frappe.new_doc("Contract")
doc.party_type = "Customer"
diff --git a/erpnext/crm/doctype/contract_template/contract_template.py b/erpnext/crm/doctype/contract_template/contract_template.py
index 7439e4c917..a5b0ee08f0 100644
--- a/erpnext/crm/doctype/contract_template/contract_template.py
+++ b/erpnext/crm/doctype/contract_template/contract_template.py
@@ -14,6 +14,7 @@ class ContractTemplate(Document):
if self.contract_terms:
validate_template(self.contract_terms)
+
@frappe.whitelist()
def get_contract_template(template_name, doc):
if isinstance(doc, str):
@@ -25,7 +26,4 @@ def get_contract_template(template_name, doc):
if contract_template.contract_terms:
contract_terms = frappe.render_template(contract_template.contract_terms, doc)
- return {
- 'contract_template': contract_template,
- 'contract_terms': contract_terms
- }
+ return {"contract_template": contract_template, "contract_terms": contract_terms}
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py
index d44443237e..9ec54ffc1e 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.py
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.py
@@ -12,7 +12,7 @@ from frappe.utils import add_days, getdate, today
class EmailCampaign(Document):
def validate(self):
self.set_date()
- #checking if email is set for lead. Not checking for contact as email is a mandatory field for contact.
+ # checking if email is set for lead. Not checking for contact as email is a mandatory field for contact.
if self.email_campaign_for == "Lead":
self.validate_lead()
self.validate_email_campaign_already_exists()
@@ -21,7 +21,7 @@ class EmailCampaign(Document):
def set_date(self):
if getdate(self.start_date) < getdate(today()):
frappe.throw(_("Start Date cannot be before the current date"))
- #set the end date as start date + max(send after days) in campaign schedule
+ # set the end date as start date + max(send after days) in campaign schedule
send_after_days = []
campaign = frappe.get_doc("Campaign", self.campaign_name)
for entry in campaign.get("campaign_schedules"):
@@ -29,23 +29,32 @@ class EmailCampaign(Document):
try:
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
except ValueError:
- frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
+ frappe.throw(
+ _("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name)
+ )
def validate_lead(self):
- lead_email_id = frappe.db.get_value("Lead", self.recipient, 'email_id')
+ lead_email_id = frappe.db.get_value("Lead", self.recipient, "email_id")
if not lead_email_id:
- lead_name = frappe.db.get_value("Lead", self.recipient, 'lead_name')
+ lead_name = frappe.db.get_value("Lead", self.recipient, "lead_name")
frappe.throw(_("Please set an email id for the Lead {0}").format(lead_name))
def validate_email_campaign_already_exists(self):
- email_campaign_exists = frappe.db.exists("Email Campaign", {
- "campaign_name": self.campaign_name,
- "recipient": self.recipient,
- "status": ("in", ["In Progress", "Scheduled"]),
- "name": ("!=", self.name)
- })
+ email_campaign_exists = frappe.db.exists(
+ "Email Campaign",
+ {
+ "campaign_name": self.campaign_name,
+ "recipient": self.recipient,
+ "status": ("in", ["In Progress", "Scheduled"]),
+ "name": ("!=", self.name),
+ },
+ )
if email_campaign_exists:
- frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
+ frappe.throw(
+ _("The Campaign '{0}' already exists for the {1} '{2}'").format(
+ self.campaign_name, self.email_campaign_for, self.recipient
+ )
+ )
def update_status(self):
start_date = getdate(self.start_date)
@@ -58,51 +67,63 @@ class EmailCampaign(Document):
elif end_date < today_date:
self.status = "Completed"
-#called through hooks to send campaign mails to leads
+
+# called through hooks to send campaign mails to leads
def send_email_to_leads_or_contacts():
- email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']) })
+ email_campaigns = frappe.get_all(
+ "Email Campaign", filters={"status": ("not in", ["Unsubscribed", "Completed", "Scheduled"])}
+ )
for camp in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", camp.name)
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
for entry in campaign.get("campaign_schedules"):
- scheduled_date = add_days(email_campaign.get('start_date'), entry.get('send_after_days'))
+ scheduled_date = add_days(email_campaign.get("start_date"), entry.get("send_after_days"))
if scheduled_date == getdate(today()):
send_mail(entry, email_campaign)
+
def send_mail(entry, email_campaign):
recipient_list = []
if email_campaign.email_campaign_for == "Email Group":
- for member in frappe.db.get_list("Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]):
- recipient_list.append(member['email'])
+ for member in frappe.db.get_list(
+ "Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]
+ ):
+ recipient_list.append(member["email"])
else:
- recipient_list.append(frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id"))
+ recipient_list.append(
+ frappe.db.get_value(
+ email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id"
+ )
+ )
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
sender = frappe.db.get_value("User", email_campaign.get("sender"), "email")
context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)}
# send mail and link communication to document
comm = make(
- doctype = "Email Campaign",
- name = email_campaign.name,
- subject = frappe.render_template(email_template.get("subject"), context),
- content = frappe.render_template(email_template.get("response"), context),
- sender = sender,
- recipients = recipient_list,
- communication_medium = "Email",
- sent_or_received = "Sent",
- send_email = True,
- email_template = email_template.name
+ doctype="Email Campaign",
+ name=email_campaign.name,
+ subject=frappe.render_template(email_template.get("subject"), context),
+ content=frappe.render_template(email_template.get("response"), context),
+ sender=sender,
+ recipients=recipient_list,
+ communication_medium="Email",
+ sent_or_received="Sent",
+ send_email=True,
+ email_template=email_template.name,
)
return comm
-#called from hooks on doc_event Email Unsubscribe
+
+# called from hooks on doc_event Email Unsubscribe
def unsubscribe_recipient(unsubscribe, method):
- if unsubscribe.reference_doctype == 'Email Campaign':
+ if unsubscribe.reference_doctype == "Email Campaign":
frappe.db.set_value("Email Campaign", unsubscribe.reference_name, "status", "Unsubscribed")
-#called through hooks to update email campaign status daily
+
+# called through hooks to update email campaign status daily
def set_email_campaign_status():
- email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('!=', 'Unsubscribed')})
+ email_campaigns = frappe.get_all("Email Campaign", filters={"status": ("!=", "Unsubscribed")})
for entry in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", entry.name)
email_campaign.update_status()
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 33ec552152..c9a64ff8e6 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -23,7 +23,7 @@ from erpnext.controllers.selling_controller import SellingController
class Lead(SellingController):
def get_feed(self):
- return '{0}: {1}'.format(_(self.status), self.lead_name)
+ return "{0}: {1}".format(_(self.status), self.lead_name)
def onload(self):
customer = frappe.db.get_value("Customer", {"lead_name": self.name})
@@ -62,8 +62,7 @@ class Lead(SellingController):
if self.contact_date and getdate(self.contact_date) < getdate(nowdate()):
frappe.throw(_("Next Contact Date cannot be in the past"))
- if (self.ends_on and self.contact_date and
- (getdate(self.ends_on) < getdate(self.contact_date))):
+ if self.ends_on and self.contact_date and (getdate(self.ends_on) < getdate(self.contact_date)):
frappe.throw(_("Ends On date cannot be before Next Contact Date."))
def on_update(self):
@@ -72,13 +71,11 @@ class Lead(SellingController):
def set_prev(self):
if self.is_new():
- self._prev = frappe._dict({
- "contact_date": None,
- "ends_on": None,
- "contact_by": None
- })
+ self._prev = frappe._dict({"contact_date": None, "ends_on": None, "contact_by": None})
else:
- self._prev = frappe.db.get_value("Lead", self.name, ["contact_date", "ends_on", "contact_by"], as_dict=1)
+ self._prev = frappe.db.get_value(
+ "Lead", self.name, ["contact_date", "ends_on", "contact_by"], as_dict=1
+ )
def before_insert(self):
self.contact_doc = self.create_contact()
@@ -89,39 +86,47 @@ class Lead(SellingController):
def update_links(self):
# update contact links
if self.contact_doc:
- self.contact_doc.append("links", {
- "link_doctype": "Lead",
- "link_name": self.name,
- "link_title": self.lead_name
- })
+ self.contact_doc.append(
+ "links", {"link_doctype": "Lead", "link_name": self.name, "link_title": self.lead_name}
+ )
self.contact_doc.save()
def add_calendar_event(self, opts=None, force=False):
- if frappe.db.get_single_value('CRM Settings', 'create_event_on_next_contact_date'):
- super(Lead, self).add_calendar_event({
- "owner": self.lead_owner,
- "starts_on": self.contact_date,
- "ends_on": self.ends_on or "",
- "subject": ('Contact ' + cstr(self.lead_name)),
- "description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
- }, force)
+ if frappe.db.get_single_value("CRM Settings", "create_event_on_next_contact_date"):
+ super(Lead, self).add_calendar_event(
+ {
+ "owner": self.lead_owner,
+ "starts_on": self.contact_date,
+ "ends_on": self.ends_on or "",
+ "subject": ("Contact " + cstr(self.lead_name)),
+ "description": ("Contact " + cstr(self.lead_name))
+ + (self.contact_by and (". By : " + cstr(self.contact_by)) or ""),
+ },
+ force,
+ )
def update_prospects(self):
- prospects = frappe.get_all('Prospect Lead', filters={'lead': self.name}, fields=['parent'])
+ prospects = frappe.get_all("Prospect Lead", filters={"lead": self.name}, fields=["parent"])
for row in prospects:
- prospect = frappe.get_doc('Prospect', row.parent)
+ prospect = frappe.get_doc("Prospect", row.parent)
prospect.save(ignore_permissions=True)
def check_email_id_is_unique(self):
if self.email_id:
# validate email is unique
- if not frappe.db.get_single_value('CRM Settings', 'allow_lead_duplication_based_on_emails'):
- duplicate_leads = frappe.get_all("Lead", filters={"email_id": self.email_id, "name": ["!=", self.name]})
- duplicate_leads = [frappe.bold(get_link_to_form('Lead', lead.name)) for lead in duplicate_leads]
+ if not frappe.db.get_single_value("CRM Settings", "allow_lead_duplication_based_on_emails"):
+ duplicate_leads = frappe.get_all(
+ "Lead", filters={"email_id": self.email_id, "name": ["!=", self.name]}
+ )
+ duplicate_leads = [
+ frappe.bold(get_link_to_form("Lead", lead.name)) for lead in duplicate_leads
+ ]
if duplicate_leads:
- frappe.throw(_("Email Address must be unique, already exists for {0}")
- .format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
+ frappe.throw(
+ _("Email Address must be unique, already exists for {0}").format(comma_and(duplicate_leads)),
+ frappe.DuplicateEntryError,
+ )
def on_trash(self):
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
@@ -130,16 +135,20 @@ class Lead(SellingController):
self.delete_events()
def unlink_dynamic_links(self):
- links = frappe.get_all('Dynamic Link', filters={'link_doctype': self.doctype, 'link_name': self.name}, fields=['parent', 'parenttype'])
+ links = frappe.get_all(
+ "Dynamic Link",
+ filters={"link_doctype": self.doctype, "link_name": self.name},
+ fields=["parent", "parenttype"],
+ )
for link in links:
- linked_doc = frappe.get_doc(link['parenttype'], link['parent'])
+ linked_doc = frappe.get_doc(link["parenttype"], link["parent"])
- if len(linked_doc.get('links')) == 1:
+ if len(linked_doc.get("links")) == 1:
linked_doc.delete(ignore_permissions=True)
else:
to_remove = None
- for d in linked_doc.get('links'):
+ for d in linked_doc.get("links"):
if d.link_doctype == self.doctype and d.link_name == self.name:
to_remove = d
if to_remove:
@@ -153,18 +162,14 @@ class Lead(SellingController):
return frappe.db.get_value("Opportunity", {"party_name": self.name, "status": ["!=", "Lost"]})
def has_quotation(self):
- return frappe.db.get_value("Quotation", {
- "party_name": self.name,
- "docstatus": 1,
- "status": ["!=", "Lost"]
- })
+ return frappe.db.get_value(
+ "Quotation", {"party_name": self.name, "docstatus": 1, "status": ["!=", "Lost"]}
+ )
def has_lost_quotation(self):
- return frappe.db.get_value("Quotation", {
- "party_name": self.name,
- "docstatus": 1,
- "status": "Lost"
- })
+ return frappe.db.get_value(
+ "Quotation", {"party_name": self.name, "docstatus": 1, "status": "Lost"}
+ )
def set_lead_name(self):
if not self.lead_name:
@@ -180,44 +185,38 @@ class Lead(SellingController):
self.title = self.company_name or self.lead_name
def create_contact(self):
- if frappe.db.get_single_value('CRM Settings', 'auto_creation_of_contact'):
+ if frappe.db.get_single_value("CRM Settings", "auto_creation_of_contact"):
if not self.lead_name:
self.set_full_name()
self.set_lead_name()
contact = frappe.new_doc("Contact")
- contact.update({
- "first_name": self.first_name or self.lead_name,
- "last_name": self.last_name,
- "salutation": self.salutation,
- "gender": self.gender,
- "designation": self.designation,
- "company_name": self.company_name,
- })
+ contact.update(
+ {
+ "first_name": self.first_name or self.lead_name,
+ "last_name": self.last_name,
+ "salutation": self.salutation,
+ "gender": self.gender,
+ "designation": self.designation,
+ "company_name": self.company_name,
+ }
+ )
if self.email_id:
- contact.append("email_ids", {
- "email_id": self.email_id,
- "is_primary": 1
- })
+ contact.append("email_ids", {"email_id": self.email_id, "is_primary": 1})
if self.phone:
- contact.append("phone_nos", {
- "phone": self.phone,
- "is_primary_phone": 1
- })
+ contact.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1})
if self.mobile_no:
- contact.append("phone_nos", {
- "phone": self.mobile_no,
- "is_primary_mobile_no":1
- })
+ contact.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1})
contact.insert(ignore_permissions=True)
- contact.reload() # load changes by hooks on contact
+ contact.reload() # load changes by hooks on contact
return contact
+
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
return _make_customer(source_name, target_doc)
@@ -234,16 +233,24 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
target.customer_group = frappe.db.get_default("Customer Group")
- doclist = get_mapped_doc("Lead", source_name,
- {"Lead": {
- "doctype": "Customer",
- "field_map": {
- "name": "lead_name",
- "company_name": "customer_name",
- "contact_no": "phone_1",
- "fax": "fax_1"
+ doclist = get_mapped_doc(
+ "Lead",
+ source_name,
+ {
+ "Lead": {
+ "doctype": "Customer",
+ "field_map": {
+ "name": "lead_name",
+ "company_name": "customer_name",
+ "contact_no": "phone_1",
+ "fax": "fax_1",
+ },
}
- }}, target_doc, set_missing_values, ignore_permissions=ignore_permissions)
+ },
+ target_doc,
+ set_missing_values,
+ ignore_permissions=ignore_permissions,
+ )
return doclist
@@ -253,19 +260,26 @@ def make_opportunity(source_name, target_doc=None):
def set_missing_values(source, target):
_set_missing_values(source, target)
- target_doc = get_mapped_doc("Lead", source_name,
- {"Lead": {
- "doctype": "Opportunity",
- "field_map": {
- "campaign_name": "campaign",
- "doctype": "opportunity_from",
- "name": "party_name",
- "lead_name": "contact_display",
- "company_name": "customer_name",
- "email_id": "contact_email",
- "mobile_no": "contact_mobile"
+ target_doc = get_mapped_doc(
+ "Lead",
+ source_name,
+ {
+ "Lead": {
+ "doctype": "Opportunity",
+ "field_map": {
+ "campaign_name": "campaign",
+ "doctype": "opportunity_from",
+ "name": "party_name",
+ "lead_name": "contact_display",
+ "company_name": "customer_name",
+ "email_id": "contact_email",
+ "mobile_no": "contact_mobile",
+ },
}
- }}, target_doc, set_missing_values)
+ },
+ target_doc,
+ set_missing_values,
+ )
return target_doc
@@ -275,13 +289,13 @@ def make_quotation(source_name, target_doc=None):
def set_missing_values(source, target):
_set_missing_values(source, target)
- target_doc = get_mapped_doc("Lead", source_name,
- {"Lead": {
- "doctype": "Quotation",
- "field_map": {
- "name": "party_name"
- }
- }}, target_doc, set_missing_values)
+ target_doc = get_mapped_doc(
+ "Lead",
+ source_name,
+ {"Lead": {"doctype": "Quotation", "field_map": {"name": "party_name"}}},
+ target_doc,
+ set_missing_values,
+ )
target_doc.quotation_to = "Lead"
target_doc.run_method("set_missing_values")
@@ -290,18 +304,29 @@ def make_quotation(source_name, target_doc=None):
return target_doc
-def _set_missing_values(source, target):
- address = frappe.get_all('Dynamic Link', {
- 'link_doctype': source.doctype,
- 'link_name': source.name,
- 'parenttype': 'Address',
- }, ['parent'], limit=1)
- contact = frappe.get_all('Dynamic Link', {
- 'link_doctype': source.doctype,
- 'link_name': source.name,
- 'parenttype': 'Contact',
- }, ['parent'], limit=1)
+def _set_missing_values(source, target):
+ address = frappe.get_all(
+ "Dynamic Link",
+ {
+ "link_doctype": source.doctype,
+ "link_name": source.name,
+ "parenttype": "Address",
+ },
+ ["parent"],
+ limit=1,
+ )
+
+ contact = frappe.get_all(
+ "Dynamic Link",
+ {
+ "link_doctype": source.doctype,
+ "link_name": source.name,
+ "parenttype": "Contact",
+ },
+ ["parent"],
+ limit=1,
+ )
if address:
target.customer_address = address[0].parent
@@ -309,39 +334,49 @@ def _set_missing_values(source, target):
if contact:
target.contact_person = contact[0].parent
+
@frappe.whitelist()
def get_lead_details(lead, posting_date=None, company=None):
if not lead:
return {}
from erpnext.accounts.party import set_address_details
+
out = frappe._dict()
lead_doc = frappe.get_doc("Lead", lead)
lead = lead_doc
- out.update({
- "territory": lead.territory,
- "customer_name": lead.company_name or lead.lead_name,
- "contact_display": " ".join(filter(None, [lead.salutation, lead.lead_name])),
- "contact_email": lead.email_id,
- "contact_mobile": lead.mobile_no,
- "contact_phone": lead.phone,
- })
+ out.update(
+ {
+ "territory": lead.territory,
+ "customer_name": lead.company_name or lead.lead_name,
+ "contact_display": " ".join(filter(None, [lead.salutation, lead.lead_name])),
+ "contact_email": lead.email_id,
+ "contact_mobile": lead.mobile_no,
+ "contact_phone": lead.phone,
+ }
+ )
set_address_details(out, lead, "Lead")
- taxes_and_charges = set_taxes(None, 'Lead', posting_date, company,
- billing_address=out.get('customer_address'), shipping_address=out.get('shipping_address_name'))
+ taxes_and_charges = set_taxes(
+ None,
+ "Lead",
+ posting_date,
+ company,
+ billing_address=out.get("customer_address"),
+ shipping_address=out.get("shipping_address_name"),
+ )
if taxes_and_charges:
- out['taxes_and_charges'] = taxes_and_charges
+ out["taxes_and_charges"] = taxes_and_charges
return out
@frappe.whitelist()
def make_lead_from_communication(communication, ignore_communication_links=False):
- """ raise a issue from email """
+ """raise a issue from email"""
doc = frappe.get_doc("Communication", communication)
lead_name = None
@@ -350,12 +385,14 @@ def make_lead_from_communication(communication, ignore_communication_links=False
if not lead_name and doc.phone_no:
lead_name = frappe.db.get_value("Lead", {"mobile_no": doc.phone_no})
if not lead_name:
- lead = frappe.get_doc({
- "doctype": "Lead",
- "lead_name": doc.sender_full_name,
- "email_id": doc.sender,
- "mobile_no": doc.phone_no
- })
+ lead = frappe.get_doc(
+ {
+ "doctype": "Lead",
+ "lead_name": doc.sender_full_name,
+ "email_id": doc.sender,
+ "mobile_no": doc.phone_no,
+ }
+ )
lead.flags.ignore_mandatory = True
lead.flags.ignore_permissions = True
lead.insert()
@@ -365,29 +402,41 @@ def make_lead_from_communication(communication, ignore_communication_links=False
link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links)
return lead_name
-def get_lead_with_phone_number(number):
- if not number: return
- leads = frappe.get_all('Lead', or_filters={
- 'phone': ['like', '%{}'.format(number)],
- 'mobile_no': ['like', '%{}'.format(number)]
- }, limit=1, order_by="creation DESC")
+def get_lead_with_phone_number(number):
+ if not number:
+ return
+
+ leads = frappe.get_all(
+ "Lead",
+ or_filters={
+ "phone": ["like", "%{}".format(number)],
+ "mobile_no": ["like", "%{}".format(number)],
+ },
+ limit=1,
+ order_by="creation DESC",
+ )
lead = leads[0].name if leads else None
return lead
+
def daily_open_lead():
- leads = frappe.get_all("Lead", filters = [["contact_date", "Between", [nowdate(), nowdate()]]])
+ leads = frappe.get_all("Lead", filters=[["contact_date", "Between", [nowdate(), nowdate()]]])
for lead in leads:
frappe.db.set_value("Lead", lead.name, "status", "Open")
+
@frappe.whitelist()
def add_lead_to_prospect(lead, prospect):
- prospect = frappe.get_doc('Prospect', prospect)
- prospect.append('prospect_lead', {
- 'lead': lead
- })
+ prospect = frappe.get_doc("Prospect", prospect)
+ prospect.append("prospect_lead", {"lead": lead})
prospect.save(ignore_permissions=True)
- frappe.msgprint(_('Lead {0} has been added to prospect {1}.').format(frappe.bold(lead), frappe.bold(prospect.name)),
- title=_('Lead Added'), indicator='green')
+ frappe.msgprint(
+ _("Lead {0} has been added to prospect {1}.").format(
+ frappe.bold(lead), frappe.bold(prospect.name)
+ ),
+ title=_("Lead Added"),
+ indicator="green",
+ )
diff --git a/erpnext/crm/doctype/lead/lead_dashboard.py b/erpnext/crm/doctype/lead/lead_dashboard.py
index 017390dc83..730e8e68a9 100644
--- a/erpnext/crm/doctype/lead/lead_dashboard.py
+++ b/erpnext/crm/doctype/lead/lead_dashboard.py
@@ -1,16 +1,9 @@
def get_data():
return {
- 'fieldname': 'lead',
- 'non_standard_fieldnames': {
- 'Quotation': 'party_name',
- 'Opportunity': 'party_name'
- },
- 'dynamic_links': {
- 'party_name': ['Lead', 'quotation_to']
- },
- 'transactions': [
- {
- 'items': ['Opportunity', 'Quotation', 'Prospect']
- },
- ]
+ "fieldname": "lead",
+ "non_standard_fieldnames": {"Quotation": "party_name", "Opportunity": "party_name"},
+ "dynamic_links": {"party_name": ["Lead", "quotation_to"]},
+ "transactions": [
+ {"items": ["Opportunity", "Quotation", "Prospect"]},
+ ],
}
diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py
index 3882974022..166ae2c353 100644
--- a/erpnext/crm/doctype/lead/test_lead.py
+++ b/erpnext/crm/doctype/lead/test_lead.py
@@ -7,7 +7,8 @@ import unittest
import frappe
from frappe.utils import random_string
-test_records = frappe.get_test_records('Lead')
+test_records = frappe.get_test_records("Lead")
+
class TestLead(unittest.TestCase):
def test_make_customer(self):
@@ -23,12 +24,16 @@ class TestLead(unittest.TestCase):
customer.customer_group = "_Test Customer Group"
customer.insert()
- #check whether lead contact is carried forward to the customer.
- contact = frappe.db.get_value('Dynamic Link', {
- "parenttype": "Contact",
- "link_doctype": "Lead",
- "link_name": customer.lead_name,
- }, "parent")
+ # check whether lead contact is carried forward to the customer.
+ contact = frappe.db.get_value(
+ "Dynamic Link",
+ {
+ "parenttype": "Contact",
+ "link_doctype": "Lead",
+ "link_name": customer.lead_name,
+ },
+ "parent",
+ )
if contact:
contact_doc = frappe.get_doc("Contact", contact)
@@ -46,51 +51,49 @@ class TestLead(unittest.TestCase):
customer.insert()
def test_create_lead_and_unlinking_dynamic_links(self):
- lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum", email_id="lorem_ipsum@example.com")
+ lead_doc = make_lead(first_name="Lorem", last_name="Ipsum", email_id="lorem_ipsum@example.com")
lead_doc_1 = make_lead()
- frappe.get_doc({
- "doctype": "Address",
- "address_type": "Billing",
- "city": "Mumbai",
- "address_line1": "Vidya Vihar West",
- "country": "India",
- "links": [{
- "link_doctype": "Lead",
- "link_name": lead_doc.name
- }]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Address",
+ "address_type": "Billing",
+ "city": "Mumbai",
+ "address_line1": "Vidya Vihar West",
+ "country": "India",
+ "links": [{"link_doctype": "Lead", "link_name": lead_doc.name}],
+ }
+ ).insert()
- address_1 = frappe.get_doc({
- "doctype": "Address",
- "address_type": "Billing",
- "address_line1": "Baner",
- "city": "Pune",
- "country": "India",
- "links": [
- {
- "link_doctype": "Lead",
- "link_name": lead_doc.name
- },
- {
- "link_doctype": "Lead",
- "link_name": lead_doc_1.name
- }
- ]
- }).insert()
+ address_1 = frappe.get_doc(
+ {
+ "doctype": "Address",
+ "address_type": "Billing",
+ "address_line1": "Baner",
+ "city": "Pune",
+ "country": "India",
+ "links": [
+ {"link_doctype": "Lead", "link_name": lead_doc.name},
+ {"link_doctype": "Lead", "link_name": lead_doc_1.name},
+ ],
+ }
+ ).insert()
lead_doc.delete()
address_1.reload()
- self.assertEqual(frappe.db.exists("Lead",lead_doc.name), None)
- self.assertEqual(len(address_1.get('links')), 1)
+ self.assertEqual(frappe.db.exists("Lead", lead_doc.name), None)
+ self.assertEqual(len(address_1.get("links")), 1)
+
def make_lead(**args):
args = frappe._dict(args)
- lead_doc = frappe.get_doc({
- "doctype": "Lead",
- "first_name": args.first_name or "_Test",
- "last_name": args.last_name or "Lead",
- "email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)),
- }).insert()
+ lead_doc = frappe.get_doc(
+ {
+ "doctype": "Lead",
+ "first_name": args.first_name or "_Test",
+ "last_name": args.last_name or "Lead",
+ "email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)),
+ }
+ ).insert()
return lead_doc
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index d2ac10adea..b4657a2e36 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -15,12 +15,16 @@ from frappe.utils.file_manager import get_file_path
class LinkedInSettings(Document):
@frappe.whitelist()
def get_authorization_url(self):
- params = urlencode({
- "response_type":"code",
- "client_id": self.consumer_key,
- "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
- "scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
- })
+ params = urlencode(
+ {
+ "response_type": "code",
+ "client_id": self.consumer_key,
+ "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(
+ frappe.utils.get_url()
+ ),
+ "scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social",
+ }
+ )
url = "https://www.linkedin.com/oauth/v2/authorization?{}".format(params)
@@ -33,11 +37,11 @@ class LinkedInSettings(Document):
"code": code,
"client_id": self.consumer_key,
"client_secret": self.get_password(fieldname="consumer_secret"),
- "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
- }
- headers = {
- "Content-Type": "application/x-www-form-urlencoded"
+ "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(
+ frappe.utils.get_url()
+ ),
}
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = self.http_post(url=url, data=body, headers=headers)
response = frappe.parse_json(response.content.decode())
@@ -47,11 +51,15 @@ class LinkedInSettings(Document):
response = requests.get(url="https://api.linkedin.com/v2/me", headers=self.get_headers())
response = frappe.parse_json(response.content.decode())
- frappe.db.set_value(self.doctype, self.name, {
- "person_urn": response["id"],
- "account_name": response["vanityName"],
- "session_status": "Active"
- })
+ frappe.db.set_value(
+ self.doctype,
+ self.name,
+ {
+ "person_urn": response["id"],
+ "account_name": response["vanityName"],
+ "session_status": "Active",
+ },
+ )
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url_to_form("LinkedIn Settings", "LinkedIn Settings")
@@ -64,8 +72,7 @@ class LinkedInSettings(Document):
if media_id:
return self.post_text(text, title, media_id=media_id)
else:
- frappe.log_error("Failed to upload media.","LinkedIn Upload Error")
-
+ frappe.log_error("Failed to upload media.", "LinkedIn Upload Error")
def upload_image(self, media):
media = get_file_path(media)
@@ -74,10 +81,9 @@ class LinkedInSettings(Document):
"registerUploadRequest": {
"recipes": ["urn:li:digitalmediaRecipe:feedshare-image"],
"owner": "urn:li:organization:{0}".format(self.company_id),
- "serviceRelationships": [{
- "relationshipType": "OWNER",
- "identifier": "urn:li:userGeneratedContent"
- }]
+ "serviceRelationships": [
+ {"relationshipType": "OWNER", "identifier": "urn:li:userGeneratedContent"}
+ ],
}
}
headers = self.get_headers()
@@ -86,11 +92,16 @@ class LinkedInSettings(Document):
if response.status_code == 200:
response = response.json()
asset = response["value"]["asset"]
- upload_url = response["value"]["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"]
- headers['Content-Type']='image/jpeg'
- response = self.http_post(upload_url, headers=headers, data=open(media,"rb"))
+ upload_url = response["value"]["uploadMechanism"][
+ "com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"
+ ]["uploadUrl"]
+ headers["Content-Type"] = "image/jpeg"
+ response = self.http_post(upload_url, headers=headers, data=open(media, "rb"))
if response.status_code < 200 and response.status_code > 299:
- frappe.throw(_("Error While Uploading Image"), title="{0} {1}".format(response.status_code, response.reason))
+ frappe.throw(
+ _("Error While Uploading Image"),
+ title="{0} {1}".format(response.status_code, response.reason),
+ )
return None
return asset
@@ -103,46 +114,26 @@ class LinkedInSettings(Document):
headers["Content-Type"] = "application/json; charset=UTF-8"
body = {
- "distribution": {
- "linkedInDistributionTarget": {}
- },
- "owner":"urn:li:organization:{0}".format(self.company_id),
+ "distribution": {"linkedInDistributionTarget": {}},
+ "owner": "urn:li:organization:{0}".format(self.company_id),
"subject": title,
- "text": {
- "text": text
- }
+ "text": {"text": text},
}
reference_url = self.get_reference_url(text)
if reference_url:
- body["content"] = {
- "contentEntities": [
- {
- "entityLocation": reference_url
- }
- ]
- }
+ body["content"] = {"contentEntities": [{"entityLocation": reference_url}]}
if media_id:
- body["content"]= {
- "contentEntities": [{
- "entity": media_id
- }],
- "shareMediaCategory": "IMAGE"
- }
+ body["content"] = {"contentEntities": [{"entity": media_id}], "shareMediaCategory": "IMAGE"}
response = self.http_post(url=url, headers=headers, body=body)
return response
def http_post(self, url, headers=None, body=None, data=None):
try:
- response = requests.post(
- url = url,
- json = body,
- data = data,
- headers = headers
- )
- if response.status_code not in [201,200]:
+ response = requests.post(url=url, json=body, data=data, headers=headers)
+ if response.status_code not in [201, 200]:
raise
except Exception as e:
@@ -151,12 +142,11 @@ class LinkedInSettings(Document):
return response
def get_headers(self):
- return {
- "Authorization": "Bearer {}".format(self.access_token)
- }
+ return {"Authorization": "Bearer {}".format(self.access_token)}
def get_reference_url(self, text):
import re
+
regex_url = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
urls = re.findall(regex_url, text)
if urls:
@@ -164,18 +154,23 @@ class LinkedInSettings(Document):
def delete_post(self, post_id):
try:
- response = requests.delete(url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id), headers=self.get_headers())
- if response.status_code !=200:
+ response = requests.delete(
+ url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id),
+ headers=self.get_headers(),
+ )
+ if response.status_code != 200:
raise
except Exception:
self.api_error(response)
def get_post(self, post_id):
- url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{0}&shares[0]=urn:li:share:{1}".format(self.company_id, post_id)
+ url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{0}&shares[0]=urn:li:share:{1}".format(
+ self.company_id, post_id
+ )
try:
response = requests.get(url=url, headers=self.get_headers())
- if response.status_code !=200:
+ if response.status_code != 200:
raise
except Exception:
@@ -200,6 +195,7 @@ class LinkedInSettings(Document):
else:
frappe.throw(response.reason, title=response.status_code)
+
@frappe.whitelist(allow_guest=True)
def callback(code=None, error=None, error_description=None):
if not error:
@@ -209,4 +205,4 @@ def callback(code=None, error=None, error_description=None):
frappe.db.commit()
else:
frappe.local.response["type"] = "redirect"
- frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")
+ frappe.local.response["location"] = get_url_to_form("LinkedIn Settings", "LinkedIn Settings")
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 2d538748ec..03ff2691e7 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -27,12 +27,16 @@ class Opportunity(TransactionBase):
add_link_in_communication(self.opportunity_from, self.party_name, self)
def validate(self):
- self._prev = frappe._dict({
- "contact_date": frappe.db.get_value("Opportunity", self.name, "contact_date") if \
- (not cint(self.get("__islocal"))) else None,
- "contact_by": frappe.db.get_value("Opportunity", self.name, "contact_by") if \
- (not cint(self.get("__islocal"))) else None,
- })
+ self._prev = frappe._dict(
+ {
+ "contact_date": frappe.db.get_value("Opportunity", self.name, "contact_date")
+ if (not cint(self.get("__islocal")))
+ else None,
+ "contact_by": frappe.db.get_value("Opportunity", self.name, "contact_by")
+ if (not cint(self.get("__islocal")))
+ else None,
+ }
+ )
self.make_new_lead_if_required()
self.validate_item_details()
@@ -60,7 +64,7 @@ class Opportunity(TransactionBase):
def calculate_totals(self):
total = base_total = 0
- for item in self.get('items'):
+ for item in self.get("items"):
item.amount = flt(item.rate) * flt(item.qty)
item.base_rate = flt(self.conversion_rate * item.rate)
item.base_amount = flt(self.conversion_rate * item.amount)
@@ -75,17 +79,18 @@ class Opportunity(TransactionBase):
if (not self.get("party_name")) and self.contact_email:
# check if customer is already created agains the self.contact_email
dynamic_link, contact = DocType("Dynamic Link"), DocType("Contact")
- customer = frappe.qb.from_(
- dynamic_link
- ).join(
- contact
- ).on(
- (contact.name == dynamic_link.parent)
- & (dynamic_link.link_doctype == "Customer")
- & (contact.email_id == self.contact_email)
- ).select(
- dynamic_link.link_name
- ).distinct().run(as_dict=True)
+ customer = (
+ frappe.qb.from_(dynamic_link)
+ .join(contact)
+ .on(
+ (contact.name == dynamic_link.parent)
+ & (dynamic_link.link_doctype == "Customer")
+ & (contact.email_id == self.contact_email)
+ )
+ .select(dynamic_link.link_name)
+ .distinct()
+ .run(as_dict=True)
+ )
if customer and customer[0].link_name:
self.party_name = customer[0].link_name
@@ -98,19 +103,17 @@ class Opportunity(TransactionBase):
if sender_name == self.contact_email:
sender_name = None
- if not sender_name and ('@' in self.contact_email):
- email_name = self.contact_email.split('@')[0]
+ if not sender_name and ("@" in self.contact_email):
+ email_name = self.contact_email.split("@")[0]
- email_split = email_name.split('.')
- sender_name = ''
+ email_split = email_name.split(".")
+ sender_name = ""
for s in email_split:
- sender_name += s.capitalize() + ' '
+ sender_name += s.capitalize() + " "
- lead = frappe.get_doc({
- "doctype": "Lead",
- "email_id": self.contact_email,
- "lead_name": sender_name or 'Unknown'
- })
+ lead = frappe.get_doc(
+ {"doctype": "Lead", "email_id": self.contact_email, "lead_name": sender_name or "Unknown"}
+ )
lead.flags.ignore_email_validation = True
lead.insert(ignore_permissions=True)
@@ -122,17 +125,17 @@ class Opportunity(TransactionBase):
@frappe.whitelist()
def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None):
if not self.has_active_quotation():
- self.status = 'Lost'
+ self.status = "Lost"
self.lost_reasons = self.competitors = []
if detailed_reason:
self.order_lost_reason = detailed_reason
for reason in lost_reasons_list:
- self.append('lost_reasons', reason)
+ self.append("lost_reasons", reason)
for competitor in competitors:
- self.append('competitors', competitor)
+ self.append("competitors", competitor)
self.save()
@@ -144,85 +147,92 @@ class Opportunity(TransactionBase):
def has_active_quotation(self):
if not self.with_items:
- return frappe.get_all('Quotation',
- {
- 'opportunity': self.name,
- 'status': ("not in", ['Lost', 'Closed']),
- 'docstatus': 1
- }, 'name')
+ return frappe.get_all(
+ "Quotation",
+ {"opportunity": self.name, "status": ("not in", ["Lost", "Closed"]), "docstatus": 1},
+ "name",
+ )
else:
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select q.name
from `tabQuotation` q, `tabQuotation Item` qi
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
- and q.status not in ('Lost', 'Closed')""", self.name)
+ and q.status not in ('Lost', 'Closed')""",
+ self.name,
+ )
def has_ordered_quotation(self):
if not self.with_items:
- return frappe.get_all('Quotation',
- {
- 'opportunity': self.name,
- 'status': 'Ordered',
- 'docstatus': 1
- }, 'name')
+ return frappe.get_all(
+ "Quotation", {"opportunity": self.name, "status": "Ordered", "docstatus": 1}, "name"
+ )
else:
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select q.name
from `tabQuotation` q, `tabQuotation Item` qi
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
- and q.status = 'Ordered'""", self.name)
+ and q.status = 'Ordered'""",
+ self.name,
+ )
def has_lost_quotation(self):
- lost_quotation = frappe.db.sql("""
+ lost_quotation = frappe.db.sql(
+ """
select name
from `tabQuotation`
where docstatus=1
and opportunity =%s and status = 'Lost'
- """, self.name)
+ """,
+ self.name,
+ )
if lost_quotation:
if self.has_active_quotation():
return False
return True
def validate_cust_name(self):
- if self.party_name and self.opportunity_from == 'Customer':
+ if self.party_name and self.opportunity_from == "Customer":
self.customer_name = frappe.db.get_value("Customer", self.party_name, "customer_name")
- elif self.party_name and self.opportunity_from == 'Lead':
- lead_name, company_name = frappe.db.get_value("Lead", self.party_name, ["lead_name", "company_name"])
+ elif self.party_name and self.opportunity_from == "Lead":
+ lead_name, company_name = frappe.db.get_value(
+ "Lead", self.party_name, ["lead_name", "company_name"]
+ )
self.customer_name = company_name or lead_name
def on_update(self):
self.add_calendar_event()
def add_calendar_event(self, opts=None, force=False):
- if frappe.db.get_single_value('CRM Settings', 'create_event_on_next_contact_date_opportunity'):
+ if frappe.db.get_single_value("CRM Settings", "create_event_on_next_contact_date_opportunity"):
if not opts:
opts = frappe._dict()
opts.description = ""
opts.contact_date = self.contact_date
- if self.party_name and self.opportunity_from == 'Customer':
+ if self.party_name and self.opportunity_from == "Customer":
if self.contact_person:
- opts.description = 'Contact '+cstr(self.contact_person)
+ opts.description = "Contact " + cstr(self.contact_person)
else:
- opts.description = 'Contact customer '+cstr(self.party_name)
- elif self.party_name and self.opportunity_from == 'Lead':
+ opts.description = "Contact customer " + cstr(self.party_name)
+ elif self.party_name and self.opportunity_from == "Lead":
if self.contact_display:
- opts.description = 'Contact '+cstr(self.contact_display)
+ opts.description = "Contact " + cstr(self.contact_display)
else:
- opts.description = 'Contact lead '+cstr(self.party_name)
+ opts.description = "Contact lead " + cstr(self.party_name)
opts.subject = opts.description
- opts.description += '. By : ' + cstr(self.contact_by)
+ opts.description += ". By : " + cstr(self.contact_by)
if self.to_discuss:
- opts.description += ' To Discuss : ' + cstr(self.to_discuss)
+ opts.description += " To Discuss : " + cstr(self.to_discuss)
super(Opportunity, self).add_calendar_event(opts, force)
def validate_item_details(self):
- if not self.get('items'):
+ if not self.get("items"):
return
# set missing values
@@ -234,41 +244,51 @@ class Opportunity(TransactionBase):
item = frappe.db.get_value("Item", d.item_code, item_fields, as_dict=True)
for key in item_fields:
- if not d.get(key): d.set(key, item.get(key))
+ if not d.get(key):
+ d.set(key, item.get(key))
@frappe.whitelist()
def get_item_details(item_code):
- item = frappe.db.sql("""select item_name, stock_uom, image, description, item_group, brand
- from `tabItem` where name = %s""", item_code, as_dict=1)
+ item = frappe.db.sql(
+ """select item_name, stock_uom, image, description, item_group, brand
+ from `tabItem` where name = %s""",
+ item_code,
+ as_dict=1,
+ )
return {
- 'item_name': item and item[0]['item_name'] or '',
- 'uom': item and item[0]['stock_uom'] or '',
- 'description': item and item[0]['description'] or '',
- 'image': item and item[0]['image'] or '',
- 'item_group': item and item[0]['item_group'] or '',
- 'brand': item and item[0]['brand'] or ''
+ "item_name": item and item[0]["item_name"] or "",
+ "uom": item and item[0]["stock_uom"] or "",
+ "description": item and item[0]["description"] or "",
+ "image": item and item[0]["image"] or "",
+ "item_group": item and item[0]["item_group"] or "",
+ "brand": item and item[0]["brand"] or "",
}
+
@frappe.whitelist()
def make_quotation(source_name, target_doc=None):
def set_missing_values(source, target):
from erpnext.controllers.accounts_controller import get_default_taxes_and_charges
+
quotation = frappe.get_doc(target)
- company_currency = frappe.get_cached_value('Company', quotation.company, "default_currency")
+ company_currency = frappe.get_cached_value("Company", quotation.company, "default_currency")
if company_currency == quotation.currency:
exchange_rate = 1
else:
- exchange_rate = get_exchange_rate(quotation.currency, company_currency,
- quotation.transaction_date, args="for_selling")
+ exchange_rate = get_exchange_rate(
+ quotation.currency, company_currency, quotation.transaction_date, args="for_selling"
+ )
quotation.conversion_rate = exchange_rate
# get default taxes
- taxes = get_default_taxes_and_charges("Sales Taxes and Charges Template", company=quotation.company)
- if taxes.get('taxes'):
+ taxes = get_default_taxes_and_charges(
+ "Sales Taxes and Charges Template", company=quotation.company
+ )
+ if taxes.get("taxes"):
quotation.update(taxes)
quotation.run_method("set_missing_values")
@@ -276,49 +296,53 @@ def make_quotation(source_name, target_doc=None):
if not source.with_items:
quotation.opportunity = source.name
- doclist = get_mapped_doc("Opportunity", source_name, {
- "Opportunity": {
- "doctype": "Quotation",
- "field_map": {
- "opportunity_from": "quotation_to",
- "name": "enq_no"
- }
- },
- "Opportunity Item": {
- "doctype": "Quotation Item",
- "field_map": {
- "parent": "prevdoc_docname",
- "parenttype": "prevdoc_doctype",
- "uom": "stock_uom"
+ doclist = get_mapped_doc(
+ "Opportunity",
+ source_name,
+ {
+ "Opportunity": {
+ "doctype": "Quotation",
+ "field_map": {"opportunity_from": "quotation_to", "name": "enq_no"},
},
- "add_if_empty": True
- }
- }, target_doc, set_missing_values)
+ "Opportunity Item": {
+ "doctype": "Quotation Item",
+ "field_map": {
+ "parent": "prevdoc_docname",
+ "parenttype": "prevdoc_doctype",
+ "uom": "stock_uom",
+ },
+ "add_if_empty": True,
+ },
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
@frappe.whitelist()
def make_request_for_quotation(source_name, target_doc=None):
def update_item(obj, target, source_parent):
target.conversion_factor = 1.0
- doclist = get_mapped_doc("Opportunity", source_name, {
- "Opportunity": {
- "doctype": "Request for Quotation"
+ doclist = get_mapped_doc(
+ "Opportunity",
+ source_name,
+ {
+ "Opportunity": {"doctype": "Request for Quotation"},
+ "Opportunity Item": {
+ "doctype": "Request for Quotation Item",
+ "field_map": [["name", "opportunity_item"], ["parent", "opportunity"], ["uom", "uom"]],
+ "postprocess": update_item,
+ },
},
- "Opportunity Item": {
- "doctype": "Request for Quotation Item",
- "field_map": [
- ["name", "opportunity_item"],
- ["parent", "opportunity"],
- ["uom", "uom"]
- ],
- "postprocess": update_item
- }
- }, target_doc)
+ target_doc,
+ )
return doclist
+
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
def set_missing_values(source, target):
@@ -327,37 +351,37 @@ def make_customer(source_name, target_doc=None):
if source.opportunity_from == "Lead":
target.lead_name = source.party_name
- doclist = get_mapped_doc("Opportunity", source_name, {
- "Opportunity": {
- "doctype": "Customer",
- "field_map": {
- "currency": "default_currency",
- "customer_name": "customer_name"
+ doclist = get_mapped_doc(
+ "Opportunity",
+ source_name,
+ {
+ "Opportunity": {
+ "doctype": "Customer",
+ "field_map": {"currency": "default_currency", "customer_name": "customer_name"},
}
- }
- }, target_doc, set_missing_values)
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
@frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None):
- doclist = get_mapped_doc("Opportunity", source_name, {
- "Opportunity": {
- "doctype": "Supplier Quotation",
- "field_map": {
- "name": "opportunity"
- }
+ doclist = get_mapped_doc(
+ "Opportunity",
+ source_name,
+ {
+ "Opportunity": {"doctype": "Supplier Quotation", "field_map": {"name": "opportunity"}},
+ "Opportunity Item": {"doctype": "Supplier Quotation Item", "field_map": {"uom": "stock_uom"}},
},
- "Opportunity Item": {
- "doctype": "Supplier Quotation Item",
- "field_map": {
- "uom": "stock_uom"
- }
- }
- }, target_doc)
+ target_doc,
+ )
return doclist
+
@frappe.whitelist()
def set_multiple_status(names, status):
names = json.loads(names)
@@ -366,12 +390,19 @@ def set_multiple_status(names, status):
opp.status = status
opp.save()
-def auto_close_opportunity():
- """ auto close the `Replied` Opportunities after 7 days """
- auto_close_after_days = frappe.db.get_single_value("CRM Settings", "close_opportunity_after_days") or 15
- opportunities = frappe.db.sql(""" select name from tabOpportunity where status='Replied' and
- modified{subject}
It is {0}").format(str(total_w) + "%"))
+ frappe.throw(
+ _("Total weightage assigned should be 100%.
It is {0}").format(str(total_w) + "%")
+ )
- if frappe.db.get_value("Employee", self.employee, "user_id") != \
- frappe.session.user and total == 0:
+ if (
+ frappe.db.get_value("Employee", self.employee, "user_id") != frappe.session.user and total == 0
+ ):
frappe.throw(_("Total cannot be zero"))
self.total_score = total
def on_submit(self):
- frappe.db.set(self, 'status', 'Submitted')
+ frappe.db.set(self, "status", "Submitted")
def on_cancel(self):
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
+
@frappe.whitelist()
def fetch_appraisal_template(source_name, target_doc=None):
- target_doc = get_mapped_doc("Appraisal Template", source_name, {
- "Appraisal Template": {
- "doctype": "Appraisal",
+ target_doc = get_mapped_doc(
+ "Appraisal Template",
+ source_name,
+ {
+ "Appraisal Template": {
+ "doctype": "Appraisal",
+ },
+ "Appraisal Template Goal": {
+ "doctype": "Appraisal Goal",
+ },
},
- "Appraisal Template Goal": {
- "doctype": "Appraisal Goal",
- }
- }, target_doc)
+ target_doc,
+ )
return target_doc
diff --git a/erpnext/hr/doctype/appraisal/test_appraisal.py b/erpnext/hr/doctype/appraisal/test_appraisal.py
index 90c30ef347..13a39f3820 100644
--- a/erpnext/hr/doctype/appraisal/test_appraisal.py
+++ b/erpnext/hr/doctype/appraisal/test_appraisal.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Appraisal')
+
class TestAppraisal(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
index 116a3f9118..476de4f51b 100644
--- a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
+++ b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'kra_template',
- 'transactions': [
- {
- 'items': ['Appraisal']
- },
- ],
- }
+ return {
+ "fieldname": "kra_template",
+ "transactions": [
+ {"items": ["Appraisal"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py
index d0e81a7dc5..560e992e8a 100644
--- a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py
+++ b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Appraisal Template')
+
class TestAppraisalTemplate(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index b1e373e218..7f4bd83685 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -13,6 +13,7 @@ from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_emp
class Attendance(Document):
def validate(self):
from erpnext.controllers.status_updater import validate_status
+
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
validate_active_employee(self.employee)
self.validate_attendance_date()
@@ -24,62 +25,84 @@ class Attendance(Document):
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
# leaves can be marked for future dates
- if self.status != 'On Leave' and not self.leave_application and getdate(self.attendance_date) > getdate(nowdate()):
+ if (
+ self.status != "On Leave"
+ and not self.leave_application
+ and getdate(self.attendance_date) > getdate(nowdate())
+ ):
frappe.throw(_("Attendance can not be marked for future dates"))
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
frappe.throw(_("Attendance date can not be less than employee's joining date"))
def validate_duplicate_record(self):
- res = frappe.db.sql("""
+ res = frappe.db.sql(
+ """
select name from `tabAttendance`
where employee = %s
and attendance_date = %s
and name != %s
and docstatus != 2
- """, (self.employee, getdate(self.attendance_date), self.name))
+ """,
+ (self.employee, getdate(self.attendance_date), self.name),
+ )
if res:
- frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format(
- frappe.bold(self.employee), frappe.bold(self.attendance_date)))
+ frappe.throw(
+ _("Attendance for employee {0} is already marked for the date {1}").format(
+ frappe.bold(self.employee), frappe.bold(self.attendance_date)
+ )
+ )
def validate_employee_status(self):
if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))
def check_leave_record(self):
- leave_record = frappe.db.sql("""
+ leave_record = frappe.db.sql(
+ """
select leave_type, half_day, half_day_date
from `tabLeave Application`
where employee = %s
and %s between from_date and to_date
and status = 'Approved'
and docstatus = 1
- """, (self.employee, self.attendance_date), as_dict=True)
+ """,
+ (self.employee, self.attendance_date),
+ as_dict=True,
+ )
if leave_record:
for d in leave_record:
self.leave_type = d.leave_type
if d.half_day_date == getdate(self.attendance_date):
- self.status = 'Half Day'
- frappe.msgprint(_("Employee {0} on Half day on {1}")
- .format(self.employee, formatdate(self.attendance_date)))
+ self.status = "Half Day"
+ frappe.msgprint(
+ _("Employee {0} on Half day on {1}").format(self.employee, formatdate(self.attendance_date))
+ )
else:
- self.status = 'On Leave'
- frappe.msgprint(_("Employee {0} is on Leave on {1}")
- .format(self.employee, formatdate(self.attendance_date)))
+ self.status = "On Leave"
+ frappe.msgprint(
+ _("Employee {0} is on Leave on {1}").format(self.employee, formatdate(self.attendance_date))
+ )
if self.status in ("On Leave", "Half Day"):
if not leave_record:
- frappe.msgprint(_("No leave record found for employee {0} on {1}")
- .format(self.employee, formatdate(self.attendance_date)), alert=1)
+ frappe.msgprint(
+ _("No leave record found for employee {0} on {1}").format(
+ self.employee, formatdate(self.attendance_date)
+ ),
+ alert=1,
+ )
elif self.leave_type:
self.leave_type = None
self.leave_application = None
def validate_employee(self):
- emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'",
- self.employee)
+ emp = frappe.db.sql(
+ "select name from `tabEmployee` where name = %s and status = 'Active'", self.employee
+ )
if not emp:
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
+
@frappe.whitelist()
def get_events(start, end, filters=None):
events = []
@@ -90,10 +113,12 @@ def get_events(start, end, filters=None):
return events
from frappe.desk.reportview import get_filters_cond
+
conditions = get_filters_cond("Attendance", filters, [])
add_attendance(events, start, end, conditions=conditions)
return events
+
def add_attendance(events, start, end, conditions=None):
query = """select name, attendance_date, status
from `tabAttendance` where
@@ -102,81 +127,97 @@ def add_attendance(events, start, end, conditions=None):
if conditions:
query += conditions
- for d in frappe.db.sql(query, {"from_date":start, "to_date":end}, as_dict=True):
+ for d in frappe.db.sql(query, {"from_date": start, "to_date": end}, as_dict=True):
e = {
"name": d.name,
"doctype": "Attendance",
"start": d.attendance_date,
"end": d.attendance_date,
"title": cstr(d.status),
- "docstatus": d.docstatus
+ "docstatus": d.docstatus,
}
if e not in events:
events.append(e)
-def mark_attendance(employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False):
- if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
- company = frappe.db.get_value('Employee', employee, 'company')
- attendance = frappe.get_doc({
- 'doctype': 'Attendance',
- 'employee': employee,
- 'attendance_date': attendance_date,
- 'status': status,
- 'company': company,
- 'shift': shift,
- 'leave_type': leave_type
- })
+
+def mark_attendance(
+ employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False
+):
+ if not frappe.db.exists(
+ "Attendance",
+ {"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")},
+ ):
+ company = frappe.db.get_value("Employee", employee, "company")
+ attendance = frappe.get_doc(
+ {
+ "doctype": "Attendance",
+ "employee": employee,
+ "attendance_date": attendance_date,
+ "status": status,
+ "company": company,
+ "shift": shift,
+ "leave_type": leave_type,
+ }
+ )
attendance.flags.ignore_validate = ignore_validate
attendance.insert()
attendance.submit()
return attendance.name
+
@frappe.whitelist()
def mark_bulk_attendance(data):
import json
+
if isinstance(data, str):
data = json.loads(data)
data = frappe._dict(data)
- company = frappe.get_value('Employee', data.employee, 'company')
+ company = frappe.get_value("Employee", data.employee, "company")
if not data.unmarked_days:
frappe.throw(_("Please select a date."))
return
for date in data.unmarked_days:
doc_dict = {
- 'doctype': 'Attendance',
- 'employee': data.employee,
- 'attendance_date': get_datetime(date),
- 'status': data.status,
- 'company': company,
+ "doctype": "Attendance",
+ "employee": data.employee,
+ "attendance_date": get_datetime(date),
+ "status": data.status,
+ "company": company,
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
def get_month_map():
- return frappe._dict({
- "January": 1,
- "February": 2,
- "March": 3,
- "April": 4,
- "May": 5,
- "June": 6,
- "July": 7,
- "August": 8,
- "September": 9,
- "October": 10,
- "November": 11,
- "December": 12
- })
+ return frappe._dict(
+ {
+ "January": 1,
+ "February": 2,
+ "March": 3,
+ "April": 4,
+ "May": 5,
+ "June": 6,
+ "July": 7,
+ "August": 8,
+ "September": 9,
+ "October": 10,
+ "November": 11,
+ "December": 12,
+ }
+ )
+
@frappe.whitelist()
def get_unmarked_days(employee, month, exclude_holidays=0):
import calendar
+
month_map = get_month_map()
today = get_datetime()
- joining_date, relieving_date = frappe.get_cached_value("Employee", employee, ["date_of_joining", "relieving_date"])
+ joining_date, relieving_date = frappe.get_cached_value(
+ "Employee", employee, ["date_of_joining", "relieving_date"]
+ )
start_day = 1
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
@@ -186,15 +227,21 @@ def get_unmarked_days(employee, month, exclude_holidays=0):
if relieving_date and relieving_date.month == month_map[month]:
end_day = relieving_date.day + 1
- dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(start_day, end_day)]
+ dates_of_month = [
+ "{}-{}-{}".format(today.year, month_map[month], r) for r in range(start_day, end_day)
+ ]
month_start, month_end = dates_of_month[0], dates_of_month[-1]
- records = frappe.get_all("Attendance", fields=['attendance_date', 'employee'], filters=[
- ["attendance_date", ">=", month_start],
- ["attendance_date", "<=", month_end],
- ["employee", "=", employee],
- ["docstatus", "!=", 2]
- ])
+ records = frappe.get_all(
+ "Attendance",
+ fields=["attendance_date", "employee"],
+ filters=[
+ ["attendance_date", ">=", month_start],
+ ["attendance_date", "<=", month_end],
+ ["employee", "=", employee],
+ ["docstatus", "!=", 2],
+ ],
+ )
marked_days = [get_datetime(record.attendance_date) for record in records]
if cint(exclude_holidays):
diff --git a/erpnext/hr/doctype/attendance/attendance_dashboard.py b/erpnext/hr/doctype/attendance/attendance_dashboard.py
index 4bb36a0d7f..abed78fbbb 100644
--- a/erpnext/hr/doctype/attendance/attendance_dashboard.py
+++ b/erpnext/hr/doctype/attendance/attendance_dashboard.py
@@ -1,10 +1,2 @@
def get_data():
- return {
- 'fieldname': 'attendance',
- 'transactions': [
- {
- 'label': '',
- 'items': ['Employee Checkin']
- }
- ]
- }
+ return {"fieldname": "attendance", "transactions": [{"label": "", "items": ["Employee Checkin"]}]}
diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py
index 585059ff47..058bc93d72 100644
--- a/erpnext/hr/doctype/attendance/test_attendance.py
+++ b/erpnext/hr/doctype/attendance/test_attendance.py
@@ -13,7 +13,8 @@ from erpnext.hr.doctype.attendance.attendance import (
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
-test_records = frappe.get_test_records('Attendance')
+test_records = frappe.get_test_records("Attendance")
+
class TestAttendance(FrappeTestCase):
def setUp(self):
@@ -26,9 +27,11 @@ class TestAttendance(FrappeTestCase):
def test_mark_absent(self):
employee = make_employee("test_mark_absent@example.com")
date = nowdate()
- frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date})
- attendance = mark_attendance(employee, date, 'Absent')
- fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'})
+ frappe.db.delete("Attendance", {"employee": employee, "attendance_date": date})
+ attendance = mark_attendance(employee, date, "Absent")
+ fetch_attendance = frappe.get_value(
+ "Attendance", {"employee": employee, "attendance_date": date, "status": "Absent"}
+ )
self.assertEqual(attendance, fetch_attendance)
def test_unmarked_days(self):
@@ -36,12 +39,14 @@ class TestAttendance(FrappeTestCase):
previous_month = now.month - 1
first_day = now.replace(day=1).replace(month=previous_month).date()
- employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
- frappe.db.delete('Attendance', {'employee': employee})
- frappe.db.set_value('Employee', employee, 'holiday_list', self.holiday_list)
+ employee = make_employee(
+ "test_unmarked_days@example.com", date_of_joining=add_days(first_day, -1)
+ )
+ frappe.db.delete("Attendance", {"employee": employee})
+ frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
first_sunday = get_first_sunday(self.holiday_list, for_date=first_day)
- mark_attendance(employee, first_day, 'Present')
+ mark_attendance(employee, first_day, "Present")
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
@@ -59,13 +64,15 @@ class TestAttendance(FrappeTestCase):
previous_month = now.month - 1
first_day = now.replace(day=1).replace(month=previous_month).date()
- employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
- frappe.db.delete('Attendance', {'employee': employee})
+ employee = make_employee(
+ "test_unmarked_days@example.com", date_of_joining=add_days(first_day, -1)
+ )
+ frappe.db.delete("Attendance", {"employee": employee})
- frappe.db.set_value('Employee', employee, 'holiday_list', self.holiday_list)
+ frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
first_sunday = get_first_sunday(self.holiday_list, for_date=first_day)
- mark_attendance(employee, first_day, 'Present')
+ mark_attendance(employee, first_day, "Present")
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
@@ -85,14 +92,15 @@ class TestAttendance(FrappeTestCase):
doj = add_days(first_day, 1)
relieving_date = add_days(first_day, 5)
- employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj,
- relieving_date=relieving_date)
- frappe.db.delete('Attendance', {'employee': employee})
+ employee = make_employee(
+ "test_unmarked_days_as_per_doj@example.com", date_of_joining=doj, relieving_date=relieving_date
+ )
+ frappe.db.delete("Attendance", {"employee": employee})
- frappe.db.set_value('Employee', employee, 'holiday_list', self.holiday_list)
+ frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
attendance_date = add_days(first_day, 2)
- mark_attendance(employee, attendance_date, 'Present')
+ mark_attendance(employee, attendance_date, "Present")
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py
index 8fbe7c7a9a..78652f669d 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request.py
@@ -16,17 +16,19 @@ class AttendanceRequest(Document):
validate_active_employee(self.employee)
validate_dates(self, self.from_date, self.to_date)
if self.half_day:
- if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
+ if not getdate(self.from_date) <= getdate(self.half_day_date) <= getdate(self.to_date):
frappe.throw(_("Half day date should be in between from date and to date"))
def on_submit(self):
self.create_attendance()
def on_cancel(self):
- attendance_list = frappe.get_list("Attendance", {'employee': self.employee, 'attendance_request': self.name})
+ attendance_list = frappe.get_list(
+ "Attendance", {"employee": self.employee, "attendance_request": self.name}
+ )
if attendance_list:
for attendance in attendance_list:
- attendance_obj = frappe.get_doc("Attendance", attendance['name'])
+ attendance_obj = frappe.get_doc("Attendance", attendance["name"])
attendance_obj.cancel()
def create_attendance(self):
@@ -53,15 +55,24 @@ class AttendanceRequest(Document):
def validate_if_attendance_not_applicable(self, attendance_date):
# Check if attendance_date is a Holiday
if is_holiday(self.employee, attendance_date):
- frappe.msgprint(_("Attendance not submitted for {0} as it is a Holiday.").format(attendance_date), alert=1)
+ frappe.msgprint(
+ _("Attendance not submitted for {0} as it is a Holiday.").format(attendance_date), alert=1
+ )
return True
# Check if employee on Leave
- leave_record = frappe.db.sql("""select half_day from `tabLeave Application`
+ leave_record = frappe.db.sql(
+ """select half_day from `tabLeave Application`
where employee = %s and %s between from_date and to_date
- and docstatus = 1""", (self.employee, attendance_date), as_dict=True)
+ and docstatus = 1""",
+ (self.employee, attendance_date),
+ as_dict=True,
+ )
if leave_record:
- frappe.msgprint(_("Attendance not submitted for {0} as {1} on leave.").format(attendance_date, self.employee), alert=1)
+ frappe.msgprint(
+ _("Attendance not submitted for {0} as {1} on leave.").format(attendance_date, self.employee),
+ alert=1,
+ )
return True
return False
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
index 91970575a1..059725cb44 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'attendance_request',
- 'transactions': [
- {
- 'items': ['Attendance']
- }
- ]
- }
+ return {"fieldname": "attendance_request", "transactions": [{"items": ["Attendance"]}]}
diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
index 3f0442c7d6..ee436f5068 100644
--- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
@@ -9,6 +9,7 @@ from frappe.utils import nowdate
test_dependencies = ["Employee"]
+
class TestAttendanceRequest(unittest.TestCase):
def setUp(self):
for doctype in ["Attendance Request", "Attendance"]:
@@ -34,10 +35,10 @@ class TestAttendanceRequest(unittest.TestCase):
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
fieldname=["status", "docstatus"],
- as_dict=True
+ as_dict=True,
)
self.assertEqual(attendance.status, "Present")
self.assertEqual(attendance.docstatus, 1)
@@ -51,9 +52,9 @@ class TestAttendanceRequest(unittest.TestCase):
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
- fieldname="docstatus"
+ fieldname="docstatus",
)
self.assertEqual(attendance_docstatus, 2)
@@ -74,11 +75,11 @@ class TestAttendanceRequest(unittest.TestCase):
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
- fieldname="status"
+ fieldname="status",
)
- self.assertEqual(attendance_status, 'Work From Home')
+ self.assertEqual(attendance_status, "Work From Home")
attendance_request.cancel()
@@ -88,11 +89,12 @@ class TestAttendanceRequest(unittest.TestCase):
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
- fieldname="docstatus"
+ fieldname="docstatus",
)
self.assertEqual(attendance_docstatus, 2)
+
def get_employee():
return frappe.get_doc("Employee", "_T-Employee-00001")
diff --git a/erpnext/hr/doctype/branch/test_branch.py b/erpnext/hr/doctype/branch/test_branch.py
index e84c6e4c60..c14d4aad69 100644
--- a/erpnext/hr/doctype/branch/test_branch.py
+++ b/erpnext/hr/doctype/branch/test_branch.py
@@ -3,4 +3,4 @@
import frappe
-test_records = frappe.get_test_records('Branch')
+test_records = frappe.get_test_records("Branch")
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index 7d6051508a..d233226e66 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -18,14 +18,15 @@ from erpnext.hr.utils import (
class CompensatoryLeaveRequest(Document):
-
def validate(self):
validate_active_employee(self.employee)
validate_dates(self, self.work_from_date, self.work_end_date)
if self.half_day:
if not self.half_day_date:
frappe.throw(_("Half Day Date is mandatory"))
- if not getdate(self.work_from_date)<=getdate(self.half_day_date)<=getdate(self.work_end_date):
+ if (
+ not getdate(self.work_from_date) <= getdate(self.half_day_date) <= getdate(self.work_end_date)
+ ):
frappe.throw(_("Half Day Date should be in between Work From Date and Work End Date"))
validate_overlap(self, self.work_from_date, self.work_end_date)
self.validate_holidays()
@@ -34,13 +35,16 @@ class CompensatoryLeaveRequest(Document):
frappe.throw(_("Leave Type is madatory"))
def validate_attendance(self):
- attendance = frappe.get_all('Attendance',
+ attendance = frappe.get_all(
+ "Attendance",
filters={
- 'attendance_date': ['between', (self.work_from_date, self.work_end_date)],
- 'status': 'Present',
- 'docstatus': 1,
- 'employee': self.employee
- }, fields=['attendance_date', 'status'])
+ "attendance_date": ["between", (self.work_from_date, self.work_end_date)],
+ "status": "Present",
+ "docstatus": 1,
+ "employee": self.employee,
+ },
+ fields=["attendance_date", "status"],
+ )
if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1:
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
@@ -49,7 +53,9 @@ class CompensatoryLeaveRequest(Document):
holidays = get_holiday_dates_for_employee(self.employee, self.work_from_date, self.work_end_date)
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
if date_diff(self.work_end_date, self.work_from_date):
- msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
+ msg = _("The days between {0} to {1} are not valid holidays.").format(
+ frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date))
+ )
else:
msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
@@ -70,13 +76,19 @@ class CompensatoryLeaveRequest(Document):
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
# generate additional ledger entry for the new compensatory leaves off
- create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1))
+ create_additional_leave_ledger_entry(
+ leave_allocation, date_difference, add_days(self.work_end_date, 1)
+ )
else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
self.db_set("leave_allocation", leave_allocation.name)
else:
- frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
+ frappe.throw(
+ _("There is no leave period in between {0} and {1}").format(
+ format_date(self.work_from_date), format_date(self.work_end_date)
+ )
+ )
def on_cancel(self):
if self.leave_allocation:
@@ -93,10 +105,13 @@ class CompensatoryLeaveRequest(Document):
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
# create reverse entry on cancelation
- create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1))
+ create_additional_leave_ledger_entry(
+ leave_allocation, date_difference * -1, add_days(self.work_end_date, 1)
+ )
def get_existing_allocation_for_period(self, leave_period):
- leave_allocation = frappe.db.sql("""
+ leave_allocation = frappe.db.sql(
+ """
select name
from `tabLeave Allocation`
where employee=%(employee)s and leave_type=%(leave_type)s
@@ -104,12 +119,15 @@ class CompensatoryLeaveRequest(Document):
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
- """, {
- "from_date": leave_period[0].from_date,
- "to_date": leave_period[0].to_date,
- "employee": self.employee,
- "leave_type": self.leave_type
- }, as_dict=1)
+ """,
+ {
+ "from_date": leave_period[0].from_date,
+ "to_date": leave_period[0].to_date,
+ "employee": self.employee,
+ "leave_type": self.leave_type,
+ },
+ as_dict=1,
+ )
if leave_allocation:
return frappe.get_doc("Leave Allocation", leave_allocation[0].name)
@@ -118,18 +136,20 @@ class CompensatoryLeaveRequest(Document):
def create_leave_allocation(self, leave_period, date_difference):
is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward")
- allocation = frappe.get_doc(dict(
- doctype="Leave Allocation",
- employee=self.employee,
- employee_name=self.employee_name,
- leave_type=self.leave_type,
- from_date=add_days(self.work_end_date, 1),
- to_date=leave_period[0].to_date,
- carry_forward=cint(is_carry_forward),
- new_leaves_allocated=date_difference,
- total_leaves_allocated=date_difference,
- description=self.reason
- ))
+ allocation = frappe.get_doc(
+ dict(
+ doctype="Leave Allocation",
+ employee=self.employee,
+ employee_name=self.employee_name,
+ leave_type=self.leave_type,
+ from_date=add_days(self.work_end_date, 1),
+ to_date=leave_period[0].to_date,
+ carry_forward=cint(is_carry_forward),
+ new_leaves_allocated=date_difference,
+ total_leaves_allocated=date_difference,
+ description=self.reason,
+ )
+ )
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
index 5e51879328..7bbec293f4 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
@@ -12,12 +12,17 @@ from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_perio
test_dependencies = ["Employee"]
+
class TestCompensatoryLeaveRequest(unittest.TestCase):
def setUp(self):
- frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
- frappe.db.sql(''' delete from `tabLeave Ledger Entry`''')
- frappe.db.sql(''' delete from `tabLeave Allocation`''')
- frappe.db.sql(''' delete from `tabAttendance` where attendance_date in {0} '''.format((today(), add_days(today(), -1)))) #nosec
+ frappe.db.sql(""" delete from `tabCompensatory Leave Request`""")
+ frappe.db.sql(""" delete from `tabLeave Ledger Entry`""")
+ frappe.db.sql(""" delete from `tabLeave Allocation`""")
+ frappe.db.sql(
+ """ delete from `tabAttendance` where attendance_date in {0} """.format(
+ (today(), add_days(today(), -1))
+ )
+ ) # nosec
create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company")
create_holiday_list()
@@ -26,7 +31,7 @@ class TestCompensatoryLeaveRequest(unittest.TestCase):
employee.save()
def test_leave_balance_on_submit(self):
- ''' check creation of leave allocation on submission of compensatory leave request '''
+ """check creation of leave allocation on submission of compensatory leave request"""
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
@@ -34,18 +39,27 @@ class TestCompensatoryLeaveRequest(unittest.TestCase):
before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today())
compensatory_leave_request.submit()
- self.assertEqual(get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)), before + 1)
+ self.assertEqual(
+ get_leave_balance_on(
+ employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)
+ ),
+ before + 1,
+ )
def test_leave_allocation_update_on_submit(self):
employee = get_employee()
mark_attendance(employee, date=add_days(today(), -1))
- compensatory_leave_request = get_compensatory_leave_request(employee.name, leave_date=add_days(today(), -1))
+ compensatory_leave_request = get_compensatory_leave_request(
+ employee.name, leave_date=add_days(today(), -1)
+ )
compensatory_leave_request.submit()
# leave allocation creation on submit
- leaves_allocated = frappe.db.get_value('Leave Allocation', {
- 'name': compensatory_leave_request.leave_allocation
- }, ['total_leaves_allocated'])
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"name": compensatory_leave_request.leave_allocation},
+ ["total_leaves_allocated"],
+ )
self.assertEqual(leaves_allocated, 1)
mark_attendance(employee)
@@ -53,20 +67,22 @@ class TestCompensatoryLeaveRequest(unittest.TestCase):
compensatory_leave_request.submit()
# leave allocation updates on submission of second compensatory leave request
- leaves_allocated = frappe.db.get_value('Leave Allocation', {
- 'name': compensatory_leave_request.leave_allocation
- }, ['total_leaves_allocated'])
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"name": compensatory_leave_request.leave_allocation},
+ ["total_leaves_allocated"],
+ )
self.assertEqual(leaves_allocated, 2)
def test_creation_of_leave_ledger_entry_on_submit(self):
- ''' check creation of leave ledger entry on submission of leave request '''
+ """check creation of leave ledger entry on submission of leave request"""
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
compensatory_leave_request.submit()
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
+ leave_ledger_entry = frappe.get_all("Leave Ledger Entry", fields="*", filters=filters)
self.assertEqual(len(leave_ledger_entry), 1)
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
@@ -75,60 +91,67 @@ class TestCompensatoryLeaveRequest(unittest.TestCase):
# check reverse leave ledger entry on cancellation
compensatory_leave_request.cancel()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=filters, order_by="creation desc"
+ )
self.assertEqual(len(leave_ledger_entry), 2)
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
self.assertEqual(leave_ledger_entry[0].leaves, -1)
+
def get_compensatory_leave_request(employee, leave_date=today()):
- prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
- dict(leave_type='Compensatory Off',
+ prev_comp_leave_req = frappe.db.get_value(
+ "Compensatory Leave Request",
+ dict(
+ leave_type="Compensatory Off",
work_from_date=leave_date,
work_end_date=leave_date,
- employee=employee), 'name')
- if prev_comp_leave_req:
- return frappe.get_doc('Compensatory Leave Request', prev_comp_leave_req)
-
- return frappe.get_doc(dict(
- doctype='Compensatory Leave Request',
employee=employee,
- leave_type='Compensatory Off',
+ ),
+ "name",
+ )
+ if prev_comp_leave_req:
+ return frappe.get_doc("Compensatory Leave Request", prev_comp_leave_req)
+
+ return frappe.get_doc(
+ dict(
+ doctype="Compensatory Leave Request",
+ employee=employee,
+ leave_type="Compensatory Off",
work_from_date=leave_date,
work_end_date=leave_date,
- reason='test'
- )).insert()
+ reason="test",
+ )
+ ).insert()
-def mark_attendance(employee, date=today(), status='Present'):
- if not frappe.db.exists(dict(doctype='Attendance', employee=employee.name, attendance_date=date, status='Present')):
- attendance = frappe.get_doc({
- "doctype": "Attendance",
- "employee": employee.name,
- "attendance_date": date,
- "status": status
- })
+
+def mark_attendance(employee, date=today(), status="Present"):
+ if not frappe.db.exists(
+ dict(doctype="Attendance", employee=employee.name, attendance_date=date, status="Present")
+ ):
+ attendance = frappe.get_doc(
+ {"doctype": "Attendance", "employee": employee.name, "attendance_date": date, "status": status}
+ )
attendance.save()
attendance.submit()
+
def create_holiday_list():
if frappe.db.exists("Holiday List", "_Test Compensatory Leave"):
return
- holiday_list = frappe.get_doc({
- "doctype": "Holiday List",
- "from_date": add_months(today(), -3),
- "to_date": add_months(today(), 3),
- "holidays": [
- {
- "description": "Test Holiday",
- "holiday_date": today()
- },
- {
- "description": "Test Holiday 1",
- "holiday_date": add_days(today(), -1)
- }
- ],
- "holiday_list_name": "_Test Compensatory Leave"
- })
+ holiday_list = frappe.get_doc(
+ {
+ "doctype": "Holiday List",
+ "from_date": add_months(today(), -3),
+ "to_date": add_months(today(), 3),
+ "holidays": [
+ {"description": "Test Holiday", "holiday_date": today()},
+ {"description": "Test Holiday 1", "holiday_date": add_days(today(), -1)},
+ ],
+ "holiday_list_name": "_Test Compensatory Leave",
+ }
+ )
holiday_list.save()
diff --git a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
index fe11c47e60..bcb0161841 100644
--- a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
+++ b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
@@ -11,53 +11,59 @@ from frappe.utils import global_date_format
class DailyWorkSummary(Document):
def send_mails(self, dws_group, emails):
- '''Send emails to get daily work summary to all users \
- in selected daily work summary group'''
- incoming_email_account = frappe.db.get_value('Email Account',
- dict(enable_incoming=1, default_incoming=1),
- 'email_id')
+ """Send emails to get daily work summary to all users \
+ in selected daily work summary group"""
+ incoming_email_account = frappe.db.get_value(
+ "Email Account", dict(enable_incoming=1, default_incoming=1), "email_id"
+ )
- self.db_set('email_sent_to', '\n'.join(emails))
- frappe.sendmail(recipients=emails,
+ self.db_set("email_sent_to", "\n".join(emails))
+ frappe.sendmail(
+ recipients=emails,
message=dws_group.message,
subject=dws_group.subject,
reference_doctype=self.doctype,
reference_name=self.name,
- reply_to=incoming_email_account)
+ reply_to=incoming_email_account,
+ )
def send_summary(self):
- '''Send summary of all replies. Called at midnight'''
+ """Send summary of all replies. Called at midnight"""
args = self.get_message_details()
emails = get_user_emails_from_group(self.daily_work_summary_group)
- frappe.sendmail(recipients=emails,
- template='daily_work_summary',
+ frappe.sendmail(
+ recipients=emails,
+ template="daily_work_summary",
args=args,
subject=_(self.daily_work_summary_group),
reference_doctype=self.doctype,
- reference_name=self.name)
+ reference_name=self.name,
+ )
- self.db_set('status', 'Sent')
+ self.db_set("status", "Sent")
def get_message_details(self):
- '''Return args for template'''
- dws_group = frappe.get_doc('Daily Work Summary Group',
- self.daily_work_summary_group)
+ """Return args for template"""
+ dws_group = frappe.get_doc("Daily Work Summary Group", self.daily_work_summary_group)
- replies = frappe.get_all('Communication',
- fields=['content', 'text_content', 'sender'],
- filters=dict(reference_doctype=self.doctype,
+ replies = frappe.get_all(
+ "Communication",
+ fields=["content", "text_content", "sender"],
+ filters=dict(
+ reference_doctype=self.doctype,
reference_name=self.name,
- communication_type='Communication',
- sent_or_received='Received'),
- order_by='creation asc')
+ communication_type="Communication",
+ sent_or_received="Received",
+ ),
+ order_by="creation asc",
+ )
did_not_reply = self.email_sent_to.split()
for d in replies:
- user = frappe.db.get_values("User",
- {"email": d.sender},
- ["full_name", "user_image"],
- as_dict=True)
+ user = frappe.db.get_values(
+ "User", {"email": d.sender}, ["full_name", "user_image"], as_dict=True
+ )
d.sender_name = user[0].full_name if user else d.sender
d.image = user[0].image if user and user[0].image else None
@@ -66,17 +72,13 @@ class DailyWorkSummary(Document):
# make thumbnail image
try:
if original_image:
- file_name = frappe.get_list('File',
- {'file_url': original_image})
+ file_name = frappe.get_list("File", {"file_url": original_image})
if file_name:
file_name = file_name[0].name
- file_doc = frappe.get_doc('File', file_name)
+ file_doc = frappe.get_doc("File", file_name)
thumbnail_image = file_doc.make_thumbnail(
- set_as_thumbnail=False,
- width=100,
- height=100,
- crop=True
+ set_as_thumbnail=False, width=100, height=100, crop=True
)
d.image = thumbnail_image
except Exception:
@@ -85,34 +87,33 @@ class DailyWorkSummary(Document):
if d.sender in did_not_reply:
did_not_reply.remove(d.sender)
if d.text_content:
- d.content = frappe.utils.md_to_html(
- EmailReplyParser.parse_reply(d.text_content)
- )
+ d.content = frappe.utils.md_to_html(EmailReplyParser.parse_reply(d.text_content))
- did_not_reply = [(frappe.db.get_value("User", {"email": email}, "full_name") or email)
- for email in did_not_reply]
+ did_not_reply = [
+ (frappe.db.get_value("User", {"email": email}, "full_name") or email) for email in did_not_reply
+ ]
- return dict(replies=replies,
+ return dict(
+ replies=replies,
original_message=dws_group.message,
- title=_('Work Summary for {0}').format(
- global_date_format(self.creation)
- ),
- did_not_reply=', '.join(did_not_reply) or '',
- did_not_reply_title=_('No replies from'))
+ title=_("Work Summary for {0}").format(global_date_format(self.creation)),
+ did_not_reply=", ".join(did_not_reply) or "",
+ did_not_reply_title=_("No replies from"),
+ )
def get_user_emails_from_group(group):
- '''Returns list of email of enabled users from the given group
+ """Returns list of email of enabled users from the given group
- :param group: Daily Work Summary Group `name`'''
+ :param group: Daily Work Summary Group `name`"""
group_doc = group
if isinstance(group_doc, str):
- group_doc = frappe.get_doc('Daily Work Summary Group', group)
+ group_doc = frappe.get_doc("Daily Work Summary Group", group)
emails = get_users_email(group_doc)
return emails
+
def get_users_email(doc):
- return [d.email for d in doc.users
- if frappe.db.get_value("User", d.user, "enabled")]
+ return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")]
diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py
index 5edfb31556..703436529d 100644
--- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py
+++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py
@@ -9,82 +9,96 @@ import frappe.utils
# test_records = frappe.get_test_records('Daily Work Summary')
+
class TestDailyWorkSummary(unittest.TestCase):
def test_email_trigger(self):
self.setup_and_prepare_test()
for d in self.users:
# check that email is sent to users
if d.message:
- self.assertTrue(d.email in [d.recipient for d in self.emails
- if self.groups.subject in d.message])
+ self.assertTrue(
+ d.email in [d.recipient for d in self.emails if self.groups.subject in d.message]
+ )
def test_email_trigger_failed(self):
- hour = '00:00'
- if frappe.utils.nowtime().split(':')[0] == '00':
- hour = '01:00'
+ hour = "00:00"
+ if frappe.utils.nowtime().split(":")[0] == "00":
+ hour = "01:00"
self.setup_and_prepare_test(hour)
for d in self.users:
# check that email is not sent to users
- self.assertFalse(d.email in [d.recipient for d in self.emails
- if self.groups.subject in d.message])
+ self.assertFalse(
+ d.email in [d.recipient for d in self.emails if self.groups.subject in d.message]
+ )
def test_incoming(self):
# get test mail with message-id as in-reply-to
self.setup_and_prepare_test()
with open(os.path.join(os.path.dirname(__file__), "test_data", "test-reply.raw"), "r") as f:
- if not self.emails: return
- test_mails = [f.read().replace('{{ sender }}',
- self.users[-1].email).replace('{{ message_id }}',
- self.emails[-1].message_id)]
+ if not self.emails:
+ return
+ test_mails = [
+ f.read()
+ .replace("{{ sender }}", self.users[-1].email)
+ .replace("{{ message_id }}", self.emails[-1].message_id)
+ ]
# pull the mail
email_account = frappe.get_doc("Email Account", "_Test Email Account 1")
- email_account.db_set('enable_incoming', 1)
+ email_account.db_set("enable_incoming", 1)
email_account.receive(test_mails=test_mails)
- daily_work_summary = frappe.get_doc('Daily Work Summary',
- frappe.get_all('Daily Work Summary')[0].name)
+ daily_work_summary = frappe.get_doc(
+ "Daily Work Summary", frappe.get_all("Daily Work Summary")[0].name
+ )
args = daily_work_summary.get_message_details()
- self.assertTrue('I built Daily Work Summary!' in args.get('replies')[0].content)
+ self.assertTrue("I built Daily Work Summary!" in args.get("replies")[0].content)
def setup_and_prepare_test(self, hour=None):
- frappe.db.sql('delete from `tabDaily Work Summary`')
- frappe.db.sql('delete from `tabEmail Queue`')
- frappe.db.sql('delete from `tabEmail Queue Recipient`')
- frappe.db.sql('delete from `tabCommunication`')
- frappe.db.sql('delete from `tabDaily Work Summary Group`')
+ frappe.db.sql("delete from `tabDaily Work Summary`")
+ frappe.db.sql("delete from `tabEmail Queue`")
+ frappe.db.sql("delete from `tabEmail Queue Recipient`")
+ frappe.db.sql("delete from `tabCommunication`")
+ frappe.db.sql("delete from `tabDaily Work Summary Group`")
- self.users = frappe.get_all('User',
- fields=['email'],
- filters=dict(email=('!=', 'test@example.com')))
+ self.users = frappe.get_all(
+ "User", fields=["email"], filters=dict(email=("!=", "test@example.com"))
+ )
self.setup_groups(hour)
from erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group import trigger_emails
+
trigger_emails()
# check if emails are created
- self.emails = frappe.db.sql("""select r.recipient, q.message, q.message_id \
+ self.emails = frappe.db.sql(
+ """select r.recipient, q.message, q.message_id \
from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \
- where q.name = r.parent""", as_dict=1)
-
+ where q.name = r.parent""",
+ as_dict=1,
+ )
def setup_groups(self, hour=None):
# setup email to trigger at this hour
if not hour:
- hour = frappe.utils.nowtime().split(':')[0]
- hour = hour+':00'
+ hour = frappe.utils.nowtime().split(":")[0]
+ hour = hour + ":00"
- groups = frappe.get_doc(dict(doctype="Daily Work Summary Group",
- name="Daily Work Summary",
- users=self.users,
- send_emails_at=hour,
- subject="this is a subject for testing summary emails",
- message='this is a message for testing summary emails'))
+ groups = frappe.get_doc(
+ dict(
+ doctype="Daily Work Summary Group",
+ name="Daily Work Summary",
+ users=self.users,
+ send_emails_at=hour,
+ subject="this is a subject for testing summary emails",
+ message="this is a message for testing summary emails",
+ )
+ )
groups.insert()
self.groups = groups
diff --git a/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py b/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py
index ed98168aef..4342f1c450 100644
--- a/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py
+++ b/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py
@@ -15,37 +15,41 @@ class DailyWorkSummaryGroup(Document):
def validate(self):
if self.users:
if not frappe.flags.in_test and not is_incoming_account_enabled():
- frappe.throw(_('Please enable default incoming account before creating Daily Work Summary Group'))
+ frappe.throw(
+ _("Please enable default incoming account before creating Daily Work Summary Group")
+ )
def trigger_emails():
- '''Send emails to Employees at the given hour asking
- them what did they work on today'''
+ """Send emails to Employees at the given hour asking
+ them what did they work on today"""
groups = frappe.get_all("Daily Work Summary Group")
for d in groups:
group_doc = frappe.get_doc("Daily Work Summary Group", d)
- if (is_current_hour(group_doc.send_emails_at)
+ if (
+ is_current_hour(group_doc.send_emails_at)
and not is_holiday(group_doc.holiday_list)
- and group_doc.enabled):
+ and group_doc.enabled
+ ):
emails = get_user_emails_from_group(group_doc)
# find emails relating to a company
if emails:
daily_work_summary = frappe.get_doc(
- dict(doctype='Daily Work Summary', daily_work_summary_group=group_doc.name)
+ dict(doctype="Daily Work Summary", daily_work_summary_group=group_doc.name)
).insert()
daily_work_summary.send_mails(group_doc, emails)
def is_current_hour(hour):
- return frappe.utils.nowtime().split(':')[0] == hour.split(':')[0]
+ return frappe.utils.nowtime().split(":")[0] == hour.split(":")[0]
def send_summary():
- '''Send summary to everyone'''
- for d in frappe.get_all('Daily Work Summary', dict(status='Open')):
- daily_work_summary = frappe.get_doc('Daily Work Summary', d.name)
+ """Send summary to everyone"""
+ for d in frappe.get_all("Daily Work Summary", dict(status="Open")):
+ daily_work_summary = frappe.get_doc("Daily Work Summary", d.name)
daily_work_summary.send_summary()
def is_incoming_account_enabled():
- return frappe.db.get_value('Email Account', dict(enable_incoming=1, default_incoming=1))
+ return frappe.db.get_value("Email Account", dict(enable_incoming=1, default_incoming=1))
diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py
index 71300c40b0..159fa02612 100644
--- a/erpnext/hr/doctype/department/department.py
+++ b/erpnext/hr/doctype/department/department.py
@@ -9,7 +9,7 @@ from erpnext.utilities.transaction_base import delete_events
class Department(NestedSet):
- nsm_parent_field = 'parent_department'
+ nsm_parent_field = "parent_department"
def autoname(self):
root = get_root_of("Department")
@@ -26,7 +26,7 @@ class Department(NestedSet):
def before_rename(self, old, new, merge=False):
# renaming consistency with abbreviation
- if not frappe.get_cached_value('Company', self.company, 'abbr') in new:
+ if not frappe.get_cached_value("Company", self.company, "abbr") in new:
new = get_abbreviated_name(new, self.company)
return new
@@ -39,17 +39,20 @@ class Department(NestedSet):
super(Department, self).on_trash()
delete_events(self.doctype, self.name)
+
def on_doctype_update():
frappe.db.add_index("Department", ["lft", "rgt"])
+
def get_abbreviated_name(name, company):
- abbr = frappe.get_cached_value('Company', company, 'abbr')
- new_name = '{0} - {1}'.format(name, abbr)
+ abbr = frappe.get_cached_value("Company", company, "abbr")
+ new_name = "{0} - {1}".format(name, abbr)
return new_name
+
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False):
- condition = ''
+ condition = ""
var_dict = {
"name": get_root_of("Department"),
"parent": parent,
@@ -62,18 +65,26 @@ def get_children(doctype, parent=None, company=None, is_root=False):
else:
condition = "parent_department = %(parent)s"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
name as value,
is_group as expandable
from `tab{doctype}`
where
{condition}
- order by name""".format(doctype=doctype, condition=condition), var_dict, as_dict=1)
+ order by name""".format(
+ doctype=doctype, condition=condition
+ ),
+ var_dict,
+ as_dict=1,
+ )
+
@frappe.whitelist()
def add_node():
from frappe.desk.treeview import make_tree_args
+
args = frappe.form_dict
args = make_tree_args(**args)
diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py
index 95bf663501..b8c043f0b2 100644
--- a/erpnext/hr/doctype/department/test_department.py
+++ b/erpnext/hr/doctype/department/test_department.py
@@ -6,20 +6,26 @@ import unittest
import frappe
test_ignore = ["Leave Block List"]
+
+
class TestDepartment(unittest.TestCase):
- def test_remove_department_data(self):
- doc = create_department("Test Department")
- frappe.delete_doc('Department', doc.name)
+ def test_remove_department_data(self):
+ doc = create_department("Test Department")
+ frappe.delete_doc("Department", doc.name)
+
def create_department(department_name, parent_department=None):
- doc = frappe.get_doc({
- 'doctype': 'Department',
- 'is_group': 0,
- 'parent_department': parent_department,
- 'department_name': department_name,
- 'company': frappe.defaults.get_defaults().company
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Department",
+ "is_group": 0,
+ "parent_department": parent_department,
+ "department_name": department_name,
+ "company": frappe.defaults.get_defaults().company,
+ }
+ ).insert()
- return doc
+ return doc
-test_records = frappe.get_test_records('Department')
+
+test_records = frappe.get_test_records("Department")
diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index 375ae7c70a..d849900ef2 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -10,6 +10,7 @@ from frappe.model.document import Document
class DepartmentApprover(Document):
pass
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_approvers(doctype, txt, searchfield, start, page_len, filters):
@@ -20,25 +21,44 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
approvers = []
department_details = {}
department_list = []
- employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
+ employee = frappe.get_value(
+ "Employee",
+ filters.get("employee"),
+ ["employee_name", "department", "leave_approver", "expense_approver", "shift_request_approver"],
+ as_dict=True,
+ )
employee_department = filters.get("department") or employee.department
if employee_department:
- department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True)
+ department_details = frappe.db.get_value(
+ "Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True
+ )
if department_details:
- department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s
+ department_list = frappe.db.sql(
+ """select name from `tabDepartment` where lft <= %s
and rgt >= %s
and disabled=0
- order by lft desc""", (department_details.lft, department_details.rgt), as_list=True)
+ order by lft desc""",
+ (department_details.lft, department_details.rgt),
+ as_list=True,
+ )
if filters.get("doctype") == "Leave Application" and employee.leave_approver:
- approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']))
+ approvers.append(
+ frappe.db.get_value("User", employee.leave_approver, ["name", "first_name", "last_name"])
+ )
if filters.get("doctype") == "Expense Claim" and employee.expense_approver:
- approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name']))
+ approvers.append(
+ frappe.db.get_value("User", employee.expense_approver, ["name", "first_name", "last_name"])
+ )
if filters.get("doctype") == "Shift Request" and employee.shift_request_approver:
- approvers.append(frappe.db.get_value("User", employee.shift_request_approver, ['name', 'first_name', 'last_name']))
+ approvers.append(
+ frappe.db.get_value(
+ "User", employee.shift_request_approver, ["name", "first_name", "last_name"]
+ )
+ )
if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers"
@@ -51,15 +71,21 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
field_name = "Shift Request Approver"
if department_list:
for d in department_list:
- approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
+ approvers += frappe.db.sql(
+ """select user.name, user.first_name, user.last_name from
tabUser user, `tabDepartment Approver` approver where
approver.parent = %s
and user.name like %s
and approver.parentfield = %s
- and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
+ and approver.approver=user.name""",
+ (d, "%" + txt + "%", parentfield),
+ as_list=True,
+ )
if len(approvers) == 0:
- error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name))
+ error_msg = _("Please set {0} for the Employee: {1}").format(
+ field_name, frappe.bold(employee.employee_name)
+ )
if department_list:
error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
frappe.throw(error_msg, title=_(field_name + " Missing"))
diff --git a/erpnext/hr/doctype/designation/test_designation.py b/erpnext/hr/doctype/designation/test_designation.py
index f2d6d36ff8..0840d13d1f 100644
--- a/erpnext/hr/doctype/designation/test_designation.py
+++ b/erpnext/hr/doctype/designation/test_designation.py
@@ -5,15 +5,18 @@ import frappe
# test_records = frappe.get_test_records('Designation')
-def create_designation(**args):
- args = frappe._dict(args)
- if frappe.db.exists("Designation", args.designation_name or "_Test designation"):
- return frappe.get_doc("Designation", args.designation_name or "_Test designation")
- designation = frappe.get_doc({
- "doctype": "Designation",
- "designation_name": args.designation_name or "_Test designation",
- "description": args.description or "_Test description"
- })
- designation.save()
- return designation
+def create_designation(**args):
+ args = frappe._dict(args)
+ if frappe.db.exists("Designation", args.designation_name or "_Test designation"):
+ return frappe.get_doc("Designation", args.designation_name or "_Test designation")
+
+ designation = frappe.get_doc(
+ {
+ "doctype": "Designation",
+ "designation_name": args.designation_name or "_Test designation",
+ "description": args.description or "_Test description",
+ }
+ )
+ designation.save()
+ return designation
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 6e52eb97ca..d6a911dde4 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -18,22 +18,25 @@ from erpnext.utilities.transaction_base import delete_events
class EmployeeUserDisabledError(frappe.ValidationError):
pass
+
+
class InactiveEmployeeStatusError(frappe.ValidationError):
pass
+
class Employee(NestedSet):
- nsm_parent_field = 'reports_to'
+ nsm_parent_field = "reports_to"
def autoname(self):
naming_method = frappe.db.get_value("HR Settings", None, "emp_created_by")
if not naming_method:
throw(_("Please setup Employee Naming System in Human Resource > HR Settings"))
else:
- if naming_method == 'Naming Series':
+ if naming_method == "Naming Series":
set_name_by_naming_series(self)
- elif naming_method == 'Employee Number':
+ elif naming_method == "Employee Number":
self.name = self.employee_number
- elif naming_method == 'Full Name':
+ elif naming_method == "Full Name":
self.set_employee_name()
self.name = self.employee_name
@@ -41,6 +44,7 @@ class Employee(NestedSet):
def validate(self):
from erpnext.controllers.status_updater import validate_status
+
validate_status(self.status, ["Active", "Inactive", "Suspended", "Left"])
self.employee = self.name
@@ -58,25 +62,25 @@ class Employee(NestedSet):
else:
existing_user_id = frappe.db.get_value("Employee", self.name, "user_id")
if existing_user_id:
- remove_user_permission(
- "Employee", self.name, existing_user_id)
+ remove_user_permission("Employee", self.name, existing_user_id)
def after_rename(self, old, new, merge):
self.db_set("employee", new)
def set_employee_name(self):
- self.employee_name = ' '.join(filter(lambda x: x, [self.first_name, self.middle_name, self.last_name]))
+ self.employee_name = " ".join(
+ filter(lambda x: x, [self.first_name, self.middle_name, self.last_name])
+ )
def validate_user_details(self):
if self.user_id:
- data = frappe.db.get_value('User',
- self.user_id, ['enabled', 'user_image'], as_dict=1)
+ data = frappe.db.get_value("User", self.user_id, ["enabled", "user_image"], as_dict=1)
if not data:
self.user_id = None
return
- if data.get("user_image") and self.image == '':
+ if data.get("user_image") and self.image == "":
self.image = data.get("user_image")
self.validate_for_enabled_user_id(data.get("enabled", 0))
self.validate_duplicate_user_id()
@@ -93,14 +97,14 @@ class Employee(NestedSet):
self.update_approver_role()
def update_user_permissions(self):
- if not self.create_user_permission: return
- if not has_permission('User Permission', ptype='write', raise_exception=False): return
+ if not self.create_user_permission:
+ return
+ if not has_permission("User Permission", ptype="write", raise_exception=False):
+ return
- employee_user_permission_exists = frappe.db.exists('User Permission', {
- 'allow': 'Employee',
- 'for_value': self.name,
- 'user': self.user_id
- })
+ employee_user_permission_exists = frappe.db.exists(
+ "User Permission", {"allow": "Employee", "for_value": self.name, "user": self.user_id}
+ )
if employee_user_permission_exists:
return
@@ -137,12 +141,14 @@ class Employee(NestedSet):
if not user.user_image:
user.user_image = self.image
try:
- frappe.get_doc({
- "doctype": "File",
- "file_url": self.image,
- "attached_to_doctype": "User",
- "attached_to_name": self.user_id
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "File",
+ "file_url": self.image,
+ "attached_to_doctype": "User",
+ "attached_to_name": self.user_id,
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
# already exists
pass
@@ -164,16 +170,32 @@ class Employee(NestedSet):
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today."))
- if self.date_of_birth and self.date_of_joining and getdate(self.date_of_birth) >= getdate(self.date_of_joining):
+ if (
+ self.date_of_birth
+ and self.date_of_joining
+ and getdate(self.date_of_birth) >= getdate(self.date_of_joining)
+ ):
throw(_("Date of Joining must be greater than Date of Birth"))
- elif self.date_of_retirement and self.date_of_joining and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining)):
+ elif (
+ self.date_of_retirement
+ and self.date_of_joining
+ and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining))
+ ):
throw(_("Date Of Retirement must be greater than Date of Joining"))
- elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) < getdate(self.date_of_joining)):
+ elif (
+ self.relieving_date
+ and self.date_of_joining
+ and (getdate(self.relieving_date) < getdate(self.date_of_joining))
+ ):
throw(_("Relieving Date must be greater than or equal to Date of Joining"))
- elif self.contract_end_date and self.date_of_joining and (getdate(self.contract_end_date) <= getdate(self.date_of_joining)):
+ elif (
+ self.contract_end_date
+ and self.date_of_joining
+ and (getdate(self.contract_end_date) <= getdate(self.date_of_joining))
+ ):
throw(_("Contract End Date must be greater than Date of Joining"))
def validate_email(self):
@@ -189,14 +211,20 @@ class Employee(NestedSet):
self.prefered_email = preferred_email
def validate_status(self):
- if self.status == 'Left':
- reports_to = frappe.db.get_all('Employee',
- filters={'reports_to': self.name, 'status': "Active"},
- fields=['name','employee_name']
+ if self.status == "Left":
+ reports_to = frappe.db.get_all(
+ "Employee",
+ filters={"reports_to": self.name, "status": "Active"},
+ fields=["name", "employee_name"],
)
if reports_to:
- link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to]
- message = _("The following employees are currently still reporting to {0}:").format(frappe.bold(self.employee_name))
+ link_to_employees = [
+ frappe.utils.get_link_to_form("Employee", employee.name, label=employee.employee_name)
+ for employee in reports_to
+ ]
+ message = _("The following employees are currently still reporting to {0}:").format(
+ frappe.bold(self.employee_name)
+ )
message += "
"
message += _("Please make sure the employees above report to another Active employee.")
@@ -205,7 +233,7 @@ class Employee(NestedSet):
throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled):
- if not self.status == 'Active':
+ if not self.status == "Active":
return
if enabled is None:
@@ -214,11 +242,16 @@ class Employee(NestedSet):
frappe.throw(_("User {0} is disabled").format(self.user_id), EmployeeUserDisabledError)
def validate_duplicate_user_id(self):
- employee = frappe.db.sql_list("""select name from `tabEmployee` where
- user_id=%s and status='Active' and name!=%s""", (self.user_id, self.name))
+ employee = frappe.db.sql_list(
+ """select name from `tabEmployee` where
+ user_id=%s and status='Active' and name!=%s""",
+ (self.user_id, self.name),
+ )
if employee:
- throw(_("User {0} is already assigned to Employee {1}").format(
- self.user_id, employee[0]), frappe.DuplicateEntryError)
+ throw(
+ _("User {0} is already assigned to Employee {1}").format(self.user_id, employee[0]),
+ frappe.DuplicateEntryError,
+ )
def validate_reports_to(self):
if self.reports_to == self.name:
@@ -227,17 +260,25 @@ class Employee(NestedSet):
def on_trash(self):
self.update_nsm_model()
delete_events(self.doctype, self.name)
- if frappe.db.exists("Employee Transfer", {'new_employee_id': self.name, 'docstatus': 1}):
- emp_transfer = frappe.get_doc("Employee Transfer", {'new_employee_id': self.name, 'docstatus': 1})
- emp_transfer.db_set("new_employee_id", '')
+ if frappe.db.exists("Employee Transfer", {"new_employee_id": self.name, "docstatus": 1}):
+ emp_transfer = frappe.get_doc(
+ "Employee Transfer", {"new_employee_id": self.name, "docstatus": 1}
+ )
+ emp_transfer.db_set("new_employee_id", "")
def validate_preferred_email(self):
if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)):
frappe.msgprint(_("Please enter {0}").format(self.prefered_contact_email))
def validate_onboarding_process(self):
- employee_onboarding = frappe.get_all("Employee Onboarding",
- filters={"job_applicant": self.job_applicant, "docstatus": 1, "boarding_status": ("!=", "Completed")})
+ employee_onboarding = frappe.get_all(
+ "Employee Onboarding",
+ filters={
+ "job_applicant": self.job_applicant,
+ "docstatus": 1,
+ "boarding_status": ("!=", "Completed"),
+ },
+ )
if employee_onboarding:
doc = frappe.get_doc("Employee Onboarding", employee_onboarding[0].name)
doc.validate_employee_creation()
@@ -245,20 +286,26 @@ class Employee(NestedSet):
def reset_employee_emails_cache(self):
prev_doc = self.get_doc_before_save() or {}
- cell_number = cstr(self.get('cell_number'))
- prev_number = cstr(prev_doc.get('cell_number'))
- if (cell_number != prev_number or
- self.get('user_id') != prev_doc.get('user_id')):
- frappe.cache().hdel('employees_with_number', cell_number)
- frappe.cache().hdel('employees_with_number', prev_number)
+ cell_number = cstr(self.get("cell_number"))
+ prev_number = cstr(prev_doc.get("cell_number"))
+ if cell_number != prev_number or self.get("user_id") != prev_doc.get("user_id"):
+ frappe.cache().hdel("employees_with_number", cell_number)
+ frappe.cache().hdel("employees_with_number", prev_number)
+
def get_timeline_data(doctype, name):
- '''Return timeline for attendance'''
- return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*)
+ """Return timeline for attendance"""
+ return dict(
+ frappe.db.sql(
+ """select unix_timestamp(attendance_date), count(*)
from `tabAttendance` where employee=%s
and attendance_date > date_sub(curdate(), interval 1 year)
and status in ('Present', 'Half Day')
- group by attendance_date''', name))
+ group by attendance_date""",
+ name,
+ )
+ )
+
@frappe.whitelist()
def get_retirement_date(date_of_birth=None):
@@ -266,14 +313,15 @@ def get_retirement_date(date_of_birth=None):
if date_of_birth:
try:
retirement_age = int(frappe.db.get_single_value("HR Settings", "retirement_age") or 60)
- dt = add_years(getdate(date_of_birth),retirement_age)
- ret = {'date_of_retirement': dt.strftime('%Y-%m-%d')}
+ dt = add_years(getdate(date_of_birth), retirement_age)
+ ret = {"date_of_retirement": dt.strftime("%Y-%m-%d")}
except ValueError:
# invalid date
ret = {}
return ret
+
def validate_employee_role(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("roles")]:
@@ -281,39 +329,52 @@ def validate_employee_role(doc, method):
frappe.msgprint(_("Please set User ID field in an Employee record to set Employee Role"))
doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0])
+
def update_user_permissions(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("roles")]:
- if not has_permission('User Permission', ptype='write', raise_exception=False): return
+ if not has_permission("User Permission", ptype="write", raise_exception=False):
+ return
employee = frappe.get_doc("Employee", {"user_id": doc.name})
employee.update_user_permissions()
+
def get_employee_email(employee_doc):
- return employee_doc.get("user_id") or employee_doc.get("personal_email") or employee_doc.get("company_email")
+ return (
+ employee_doc.get("user_id")
+ or employee_doc.get("personal_email")
+ or employee_doc.get("company_email")
+ )
+
def get_holiday_list_for_employee(employee, raise_exception=True):
if employee:
holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"])
else:
- holiday_list=''
- company=frappe.db.get_value("Global Defaults", None, "default_company")
+ holiday_list = ""
+ company = frappe.db.get_value("Global Defaults", None, "default_company")
if not holiday_list:
- holiday_list = frappe.get_cached_value('Company', company, "default_holiday_list")
+ holiday_list = frappe.get_cached_value("Company", company, "default_holiday_list")
if not holiday_list and raise_exception:
- frappe.throw(_('Please set a default Holiday List for Employee {0} or Company {1}').format(employee, company))
+ frappe.throw(
+ _("Please set a default Holiday List for Employee {0} or Company {1}").format(employee, company)
+ )
return holiday_list
-def is_holiday(employee, date=None, raise_exception=True, only_non_weekly=False, with_description=False):
- '''
+
+def is_holiday(
+ employee, date=None, raise_exception=True, only_non_weekly=False, with_description=False
+):
+ """
Returns True if given Employee has an holiday on the given date
- :param employee: Employee `name`
- :param date: Date to check. Will check for today if None
- :param raise_exception: Raise an exception if no holiday list found, default is True
- :param only_non_weekly: Check only non-weekly holidays, default is False
- '''
+ :param employee: Employee `name`
+ :param date: Date to check. Will check for today if None
+ :param raise_exception: Raise an exception if no holiday list found, default is True
+ :param only_non_weekly: Check only non-weekly holidays, default is False
+ """
holiday_list = get_holiday_list_for_employee(employee, raise_exception)
if not date:
@@ -322,34 +383,28 @@ def is_holiday(employee, date=None, raise_exception=True, only_non_weekly=False,
if not holiday_list:
return False
- filters = {
- 'parent': holiday_list,
- 'holiday_date': date
- }
+ filters = {"parent": holiday_list, "holiday_date": date}
if only_non_weekly:
- filters['weekly_off'] = False
+ filters["weekly_off"] = False
- holidays = frappe.get_all(
- 'Holiday',
- fields=['description'],
- filters=filters,
- pluck='description'
- )
+ holidays = frappe.get_all("Holiday", fields=["description"], filters=filters, pluck="description")
if with_description:
return len(holidays) > 0, holidays
return len(holidays) > 0
+
@frappe.whitelist()
-def deactivate_sales_person(status = None, employee = None):
+def deactivate_sales_person(status=None, employee=None):
if status == "Left":
sales_person = frappe.db.get_value("Sales Person", {"Employee": employee})
if sales_person:
frappe.db.set_value("Sales Person", sales_person, "enabled", 0)
+
@frappe.whitelist()
-def create_user(employee, user = None, email=None):
+def create_user(employee, user=None, email=None):
emp = frappe.get_doc("Employee", employee)
employee_name = emp.employee_name.split(" ")
@@ -367,93 +422,98 @@ def create_user(employee, user = None, email=None):
emp.prefered_email = email
user = frappe.new_doc("User")
- user.update({
- "name": emp.employee_name,
- "email": emp.prefered_email,
- "enabled": 1,
- "first_name": first_name,
- "middle_name": middle_name,
- "last_name": last_name,
- "gender": emp.gender,
- "birth_date": emp.date_of_birth,
- "phone": emp.cell_number,
- "bio": emp.bio
- })
+ user.update(
+ {
+ "name": emp.employee_name,
+ "email": emp.prefered_email,
+ "enabled": 1,
+ "first_name": first_name,
+ "middle_name": middle_name,
+ "last_name": last_name,
+ "gender": emp.gender,
+ "birth_date": emp.date_of_birth,
+ "phone": emp.cell_number,
+ "bio": emp.bio,
+ }
+ )
user.insert()
return user.name
+
def get_all_employee_emails(company):
- '''Returns list of employee emails either based on user_id or company_email'''
- employee_list = frappe.get_all('Employee',
- fields=['name','employee_name'],
- filters={
- 'status': 'Active',
- 'company': company
- }
+ """Returns list of employee emails either based on user_id or company_email"""
+ employee_list = frappe.get_all(
+ "Employee", fields=["name", "employee_name"], filters={"status": "Active", "company": company}
)
employee_emails = []
for employee in employee_list:
if not employee:
continue
- user, company_email, personal_email = frappe.db.get_value('Employee',
- employee, ['user_id', 'company_email', 'personal_email'])
+ user, company_email, personal_email = frappe.db.get_value(
+ "Employee", employee, ["user_id", "company_email", "personal_email"]
+ )
email = user or company_email or personal_email
if email:
employee_emails.append(email)
return employee_emails
+
def get_employee_emails(employee_list):
- '''Returns list of employee emails either based on user_id or company_email'''
+ """Returns list of employee emails either based on user_id or company_email"""
employee_emails = []
for employee in employee_list:
if not employee:
continue
- user, company_email, personal_email = frappe.db.get_value('Employee', employee,
- ['user_id', 'company_email', 'personal_email'])
+ user, company_email, personal_email = frappe.db.get_value(
+ "Employee", employee, ["user_id", "company_email", "personal_email"]
+ )
email = user or company_email or personal_email
if email:
employee_emails.append(email)
return employee_emails
+
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
- filters = [['status', '=', 'Active']]
- if company and company != 'All Companies':
- filters.append(['company', '=', company])
+ filters = [["status", "=", "Active"]]
+ if company and company != "All Companies":
+ filters.append(["company", "=", company])
- fields = ['name as value', 'employee_name as title']
+ fields = ["name as value", "employee_name as title"]
if is_root:
- parent = ''
- if parent and company and parent!=company:
- filters.append(['reports_to', '=', parent])
+ parent = ""
+ if parent and company and parent != company:
+ filters.append(["reports_to", "=", parent])
else:
- filters.append(['reports_to', '=', ''])
+ filters.append(["reports_to", "=", ""])
- employees = frappe.get_list(doctype, fields=fields,
- filters=filters, order_by='name')
+ employees = frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
for employee in employees:
- is_expandable = frappe.get_all(doctype, filters=[
- ['reports_to', '=', employee.get('value')]
- ])
+ is_expandable = frappe.get_all(doctype, filters=[["reports_to", "=", employee.get("value")]])
employee.expandable = 1 if is_expandable else 0
return employees
+
def on_doctype_update():
frappe.db.add_index("Employee", ["lft", "rgt"])
-def has_user_permission_for_employee(user_name, employee_name):
- return frappe.db.exists({
- 'doctype': 'User Permission',
- 'user': user_name,
- 'allow': 'Employee',
- 'for_value': employee_name
- })
-def has_upload_permission(doc, ptype='read', user=None):
+def has_user_permission_for_employee(user_name, employee_name):
+ return frappe.db.exists(
+ {
+ "doctype": "User Permission",
+ "user": user_name,
+ "allow": "Employee",
+ "for_value": employee_name,
+ }
+ )
+
+
+def has_upload_permission(doc, ptype="read", user=None):
if not user:
user = frappe.session.user
if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index fb62b0414d..da89a1d651 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -3,53 +3,48 @@ from frappe import _
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on the attendance of this Employee'),
- 'fieldname': 'employee',
- 'non_standard_fieldnames': {
- 'Bank Account': 'party',
- 'Employee Grievance': 'raised_by'
- },
- 'transactions': [
+ "heatmap": True,
+ "heatmap_message": _("This is based on the attendance of this Employee"),
+ "fieldname": "employee",
+ "non_standard_fieldnames": {"Bank Account": "party", "Employee Grievance": "raised_by"},
+ "transactions": [
+ {"label": _("Attendance"), "items": ["Attendance", "Attendance Request", "Employee Checkin"]},
{
- 'label': _('Attendance'),
- 'items': ['Attendance', 'Attendance Request', 'Employee Checkin']
+ "label": _("Leave"),
+ "items": ["Leave Application", "Leave Allocation", "Leave Policy Assignment"],
},
{
- 'label': _('Leave'),
- 'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment']
+ "label": _("Lifecycle"),
+ "items": [
+ "Employee Onboarding",
+ "Employee Transfer",
+ "Employee Promotion",
+ "Employee Grievance",
+ ],
},
{
- 'label': _('Lifecycle'),
- 'items': ['Employee Onboarding', 'Employee Transfer', 'Employee Promotion', 'Employee Grievance']
+ "label": _("Exit"),
+ "items": ["Employee Separation", "Exit Interview", "Full and Final Statement"],
+ },
+ {"label": _("Shift"), "items": ["Shift Request", "Shift Assignment"]},
+ {"label": _("Expense"), "items": ["Expense Claim", "Travel Request", "Employee Advance"]},
+ {"label": _("Benefit"), "items": ["Employee Benefit Application", "Employee Benefit Claim"]},
+ {
+ "label": _("Payroll"),
+ "items": [
+ "Salary Structure Assignment",
+ "Salary Slip",
+ "Additional Salary",
+ "Timesheet",
+ "Employee Incentive",
+ "Retention Bonus",
+ "Bank Account",
+ ],
},
{
- 'label': _('Exit'),
- 'items': ['Employee Separation', 'Exit Interview', 'Full and Final Statement']
+ "label": _("Training"),
+ "items": ["Training Event", "Training Result", "Training Feedback", "Employee Skill Map"],
},
- {
- 'label': _('Shift'),
- 'items': ['Shift Request', 'Shift Assignment']
- },
- {
- 'label': _('Expense'),
- 'items': ['Expense Claim', 'Travel Request', 'Employee Advance']
- },
- {
- 'label': _('Benefit'),
- 'items': ['Employee Benefit Application', 'Employee Benefit Claim']
- },
- {
- 'label': _('Payroll'),
- 'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
- },
- {
- 'label': _('Training'),
- 'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
- },
- {
- 'label': _('Evaluation'),
- 'items': ['Appraisal']
- },
- ]
+ {"label": _("Evaluation"), "items": ["Appraisal"]},
+ ],
}
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
index 0bb66374d1..1829bc4f2f 100644
--- a/erpnext/hr/doctype/employee/employee_reminders.py
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -44,13 +44,10 @@ def send_advance_holiday_reminders(frequency):
else:
return
- employees = frappe.db.get_all('Employee', filters={'status': 'Active'}, pluck='name')
+ employees = frappe.db.get_all("Employee", filters={"status": "Active"}, pluck="name")
for employee in employees:
holidays = get_holidays_for_employee(
- employee,
- start_date, end_date,
- only_non_weekly=True,
- raise_exception=False
+ employee, start_date, end_date, only_non_weekly=True, raise_exception=False
)
send_holidays_reminder_in_advance(employee, holidays)
@@ -60,7 +57,7 @@ def send_holidays_reminder_in_advance(employee, holidays):
if not holidays:
return
- employee_doc = frappe.get_doc('Employee', employee)
+ employee_doc = frappe.get_doc("Employee", employee)
employee_email = get_employee_email(employee_doc)
frequency = frappe.db.get_single_value("HR Settings", "frequency")
@@ -70,15 +67,18 @@ def send_holidays_reminder_in_advance(employee, holidays):
subject=_("Upcoming Holidays Reminder"),
template="holiday_reminder",
args=dict(
- reminder_text=_("Hey {}! This email is to remind you about the upcoming holidays.").format(employee_doc.get('first_name')),
+ reminder_text=_("Hey {}! This email is to remind you about the upcoming holidays.").format(
+ employee_doc.get("first_name")
+ ),
message=_("Below is the list of upcoming holidays for you:"),
advance_holiday_reminder=True,
holidays=holidays,
- frequency=frequency[:-2]
+ frequency=frequency[:-2],
),
- header=email_header
+ header=email_header,
)
+
# ------------------
# BIRTHDAY REMINDERS
# ------------------
@@ -109,10 +109,10 @@ def send_birthday_reminders():
def get_birthday_reminder_text_and_message(birthday_persons):
if len(birthday_persons) == 1:
- birthday_person_text = birthday_persons[0]['name']
+ birthday_person_text = birthday_persons[0]["name"]
else:
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
- person_names = [d['name'] for d in birthday_persons]
+ person_names = [d["name"] for d in birthday_persons]
birthday_person_text = comma_sep(person_names, frappe._("{0} & {1}"), False)
reminder_text = _("Today is {0}'s birthday 🎉").format(birthday_person_text)
@@ -133,7 +133,7 @@ def send_birthday_reminder(recipients, reminder_text, birthday_persons, message)
birthday_persons=birthday_persons,
message=message,
),
- header=_("Birthday Reminder 🎂")
+ header=_("Birthday Reminder 🎂"),
)
@@ -150,15 +150,16 @@ def get_employees_having_an_event_today(event_type):
from collections import defaultdict
# Set column based on event type
- if event_type == 'birthday':
- condition_column = 'date_of_birth'
- elif event_type == 'work_anniversary':
- condition_column = 'date_of_joining'
+ if event_type == "birthday":
+ condition_column = "date_of_birth"
+ elif event_type == "work_anniversary":
+ condition_column = "date_of_joining"
else:
return
- employees_born_today = frappe.db.multisql({
- "mariadb": f"""
+ employees_born_today = frappe.db.multisql(
+ {
+ "mariadb": f"""
SELECT `personal_email`, `company`, `company_email`, `user_id`, `employee_name` AS 'name', `image`, `date_of_joining`
FROM `tabEmployee`
WHERE
@@ -170,7 +171,7 @@ def get_employees_having_an_event_today(event_type):
AND
`status` = 'Active'
""",
- "postgres": f"""
+ "postgres": f"""
SELECT "personal_email", "company", "company_email", "user_id", "employee_name" AS 'name', "image"
FROM "tabEmployee"
WHERE
@@ -182,12 +183,15 @@ def get_employees_having_an_event_today(event_type):
AND
"status" = 'Active'
""",
- }, dict(today=today(), condition_column=condition_column), as_dict=1)
+ },
+ dict(today=today(), condition_column=condition_column),
+ as_dict=1,
+ )
grouped_employees = defaultdict(lambda: [])
for employee_doc in employees_born_today:
- grouped_employees[employee_doc.get('company')].append(employee_doc)
+ grouped_employees[employee_doc.get("company")].append(employee_doc)
return grouped_employees
@@ -222,19 +226,19 @@ def send_work_anniversary_reminders():
def get_work_anniversary_reminder_text_and_message(anniversary_persons):
if len(anniversary_persons) == 1:
- anniversary_person = anniversary_persons[0]['name']
+ anniversary_person = anniversary_persons[0]["name"]
persons_name = anniversary_person
# Number of years completed at the company
- completed_years = getdate().year - anniversary_persons[0]['date_of_joining'].year
+ completed_years = getdate().year - anniversary_persons[0]["date_of_joining"].year
anniversary_person += f" completed {completed_years} year(s)"
else:
person_names_with_years = []
names = []
for person in anniversary_persons:
- person_text = person['name']
+ person_text = person["name"]
names.append(person_text)
# Number of years completed at the company
- completed_years = getdate().year - person['date_of_joining'].year
+ completed_years = getdate().year - person["date_of_joining"].year
person_text += f" completed {completed_years} year(s)"
person_names_with_years.append(person_text)
@@ -260,5 +264,5 @@ def send_work_anniversary_reminder(recipients, reminder_text, anniversary_person
anniversary_persons=anniversary_persons,
message=message,
),
- header=_("Work Anniversary Reminder")
+ header=_("Work Anniversary Reminder"),
)
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 67cbea67e1..50894b6171 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -9,7 +9,8 @@ import frappe.utils
import erpnext
from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
-test_records = frappe.get_test_records('Employee')
+test_records = frappe.get_test_records("Employee")
+
class TestEmployee(unittest.TestCase):
def test_employee_status_left(self):
@@ -21,7 +22,7 @@ class TestEmployee(unittest.TestCase):
employee2_doc.reports_to = employee1_doc.name
employee2_doc.save()
employee1_doc.reload()
- employee1_doc.status = 'Left'
+ employee1_doc.status = "Left"
self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
def test_employee_status_inactive(self):
@@ -36,11 +37,19 @@ class TestEmployee(unittest.TestCase):
employee_doc.reload()
make_holiday_list()
- frappe.db.set_value("Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List")
+ frappe.db.set_value(
+ "Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List"
+ )
- frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
- salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
- employee=employee_doc.name, company=employee_doc.company)
+ frappe.db.sql(
+ """delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'"""
+ )
+ salary_structure = make_salary_structure(
+ "Test Inactive Employee Salary Slip",
+ "Monthly",
+ employee=employee_doc.name,
+ company=employee_doc.company,
+ )
salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
@@ -48,38 +57,43 @@ class TestEmployee(unittest.TestCase):
def tearDown(self):
frappe.db.rollback()
+
def make_employee(user, company=None, **kwargs):
if not frappe.db.get_value("User", user):
- frappe.get_doc({
- "doctype": "User",
- "email": user,
- "first_name": user,
- "new_password": "password",
- "send_welcome_email": 0,
- "roles": [{"doctype": "Has Role", "role": "Employee"}]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "email": user,
+ "first_name": user,
+ "new_password": "password",
+ "send_welcome_email": 0,
+ "roles": [{"doctype": "Has Role", "role": "Employee"}],
+ }
+ ).insert()
if not frappe.db.get_value("Employee", {"user_id": user}):
- employee = frappe.get_doc({
- "doctype": "Employee",
- "naming_series": "EMP-",
- "first_name": user,
- "company": company or erpnext.get_default_company(),
- "user_id": user,
- "date_of_birth": "1990-05-08",
- "date_of_joining": "2013-01-01",
- "department": frappe.get_all("Department", fields="name")[0].name,
- "gender": "Female",
- "company_email": user,
- "prefered_contact_email": "Company Email",
- "prefered_email": user,
- "status": "Active",
- "employment_type": "Intern"
- })
+ employee = frappe.get_doc(
+ {
+ "doctype": "Employee",
+ "naming_series": "EMP-",
+ "first_name": user,
+ "company": company or erpnext.get_default_company(),
+ "user_id": user,
+ "date_of_birth": "1990-05-08",
+ "date_of_joining": "2013-01-01",
+ "department": frappe.get_all("Department", fields="name")[0].name,
+ "gender": "Female",
+ "company_email": user,
+ "prefered_contact_email": "Company Email",
+ "prefered_email": user,
+ "status": "Active",
+ "employment_type": "Intern",
+ }
+ )
if kwargs:
employee.update(kwargs)
employee.insert()
return employee.name
else:
- frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
- return frappe.get_value("Employee", {"employee_name":user}, "name")
+ frappe.db.set_value("Employee", {"employee_name": user}, "status", "Active")
+ return frappe.get_value("Employee", {"employee_name": user}, "name")
diff --git a/erpnext/hr/doctype/employee/test_employee_reminders.py b/erpnext/hr/doctype/employee/test_employee_reminders.py
index a4097ab9d1..9bde77c956 100644
--- a/erpnext/hr/doctype/employee/test_employee_reminders.py
+++ b/erpnext/hr/doctype/employee/test_employee_reminders.py
@@ -21,23 +21,22 @@ class TestEmployeeReminders(unittest.TestCase):
# Create a test holiday list
test_holiday_dates = cls.get_test_holiday_dates()
test_holiday_list = make_holiday_list(
- 'TestHolidayRemindersList',
+ "TestHolidayRemindersList",
holiday_dates=[
- {'holiday_date': test_holiday_dates[0], 'description': 'test holiday1'},
- {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'},
- {'holiday_date': test_holiday_dates[2], 'description': 'test holiday3', 'weekly_off': 1},
- {'holiday_date': test_holiday_dates[3], 'description': 'test holiday4'},
- {'holiday_date': test_holiday_dates[4], 'description': 'test holiday5'},
- {'holiday_date': test_holiday_dates[5], 'description': 'test holiday6'},
+ {"holiday_date": test_holiday_dates[0], "description": "test holiday1"},
+ {"holiday_date": test_holiday_dates[1], "description": "test holiday2"},
+ {"holiday_date": test_holiday_dates[2], "description": "test holiday3", "weekly_off": 1},
+ {"holiday_date": test_holiday_dates[3], "description": "test holiday4"},
+ {"holiday_date": test_holiday_dates[4], "description": "test holiday5"},
+ {"holiday_date": test_holiday_dates[5], "description": "test holiday6"},
],
- from_date=getdate()-timedelta(days=10),
- to_date=getdate()+timedelta(weeks=5)
+ from_date=getdate() - timedelta(days=10),
+ to_date=getdate() + timedelta(weeks=5),
)
# Create a test employee
test_employee = frappe.get_doc(
- 'Employee',
- make_employee('test@gopher.io', company="_Test Company")
+ "Employee", make_employee("test@gopher.io", company="_Test Company")
)
# Attach the holiday list to employee
@@ -49,16 +48,16 @@ class TestEmployeeReminders(unittest.TestCase):
cls.test_holiday_dates = test_holiday_dates
# Employee without holidays in this month/week
- test_employee_2 = make_employee('test@empwithoutholiday.io', company="_Test Company")
- test_employee_2 = frappe.get_doc('Employee', test_employee_2)
+ test_employee_2 = make_employee("test@empwithoutholiday.io", company="_Test Company")
+ test_employee_2 = frappe.get_doc("Employee", test_employee_2)
test_holiday_list = make_holiday_list(
- 'TestHolidayRemindersList2',
+ "TestHolidayRemindersList2",
holiday_dates=[
- {'holiday_date': add_months(getdate(), 1), 'description': 'test holiday1'},
+ {"holiday_date": add_months(getdate(), 1), "description": "test holiday1"},
],
from_date=add_months(getdate(), -2),
- to_date=add_months(getdate(), 2)
+ to_date=add_months(getdate(), 2),
)
test_employee_2.holiday_list = test_holiday_list.name
test_employee_2.save()
@@ -71,11 +70,11 @@ class TestEmployeeReminders(unittest.TestCase):
today_date = getdate()
return [
today_date,
- today_date-timedelta(days=4),
- today_date-timedelta(days=3),
- today_date+timedelta(days=1),
- today_date+timedelta(days=3),
- today_date+timedelta(weeks=3)
+ today_date - timedelta(days=4),
+ today_date - timedelta(days=3),
+ today_date + timedelta(days=1),
+ today_date + timedelta(days=3),
+ today_date + timedelta(weeks=3),
]
def setUp(self):
@@ -88,19 +87,23 @@ class TestEmployeeReminders(unittest.TestCase):
self.assertTrue(is_holiday(self.test_employee.name))
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[1]))
- self.assertFalse(is_holiday(self.test_employee.name, date=getdate()-timedelta(days=1)))
+ self.assertFalse(is_holiday(self.test_employee.name, date=getdate() - timedelta(days=1)))
# Test weekly_off holidays
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2]))
- self.assertFalse(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2], only_non_weekly=True))
+ self.assertFalse(
+ is_holiday(self.test_employee.name, date=self.test_holiday_dates[2], only_non_weekly=True)
+ )
# Test with descriptions
has_holiday, descriptions = is_holiday(self.test_employee.name, with_description=True)
self.assertTrue(has_holiday)
- self.assertTrue('test holiday1' in descriptions)
+ self.assertTrue("test holiday1" in descriptions)
def test_birthday_reminders(self):
- employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+ employee = frappe.get_doc(
+ "Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]
+ )
employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:]
employee.company_email = "test@example.com"
employee.company = "_Test Company"
@@ -124,7 +127,8 @@ class TestEmployeeReminders(unittest.TestCase):
self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
def test_work_anniversary_reminders(self):
- make_employee("test_work_anniversary@gmail.com",
+ make_employee(
+ "test_work_anniversary@gmail.com",
date_of_joining="1998" + frappe.utils.nowdate()[4:],
company="_Test Company",
)
@@ -134,7 +138,7 @@ class TestEmployeeReminders(unittest.TestCase):
send_work_anniversary_reminders,
)
- employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
+ employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary")
employees = employees_having_work_anniversary.get("_Test Company") or []
user_ids = []
for entry in employees:
@@ -152,14 +156,15 @@ class TestEmployeeReminders(unittest.TestCase):
self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message)
def test_work_anniversary_reminder_not_sent_for_0_years(self):
- make_employee("test_work_anniversary_2@gmail.com",
+ make_employee(
+ "test_work_anniversary_2@gmail.com",
date_of_joining=getdate(),
company="_Test Company",
)
from erpnext.hr.doctype.employee.employee_reminders import get_employees_having_an_event_today
- employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
+ employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary")
employees = employees_having_work_anniversary.get("_Test Company") or []
user_ids = []
for entry in employees:
@@ -168,20 +173,18 @@ class TestEmployeeReminders(unittest.TestCase):
self.assertTrue("test_work_anniversary_2@gmail.com" not in user_ids)
def test_send_holidays_reminder_in_advance(self):
- setup_hr_settings('Weekly')
+ setup_hr_settings("Weekly")
holidays = get_holidays_for_employee(
- self.test_employee.get('name'),
- getdate(), getdate() + timedelta(days=3),
- only_non_weekly=True,
- raise_exception=False
- )
-
- send_holidays_reminder_in_advance(
- self.test_employee.get('name'),
- holidays
+ self.test_employee.get("name"),
+ getdate(),
+ getdate() + timedelta(days=3),
+ only_non_weekly=True,
+ raise_exception=False,
)
+ send_holidays_reminder_in_advance(self.test_employee.get("name"), holidays)
+
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertEqual(len(email_queue), 1)
self.assertTrue("Holidays this Week." in email_queue[0].message)
@@ -189,67 +192,69 @@ class TestEmployeeReminders(unittest.TestCase):
def test_advance_holiday_reminders_monthly(self):
from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_monthly
- setup_hr_settings('Monthly')
+ setup_hr_settings("Monthly")
# disable emp 2, set same holiday list
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Left',
- 'holiday_list': self.test_employee.holiday_list
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Left", "holiday_list": self.test_employee.holiday_list},
+ )
send_reminders_in_advance_monthly()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue(len(email_queue) > 0)
# even though emp 2 has holiday, non-active employees should not be recipients
- recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+ recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient")
self.assertTrue(self.test_employee_2.user_id not in recipients)
# teardown: enable emp 2
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Active',
- 'holiday_list': self.holiday_list_2.name
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Active", "holiday_list": self.holiday_list_2.name},
+ )
def test_advance_holiday_reminders_weekly(self):
from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_weekly
- setup_hr_settings('Weekly')
+ setup_hr_settings("Weekly")
# disable emp 2, set same holiday list
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Left',
- 'holiday_list': self.test_employee.holiday_list
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Left", "holiday_list": self.test_employee.holiday_list},
+ )
send_reminders_in_advance_weekly()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue(len(email_queue) > 0)
# even though emp 2 has holiday, non-active employees should not be recipients
- recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+ recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient")
self.assertTrue(self.test_employee_2.user_id not in recipients)
# teardown: enable emp 2
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Active',
- 'holiday_list': self.holiday_list_2.name
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Active", "holiday_list": self.holiday_list_2.name},
+ )
def test_reminder_not_sent_if_no_holdays(self):
- setup_hr_settings('Monthly')
+ setup_hr_settings("Monthly")
# reminder not sent if there are no holidays
holidays = get_holidays_for_employee(
- self.test_employee_2.get('name'),
- getdate(), getdate() + timedelta(days=3),
+ self.test_employee_2.get("name"),
+ getdate(),
+ getdate() + timedelta(days=3),
only_non_weekly=True,
- raise_exception=False
- )
- send_holidays_reminder_in_advance(
- self.test_employee_2.get('name'),
- holidays
+ raise_exception=False,
)
+ send_holidays_reminder_in_advance(self.test_employee_2.get("name"), holidays)
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertEqual(len(email_queue), 0)
@@ -259,5 +264,5 @@ def setup_hr_settings(frequency=None):
hr_settings = frappe.get_doc("HR Settings", "HR Settings")
hr_settings.send_holiday_reminders = 1
set_proceed_with_frequency_change()
- hr_settings.frequency = frequency or 'Weekly'
- hr_settings.save()
\ No newline at end of file
+ hr_settings.frequency = frequency or "Weekly"
+ hr_settings.save()
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 79d389d440..c1876b1175 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -16,17 +16,19 @@ from erpnext.hr.utils import validate_active_employee
class EmployeeAdvanceOverPayment(frappe.ValidationError):
pass
+
class EmployeeAdvance(Document):
def onload(self):
- self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings',
- 'make_payment_via_journal_entry')
+ self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value(
+ "Accounts Settings", "make_payment_via_journal_entry"
+ )
def validate(self):
validate_active_employee(self.employee)
self.set_status()
def on_cancel(self):
- self.ignore_linked_doctypes = ('GL Entry')
+ self.ignore_linked_doctypes = "GL Entry"
self.set_status(update=True)
def set_status(self, update=False):
@@ -37,13 +39,23 @@ class EmployeeAdvance(Document):
if self.docstatus == 0:
status = "Draft"
elif self.docstatus == 1:
- if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(self.paid_amount, precision):
+ if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(
+ self.paid_amount, precision
+ ):
status = "Claimed"
- elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(self.paid_amount, precision):
+ elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(
+ self.paid_amount, precision
+ ):
status = "Returned"
- elif flt(self.claimed_amount) > 0 and (flt(self.return_amount) > 0) and total_amount == flt(self.paid_amount, precision):
+ elif (
+ flt(self.claimed_amount) > 0
+ and (flt(self.return_amount) > 0)
+ and total_amount == flt(self.paid_amount, precision)
+ ):
status = "Partly Claimed and Returned"
- elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(self.paid_amount, precision):
+ elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(
+ self.paid_amount, precision
+ ):
status = "Paid"
else:
status = "Unpaid"
@@ -60,30 +72,30 @@ class EmployeeAdvance(Document):
paid_amount = (
frappe.qb.from_(gle)
- .select(Sum(gle.debit).as_("paid_amount"))
- .where(
- (gle.against_voucher_type == 'Employee Advance')
- & (gle.against_voucher == self.name)
- & (gle.party_type == 'Employee')
- & (gle.party == self.employee)
- & (gle.docstatus == 1)
- & (gle.is_cancelled == 0)
- )
- ).run(as_dict=True)[0].paid_amount or 0
+ .select(Sum(gle.debit).as_("paid_amount"))
+ .where(
+ (gle.against_voucher_type == "Employee Advance")
+ & (gle.against_voucher == self.name)
+ & (gle.party_type == "Employee")
+ & (gle.party == self.employee)
+ & (gle.docstatus == 1)
+ & (gle.is_cancelled == 0)
+ )
+ ).run(as_dict=True)[0].paid_amount or 0
return_amount = (
frappe.qb.from_(gle)
- .select(Sum(gle.credit).as_("return_amount"))
- .where(
- (gle.against_voucher_type == 'Employee Advance')
- & (gle.voucher_type != 'Expense Claim')
- & (gle.against_voucher == self.name)
- & (gle.party_type == 'Employee')
- & (gle.party == self.employee)
- & (gle.docstatus == 1)
- & (gle.is_cancelled == 0)
- )
- ).run(as_dict=True)[0].return_amount or 0
+ .select(Sum(gle.credit).as_("return_amount"))
+ .where(
+ (gle.against_voucher_type == "Employee Advance")
+ & (gle.voucher_type != "Expense Claim")
+ & (gle.against_voucher == self.name)
+ & (gle.party_type == "Employee")
+ & (gle.party == self.employee)
+ & (gle.docstatus == 1)
+ & (gle.is_cancelled == 0)
+ )
+ ).run(as_dict=True)[0].return_amount or 0
if paid_amount != 0:
paid_amount = flt(paid_amount) / flt(self.exchange_rate)
@@ -91,8 +103,10 @@ class EmployeeAdvance(Document):
return_amount = flt(return_amount) / flt(self.exchange_rate)
if flt(paid_amount) > self.advance_amount:
- frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"),
- EmployeeAdvanceOverPayment)
+ frappe.throw(
+ _("Row {0}# Paid Amount cannot be greater than requested advance amount"),
+ EmployeeAdvanceOverPayment,
+ )
if flt(return_amount) > self.paid_amount - self.claimed_amount:
frappe.throw(_("Return amount cannot be greater unclaimed amount"))
@@ -102,7 +116,9 @@ class EmployeeAdvance(Document):
self.set_status(update=True)
def update_claimed_amount(self):
- claimed_amount = frappe.db.sql("""
+ claimed_amount = (
+ frappe.db.sql(
+ """
SELECT sum(ifnull(allocated_amount, 0))
FROM `tabExpense Claim Advance` eca, `tabExpense Claim` ec
WHERE
@@ -111,7 +127,11 @@ class EmployeeAdvance(Document):
AND ec.name = eca.parent
AND ec.docstatus=1
AND eca.allocated_amount > 0
- """, self.name)[0][0] or 0
+ """,
+ self.name,
+ )[0][0]
+ or 0
+ )
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload()
@@ -120,56 +140,69 @@ class EmployeeAdvance(Document):
@frappe.whitelist()
def get_pending_amount(employee, posting_date):
- employee_due_amount = frappe.get_all("Employee Advance", \
- filters = {"employee":employee, "docstatus":1, "posting_date":("<=", posting_date)}, \
- fields = ["advance_amount", "paid_amount"])
+ employee_due_amount = frappe.get_all(
+ "Employee Advance",
+ filters={"employee": employee, "docstatus": 1, "posting_date": ("<=", posting_date)},
+ fields=["advance_amount", "paid_amount"],
+ )
return sum([(emp.advance_amount - emp.paid_amount) for emp in employee_due_amount])
+
@frappe.whitelist()
def make_bank_entry(dt, dn):
doc = frappe.get_doc(dt, dn)
- payment_account = get_default_bank_cash_account(doc.company, account_type="Cash",
- mode_of_payment=doc.mode_of_payment)
+ payment_account = get_default_bank_cash_account(
+ doc.company, account_type="Cash", mode_of_payment=doc.mode_of_payment
+ )
if not payment_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
- advance_account_currency = frappe.db.get_value('Account', doc.advance_account, 'account_currency')
+ advance_account_currency = frappe.db.get_value("Account", doc.advance_account, "account_currency")
- advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(advance_account_currency,doc )
+ advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(
+ advance_account_currency, doc
+ )
paying_amount, paying_exchange_rate = get_paying_amount_paying_exchange_rate(payment_account, doc)
je = frappe.new_doc("Journal Entry")
je.posting_date = nowdate()
- je.voucher_type = 'Bank Entry'
+ je.voucher_type = "Bank Entry"
je.company = doc.company
- je.remark = 'Payment against Employee Advance: ' + dn + '\n' + doc.purpose
+ je.remark = "Payment against Employee Advance: " + dn + "\n" + doc.purpose
je.multi_currency = 1 if advance_account_currency != payment_account.account_currency else 0
- je.append("accounts", {
- "account": doc.advance_account,
- "account_currency": advance_account_currency,
- "exchange_rate": flt(advance_exchange_rate),
- "debit_in_account_currency": flt(advance_amount),
- "reference_type": "Employee Advance",
- "reference_name": doc.name,
- "party_type": "Employee",
- "cost_center": erpnext.get_default_cost_center(doc.company),
- "party": doc.employee,
- "is_advance": "Yes"
- })
+ je.append(
+ "accounts",
+ {
+ "account": doc.advance_account,
+ "account_currency": advance_account_currency,
+ "exchange_rate": flt(advance_exchange_rate),
+ "debit_in_account_currency": flt(advance_amount),
+ "reference_type": "Employee Advance",
+ "reference_name": doc.name,
+ "party_type": "Employee",
+ "cost_center": erpnext.get_default_cost_center(doc.company),
+ "party": doc.employee,
+ "is_advance": "Yes",
+ },
+ )
- je.append("accounts", {
- "account": payment_account.account,
- "cost_center": erpnext.get_default_cost_center(doc.company),
- "credit_in_account_currency": flt(paying_amount),
- "account_currency": payment_account.account_currency,
- "account_type": payment_account.account_type,
- "exchange_rate": flt(paying_exchange_rate)
- })
+ je.append(
+ "accounts",
+ {
+ "account": payment_account.account,
+ "cost_center": erpnext.get_default_cost_center(doc.company),
+ "credit_in_account_currency": flt(paying_amount),
+ "account_currency": payment_account.account_currency,
+ "account_type": payment_account.account_type,
+ "exchange_rate": flt(paying_exchange_rate),
+ },
+ )
return je.as_dict()
+
def get_advance_amount_advance_exchange_rate(advance_account_currency, doc):
if advance_account_currency != doc.currency:
advance_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
@@ -180,6 +213,7 @@ def get_advance_amount_advance_exchange_rate(advance_account_currency, doc):
return advance_amount, advance_exchange_rate
+
def get_paying_amount_paying_exchange_rate(payment_account, doc):
if payment_account.account_currency != doc.currency:
paying_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
@@ -190,6 +224,7 @@ def get_paying_amount_paying_exchange_rate(payment_account, doc):
return paying_amount, paying_exchange_rate
+
@frappe.whitelist()
def create_return_through_additional_salary(doc):
import json
@@ -197,7 +232,7 @@ def create_return_through_additional_salary(doc):
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
- additional_salary = frappe.new_doc('Additional Salary')
+ additional_salary = frappe.new_doc("Additional Salary")
additional_salary.employee = doc.employee
additional_salary.currency = doc.currency
additional_salary.amount = doc.paid_amount - doc.claimed_amount
@@ -207,56 +242,81 @@ def create_return_through_additional_salary(doc):
return additional_salary
+
@frappe.whitelist()
-def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, currency, exchange_rate, mode_of_payment=None):
- bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
+def make_return_entry(
+ employee,
+ company,
+ employee_advance_name,
+ return_amount,
+ advance_account,
+ currency,
+ exchange_rate,
+ mode_of_payment=None,
+):
+ bank_cash_account = get_default_bank_cash_account(
+ company, account_type="Cash", mode_of_payment=mode_of_payment
+ )
if not bank_cash_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
- advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
+ advance_account_currency = frappe.db.get_value("Account", advance_account, "account_currency")
- je = frappe.new_doc('Journal Entry')
+ je = frappe.new_doc("Journal Entry")
je.posting_date = nowdate()
je.voucher_type = get_voucher_type(mode_of_payment)
je.company = company
- je.remark = 'Return against Employee Advance: ' + employee_advance_name
+ je.remark = "Return against Employee Advance: " + employee_advance_name
je.multi_currency = 1 if advance_account_currency != bank_cash_account.account_currency else 0
- advance_account_amount = flt(return_amount) if advance_account_currency==currency \
+ advance_account_amount = (
+ flt(return_amount)
+ if advance_account_currency == currency
else flt(return_amount) * flt(exchange_rate)
+ )
- je.append('accounts', {
- 'account': advance_account,
- 'credit_in_account_currency': advance_account_amount,
- 'account_currency': advance_account_currency,
- 'exchange_rate': flt(exchange_rate) if advance_account_currency == currency else 1,
- 'reference_type': 'Employee Advance',
- 'reference_name': employee_advance_name,
- 'party_type': 'Employee',
- 'party': employee,
- 'is_advance': 'Yes',
- 'cost_center': erpnext.get_default_cost_center(company)
- })
+ je.append(
+ "accounts",
+ {
+ "account": advance_account,
+ "credit_in_account_currency": advance_account_amount,
+ "account_currency": advance_account_currency,
+ "exchange_rate": flt(exchange_rate) if advance_account_currency == currency else 1,
+ "reference_type": "Employee Advance",
+ "reference_name": employee_advance_name,
+ "party_type": "Employee",
+ "party": employee,
+ "is_advance": "Yes",
+ "cost_center": erpnext.get_default_cost_center(company),
+ },
+ )
- bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
+ bank_amount = (
+ flt(return_amount)
+ if bank_cash_account.account_currency == currency
else flt(return_amount) * flt(exchange_rate)
+ )
- je.append("accounts", {
- "account": bank_cash_account.account,
- "debit_in_account_currency": bank_amount,
- "account_currency": bank_cash_account.account_currency,
- "account_type": bank_cash_account.account_type,
- "exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
- "cost_center": erpnext.get_default_cost_center(company)
- })
+ je.append(
+ "accounts",
+ {
+ "account": bank_cash_account.account,
+ "debit_in_account_currency": bank_amount,
+ "account_currency": bank_cash_account.account_currency,
+ "account_type": bank_cash_account.account_type,
+ "exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
+ "cost_center": erpnext.get_default_cost_center(company),
+ },
+ )
return je.as_dict()
+
def get_voucher_type(mode_of_payment=None):
voucher_type = "Cash Entry"
if mode_of_payment:
- mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
+ mode_of_payment_type = frappe.get_cached_value("Mode of Payment", mode_of_payment, "type")
if mode_of_payment_type == "Bank":
voucher_type = "Bank Entry"
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
index 9450258a8a..73fac5131e 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
@@ -1,16 +1,9 @@
def get_data():
return {
- 'fieldname': 'employee_advance',
- 'non_standard_fieldnames': {
- 'Payment Entry': 'reference_name',
- 'Journal Entry': 'reference_name'
+ "fieldname": "employee_advance",
+ "non_standard_fieldnames": {
+ "Payment Entry": "reference_name",
+ "Journal Entry": "reference_name",
},
- 'transactions': [
- {
- 'items': ['Expense Claim']
- },
- {
- 'items': ['Payment Entry', 'Journal Entry']
- }
- ]
+ "transactions": [{"items": ["Expense Claim"]}, {"items": ["Payment Entry", "Journal Entry"]}],
}
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index e3c1487ca2..44d68c9483 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -68,7 +68,9 @@ class TestEmployeeAdvance(unittest.TestCase):
def test_claimed_status(self):
# CLAIMED Status check, full amount claimed
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -95,7 +97,9 @@ class TestEmployeeAdvance(unittest.TestCase):
def test_partly_claimed_and_returned_status(self):
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -103,7 +107,9 @@ class TestEmployeeAdvance(unittest.TestCase):
# PARTLY CLAIMED AND RETURNED status check
# 500 Claimed, 500 Returned
- claim = make_expense_claim(payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -125,7 +131,7 @@ class TestEmployeeAdvance(unittest.TestCase):
advance_account=advance.advance_account,
mode_of_payment=advance.mode_of_payment,
currency=advance.currency,
- exchange_rate=advance.exchange_rate
+ exchange_rate=advance.exchange_rate,
)
entry = frappe.get_doc(entry)
@@ -160,7 +166,9 @@ class TestEmployeeAdvance(unittest.TestCase):
args = {"type": "Deduction"}
create_salary_component("Advance Salary - Deduction", **args)
- make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name)
+ make_salary_structure(
+ "Test Additional Salary for Advance Return", "Monthly", employee=employee_name
+ )
# additional salary for 700 first
advance.reload()
@@ -204,10 +212,11 @@ def make_payment_entry(advance):
return journal_entry
+
def make_employee_advance(employee_name, args=None):
doc = frappe.new_doc("Employee Advance")
doc.employee = employee_name
- doc.company = "_Test company"
+ doc.company = "_Test company"
doc.purpose = "For site visit"
doc.currency = erpnext.get_company_currency("_Test company")
doc.exchange_rate = 1
@@ -233,13 +242,16 @@ def get_advances_for_claim(claim, advance_name, amount=None):
else:
allocated_amount = flt(entry.paid_amount) - flt(entry.claimed_amount)
- claim.append("advances", {
- "employee_advance": entry.name,
- "posting_date": entry.posting_date,
- "advance_account": entry.advance_account,
- "advance_paid": entry.paid_amount,
- "unclaimed_amount": allocated_amount,
- "allocated_amount": allocated_amount
- })
+ claim.append(
+ "advances",
+ {
+ "employee_advance": entry.name,
+ "posting_date": entry.posting_date,
+ "advance_account": entry.advance_account,
+ "advance_paid": entry.paid_amount,
+ "unclaimed_amount": allocated_amount,
+ "allocated_amount": allocated_amount,
+ },
+ )
- return claim
\ No newline at end of file
+ return claim
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
index af2ca50b78..43665cc8b2 100644
--- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
@@ -14,32 +14,31 @@ class EmployeeAttendanceTool(Document):
@frappe.whitelist()
-def get_employees(date, department = None, branch = None, company = None):
+def get_employees(date, department=None, branch=None, company=None):
attendance_not_marked = []
attendance_marked = []
filters = {"status": "Active", "date_of_joining": ["<=", date]}
- for field, value in {'department': department,
- 'branch': branch, 'company': company}.items():
+ for field, value in {"department": department, "branch": branch, "company": company}.items():
if value:
filters[field] = value
- employee_list = frappe.get_list("Employee", fields=["employee", "employee_name"], filters=filters, order_by="employee_name")
+ employee_list = frappe.get_list(
+ "Employee", fields=["employee", "employee_name"], filters=filters, order_by="employee_name"
+ )
marked_employee = {}
- for emp in frappe.get_list("Attendance", fields=["employee", "status"],
- filters={"attendance_date": date}):
- marked_employee[emp['employee']] = emp['status']
+ for emp in frappe.get_list(
+ "Attendance", fields=["employee", "status"], filters={"attendance_date": date}
+ ):
+ marked_employee[emp["employee"]] = emp["status"]
for employee in employee_list:
- employee['status'] = marked_employee.get(employee['employee'])
- if employee['employee'] not in marked_employee:
+ employee["status"] = marked_employee.get(employee["employee"])
+ if employee["employee"] not in marked_employee:
attendance_not_marked.append(employee)
else:
attendance_marked.append(employee)
- return {
- "marked": attendance_marked,
- "unmarked": attendance_not_marked
- }
+ return {"marked": attendance_marked, "unmarked": attendance_not_marked}
@frappe.whitelist()
@@ -53,16 +52,18 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa
else:
leave_type = None
- company = frappe.db.get_value("Employee", employee['employee'], "Company", cache=True)
+ company = frappe.db.get_value("Employee", employee["employee"], "Company", cache=True)
- attendance=frappe.get_doc(dict(
- doctype='Attendance',
- employee=employee.get('employee'),
- employee_name=employee.get('employee_name'),
- attendance_date=getdate(date),
- status=status,
- leave_type=leave_type,
- company=company
- ))
+ attendance = frappe.get_doc(
+ dict(
+ doctype="Attendance",
+ employee=employee.get("employee"),
+ employee_name=employee.get("employee_name"),
+ attendance_date=getdate(date),
+ status=status,
+ leave_type=leave_type,
+ company=company,
+ )
+ )
attendance.insert()
- attendance.submit()
\ No newline at end of file
+ attendance.submit()
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index c1d4ac7fde..87f48b7e25 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -20,20 +20,31 @@ class EmployeeCheckin(Document):
self.fetch_shift()
def validate_duplicate_log(self):
- doc = frappe.db.exists('Employee Checkin', {
- 'employee': self.employee,
- 'time': self.time,
- 'name': ['!=', self.name]})
+ doc = frappe.db.exists(
+ "Employee Checkin", {"employee": self.employee, "time": self.time, "name": ["!=", self.name]}
+ )
if doc:
- doc_link = frappe.get_desk_link('Employee Checkin', doc)
- frappe.throw(_('This employee already has a log with the same timestamp.{0}')
- .format("
" + doc_link))
+ doc_link = frappe.get_desk_link("Employee Checkin", doc)
+ frappe.throw(
+ _("This employee already has a log with the same timestamp.{0}").format("
" + doc_link)
+ )
def fetch_shift(self):
- shift_actual_timings = get_actual_start_end_datetime_of_shift(self.employee, get_datetime(self.time), True)
+ shift_actual_timings = get_actual_start_end_datetime_of_shift(
+ self.employee, get_datetime(self.time), True
+ )
if shift_actual_timings[0] and shift_actual_timings[1]:
- if shift_actual_timings[2].shift_type.determine_check_in_and_check_out == 'Strictly based on Log Type in Employee Checkin' and not self.log_type and not self.skip_auto_attendance:
- frappe.throw(_('Log Type is required for check-ins falling in the shift: {0}.').format(shift_actual_timings[2].shift_type.name))
+ if (
+ shift_actual_timings[2].shift_type.determine_check_in_and_check_out
+ == "Strictly based on Log Type in Employee Checkin"
+ and not self.log_type
+ and not self.skip_auto_attendance
+ ):
+ frappe.throw(
+ _("Log Type is required for check-ins falling in the shift: {0}.").format(
+ shift_actual_timings[2].shift_type.name
+ )
+ )
if not self.attendance:
self.shift = shift_actual_timings[2].shift_type.name
self.shift_actual_start = shift_actual_timings[0]
@@ -43,8 +54,16 @@ class EmployeeCheckin(Document):
else:
self.shift = None
+
@frappe.whitelist()
-def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=None, log_type=None, skip_auto_attendance=0, employee_fieldname='attendance_device_id'):
+def add_log_based_on_employee_field(
+ employee_field_value,
+ timestamp,
+ device_id=None,
+ log_type=None,
+ skip_auto_attendance=0,
+ employee_fieldname="attendance_device_id",
+):
"""Finds the relevant Employee using the employee field value and creates a Employee Checkin.
:param employee_field_value: The value to look for in employee field.
@@ -58,11 +77,20 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
if not employee_field_value or not timestamp:
frappe.throw(_("'employee_field_value' and 'timestamp' are required."))
- employee = frappe.db.get_values("Employee", {employee_fieldname: employee_field_value}, ["name", "employee_name", employee_fieldname], as_dict=True)
+ employee = frappe.db.get_values(
+ "Employee",
+ {employee_fieldname: employee_field_value},
+ ["name", "employee_name", employee_fieldname],
+ as_dict=True,
+ )
if employee:
employee = employee[0]
else:
- frappe.throw(_("No Employee found for the given employee field value. '{}': {}").format(employee_fieldname,employee_field_value))
+ frappe.throw(
+ _("No Employee found for the given employee field value. '{}': {}").format(
+ employee_fieldname, employee_field_value
+ )
+ )
doc = frappe.new_doc("Employee Checkin")
doc.employee = employee.name
@@ -70,13 +98,24 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
doc.time = timestamp
doc.device_id = device_id
doc.log_type = log_type
- if cint(skip_auto_attendance) == 1: doc.skip_auto_attendance = '1'
+ if cint(skip_auto_attendance) == 1:
+ doc.skip_auto_attendance = "1"
doc.insert()
return doc
-def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, in_time=None, out_time=None, shift=None):
+def mark_attendance_and_link_log(
+ logs,
+ attendance_status,
+ attendance_date,
+ working_hours=None,
+ late_entry=False,
+ early_exit=False,
+ in_time=None,
+ out_time=None,
+ shift=None,
+):
"""Creates an attendance and links the attendance to the Employee Checkin.
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
@@ -87,40 +126,52 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
"""
log_names = [x.name for x in logs]
employee = logs[0].employee
- if attendance_status == 'Skip':
- frappe.db.sql("""update `tabEmployee Checkin`
+ if attendance_status == "Skip":
+ frappe.db.sql(
+ """update `tabEmployee Checkin`
set skip_auto_attendance = %s
- where name in %s""", ('1', log_names))
+ where name in %s""",
+ ("1", log_names),
+ )
return None
- elif attendance_status in ('Present', 'Absent', 'Half Day'):
- employee_doc = frappe.get_doc('Employee', employee)
- if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
+ elif attendance_status in ("Present", "Absent", "Half Day"):
+ employee_doc = frappe.get_doc("Employee", employee)
+ if not frappe.db.exists(
+ "Attendance",
+ {"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")},
+ ):
doc_dict = {
- 'doctype': 'Attendance',
- 'employee': employee,
- 'attendance_date': attendance_date,
- 'status': attendance_status,
- 'working_hours': working_hours,
- 'company': employee_doc.company,
- 'shift': shift,
- 'late_entry': late_entry,
- 'early_exit': early_exit,
- 'in_time': in_time,
- 'out_time': out_time
+ "doctype": "Attendance",
+ "employee": employee,
+ "attendance_date": attendance_date,
+ "status": attendance_status,
+ "working_hours": working_hours,
+ "company": employee_doc.company,
+ "shift": shift,
+ "late_entry": late_entry,
+ "early_exit": early_exit,
+ "in_time": in_time,
+ "out_time": out_time,
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
- frappe.db.sql("""update `tabEmployee Checkin`
+ frappe.db.sql(
+ """update `tabEmployee Checkin`
set attendance = %s
- where name in %s""", (attendance.name, log_names))
+ where name in %s""",
+ (attendance.name, log_names),
+ )
return attendance
else:
- frappe.db.sql("""update `tabEmployee Checkin`
+ frappe.db.sql(
+ """update `tabEmployee Checkin`
set skip_auto_attendance = %s
- where name in %s""", ('1', log_names))
+ where name in %s""",
+ ("1", log_names),
+ )
return None
else:
- frappe.throw(_('{} is an invalid Attendance Status.').format(attendance_status))
+ frappe.throw(_("{} is an invalid Attendance Status.").format(attendance_status))
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
@@ -133,29 +184,35 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
"""
total_hours = 0
in_time = out_time = None
- if check_in_out_type == 'Alternating entries as IN and OUT during the same shift':
+ if check_in_out_type == "Alternating entries as IN and OUT during the same shift":
in_time = logs[0].time
if len(logs) >= 2:
out_time = logs[-1].time
- if working_hours_calc_type == 'First Check-in and Last Check-out':
+ if working_hours_calc_type == "First Check-in and Last Check-out":
# assumption in this case: First log always taken as IN, Last log always taken as OUT
total_hours = time_diff_in_hours(in_time, logs[-1].time)
- elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
+ elif working_hours_calc_type == "Every Valid Check-in and Check-out":
logs = logs[:]
while len(logs) >= 2:
total_hours += time_diff_in_hours(logs[0].time, logs[1].time)
del logs[:2]
- elif check_in_out_type == 'Strictly based on Log Type in Employee Checkin':
- if working_hours_calc_type == 'First Check-in and Last Check-out':
- first_in_log_index = find_index_in_dict(logs, 'log_type', 'IN')
- first_in_log = logs[first_in_log_index] if first_in_log_index or first_in_log_index == 0 else None
- last_out_log_index = find_index_in_dict(reversed(logs), 'log_type', 'OUT')
- last_out_log = logs[len(logs)-1-last_out_log_index] if last_out_log_index or last_out_log_index == 0 else None
+ elif check_in_out_type == "Strictly based on Log Type in Employee Checkin":
+ if working_hours_calc_type == "First Check-in and Last Check-out":
+ first_in_log_index = find_index_in_dict(logs, "log_type", "IN")
+ first_in_log = (
+ logs[first_in_log_index] if first_in_log_index or first_in_log_index == 0 else None
+ )
+ last_out_log_index = find_index_in_dict(reversed(logs), "log_type", "OUT")
+ last_out_log = (
+ logs[len(logs) - 1 - last_out_log_index]
+ if last_out_log_index or last_out_log_index == 0
+ else None
+ )
if first_in_log and last_out_log:
in_time, out_time = first_in_log.time, last_out_log.time
total_hours = time_diff_in_hours(in_time, out_time)
- elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
+ elif working_hours_calc_type == "Every Valid Check-in and Check-out":
in_log = out_log = None
for log in logs:
if in_log and out_log:
@@ -165,16 +222,18 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
total_hours += time_diff_in_hours(in_log.time, out_log.time)
in_log = out_log = None
if not in_log:
- in_log = log if log.log_type == 'IN' else None
+ in_log = log if log.log_type == "IN" else None
elif not out_log:
- out_log = log if log.log_type == 'OUT' else None
+ out_log = log if log.log_type == "OUT" else None
if in_log and out_log:
out_time = out_log.time
total_hours += time_diff_in_hours(in_log.time, out_log.time)
return total_hours, in_time, out_time
+
def time_diff_in_hours(start, end):
- return round((end-start).total_seconds() / 3600, 1)
+ return round((end - start).total_seconds() / 3600, 1)
+
def find_index_in_dict(dict_list, key, value):
return next((index for (index, d) in enumerate(dict_list) if d[key] == value), None)
diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
index 254bf9e256..97f76b0350 100644
--- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
@@ -19,85 +19,108 @@ class TestEmployeeCheckin(unittest.TestCase):
def test_add_log_based_on_employee_field(self):
employee = make_employee("test_add_log_based_on_employee_field@example.com")
employee = frappe.get_doc("Employee", employee)
- employee.attendance_device_id = '3344'
+ employee.attendance_device_id = "3344"
employee.save()
time_now = now_datetime().__str__()[:-7]
- employee_checkin = add_log_based_on_employee_field('3344', time_now, 'mumbai_first_floor', 'IN')
+ employee_checkin = add_log_based_on_employee_field("3344", time_now, "mumbai_first_floor", "IN")
self.assertEqual(employee_checkin.employee, employee.name)
self.assertEqual(employee_checkin.time, time_now)
- self.assertEqual(employee_checkin.device_id, 'mumbai_first_floor')
- self.assertEqual(employee_checkin.log_type, 'IN')
+ self.assertEqual(employee_checkin.device_id, "mumbai_first_floor")
+ self.assertEqual(employee_checkin.log_type, "IN")
def test_mark_attendance_and_link_log(self):
employee = make_employee("test_mark_attendance_and_link_log@example.com")
logs = make_n_checkins(employee, 3)
- mark_attendance_and_link_log(logs, 'Skip', nowdate())
+ mark_attendance_and_link_log(logs, "Skip", nowdate())
log_names = [log.name for log in logs]
- logs_count = frappe.db.count('Employee Checkin', {'name':['in', log_names], 'skip_auto_attendance':1})
+ logs_count = frappe.db.count(
+ "Employee Checkin", {"name": ["in", log_names], "skip_auto_attendance": 1}
+ )
self.assertEqual(logs_count, 3)
logs = make_n_checkins(employee, 4, 2)
now_date = nowdate()
- frappe.db.delete('Attendance', {'employee':employee})
- attendance = mark_attendance_and_link_log(logs, 'Present', now_date, 8.2)
+ frappe.db.delete("Attendance", {"employee": employee})
+ attendance = mark_attendance_and_link_log(logs, "Present", now_date, 8.2)
log_names = [log.name for log in logs]
- logs_count = frappe.db.count('Employee Checkin', {'name':['in', log_names], 'attendance':attendance.name})
+ logs_count = frappe.db.count(
+ "Employee Checkin", {"name": ["in", log_names], "attendance": attendance.name}
+ )
self.assertEqual(logs_count, 4)
- attendance_count = frappe.db.count('Attendance', {'status':'Present', 'working_hours':8.2,
- 'employee':employee, 'attendance_date':now_date})
+ attendance_count = frappe.db.count(
+ "Attendance",
+ {"status": "Present", "working_hours": 8.2, "employee": employee, "attendance_date": now_date},
+ )
self.assertEqual(attendance_count, 1)
def test_calculate_working_hours(self):
- check_in_out_type = ['Alternating entries as IN and OUT during the same shift',
- 'Strictly based on Log Type in Employee Checkin']
- working_hours_calc_type = ['First Check-in and Last Check-out',
- 'Every Valid Check-in and Check-out']
+ check_in_out_type = [
+ "Alternating entries as IN and OUT during the same shift",
+ "Strictly based on Log Type in Employee Checkin",
+ ]
+ working_hours_calc_type = [
+ "First Check-in and Last Check-out",
+ "Every Valid Check-in and Check-out",
+ ]
logs_type_1 = [
- {'time':now_datetime()-timedelta(minutes=390)},
- {'time':now_datetime()-timedelta(minutes=300)},
- {'time':now_datetime()-timedelta(minutes=270)},
- {'time':now_datetime()-timedelta(minutes=90)},
- {'time':now_datetime()-timedelta(minutes=0)}
- ]
+ {"time": now_datetime() - timedelta(minutes=390)},
+ {"time": now_datetime() - timedelta(minutes=300)},
+ {"time": now_datetime() - timedelta(minutes=270)},
+ {"time": now_datetime() - timedelta(minutes=90)},
+ {"time": now_datetime() - timedelta(minutes=0)},
+ ]
logs_type_2 = [
- {'time':now_datetime()-timedelta(minutes=390),'log_type':'OUT'},
- {'time':now_datetime()-timedelta(minutes=360),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=300),'log_type':'OUT'},
- {'time':now_datetime()-timedelta(minutes=290),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=260),'log_type':'OUT'},
- {'time':now_datetime()-timedelta(minutes=240),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=150),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=60),'log_type':'OUT'}
- ]
+ {"time": now_datetime() - timedelta(minutes=390), "log_type": "OUT"},
+ {"time": now_datetime() - timedelta(minutes=360), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=300), "log_type": "OUT"},
+ {"time": now_datetime() - timedelta(minutes=290), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=260), "log_type": "OUT"},
+ {"time": now_datetime() - timedelta(minutes=240), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=150), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=60), "log_type": "OUT"},
+ ]
logs_type_1 = [frappe._dict(x) for x in logs_type_1]
logs_type_2 = [frappe._dict(x) for x in logs_type_2]
- working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0])
+ working_hours = calculate_working_hours(
+ logs_type_1, check_in_out_type[0], working_hours_calc_type[0]
+ )
self.assertEqual(working_hours, (6.5, logs_type_1[0].time, logs_type_1[-1].time))
- working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1])
+ working_hours = calculate_working_hours(
+ logs_type_1, check_in_out_type[0], working_hours_calc_type[1]
+ )
self.assertEqual(working_hours, (4.5, logs_type_1[0].time, logs_type_1[-1].time))
- working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0])
+ working_hours = calculate_working_hours(
+ logs_type_2, check_in_out_type[1], working_hours_calc_type[0]
+ )
self.assertEqual(working_hours, (5, logs_type_2[1].time, logs_type_2[-1].time))
- working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1])
+ working_hours = calculate_working_hours(
+ logs_type_2, check_in_out_type[1], working_hours_calc_type[1]
+ )
self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time))
+
def make_n_checkins(employee, n, hours_to_reverse=1):
- logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))]
- for i in range(n-1):
- logs.append(make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n-i)))
+ logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n + 1))]
+ for i in range(n - 1):
+ logs.append(
+ make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n - i))
+ )
return logs
def make_checkin(employee, time=now_datetime()):
- log = frappe.get_doc({
- "doctype": "Employee Checkin",
- "employee" : employee,
- "time" : time,
- "device_id" : "device1",
- "log_type" : "IN"
- }).insert()
+ log = frappe.get_doc(
+ {
+ "doctype": "Employee Checkin",
+ "employee": employee,
+ "time": time,
+ "device_id": "device1",
+ "log_type": "IN",
+ }
+ ).insert()
return log
diff --git a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
index 6825dae250..efc68ce87a 100644
--- a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
+++ b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
@@ -1,11 +1,9 @@
def get_data():
return {
- 'transactions': [
+ "transactions": [
{
- 'items': ['Employee', 'Leave Period'],
+ "items": ["Employee", "Leave Period"],
},
- {
- 'items': ['Employee Onboarding Template', 'Employee Separation Template']
- }
+ {"items": ["Employee Onboarding Template", "Employee Separation Template"]},
]
}
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
index fd9a33b377..45de79f4f5 100644
--- a/erpnext/hr/doctype/employee_grievance/employee_grievance.py
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
@@ -9,7 +9,8 @@ from frappe.model.document import Document
class EmployeeGrievance(Document):
def on_submit(self):
if self.status not in ["Invalid", "Resolved"]:
- frappe.throw(_("Only Employee Grievance with status {0} or {1} can be submitted").format(
- bold("Invalid"),
- bold("Resolved"))
+ frappe.throw(
+ _("Only Employee Grievance with status {0} or {1} can be submitted").format(
+ bold("Invalid"), bold("Resolved")
+ )
)
diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
index e2d0002aa6..910d882860 100644
--- a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
+++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
@@ -13,6 +13,7 @@ class TestEmployeeGrievance(unittest.TestCase):
def test_create_employee_grievance(self):
create_employee_grievance()
+
def create_employee_grievance():
grievance_type = create_grievance_type()
emp_1 = make_employee("test_emp_grievance_@example.com", company="_Test Company")
@@ -27,10 +28,10 @@ def create_employee_grievance():
grievance.grievance_against = emp_2
grievance.description = "test descrip"
- #set cause
+ # set cause
grievance.cause_of_grievance = "test cause"
- #resolution details
+ # resolution details
grievance.resolution_date = today()
grievance.resolution_detail = "test resolution detail"
grievance.resolved_by = "test_emp_grievance_@example.com"
diff --git a/erpnext/hr/doctype/employee_group/test_employee_group.py b/erpnext/hr/doctype/employee_group/test_employee_group.py
index a87f4007bd..3922f54f33 100644
--- a/erpnext/hr/doctype/employee_group/test_employee_group.py
+++ b/erpnext/hr/doctype/employee_group/test_employee_group.py
@@ -11,17 +11,16 @@ from erpnext.hr.doctype.employee.test_employee import make_employee
class TestEmployeeGroup(unittest.TestCase):
pass
+
def make_employee_group():
employee = make_employee("testemployee@example.com")
- employee_group = frappe.get_doc({
- "doctype": "Employee Group",
- "employee_group_name": "_Test Employee Group",
- "employee_list": [
- {
- "employee": employee
- }
- ]
- })
+ employee_group = frappe.get_doc(
+ {
+ "doctype": "Employee Group",
+ "employee_group_name": "_Test Employee Group",
+ "employee_list": [{"employee": employee}],
+ }
+ )
employee_group_exist = frappe.db.exists("Employee Group", "_Test Employee Group")
if not employee_group_exist:
employee_group.insert()
@@ -29,6 +28,7 @@ def make_employee_group():
else:
return employee_group_exist
+
def get_employee_group():
employee_group = frappe.db.exists("Employee Group", "_Test Employee Group")
return employee_group
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index a0939a847b..059f83a5a2 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -9,7 +9,9 @@ from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
-class IncompleteTaskError(frappe.ValidationError): pass
+class IncompleteTaskError(frappe.ValidationError):
+ pass
+
class EmployeeOnboarding(EmployeeBoardingController):
def validate(self):
@@ -19,12 +21,18 @@ class EmployeeOnboarding(EmployeeBoardingController):
def set_employee(self):
if not self.employee:
- self.employee = frappe.db.get_value('Employee', {'job_applicant': self.job_applicant}, 'name')
+ self.employee = frappe.db.get_value("Employee", {"job_applicant": self.job_applicant}, "name")
def validate_duplicate_employee_onboarding(self):
- emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)})
+ emp_onboarding = frappe.db.exists(
+ "Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)}
+ )
if emp_onboarding and emp_onboarding != self.name:
- frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
+ frappe.throw(
+ _("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(
+ frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)
+ )
+ )
def validate_employee_creation(self):
if self.docstatus != 1:
@@ -36,7 +44,10 @@ class EmployeeOnboarding(EmployeeBoardingController):
else:
task_status = frappe.db.get_value("Task", activity.task, "status")
if task_status not in ["Completed", "Cancelled"]:
- frappe.throw(_("All the mandatory tasks for employee creation are not completed yet."), IncompleteTaskError)
+ frappe.throw(
+ _("All the mandatory tasks for employee creation are not completed yet."),
+ IncompleteTaskError,
+ )
def on_submit(self):
super(EmployeeOnboarding, self).on_submit()
@@ -47,19 +58,29 @@ class EmployeeOnboarding(EmployeeBoardingController):
def on_cancel(self):
super(EmployeeOnboarding, self).on_cancel()
+
@frappe.whitelist()
def make_employee(source_name, target_doc=None):
doc = frappe.get_doc("Employee Onboarding", source_name)
doc.validate_employee_creation()
+
def set_missing_values(source, target):
target.personal_email = frappe.db.get_value("Job Applicant", source.job_applicant, "email_id")
target.status = "Active"
- doc = get_mapped_doc("Employee Onboarding", source_name, {
+
+ doc = get_mapped_doc(
+ "Employee Onboarding",
+ source_name,
+ {
"Employee Onboarding": {
"doctype": "Employee",
"field_map": {
"first_name": "employee_name",
"employee_grade": "grade",
- }}
- }, target_doc, set_missing_values)
+ },
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doc
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 0fb821ddb2..9d91e4bd62 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -16,8 +16,8 @@ from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_li
class TestEmployeeOnboarding(unittest.TestCase):
def setUp(self):
- if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
- frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
+ if frappe.db.exists("Employee Onboarding", {"employee_name": "Test Researcher"}):
+ frappe.delete_doc("Employee Onboarding", {"employee_name": "Test Researcher"})
project = "Employee Onboarding : test@researcher.com"
frappe.db.sql("delete from tabProject where name=%s", project)
@@ -26,100 +26,109 @@ class TestEmployeeOnboarding(unittest.TestCase):
def test_employee_onboarding_incomplete_task(self):
onboarding = create_employee_onboarding()
- project_name = frappe.db.get_value('Project', onboarding.project, 'project_name')
- self.assertEqual(project_name, 'Employee Onboarding : test@researcher.com')
+ project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
+ self.assertEqual(project_name, "Employee Onboarding : test@researcher.com")
# don't allow making employee if onboarding is not complete
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
# boarding status
- self.assertEqual(onboarding.boarding_status, 'Pending')
+ self.assertEqual(onboarding.boarding_status, "Pending")
# start and end dates
- start_date, end_date = frappe.db.get_value('Task', onboarding.activities[0].task, ['exp_start_date', 'exp_end_date'])
+ start_date, end_date = frappe.db.get_value(
+ "Task", onboarding.activities[0].task, ["exp_start_date", "exp_end_date"]
+ )
self.assertEqual(getdate(start_date), getdate(onboarding.boarding_begins_on))
self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[0].duration))
- start_date, end_date = frappe.db.get_value('Task', onboarding.activities[1].task, ['exp_start_date', 'exp_end_date'])
- self.assertEqual(getdate(start_date), add_days(onboarding.boarding_begins_on, onboarding.activities[0].duration))
+ start_date, end_date = frappe.db.get_value(
+ "Task", onboarding.activities[1].task, ["exp_start_date", "exp_end_date"]
+ )
+ self.assertEqual(
+ getdate(start_date), add_days(onboarding.boarding_begins_on, onboarding.activities[0].duration)
+ )
self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[1].duration))
# complete the task
- project = frappe.get_doc('Project', onboarding.project)
- for task in frappe.get_all('Task', dict(project=project.name)):
- task = frappe.get_doc('Task', task.name)
- task.status = 'Completed'
+ project = frappe.get_doc("Project", onboarding.project)
+ for task in frappe.get_all("Task", dict(project=project.name)):
+ task = frappe.get_doc("Task", task.name)
+ task.status = "Completed"
task.save()
# boarding status
onboarding.reload()
- self.assertEqual(onboarding.boarding_status, 'Completed')
+ self.assertEqual(onboarding.boarding_status, "Completed")
# make employee
onboarding.reload()
employee = make_employee(onboarding.name)
employee.first_name = employee.employee_name
employee.date_of_joining = getdate()
- employee.date_of_birth = '1990-05-08'
- employee.gender = 'Female'
+ employee.date_of_birth = "1990-05-08"
+ employee.gender = "Female"
employee.insert()
- self.assertEqual(employee.employee_name, 'Test Researcher')
+ self.assertEqual(employee.employee_name, "Test Researcher")
def tearDown(self):
frappe.db.rollback()
def get_job_applicant():
- if frappe.db.exists('Job Applicant', 'test@researcher.com'):
- return frappe.get_doc('Job Applicant', 'test@researcher.com')
- applicant = frappe.new_doc('Job Applicant')
- applicant.applicant_name = 'Test Researcher'
- applicant.email_id = 'test@researcher.com'
- applicant.designation = 'Researcher'
- applicant.status = 'Open'
- applicant.cover_letter = 'I am a great Researcher.'
+ if frappe.db.exists("Job Applicant", "test@researcher.com"):
+ return frappe.get_doc("Job Applicant", "test@researcher.com")
+ applicant = frappe.new_doc("Job Applicant")
+ applicant.applicant_name = "Test Researcher"
+ applicant.email_id = "test@researcher.com"
+ applicant.designation = "Researcher"
+ applicant.status = "Open"
+ applicant.cover_letter = "I am a great Researcher."
applicant.insert()
return applicant
+
def get_job_offer(applicant_name):
- job_offer = frappe.db.exists('Job Offer', {'job_applicant': applicant_name})
+ job_offer = frappe.db.exists("Job Offer", {"job_applicant": applicant_name})
if job_offer:
- return frappe.get_doc('Job Offer', job_offer)
+ return frappe.get_doc("Job Offer", job_offer)
job_offer = create_job_offer(job_applicant=applicant_name)
job_offer.submit()
return job_offer
+
def create_employee_onboarding():
applicant = get_job_applicant()
job_offer = get_job_offer(applicant.name)
- holiday_list = make_holiday_list('_Test Employee Boarding')
- holiday_list = frappe.get_doc('Holiday List', holiday_list)
+ holiday_list = make_holiday_list("_Test Employee Boarding")
+ holiday_list = frappe.get_doc("Holiday List", holiday_list)
holiday_list.holidays = []
holiday_list.save()
- onboarding = frappe.new_doc('Employee Onboarding')
+ onboarding = frappe.new_doc("Employee Onboarding")
onboarding.job_applicant = applicant.name
onboarding.job_offer = job_offer.name
onboarding.date_of_joining = onboarding.boarding_begins_on = getdate()
- onboarding.company = '_Test Company'
+ onboarding.company = "_Test Company"
onboarding.holiday_list = holiday_list.name
- onboarding.designation = 'Researcher'
- onboarding.append('activities', {
- 'activity_name': 'Assign ID Card',
- 'role': 'HR User',
- 'required_for_employee_creation': 1,
- 'begin_on': 0,
- 'duration': 1
- })
- onboarding.append('activities', {
- 'activity_name': 'Assign a laptop',
- 'role': 'HR User',
- 'begin_on': 1,
- 'duration': 1
- })
- onboarding.status = 'Pending'
+ onboarding.designation = "Researcher"
+ onboarding.append(
+ "activities",
+ {
+ "activity_name": "Assign ID Card",
+ "role": "HR User",
+ "required_for_employee_creation": 1,
+ "begin_on": 0,
+ "duration": 1,
+ },
+ )
+ onboarding.append(
+ "activities",
+ {"activity_name": "Assign a laptop", "role": "HR User", "begin_on": 1, "duration": 1},
+ )
+ onboarding.status = "Pending"
onboarding.insert()
onboarding.submit()
diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
index 3b846a0e40..93237ee379 100644
--- a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
+++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'employee_onboarding_template',
- 'transactions': [
- {
- 'items': ['Employee Onboarding']
- },
- ],
- }
+ return {
+ "fieldname": "employee_onboarding_template",
+ "transactions": [
+ {"items": ["Employee Onboarding"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
index cf6156e326..d77c1dddfd 100644
--- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
@@ -16,12 +16,16 @@ class EmployeePromotion(Document):
def before_submit(self):
if getdate(self.promotion_date) > getdate():
- frappe.throw(_("Employee Promotion cannot be submitted before Promotion Date"),
- frappe.DocstatusTransitionError)
+ frappe.throw(
+ _("Employee Promotion cannot be submitted before Promotion Date"),
+ frappe.DocstatusTransitionError,
+ )
def on_submit(self):
employee = frappe.get_doc("Employee", self.employee)
- employee = update_employee_work_history(employee, self.promotion_details, date=self.promotion_date)
+ employee = update_employee_work_history(
+ employee, self.promotion_details, date=self.promotion_date
+ )
employee.save()
def on_cancel(self):
diff --git a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py
index fc9d195a3f..06825ece91 100644
--- a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py
@@ -15,18 +15,20 @@ class TestEmployeePromotion(unittest.TestCase):
frappe.db.sql("""delete from `tabEmployee Promotion`""")
def test_submit_before_promotion_date(self):
- promotion_obj = frappe.get_doc({
- "doctype": "Employee Promotion",
- "employee": self.employee,
- "promotion_details" :[
- {
- "property": "Designation",
- "current": "Software Developer",
- "new": "Project Manager",
- "fieldname": "designation"
- }
- ]
- })
+ promotion_obj = frappe.get_doc(
+ {
+ "doctype": "Employee Promotion",
+ "employee": self.employee,
+ "promotion_details": [
+ {
+ "property": "Designation",
+ "current": "Software Developer",
+ "new": "Project Manager",
+ "fieldname": "designation",
+ }
+ ],
+ }
+ )
promotion_obj.promotion_date = add_days(getdate(), 1)
promotion_obj.save()
self.assertRaises(frappe.DocstatusTransitionError, promotion_obj.submit)
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
index eaa42c7b4c..47cbfbcd9d 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -30,7 +30,7 @@ class EmployeeReferral(Document):
@frappe.whitelist()
def create_job_applicant(source_name, target_doc=None):
emp_ref = frappe.get_doc("Employee Referral", source_name)
- #just for Api call if some set status apart from default Status
+ # just for Api call if some set status apart from default Status
status = emp_ref.status
if emp_ref.status in ["Pending", "In process"]:
status = "Open"
@@ -47,9 +47,13 @@ def create_job_applicant(source_name, target_doc=None):
job_applicant.resume_link = emp_ref.resume_link
job_applicant.save()
- frappe.msgprint(_("Job Applicant {0} created successfully.").format(
- get_link_to_form("Job Applicant", job_applicant.name)),
- title=_("Success"), indicator="green")
+ frappe.msgprint(
+ _("Job Applicant {0} created successfully.").format(
+ get_link_to_form("Job Applicant", job_applicant.name)
+ ),
+ title=_("Success"),
+ indicator="green",
+ )
emp_ref.db_set("status", "In Process")
@@ -60,7 +64,6 @@ def create_job_applicant(source_name, target_doc=None):
def create_additional_salary(doc):
import json
-
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
index 07c2402e1a..4d683fbfcf 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
@@ -1,13 +1,8 @@
def get_data():
return {
- 'fieldname': 'employee_referral',
- 'non_standard_fieldnames': {
- 'Additional Salary': 'ref_docname'
- },
- 'transactions': [
- {
- 'items': ['Job Applicant', 'Additional Salary']
- },
-
- ]
+ "fieldname": "employee_referral",
+ "non_standard_fieldnames": {"Additional Salary": "ref_docname"},
+ "transactions": [
+ {"items": ["Job Applicant", "Additional Salary"]},
+ ],
}
diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
index 529e355145..475a935e86 100644
--- a/erpnext/hr/doctype/employee_referral/test_employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
@@ -15,7 +15,6 @@ from erpnext.hr.doctype.employee_referral.employee_referral import (
class TestEmployeeReferral(unittest.TestCase):
-
def setUp(self):
frappe.db.sql("DELETE FROM `tabJob Applicant`")
frappe.db.sql("DELETE FROM `tabEmployee Referral`")
@@ -23,13 +22,12 @@ class TestEmployeeReferral(unittest.TestCase):
def test_workflow_and_status_sync(self):
emp_ref = create_employee_referral()
- #Check Initial status
+ # Check Initial status
self.assertTrue(emp_ref.status, "Pending")
job_applicant = create_job_applicant(emp_ref.name)
-
- #Check status sync
+ # Check status sync
emp_ref.reload()
self.assertTrue(emp_ref.status, "In Process")
@@ -47,7 +45,6 @@ class TestEmployeeReferral(unittest.TestCase):
emp_ref.reload()
self.assertTrue(emp_ref.status, "Accepted")
-
# Check for Referral reference in additional salary
add_sal = create_additional_salary(emp_ref)
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index f83c1e57b8..df31d09179 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -6,44 +6,43 @@ import unittest
import frappe
from frappe.utils import getdate
-test_dependencies = ['Employee Onboarding']
+test_dependencies = ["Employee Onboarding"]
+
class TestEmployeeSeparation(unittest.TestCase):
def test_employee_separation(self):
separation = create_employee_separation()
self.assertEqual(separation.docstatus, 1)
- self.assertEqual(separation.boarding_status, 'Pending')
+ self.assertEqual(separation.boarding_status, "Pending")
- project = frappe.get_doc('Project', separation.project)
- project.percent_complete_method = 'Manual'
- project.status = 'Completed'
+ project = frappe.get_doc("Project", separation.project)
+ project.percent_complete_method = "Manual"
+ project.status = "Completed"
project.save()
separation.reload()
- self.assertEqual(separation.boarding_status, 'Completed')
+ self.assertEqual(separation.boarding_status, "Completed")
separation.cancel()
- self.assertEqual(separation.project, '')
+ self.assertEqual(separation.project, "")
def tearDown(self):
- for entry in frappe.get_all('Employee Separation'):
- doc = frappe.get_doc('Employee Separation', entry.name)
+ for entry in frappe.get_all("Employee Separation"):
+ doc = frappe.get_doc("Employee Separation", entry.name)
if doc.docstatus == 1:
doc.cancel()
doc.delete()
+
def create_employee_separation():
- employee = frappe.db.get_value('Employee', {'status': 'Active', 'company': '_Test Company'})
- separation = frappe.new_doc('Employee Separation')
+ employee = frappe.db.get_value("Employee", {"status": "Active", "company": "_Test Company"})
+ separation = frappe.new_doc("Employee Separation")
separation.employee = employee
separation.boarding_begins_on = getdate()
- separation.company = '_Test Company'
- separation.append('activities', {
- 'activity_name': 'Deactivate Employee',
- 'role': 'HR User'
- })
- separation.boarding_status = 'Pending'
+ separation.company = "_Test Company"
+ separation.append("activities", {"activity_name": "Deactivate Employee", "role": "HR User"})
+ separation.boarding_status = "Pending"
separation.insert()
separation.submit()
return separation
diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
index 6e2a83eaa7..3ffd8dd6e2 100644
--- a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
+++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'employee_separation_template',
- 'transactions': [
- {
- 'items': ['Employee Separation']
- },
- ],
- }
+ return {
+ "fieldname": "employee_separation_template",
+ "transactions": [
+ {"items": ["Employee Separation"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
index f927d413ae..6dbefe59da 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
@@ -13,8 +13,10 @@ from erpnext.hr.utils import update_employee_work_history
class EmployeeTransfer(Document):
def before_submit(self):
if getdate(self.transfer_date) > getdate():
- frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
- frappe.DocstatusTransitionError)
+ frappe.throw(
+ _("Employee Transfer cannot be submitted before Transfer Date"),
+ frappe.DocstatusTransitionError,
+ )
def on_submit(self):
employee = frappe.get_doc("Employee", self.employee)
@@ -22,22 +24,26 @@ class EmployeeTransfer(Document):
new_employee = frappe.copy_doc(employee)
new_employee.name = None
new_employee.employee_number = None
- new_employee = update_employee_work_history(new_employee, self.transfer_details, date=self.transfer_date)
+ new_employee = update_employee_work_history(
+ new_employee, self.transfer_details, date=self.transfer_date
+ )
if self.new_company and self.company != self.new_company:
new_employee.internal_work_history = []
new_employee.date_of_joining = self.transfer_date
new_employee.company = self.new_company
- #move user_id to new employee before insert
+ # move user_id to new employee before insert
if employee.user_id and not self.validate_user_in_details():
new_employee.user_id = employee.user_id
employee.db_set("user_id", "")
new_employee.insert()
self.db_set("new_employee_id", new_employee.name)
- #relieve the old employee
+ # relieve the old employee
employee.db_set("relieving_date", self.transfer_date)
employee.db_set("status", "Left")
else:
- employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date)
+ employee = update_employee_work_history(
+ employee, self.transfer_details, date=self.transfer_date
+ )
if self.new_company and self.company != self.new_company:
employee.company = self.new_company
employee.date_of_joining = self.transfer_date
@@ -47,14 +53,18 @@ class EmployeeTransfer(Document):
employee = frappe.get_doc("Employee", self.employee)
if self.create_new_employee_id:
if self.new_employee_id:
- frappe.throw(_("Please delete the Employee {0} to cancel this document").format(
- "{0}".format(self.new_employee_id)
- ))
- #mark the employee as active
+ frappe.throw(
+ _("Please delete the Employee {0} to cancel this document").format(
+ "{0}".format(self.new_employee_id)
+ )
+ )
+ # mark the employee as active
employee.status = "Active"
- employee.relieving_date = ''
+ employee.relieving_date = ""
else:
- employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date, cancel=True)
+ employee = update_employee_work_history(
+ employee, self.transfer_details, date=self.transfer_date, cancel=True
+ )
if self.new_company != self.company:
employee.company = self.company
employee.save()
diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
index 64eee402fe..37a190a162 100644
--- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
@@ -19,18 +19,20 @@ class TestEmployeeTransfer(unittest.TestCase):
def test_submit_before_transfer_date(self):
make_employee("employee2@transfers.com")
- transfer_obj = frappe.get_doc({
- "doctype": "Employee Transfer",
- "employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"),
- "transfer_details" :[
- {
- "property": "Designation",
- "current": "Software Developer",
- "new": "Project Manager",
- "fieldname": "designation"
- }
- ]
- })
+ transfer_obj = frappe.get_doc(
+ {
+ "doctype": "Employee Transfer",
+ "employee": frappe.get_value("Employee", {"user_id": "employee2@transfers.com"}, "name"),
+ "transfer_details": [
+ {
+ "property": "Designation",
+ "current": "Software Developer",
+ "new": "Project Manager",
+ "fieldname": "designation",
+ }
+ ],
+ }
+ )
transfer_obj.transfer_date = add_days(getdate(), 1)
transfer_obj.save()
self.assertRaises(frappe.DocstatusTransitionError, transfer_obj.submit)
@@ -42,32 +44,35 @@ class TestEmployeeTransfer(unittest.TestCase):
def test_new_employee_creation(self):
make_employee("employee3@transfers.com")
- transfer = frappe.get_doc({
- "doctype": "Employee Transfer",
- "employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"),
- "create_new_employee_id": 1,
- "transfer_date": getdate(),
- "transfer_details" :[
- {
- "property": "Designation",
- "current": "Software Developer",
- "new": "Project Manager",
- "fieldname": "designation"
- }
- ]
- }).insert()
+ transfer = frappe.get_doc(
+ {
+ "doctype": "Employee Transfer",
+ "employee": frappe.get_value("Employee", {"user_id": "employee3@transfers.com"}, "name"),
+ "create_new_employee_id": 1,
+ "transfer_date": getdate(),
+ "transfer_details": [
+ {
+ "property": "Designation",
+ "current": "Software Developer",
+ "new": "Project Manager",
+ "fieldname": "designation",
+ }
+ ],
+ }
+ ).insert()
transfer.submit()
self.assertTrue(transfer.new_employee_id)
self.assertEqual(frappe.get_value("Employee", transfer.new_employee_id, "status"), "Active")
self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left")
def test_employee_history(self):
- employee = make_employee("employee4@transfers.com",
+ employee = make_employee(
+ "employee4@transfers.com",
company="Test Company",
date_of_birth=getdate("30-09-1980"),
date_of_joining=getdate("01-10-2021"),
department="Accounts - TC",
- designation="Accountant"
+ designation="Accountant",
)
transfer = create_employee_transfer(employee)
@@ -94,36 +99,40 @@ class TestEmployeeTransfer(unittest.TestCase):
def create_company():
if not frappe.db.exists("Company", "Test Company"):
- frappe.get_doc({
- "doctype": "Company",
- "company_name": "Test Company",
- "default_currency": "INR",
- "country": "India"
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": "Test Company",
+ "default_currency": "INR",
+ "country": "India",
+ }
+ ).insert()
def create_employee_transfer(employee):
- doc = frappe.get_doc({
- "doctype": "Employee Transfer",
- "employee": employee,
- "transfer_date": getdate(),
- "transfer_details": [
- {
- "property": "Designation",
- "current": "Accountant",
- "new": "Manager",
- "fieldname": "designation"
- },
- {
- "property": "Department",
- "current": "Accounts - TC",
- "new": "Management - TC",
- "fieldname": "department"
- }
- ]
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Employee Transfer",
+ "employee": employee,
+ "transfer_date": getdate(),
+ "transfer_details": [
+ {
+ "property": "Designation",
+ "current": "Accountant",
+ "new": "Manager",
+ "fieldname": "designation",
+ },
+ {
+ "property": "Department",
+ "current": "Accounts - TC",
+ "new": "Management - TC",
+ "fieldname": "department",
+ },
+ ],
+ }
+ )
doc.save()
doc.submit()
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/hr/doctype/employment_type/test_employment_type.py b/erpnext/hr/doctype/employment_type/test_employment_type.py
index c43f9636c7..fdf6965e48 100644
--- a/erpnext/hr/doctype/employment_type/test_employment_type.py
+++ b/erpnext/hr/doctype/employment_type/test_employment_type.py
@@ -3,4 +3,4 @@
import frappe
-test_records = frappe.get_test_records('Employment Type')
+test_records = frappe.get_test_records("Employment Type")
diff --git a/erpnext/hr/doctype/exit_interview/exit_interview.py b/erpnext/hr/doctype/exit_interview/exit_interview.py
index 59fb2fd9ca..8317310202 100644
--- a/erpnext/hr/doctype/exit_interview/exit_interview.py
+++ b/erpnext/hr/doctype/exit_interview/exit_interview.py
@@ -16,41 +16,45 @@ class ExitInterview(Document):
self.set_employee_email()
def validate_relieving_date(self):
- if not frappe.db.get_value('Employee', self.employee, 'relieving_date'):
- frappe.throw(_('Please set the relieving date for employee {0}').format(
- get_link_to_form('Employee', self.employee)),
- title=_('Relieving Date Missing'))
+ if not frappe.db.get_value("Employee", self.employee, "relieving_date"):
+ frappe.throw(
+ _("Please set the relieving date for employee {0}").format(
+ get_link_to_form("Employee", self.employee)
+ ),
+ title=_("Relieving Date Missing"),
+ )
def validate_duplicate_interview(self):
- doc = frappe.db.exists('Exit Interview', {
- 'employee': self.employee,
- 'name': ('!=', self.name),
- 'docstatus': ('!=', 2)
- })
+ doc = frappe.db.exists(
+ "Exit Interview", {"employee": self.employee, "name": ("!=", self.name), "docstatus": ("!=", 2)}
+ )
if doc:
- frappe.throw(_('Exit Interview {0} already exists for Employee: {1}').format(
- get_link_to_form('Exit Interview', doc), frappe.bold(self.employee)),
- frappe.DuplicateEntryError)
+ frappe.throw(
+ _("Exit Interview {0} already exists for Employee: {1}").format(
+ get_link_to_form("Exit Interview", doc), frappe.bold(self.employee)
+ ),
+ frappe.DuplicateEntryError,
+ )
def set_employee_email(self):
- employee = frappe.get_doc('Employee', self.employee)
+ employee = frappe.get_doc("Employee", self.employee)
self.email = get_employee_email(employee)
def on_submit(self):
- if self.status != 'Completed':
- frappe.throw(_('Only Completed documents can be submitted'))
+ if self.status != "Completed":
+ frappe.throw(_("Only Completed documents can be submitted"))
self.update_interview_date_in_employee()
def on_cancel(self):
self.update_interview_date_in_employee()
- self.db_set('status', 'Cancelled')
+ self.db_set("status", "Cancelled")
def update_interview_date_in_employee(self):
if self.docstatus == 1:
- frappe.db.set_value('Employee', self.employee, 'held_on', self.date)
+ frappe.db.set_value("Employee", self.employee, "held_on", self.date)
elif self.docstatus == 2:
- frappe.db.set_value('Employee', self.employee, 'held_on', None)
+ frappe.db.set_value("Employee", self.employee, "held_on", None)
@frappe.whitelist()
@@ -62,17 +66,19 @@ def send_exit_questionnaire(interviews):
email_failure = []
for exit_interview in interviews:
- interview = frappe.get_doc('Exit Interview', exit_interview.get('name'))
- if interview.get('questionnaire_email_sent'):
+ interview = frappe.get_doc("Exit Interview", exit_interview.get("name"))
+ if interview.get("questionnaire_email_sent"):
continue
- employee = frappe.get_doc('Employee', interview.employee)
+ employee = frappe.get_doc("Employee", interview.employee)
email = get_employee_email(employee)
context = interview.as_dict()
context.update(employee.as_dict())
- template_name = frappe.db.get_single_value('HR Settings', 'exit_questionnaire_notification_template')
- template = frappe.get_doc('Email Template', template_name)
+ template_name = frappe.db.get_single_value(
+ "HR Settings", "exit_questionnaire_notification_template"
+ )
+ template = frappe.get_doc("Email Template", template_name)
if email:
frappe.sendmail(
@@ -80,13 +86,13 @@ def send_exit_questionnaire(interviews):
subject=template.subject,
message=frappe.render_template(template.response, context),
reference_doctype=interview.doctype,
- reference_name=interview.name
+ reference_name=interview.name,
)
- interview.db_set('questionnaire_email_sent', True)
+ interview.db_set("questionnaire_email_sent", True)
interview.notify_update()
email_success.append(email)
else:
- email_failure.append(get_link_to_form('Employee', employee.name))
+ email_failure.append(get_link_to_form("Employee", employee.name))
show_email_summary(email_success, email_failure)
@@ -98,34 +104,43 @@ def get_interviews(interviews):
interviews = json.loads(interviews)
if not len(interviews):
- frappe.throw(_('Atleast one interview has to be selected.'))
+ frappe.throw(_("Atleast one interview has to be selected."))
return interviews
def validate_questionnaire_settings():
- settings = frappe.db.get_value('HR Settings', 'HR Settings',
- ['exit_questionnaire_web_form', 'exit_questionnaire_notification_template'], as_dict=True)
+ settings = frappe.db.get_value(
+ "HR Settings",
+ "HR Settings",
+ ["exit_questionnaire_web_form", "exit_questionnaire_notification_template"],
+ as_dict=True,
+ )
- if not settings.exit_questionnaire_web_form or not settings.exit_questionnaire_notification_template:
+ if (
+ not settings.exit_questionnaire_web_form or not settings.exit_questionnaire_notification_template
+ ):
frappe.throw(
- _('Please set {0} and {1} in {2}.').format(
- frappe.bold('Exit Questionnaire Web Form'),
- frappe.bold('Notification Template'),
- get_link_to_form('HR Settings', 'HR Settings')),
- title=_('Settings Missing')
+ _("Please set {0} and {1} in {2}.").format(
+ frappe.bold("Exit Questionnaire Web Form"),
+ frappe.bold("Notification Template"),
+ get_link_to_form("HR Settings", "HR Settings"),
+ ),
+ title=_("Settings Missing"),
)
def show_email_summary(email_success, email_failure):
- message = ''
+ message = ""
if email_success:
- message += _('{0}: {1}').format(
- frappe.bold('Sent Successfully'), ', '.join(email_success))
+ message += _("{0}: {1}").format(frappe.bold("Sent Successfully"), ", ".join(email_success))
if message and email_failure:
- message += '
'
+ message += "
"
if email_failure:
- message += _('{0} due to missing email information for employee(s): {1}').format(
- frappe.bold('Sending Failed'), ', '.join(email_failure))
+ message += _("{0} due to missing email information for employee(s): {1}").format(
+ frappe.bold("Sending Failed"), ", ".join(email_failure)
+ )
- frappe.msgprint(message, title=_('Exit Questionnaire'), indicator='blue', is_minimizable=True, wide=True)
+ frappe.msgprint(
+ message, title=_("Exit Questionnaire"), indicator="blue", is_minimizable=True, wide=True
+ )
diff --git a/erpnext/hr/doctype/exit_interview/test_exit_interview.py b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
index 8e076edf0b..9c2c6442f8 100644
--- a/erpnext/hr/doctype/exit_interview/test_exit_interview.py
+++ b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
@@ -16,84 +16,88 @@ from erpnext.hr.doctype.exit_interview.exit_interview import send_exit_questionn
class TestExitInterview(unittest.TestCase):
def setUp(self):
- frappe.db.sql('delete from `tabExit Interview`')
+ frappe.db.sql("delete from `tabExit Interview`")
def test_duplicate_interview(self):
- employee = make_employee('employeeexitint1@example.com')
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ employee = make_employee("employeeexitint1@example.com")
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
doc = frappe.copy_doc(interview)
self.assertRaises(frappe.DuplicateEntryError, doc.save)
def test_relieving_date_validation(self):
- employee = make_employee('employeeexitint2@example.com')
+ employee = make_employee("employeeexitint2@example.com")
# unset relieving date
- frappe.db.set_value('Employee', employee, 'relieving_date', None)
+ frappe.db.set_value("Employee", employee, "relieving_date", None)
interview = create_exit_interview(employee, save=False)
self.assertRaises(frappe.ValidationError, interview.save)
# set relieving date
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
self.assertTrue(interview.name)
def test_interview_date_updated_in_employee_master(self):
- employee = make_employee('employeeexit3@example.com')
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ employee = make_employee("employeeexit3@example.com")
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
- interview.status = 'Completed'
- interview.employee_status = 'Exit Confirmed'
+ interview.status = "Completed"
+ interview.employee_status = "Exit Confirmed"
# exit interview date updated on submit
interview.submit()
- self.assertEqual(frappe.db.get_value('Employee', employee, 'held_on'), interview.date)
+ self.assertEqual(frappe.db.get_value("Employee", employee, "held_on"), interview.date)
# exit interview reset on cancel
interview.reload()
interview.cancel()
- self.assertEqual(frappe.db.get_value('Employee', employee, 'held_on'), None)
+ self.assertEqual(frappe.db.get_value("Employee", employee, "held_on"), None)
def test_send_exit_questionnaire(self):
create_custom_doctype()
create_webform()
template = create_notification_template()
- webform = frappe.db.get_all('Web Form', limit=1)
- frappe.db.set_value('HR Settings', 'HR Settings', {
- 'exit_questionnaire_web_form': webform[0].name,
- 'exit_questionnaire_notification_template': template
- })
+ webform = frappe.db.get_all("Web Form", limit=1)
+ frappe.db.set_value(
+ "HR Settings",
+ "HR Settings",
+ {
+ "exit_questionnaire_web_form": webform[0].name,
+ "exit_questionnaire_notification_template": template,
+ },
+ )
- employee = make_employee('employeeexit3@example.com')
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ employee = make_employee("employeeexit3@example.com")
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
send_exit_questionnaire([interview])
- email_queue = frappe.db.get_all('Email Queue', ['name', 'message'], limit=1)
- self.assertTrue('Subject: Exit Questionnaire Notification' in email_queue[0].message)
+ email_queue = frappe.db.get_all("Email Queue", ["name", "message"], limit=1)
+ self.assertTrue("Subject: Exit Questionnaire Notification" in email_queue[0].message)
def tearDown(self):
frappe.db.rollback()
def create_exit_interview(employee, save=True):
- interviewer = create_user('test_exit_interviewer@example.com')
+ interviewer = create_user("test_exit_interviewer@example.com")
- doc = frappe.get_doc({
- 'doctype': 'Exit Interview',
- 'employee': employee,
- 'company': '_Test Company',
- 'status': 'Pending',
- 'date': getdate(),
- 'interviewers': [{
- 'interviewer': interviewer.name
- }],
- 'interview_summary': 'Test'
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Exit Interview",
+ "employee": employee,
+ "company": "_Test Company",
+ "status": "Pending",
+ "date": getdate(),
+ "interviewers": [{"interviewer": interviewer.name}],
+ "interview_summary": "Test",
+ }
+ )
if save:
return doc.insert()
@@ -101,18 +105,22 @@ def create_exit_interview(employee, save=True):
def create_notification_template():
- template = frappe.db.exists('Email Template', _('Exit Questionnaire Notification'))
+ template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
if not template:
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'exit_interview/exit_questionnaire_notification_template.html'))
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "exit_interview/exit_questionnaire_notification_template.html")
+ )
- template = frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Exit Questionnaire Notification'),
- 'response': response,
- 'subject': _('Exit Questionnaire Notification'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ template = frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Exit Questionnaire Notification"),
+ "response": response,
+ "subject": _("Exit Questionnaire Notification"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
template = template.name
- return template
\ No newline at end of file
+ return template
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 12a3112396..89d86c1bc7 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -13,13 +13,19 @@ from erpnext.controllers.accounts_controller import AccountsController
from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee
-class InvalidExpenseApproverError(frappe.ValidationError): pass
-class ExpenseApproverIdentityError(frappe.ValidationError): pass
+class InvalidExpenseApproverError(frappe.ValidationError):
+ pass
+
+
+class ExpenseApproverIdentityError(frappe.ValidationError):
+ pass
+
class ExpenseClaim(AccountsController):
def onload(self):
- self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings',
- 'make_payment_via_journal_entry')
+ self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value(
+ "Accounts Settings", "make_payment_via_journal_entry"
+ )
def validate(self):
validate_active_employee(self.employee)
@@ -36,29 +42,35 @@ class ExpenseClaim(AccountsController):
self.project = frappe.db.get_value("Task", self.task, "project")
def set_status(self, update=False):
- status = {
- "0": "Draft",
- "1": "Submitted",
- "2": "Cancelled"
- }[cstr(self.docstatus or 0)]
+ status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[cstr(self.docstatus or 0)]
precision = self.precision("grand_total")
if (
# set as paid
self.is_paid
- or (flt(self.total_sanctioned_amount > 0) and (
- # grand total is reimbursed
- (self.docstatus == 1 and flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision))
- # grand total (to be paid) is 0 since linked advances already cover the claimed amount
- or (flt(self.grand_total, precision) == 0)
- ))
+ or (
+ flt(self.total_sanctioned_amount > 0)
+ and (
+ # grand total is reimbursed
+ (
+ self.docstatus == 1
+ and flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision)
+ )
+ # grand total (to be paid) is 0 since linked advances already cover the claimed amount
+ or (flt(self.grand_total, precision) == 0)
+ )
+ )
) and self.approval_status == "Approved":
status = "Paid"
- elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
+ elif (
+ flt(self.total_sanctioned_amount) > 0
+ and self.docstatus == 1
+ and self.approval_status == "Approved"
+ ):
status = "Unpaid"
- elif self.docstatus == 1 and self.approval_status == 'Rejected':
- status = 'Rejected'
+ elif self.docstatus == 1 and self.approval_status == "Rejected":
+ status = "Rejected"
if update:
self.db_set("status", status)
@@ -70,14 +82,16 @@ class ExpenseClaim(AccountsController):
def set_payable_account(self):
if not self.payable_account and not self.is_paid:
- self.payable_account = frappe.get_cached_value('Company', self.company, 'default_expense_claim_payable_account')
+ self.payable_account = frappe.get_cached_value(
+ "Company", self.company, "default_expense_claim_payable_account"
+ )
def set_cost_center(self):
if not self.cost_center:
- self.cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
+ self.cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
def on_submit(self):
- if self.approval_status=="Draft":
+ if self.approval_status == "Draft":
frappe.throw(_("""Approval Status must be 'Approved' or 'Rejected'"""))
self.update_task_and_project()
@@ -91,7 +105,7 @@ class ExpenseClaim(AccountsController):
def on_cancel(self):
self.update_task_and_project()
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
if self.payable_account:
self.make_gl_entries(cancel=True)
@@ -122,43 +136,51 @@ class ExpenseClaim(AccountsController):
# payable entry
if self.grand_total:
gl_entry.append(
- self.get_gl_dict({
- "account": self.payable_account,
- "credit": self.grand_total,
- "credit_in_account_currency": self.grand_total,
- "against": ",".join([d.default_account for d in self.expenses]),
- "party_type": "Employee",
- "party": self.employee,
- "against_voucher_type": self.doctype,
- "against_voucher": self.name,
- "cost_center": self.cost_center
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.payable_account,
+ "credit": self.grand_total,
+ "credit_in_account_currency": self.grand_total,
+ "against": ",".join([d.default_account for d in self.expenses]),
+ "party_type": "Employee",
+ "party": self.employee,
+ "against_voucher_type": self.doctype,
+ "against_voucher": self.name,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
)
# expense entries
for data in self.expenses:
gl_entry.append(
- self.get_gl_dict({
- "account": data.default_account,
- "debit": data.sanctioned_amount,
- "debit_in_account_currency": data.sanctioned_amount,
- "against": self.employee,
- "cost_center": data.cost_center or self.cost_center
- }, item=data)
+ self.get_gl_dict(
+ {
+ "account": data.default_account,
+ "debit": data.sanctioned_amount,
+ "debit_in_account_currency": data.sanctioned_amount,
+ "against": self.employee,
+ "cost_center": data.cost_center or self.cost_center,
+ },
+ item=data,
+ )
)
for data in self.advances:
gl_entry.append(
- self.get_gl_dict({
- "account": data.advance_account,
- "credit": data.allocated_amount,
- "credit_in_account_currency": data.allocated_amount,
- "against": ",".join([d.default_account for d in self.expenses]),
- "party_type": "Employee",
- "party": self.employee,
- "against_voucher_type": "Employee Advance",
- "against_voucher": data.employee_advance
- })
+ self.get_gl_dict(
+ {
+ "account": data.advance_account,
+ "credit": data.allocated_amount,
+ "credit_in_account_currency": data.allocated_amount,
+ "against": ",".join([d.default_account for d in self.expenses]),
+ "party_type": "Employee",
+ "party": self.employee,
+ "against_voucher_type": "Employee Advance",
+ "against_voucher": data.employee_advance,
+ }
+ )
)
self.add_tax_gl_entries(gl_entry)
@@ -167,25 +189,31 @@ class ExpenseClaim(AccountsController):
# payment entry
payment_account = get_bank_cash_account(self.mode_of_payment, self.company).get("account")
gl_entry.append(
- self.get_gl_dict({
- "account": payment_account,
- "credit": self.grand_total,
- "credit_in_account_currency": self.grand_total,
- "against": self.employee
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": payment_account,
+ "credit": self.grand_total,
+ "credit_in_account_currency": self.grand_total,
+ "against": self.employee,
+ },
+ item=self,
+ )
)
gl_entry.append(
- self.get_gl_dict({
- "account": self.payable_account,
- "party_type": "Employee",
- "party": self.employee,
- "against": payment_account,
- "debit": self.grand_total,
- "debit_in_account_currency": self.grand_total,
- "against_voucher": self.name,
- "against_voucher_type": self.doctype,
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.payable_account,
+ "party_type": "Employee",
+ "party": self.employee,
+ "against": payment_account,
+ "debit": self.grand_total,
+ "debit_in_account_currency": self.grand_total,
+ "against_voucher": self.name,
+ "against_voucher_type": self.doctype,
+ },
+ item=self,
+ )
)
return gl_entry
@@ -194,22 +222,28 @@ class ExpenseClaim(AccountsController):
# tax table gl entries
for tax in self.get("taxes"):
gl_entries.append(
- self.get_gl_dict({
- "account": tax.account_head,
- "debit": tax.tax_amount,
- "debit_in_account_currency": tax.tax_amount,
- "against": self.employee,
- "cost_center": self.cost_center,
- "against_voucher_type": self.doctype,
- "against_voucher": self.name
- }, item=tax)
+ self.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "debit": tax.tax_amount,
+ "debit_in_account_currency": tax.tax_amount,
+ "against": self.employee,
+ "cost_center": self.cost_center,
+ "against_voucher_type": self.doctype,
+ "against_voucher": self.name,
+ },
+ item=tax,
+ )
)
def validate_account_details(self):
for data in self.expenses:
if not data.cost_center:
- frappe.throw(_("Row {0}: {1} is required in the expenses table to book an expense claim.")
- .format(data.idx, frappe.bold("Cost Center")))
+ frappe.throw(
+ _("Row {0}: {1} is required in the expenses table to book an expense claim.").format(
+ data.idx, frappe.bold("Cost Center")
+ )
+ )
if self.is_paid:
if not self.mode_of_payment:
@@ -218,8 +252,8 @@ class ExpenseClaim(AccountsController):
def calculate_total_amount(self):
self.total_claimed_amount = 0
self.total_sanctioned_amount = 0
- for d in self.get('expenses'):
- if self.approval_status == 'Rejected':
+ for d in self.get("expenses"):
+ if self.approval_status == "Rejected":
d.sanctioned_amount = 0.0
self.total_claimed_amount += flt(d.amount)
@@ -230,12 +264,16 @@ class ExpenseClaim(AccountsController):
self.total_taxes_and_charges = 0
for tax in self.taxes:
if tax.rate:
- tax.tax_amount = flt(self.total_sanctioned_amount) * flt(tax.rate/100)
+ tax.tax_amount = flt(self.total_sanctioned_amount) * flt(tax.rate / 100)
tax.total = flt(tax.tax_amount) + flt(self.total_sanctioned_amount)
self.total_taxes_and_charges += flt(tax.tax_amount)
- self.grand_total = flt(self.total_sanctioned_amount) + flt(self.total_taxes_and_charges) - flt(self.total_advance_amount)
+ self.grand_total = (
+ flt(self.total_sanctioned_amount)
+ + flt(self.total_taxes_and_charges)
+ - flt(self.total_advance_amount)
+ )
def update_task(self):
task = frappe.get_doc("Task", self.task)
@@ -245,16 +283,23 @@ class ExpenseClaim(AccountsController):
def validate_advances(self):
self.total_advance_amount = 0
for d in self.get("advances"):
- ref_doc = frappe.db.get_value("Employee Advance", d.employee_advance,
- ["posting_date", "paid_amount", "claimed_amount", "advance_account"], as_dict=1)
+ ref_doc = frappe.db.get_value(
+ "Employee Advance",
+ d.employee_advance,
+ ["posting_date", "paid_amount", "claimed_amount", "advance_account"],
+ as_dict=1,
+ )
d.posting_date = ref_doc.posting_date
d.advance_account = ref_doc.advance_account
d.advance_paid = ref_doc.paid_amount
d.unclaimed_amount = flt(ref_doc.paid_amount) - flt(ref_doc.claimed_amount)
if d.allocated_amount and flt(d.allocated_amount) > flt(d.unclaimed_amount):
- frappe.throw(_("Row {0}# Allocated amount {1} cannot be greater than unclaimed amount {2}")
- .format(d.idx, d.allocated_amount, d.unclaimed_amount))
+ frappe.throw(
+ _("Row {0}# Allocated amount {1} cannot be greater than unclaimed amount {2}").format(
+ d.idx, d.allocated_amount, d.unclaimed_amount
+ )
+ )
self.total_advance_amount += flt(d.allocated_amount)
@@ -263,27 +308,36 @@ class ExpenseClaim(AccountsController):
if flt(self.total_advance_amount, precision) > flt(self.total_claimed_amount, precision):
frappe.throw(_("Total advance amount cannot be greater than total claimed amount"))
- if self.total_sanctioned_amount \
- and flt(self.total_advance_amount, precision) > flt(self.total_sanctioned_amount, precision):
+ if self.total_sanctioned_amount and flt(self.total_advance_amount, precision) > flt(
+ self.total_sanctioned_amount, precision
+ ):
frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount"))
def validate_sanctioned_amount(self):
- for d in self.get('expenses'):
+ for d in self.get("expenses"):
if flt(d.sanctioned_amount) > flt(d.amount):
- frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx))
+ frappe.throw(
+ _("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx)
+ )
def set_expense_account(self, validate=False):
for expense in self.expenses:
if not expense.default_account or not validate:
- expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
+ expense.default_account = get_expense_claim_account(expense.expense_type, self.company)[
+ "account"
+ ]
+
def update_reimbursed_amount(doc, amount):
doc.total_amount_reimbursed += amount
- frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", doc.total_amount_reimbursed)
+ frappe.db.set_value(
+ "Expense Claim", doc.name, "total_amount_reimbursed", doc.total_amount_reimbursed
+ )
doc.set_status()
- frappe.db.set_value("Expense Claim", doc.name , "status", doc.status)
+ frappe.db.set_value("Expense Claim", doc.name, "status", doc.status)
+
@frappe.whitelist()
def make_bank_entry(dt, dn):
@@ -294,69 +348,80 @@ def make_bank_entry(dt, dn):
if not default_bank_cash_account:
default_bank_cash_account = get_default_bank_cash_account(expense_claim.company, "Cash")
- payable_amount = flt(expense_claim.total_sanctioned_amount) \
- - flt(expense_claim.total_amount_reimbursed) - flt(expense_claim.total_advance_amount)
+ payable_amount = (
+ flt(expense_claim.total_sanctioned_amount)
+ - flt(expense_claim.total_amount_reimbursed)
+ - flt(expense_claim.total_advance_amount)
+ )
je = frappe.new_doc("Journal Entry")
- je.voucher_type = 'Bank Entry'
+ je.voucher_type = "Bank Entry"
je.company = expense_claim.company
- je.remark = 'Payment against Expense Claim: ' + dn
+ je.remark = "Payment against Expense Claim: " + dn
- je.append("accounts", {
- "account": expense_claim.payable_account,
- "debit_in_account_currency": payable_amount,
- "reference_type": "Expense Claim",
- "party_type": "Employee",
- "party": expense_claim.employee,
- "cost_center": erpnext.get_default_cost_center(expense_claim.company),
- "reference_name": expense_claim.name
- })
+ je.append(
+ "accounts",
+ {
+ "account": expense_claim.payable_account,
+ "debit_in_account_currency": payable_amount,
+ "reference_type": "Expense Claim",
+ "party_type": "Employee",
+ "party": expense_claim.employee,
+ "cost_center": erpnext.get_default_cost_center(expense_claim.company),
+ "reference_name": expense_claim.name,
+ },
+ )
- je.append("accounts", {
- "account": default_bank_cash_account.account,
- "credit_in_account_currency": payable_amount,
- "reference_type": "Expense Claim",
- "reference_name": expense_claim.name,
- "balance": default_bank_cash_account.balance,
- "account_currency": default_bank_cash_account.account_currency,
- "cost_center": erpnext.get_default_cost_center(expense_claim.company),
- "account_type": default_bank_cash_account.account_type
- })
+ je.append(
+ "accounts",
+ {
+ "account": default_bank_cash_account.account,
+ "credit_in_account_currency": payable_amount,
+ "reference_type": "Expense Claim",
+ "reference_name": expense_claim.name,
+ "balance": default_bank_cash_account.balance,
+ "account_currency": default_bank_cash_account.account_currency,
+ "cost_center": erpnext.get_default_cost_center(expense_claim.company),
+ "account_type": default_bank_cash_account.account_type,
+ },
+ )
return je.as_dict()
+
@frappe.whitelist()
def get_expense_claim_account_and_cost_center(expense_claim_type, company):
data = get_expense_claim_account(expense_claim_type, company)
cost_center = erpnext.get_default_cost_center(company)
- return {
- "account": data.get("account"),
- "cost_center": cost_center
- }
+ return {"account": data.get("account"), "cost_center": cost_center}
+
@frappe.whitelist()
def get_expense_claim_account(expense_claim_type, company):
- account = frappe.db.get_value("Expense Claim Account",
- {"parent": expense_claim_type, "company": company}, "default_account")
+ account = frappe.db.get_value(
+ "Expense Claim Account", {"parent": expense_claim_type, "company": company}, "default_account"
+ )
if not account:
- frappe.throw(_("Set the default account for the {0} {1}")
- .format(frappe.bold("Expense Claim Type"), get_link_to_form("Expense Claim Type", expense_claim_type)))
+ frappe.throw(
+ _("Set the default account for the {0} {1}").format(
+ frappe.bold("Expense Claim Type"), get_link_to_form("Expense Claim Type", expense_claim_type)
+ )
+ )
+
+ return {"account": account}
- return {
- "account": account
- }
@frappe.whitelist()
def get_advances(employee, advance_id=None):
advance = frappe.qb.DocType("Employee Advance")
- query = (
- frappe.qb.from_(advance)
- .select(
- advance.name, advance.posting_date, advance.paid_amount,
- advance.claimed_amount, advance.advance_account
- )
+ query = frappe.qb.from_(advance).select(
+ advance.name,
+ advance.posting_date,
+ advance.paid_amount,
+ advance.claimed_amount,
+ advance.advance_account,
)
if not advance_id:
@@ -374,25 +439,26 @@ def get_advances(employee, advance_id=None):
@frappe.whitelist()
def get_expense_claim(
- employee_name, company, employee_advance_name, posting_date, paid_amount, claimed_amount):
- default_payable_account = frappe.get_cached_value('Company', company, "default_payable_account")
- default_cost_center = frappe.get_cached_value('Company', company, 'cost_center')
+ employee_name, company, employee_advance_name, posting_date, paid_amount, claimed_amount
+):
+ default_payable_account = frappe.get_cached_value("Company", company, "default_payable_account")
+ default_cost_center = frappe.get_cached_value("Company", company, "cost_center")
- expense_claim = frappe.new_doc('Expense Claim')
+ expense_claim = frappe.new_doc("Expense Claim")
expense_claim.company = company
expense_claim.employee = employee_name
expense_claim.payable_account = default_payable_account
expense_claim.cost_center = default_cost_center
expense_claim.is_paid = 1 if flt(paid_amount) else 0
expense_claim.append(
- 'advances',
+ "advances",
{
- 'employee_advance': employee_advance_name,
- 'posting_date': posting_date,
- 'advance_paid': flt(paid_amount),
- 'unclaimed_amount': flt(paid_amount) - flt(claimed_amount),
- 'allocated_amount': flt(paid_amount) - flt(claimed_amount)
- }
+ "employee_advance": employee_advance_name,
+ "posting_date": posting_date,
+ "advance_paid": flt(paid_amount),
+ "unclaimed_amount": flt(paid_amount) - flt(claimed_amount),
+ "allocated_amount": flt(paid_amount) - flt(claimed_amount),
+ },
)
return expense_claim
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
index 7539c7165d..8b1acc619e 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
@@ -3,18 +3,10 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'reference_name',
- 'internal_links': {
- 'Employee Advance': ['advances', 'employee_advance']
- },
- 'transactions': [
- {
- 'label': _('Payment'),
- 'items': ['Payment Entry', 'Journal Entry']
- },
- {
- 'label': _('Reference'),
- 'items': ['Employee Advance']
- },
- ]
+ "fieldname": "reference_name",
+ "internal_links": {"Employee Advance": ["advances", "employee_advance"]},
+ "transactions": [
+ {"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]},
+ {"label": _("Reference"), "items": ["Employee Advance"]},
+ ],
}
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 1244cc4364..9b3d53a210 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -10,8 +10,8 @@ from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
-test_dependencies = ['Employee']
-company_name = '_Test Company 3'
+test_dependencies = ["Employee"]
+company_name = "_Test Company 3"
class TestExpenseClaim(unittest.TestCase):
@@ -23,28 +23,26 @@ class TestExpenseClaim(unittest.TestCase):
frappe.db.sql("""delete from `tabProject`""")
frappe.db.sql("update `tabExpense Claim` set project = '', task = ''")
- project = frappe.get_doc({
- "project_name": "_Test Project 1",
- "doctype": "Project"
- })
+ project = frappe.get_doc({"project_name": "_Test Project 1", "doctype": "Project"})
project.save()
- task = frappe.get_doc(dict(
- doctype = 'Task',
- subject = '_Test Project Task 1',
- status = 'Open',
- project = project.name
- )).insert()
+ task = frappe.get_doc(
+ dict(doctype="Task", subject="_Test Project Task 1", status="Open", project=project.name)
+ ).insert()
task_name = task.name
payable_account = get_payable_account(company_name)
- make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC3", project.name, task_name)
+ make_expense_claim(
+ payable_account, 300, 200, company_name, "Travel Expenses - _TC3", project.name, task_name
+ )
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 200)
- expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC3", project.name, task_name)
+ expense_claim2 = make_expense_claim(
+ payable_account, 600, 500, company_name, "Travel Expenses - _TC3", project.name, task_name
+ )
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700)
self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 700)
@@ -56,7 +54,9 @@ class TestExpenseClaim(unittest.TestCase):
def test_expense_claim_status(self):
payable_account = get_payable_account(company_name)
- expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC3")
+ expense_claim = make_expense_claim(
+ payable_account, 300, 200, company_name, "Travel Expenses - _TC3"
+ )
je_dict = make_bank_entry("Expense Claim", expense_claim.name)
je = frappe.get_doc(je_dict)
@@ -78,7 +78,9 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEqual(claim.status, "Submitted")
# no gl entries created
- gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': claim.name})
+ gl_entry = frappe.get_all(
+ "GL Entry", {"voucher_type": "Expense Claim", "voucher_no": claim.name}
+ )
self.assertEqual(len(gl_entry), 0)
def test_expense_claim_against_fully_paid_advances(self):
@@ -91,7 +93,9 @@ class TestExpenseClaim(unittest.TestCase):
frappe.db.delete("Employee Advance")
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -117,10 +121,12 @@ class TestExpenseClaim(unittest.TestCase):
frappe.db.delete("Employee Advance")
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
# link advance for partial amount
- advance = make_employee_advance(claim.employee, {'advance_amount': 500})
+ advance = make_employee_advance(claim.employee, {"advance_amount": 500})
pe = make_advance_payment(advance)
pe.submit()
@@ -141,21 +147,35 @@ class TestExpenseClaim(unittest.TestCase):
def test_expense_claim_gl_entry(self):
payable_account = get_payable_account(company_name)
taxes = generate_taxes()
- expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC3",
- do_not_submit=True, taxes=taxes)
+ expense_claim = make_expense_claim(
+ payable_account,
+ 300,
+ 200,
+ company_name,
+ "Travel Expenses - _TC3",
+ do_not_submit=True,
+ taxes=taxes,
+ )
expense_claim.submit()
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Expense Claim' and voucher_no=%s
- order by account asc""", expense_claim.name, as_dict=1)
+ order by account asc""",
+ expense_claim.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- ['Output Tax CGST - _TC3',18.0, 0.0],
- [payable_account, 0.0, 218.0],
- ["Travel Expenses - _TC3", 200.0, 0.0]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ ["Output Tax CGST - _TC3", 18.0, 0.0],
+ [payable_account, 0.0, 218.0],
+ ["Travel Expenses - _TC3", 200.0, 0.0],
+ ]
+ )
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
@@ -164,20 +184,30 @@ class TestExpenseClaim(unittest.TestCase):
def test_rejected_expense_claim(self):
payable_account = get_payable_account(company_name)
- expense_claim = frappe.get_doc({
- "doctype": "Expense Claim",
- "employee": "_T-Employee-00001",
- "payable_account": payable_account,
- "approval_status": "Rejected",
- "expenses":
- [{"expense_type": "Travel", "default_account": "Travel Expenses - _TC3", "amount": 300, "sanctioned_amount": 200}]
- })
+ expense_claim = frappe.get_doc(
+ {
+ "doctype": "Expense Claim",
+ "employee": "_T-Employee-00001",
+ "payable_account": payable_account,
+ "approval_status": "Rejected",
+ "expenses": [
+ {
+ "expense_type": "Travel",
+ "default_account": "Travel Expenses - _TC3",
+ "amount": 300,
+ "sanctioned_amount": 200,
+ }
+ ],
+ }
+ )
expense_claim.submit()
- self.assertEqual(expense_claim.status, 'Rejected')
+ self.assertEqual(expense_claim.status, "Rejected")
self.assertEqual(expense_claim.total_sanctioned_amount, 0.0)
- gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
+ gl_entry = frappe.get_all(
+ "GL Entry", {"voucher_type": "Expense Claim", "voucher_no": expense_claim.name}
+ )
self.assertEqual(len(gl_entry), 0)
def test_expense_approver_perms(self):
@@ -186,7 +216,9 @@ class TestExpenseClaim(unittest.TestCase):
# check doc shared
payable_account = get_payable_account("_Test Company")
- expense_claim = make_expense_claim(payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ expense_claim = make_expense_claim(
+ payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
expense_claim.expense_approver = user
expense_claim.save()
self.assertTrue(expense_claim.name in frappe.share.get_shared("Expense Claim", user))
@@ -210,51 +242,76 @@ class TestExpenseClaim(unittest.TestCase):
def test_multiple_payment_entries_against_expense(self):
# Creating expense claim
payable_account = get_payable_account("_Test Company")
- expense_claim = make_expense_claim(payable_account, 5500, 5500, "_Test Company", "Travel Expenses - _TC")
+ expense_claim = make_expense_claim(
+ payable_account, 5500, 5500, "_Test Company", "Travel Expenses - _TC"
+ )
expense_claim.save()
expense_claim.submit()
# Payment entry 1: paying 500
- make_payment_entry(expense_claim, payable_account,500)
- outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+ make_payment_entry(expense_claim, payable_account, 500)
+ outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(
+ expense_claim
+ )
self.assertEqual(outstanding_amount, 5000)
self.assertEqual(total_amount_reimbursed, 500)
# Payment entry 1: paying 2000
- make_payment_entry(expense_claim, payable_account,2000)
- outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+ make_payment_entry(expense_claim, payable_account, 2000)
+ outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(
+ expense_claim
+ )
self.assertEqual(outstanding_amount, 3000)
self.assertEqual(total_amount_reimbursed, 2500)
# Payment entry 1: paying 3000
- make_payment_entry(expense_claim, payable_account,3000)
- outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+ make_payment_entry(expense_claim, payable_account, 3000)
+ outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(
+ expense_claim
+ )
self.assertEqual(outstanding_amount, 0)
self.assertEqual(total_amount_reimbursed, 5500)
def get_payable_account(company):
- return frappe.get_cached_value('Company', company, 'default_payable_account')
+ return frappe.get_cached_value("Company", company, "default_payable_account")
+
def generate_taxes():
- parent_account = frappe.db.get_value('Account',
- {'company': company_name, 'is_group':1, 'account_type': 'Tax'},
- 'name')
- account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account)
- return {'taxes':[{
- "account_head": account,
- "rate": 9,
- "description": "CGST",
- "tax_amount": 10,
- "total": 210
- }]}
+ parent_account = frappe.db.get_value(
+ "Account", {"company": company_name, "is_group": 1, "account_type": "Tax"}, "name"
+ )
+ account = create_account(
+ company=company_name,
+ account_name="Output Tax CGST",
+ account_type="Tax",
+ parent_account=parent_account,
+ )
+ return {
+ "taxes": [
+ {"account_head": account, "rate": 9, "description": "CGST", "tax_amount": 10, "total": 210}
+ ]
+ }
-def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
+
+def make_expense_claim(
+ payable_account,
+ amount,
+ sanctioned_amount,
+ company,
+ account,
+ project=None,
+ task_name=None,
+ do_not_submit=False,
+ taxes=None,
+):
employee = frappe.db.get_value("Employee", {"status": "Active"})
if not employee:
employee = make_employee("test_employee@expense_claim.com", company=company)
- currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
+ currency, cost_center = frappe.db.get_value(
+ "Company", company, ["default_currency", "cost_center"]
+ )
expense_claim = {
"doctype": "Expense Claim",
"employee": employee,
@@ -262,14 +319,16 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco
"approval_status": "Approved",
"company": company,
"currency": currency,
- "expenses": [{
- "expense_type": "Travel",
- "default_account": account,
- "currency": currency,
- "amount": amount,
- "sanctioned_amount": sanctioned_amount,
- "cost_center": cost_center
- }]
+ "expenses": [
+ {
+ "expense_type": "Travel",
+ "default_account": account,
+ "currency": currency,
+ "amount": amount,
+ "sanctioned_amount": sanctioned_amount,
+ "cost_center": cost_center,
+ }
+ ],
}
if taxes:
expense_claim.update(taxes)
@@ -286,17 +345,24 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco
expense_claim.submit()
return expense_claim
-def get_outstanding_and_total_reimbursed_amounts(expense_claim):
- outstanding_amount = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_sanctioned_amount")) - \
- flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
- total_amount_reimbursed = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
- return outstanding_amount,total_amount_reimbursed
+def get_outstanding_and_total_reimbursed_amounts(expense_claim):
+ outstanding_amount = flt(
+ frappe.db.get_value("Expense Claim", expense_claim.name, "total_sanctioned_amount")
+ ) - flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
+ total_amount_reimbursed = flt(
+ frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed")
+ )
+
+ return outstanding_amount, total_amount_reimbursed
+
def make_payment_entry(expense_claim, payable_account, amt):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
- pe = get_payment_entry("Expense Claim", expense_claim.name, bank_account="_Test Bank USD - _TC", bank_amount=amt)
+ pe = get_payment_entry(
+ "Expense Claim", expense_claim.name, bank_account="_Test Bank USD - _TC", bank_amount=amt
+ )
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.source_exchange_rate = 1
diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
index 570b2c115f..6d29f7d8e7 100644
--- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
+++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
@@ -18,12 +18,13 @@ class ExpenseClaimType(Document):
for entry in self.accounts:
accounts_list.append(entry.company)
- if len(accounts_list)!= len(set(accounts_list)):
+ if len(accounts_list) != len(set(accounts_list)):
frappe.throw(_("Same Company is entered more than once"))
def validate_accounts(self):
for entry in self.accounts:
"""Error when Company of Ledger account doesn't match with Company Selected"""
if frappe.db.get_value("Account", entry.default_account, "company") != entry.company:
- frappe.throw(_("Account {0} does not match with Company {1}"
- ).format(entry.default_account, entry.company))
+ frappe.throw(
+ _("Account {0} does not match with Company {1}").format(entry.default_account, entry.company)
+ )
diff --git a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py
index a2403b6eb8..62348e2825 100644
--- a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py
+++ b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Expense Claim Type')
+
class TestExpenseClaimType(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
index f539537fdb..8137a0de14 100644
--- a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
+++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
@@ -39,16 +39,20 @@ class FullandFinalStatement(Document):
for data in self.get_assets_movement():
self.append("assets_allocated", data)
else:
- frappe.throw(_("Set Relieving Date for Employee: {0}").format(get_link_to_form("Employee", self.employee)))
+ frappe.throw(
+ _("Set Relieving Date for Employee: {0}").format(get_link_to_form("Employee", self.employee))
+ )
def create_component_row(self, components, component_type):
for component in components:
- self.append(component_type, {
- "status": "Unsettled",
- "reference_document_type": component if component != "Bonus" else "Additional Salary",
- "component": component
- })
-
+ self.append(
+ component_type,
+ {
+ "status": "Unsettled",
+ "reference_document_type": component if component != "Bonus" else "Additional Salary",
+ "component": component,
+ },
+ )
def get_payable_component(self):
return [
@@ -66,13 +70,11 @@ class FullandFinalStatement(Document):
]
def get_assets_movement(self):
- asset_movements = frappe.get_all("Asset Movement Item",
- filters = {"docstatus": 1},
- fields = ["asset", "from_employee", "to_employee", "parent", "asset_name"],
- or_filters = {
- "from_employee": self.employee,
- "to_employee": self.employee
- }
+ asset_movements = frappe.get_all(
+ "Asset Movement Item",
+ filters={"docstatus": 1},
+ fields=["asset", "from_employee", "to_employee", "parent", "asset_name"],
+ or_filters={"from_employee": self.employee, "to_employee": self.employee},
)
data = []
@@ -90,12 +92,14 @@ class FullandFinalStatement(Document):
inwards_counts = [movement.asset for movement in inward_movements].count(movement.asset)
if inwards_counts > outwards_count:
- data.append({
- "reference": movement.parent,
- "asset_name": movement.asset_name,
- "date": frappe.db.get_value("Asset Movement", movement.parent, "transaction_date"),
- "status": "Owned"
- })
+ data.append(
+ {
+ "reference": movement.parent,
+ "asset_name": movement.asset_name,
+ "date": frappe.db.get_value("Asset Movement", movement.parent, "transaction_date"),
+ "status": "Owned",
+ }
+ )
return data
@frappe.whitelist()
@@ -112,7 +116,7 @@ class FullandFinalStatement(Document):
if data.amount > 0 and not data.paid_via_salary_slip:
account_dict = {
"account": data.account,
- "debit_in_account_currency": flt(data.amount, precision)
+ "debit_in_account_currency": flt(data.amount, precision),
}
if data.reference_document_type == "Expense Claim":
account_dict["party_type"] = "Employee"
@@ -124,7 +128,7 @@ class FullandFinalStatement(Document):
if data.amount > 0:
account_dict = {
"account": data.account,
- "credit_in_account_currency": flt(data.amount, precision)
+ "credit_in_account_currency": flt(data.amount, precision),
}
if data.reference_document_type == "Employee Advance":
account_dict["party_type"] = "Employee"
@@ -132,46 +136,67 @@ class FullandFinalStatement(Document):
jv.append("accounts", account_dict)
- jv.append("accounts", {
- "credit_in_account_currency": difference if difference > 0 else 0,
- "debit_in_account_currency": -(difference) if difference < 0 else 0,
- "reference_type": self.doctype,
- "reference_name": self.name
- })
+ jv.append(
+ "accounts",
+ {
+ "credit_in_account_currency": difference if difference > 0 else 0,
+ "debit_in_account_currency": -(difference) if difference < 0 else 0,
+ "reference_type": self.doctype,
+ "reference_name": self.name,
+ },
+ )
return jv
+
@frappe.whitelist()
def get_account_and_amount(ref_doctype, ref_document):
if not ref_doctype or not ref_document:
return None
if ref_doctype == "Salary Slip":
- salary_details = frappe.db.get_value("Salary Slip", ref_document, ["payroll_entry", "net_pay"], as_dict=1)
+ salary_details = frappe.db.get_value(
+ "Salary Slip", ref_document, ["payroll_entry", "net_pay"], as_dict=1
+ )
amount = salary_details.net_pay
- payable_account = frappe.db.get_value("Payroll Entry", salary_details.payroll_entry, "payroll_payable_account") if salary_details.payroll_entry else None
+ payable_account = (
+ frappe.db.get_value("Payroll Entry", salary_details.payroll_entry, "payroll_payable_account")
+ if salary_details.payroll_entry
+ else None
+ )
return [payable_account, amount]
if ref_doctype == "Gratuity":
- payable_account, amount = frappe.db.get_value("Gratuity", ref_document, ["payable_account", "amount"])
+ payable_account, amount = frappe.db.get_value(
+ "Gratuity", ref_document, ["payable_account", "amount"]
+ )
return [payable_account, amount]
if ref_doctype == "Expense Claim":
- details = frappe.db.get_value("Expense Claim", ref_document,
- ["payable_account", "grand_total", "total_amount_reimbursed", "total_advance_amount"], as_dict=True)
+ details = frappe.db.get_value(
+ "Expense Claim",
+ ref_document,
+ ["payable_account", "grand_total", "total_amount_reimbursed", "total_advance_amount"],
+ as_dict=True,
+ )
payable_account = details.payable_account
amount = details.grand_total - (details.total_amount_reimbursed + details.total_advance_amount)
return [payable_account, amount]
if ref_doctype == "Loan":
- details = frappe.db.get_value("Loan", ref_document,
- ["payment_account", "total_payment", "total_amount_paid"], as_dict=1)
+ details = frappe.db.get_value(
+ "Loan", ref_document, ["payment_account", "total_payment", "total_amount_paid"], as_dict=1
+ )
payment_account = details.payment_account
amount = details.total_payment - details.total_amount_paid
return [payment_account, amount]
if ref_doctype == "Employee Advance":
- details = frappe.db.get_value("Employee Advance", ref_document,
- ["advance_account","paid_amount", "claimed_amount", "return_amount"], as_dict = 1)
+ details = frappe.db.get_value(
+ "Employee Advance",
+ ref_document,
+ ["advance_account", "paid_amount", "claimed_amount", "return_amount"],
+ as_dict=1,
+ )
payment_account = details.advance_account
amount = details.paid_amount - (details.claimed_amount + details.return_amount)
return [payment_account, amount]
diff --git a/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
index f6c1d1545b..8c6723f60e 100644
--- a/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
+++ b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
@@ -12,7 +12,6 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
class TestFullandFinalStatement(unittest.TestCase):
-
def setUp(self):
create_asset_data()
@@ -27,18 +26,26 @@ class TestFullandFinalStatement(unittest.TestCase):
frappe.db.set_value("Employee", employee, "relieving_date", add_days(today(), 30))
fnf = create_full_and_final_statement(employee)
- payables_bootstraped_component = ["Salary Slip", "Gratuity",
- "Expense Claim", "Bonus", "Leave Encashment"]
+ payables_bootstraped_component = [
+ "Salary Slip",
+ "Gratuity",
+ "Expense Claim",
+ "Bonus",
+ "Leave Encashment",
+ ]
receivable_bootstraped_component = ["Loan", "Employee Advance"]
- #checking payable s and receivables bootstraped value
+ # checking payable s and receivables bootstraped value
self.assertEqual([payable.component for payable in fnf.payables], payables_bootstraped_component)
- self.assertEqual([receivable.component for receivable in fnf.receivables], receivable_bootstraped_component)
+ self.assertEqual(
+ [receivable.component for receivable in fnf.receivables], receivable_bootstraped_component
+ )
- #checking allocated asset
+ # checking allocated asset
self.assertIn(movement, [asset.reference for asset in fnf.assets_allocated])
+
def create_full_and_final_statement(employee):
fnf = frappe.new_doc("Full and Final Statement")
fnf.employee = employee
@@ -46,6 +53,7 @@ def create_full_and_final_statement(employee):
fnf.save()
return fnf
+
def create_asset_movement(employee):
asset_name = create_asset()
movement = frappe.new_doc("Asset Movement")
@@ -53,18 +61,17 @@ def create_asset_movement(employee):
movement.purpose = "Issue"
movement.transaction_date = today()
- movement.append("assets", {
- "asset": asset_name,
- "to_employee": employee
- })
+ movement.append("assets", {"asset": asset_name, "to_employee": employee})
movement.save()
movement.submit()
return movement.name
+
def create_asset():
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+ )
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
asset = frappe.get_doc("Asset", asset_name)
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index a8c8c16d0d..fad827ad8a 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -10,7 +10,9 @@ from frappe.model.document import Document
from frappe.utils import cint, formatdate, getdate, today
-class OverlapError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
class HolidayList(Document):
def validate(self):
@@ -21,9 +23,14 @@ class HolidayList(Document):
def get_weekly_off_dates(self):
self.validate_values()
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
- last_idx = max([cint(d.idx) for d in self.get("holidays")] or [0,])
+ last_idx = max(
+ [cint(d.idx) for d in self.get("holidays")]
+ or [
+ 0,
+ ]
+ )
for i, d in enumerate(date_list):
- ch = self.append('holidays', {})
+ ch = self.append("holidays", {})
ch.description = _(self.weekly_off)
ch.holiday_date = d
ch.weekly_off = 1
@@ -33,14 +40,17 @@ class HolidayList(Document):
if not self.weekly_off:
throw(_("Please select weekly off day"))
-
def validate_days(self):
if getdate(self.from_date) > getdate(self.to_date):
throw(_("To Date cannot be before From Date"))
for day in self.get("holidays"):
if not (getdate(self.from_date) <= getdate(day.holiday_date) <= getdate(self.to_date)):
- frappe.throw(_("The holiday on {0} is not between From Date and To Date").format(formatdate(day.holiday_date)))
+ frappe.throw(
+ _("The holiday on {0} is not between From Date and To Date").format(
+ formatdate(day.holiday_date)
+ )
+ )
def get_weekly_off_date_list(self, start_date, end_date):
start_date, end_date = getdate(start_date), getdate(end_date)
@@ -66,7 +76,8 @@ class HolidayList(Document):
@frappe.whitelist()
def clear_table(self):
- self.set('holidays', [])
+ self.set("holidays", [])
+
@frappe.whitelist()
def get_events(start, end, filters=None):
@@ -82,23 +93,28 @@ def get_events(start, end, filters=None):
filters = []
if start:
- filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
+ filters.append(["Holiday", "holiday_date", ">", getdate(start)])
if end:
- filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
+ filters.append(["Holiday", "holiday_date", "<", getdate(end)])
- return frappe.get_list('Holiday List',
- fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'],
- filters = filters,
- update={"allDay": 1})
+ return frappe.get_list(
+ "Holiday List",
+ fields=[
+ "name",
+ "`tabHoliday`.holiday_date",
+ "`tabHoliday`.description",
+ "`tabHoliday List`.color",
+ ],
+ filters=filters,
+ update={"allDay": 1},
+ )
def is_holiday(holiday_list, date=None):
- """Returns true if the given date is a holiday in the given holiday list
- """
+ """Returns true if the given date is a holiday in the given holiday list"""
if date is None:
date = today()
if holiday_list:
- return bool(frappe.get_all('Holiday List',
- dict(name=holiday_list, holiday_date=date)))
+ return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date)))
else:
return False
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
index 4a540ce610..0cbf09461b 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
@@ -1,19 +1,15 @@
def get_data():
return {
- 'fieldname': 'holiday_list',
- 'non_standard_fieldnames': {
- 'Company': 'default_holiday_list',
- 'Leave Period': 'optional_holiday_list'
+ "fieldname": "holiday_list",
+ "non_standard_fieldnames": {
+ "Company": "default_holiday_list",
+ "Leave Period": "optional_holiday_list",
},
- 'transactions': [
+ "transactions": [
{
- 'items': ['Company', 'Employee', 'Workstation'],
+ "items": ["Company", "Employee", "Workstation"],
},
- {
- 'items': ['Leave Period', 'Shift Type']
- },
- {
- 'items': ['Service Level', 'Service Level Agreement']
- }
- ]
+ {"items": ["Leave Period", "Shift Type"]},
+ {"items": ["Service Level", "Service Level Agreement"]},
+ ],
}
diff --git a/erpnext/hr/doctype/holiday_list/test_holiday_list.py b/erpnext/hr/doctype/holiday_list/test_holiday_list.py
index aed901aa6d..d32cfe8265 100644
--- a/erpnext/hr/doctype/holiday_list/test_holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/test_holiday_list.py
@@ -12,24 +12,31 @@ from frappe.utils import getdate
class TestHolidayList(unittest.TestCase):
def test_holiday_list(self):
today_date = getdate()
- test_holiday_dates = [today_date-timedelta(days=5), today_date-timedelta(days=4)]
- holiday_list = make_holiday_list("test_holiday_list",
+ test_holiday_dates = [today_date - timedelta(days=5), today_date - timedelta(days=4)]
+ holiday_list = make_holiday_list(
+ "test_holiday_list",
holiday_dates=[
- {'holiday_date': test_holiday_dates[0], 'description': 'test holiday'},
- {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'}
- ])
- fetched_holiday_list = frappe.get_value('Holiday List', holiday_list.name)
+ {"holiday_date": test_holiday_dates[0], "description": "test holiday"},
+ {"holiday_date": test_holiday_dates[1], "description": "test holiday2"},
+ ],
+ )
+ fetched_holiday_list = frappe.get_value("Holiday List", holiday_list.name)
self.assertEqual(holiday_list.name, fetched_holiday_list)
-def make_holiday_list(name, from_date=getdate()-timedelta(days=10), to_date=getdate(), holiday_dates=None):
+
+def make_holiday_list(
+ name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None
+):
frappe.delete_doc_if_exists("Holiday List", name, force=1)
- doc = frappe.get_doc({
- "doctype": "Holiday List",
- "holiday_list_name": name,
- "from_date" : from_date,
- "to_date" : to_date,
- "holidays" : holiday_dates
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Holiday List",
+ "holiday_list_name": name,
+ "from_date": from_date,
+ "to_date": to_date,
+ "holidays": holiday_dates,
+ }
+ ).insert()
return doc
@@ -39,7 +46,7 @@ def set_holiday_list(holiday_list, company_name):
Context manager for setting holiday list in tests
"""
try:
- company = frappe.get_doc('Company', company_name)
+ company = frappe.get_doc("Company", company_name)
previous_holiday_list = company.default_holiday_list
company.default_holiday_list = holiday_list
@@ -49,6 +56,6 @@ def set_holiday_list(holiday_list, company_name):
finally:
# restore holiday list setup
- company = frappe.get_doc('Company', company_name)
+ company = frappe.get_doc("Company", company_name)
company.default_holiday_list = previous_holiday_list
company.save()
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.py b/erpnext/hr/doctype/hr_settings/hr_settings.py
index c295bcbc0d..72a49e285a 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.py
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.py
@@ -10,6 +10,7 @@ from frappe.utils import format_date
# Wether to proceed with frequency change
PROCEED_WITH_FREQUENCY_CHANGE = False
+
class HRSettings(Document):
def validate(self):
self.set_naming_series()
@@ -22,21 +23,24 @@ class HRSettings(Document):
def set_naming_series(self):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
- set_by_naming_series("Employee", "employee_number",
- self.get("emp_created_by")=="Naming Series", hide_name_field=True)
+
+ set_by_naming_series(
+ "Employee",
+ "employee_number",
+ self.get("emp_created_by") == "Naming Series",
+ hide_name_field=True,
+ )
def validate_frequency_change(self):
weekly_job, monthly_job = None, None
try:
weekly_job = frappe.get_doc(
- 'Scheduled Job Type',
- 'employee_reminders.send_reminders_in_advance_weekly'
+ "Scheduled Job Type", "employee_reminders.send_reminders_in_advance_weekly"
)
monthly_job = frappe.get_doc(
- 'Scheduled Job Type',
- 'employee_reminders.send_reminders_in_advance_monthly'
+ "Scheduled Job Type", "employee_reminders.send_reminders_in_advance_monthly"
)
except frappe.DoesNotExistError:
return
@@ -62,17 +66,20 @@ class HRSettings(Document):
from_date = frappe.bold(format_date(from_date))
to_date = frappe.bold(format_date(to_date))
frappe.msgprint(
- msg=frappe._('Employees will miss holiday reminders from {} until {}.
Do you want to proceed with this change?').format(from_date, to_date),
- title='Confirm change in Frequency',
+ msg=frappe._(
+ "Employees will miss holiday reminders from {} until {}.
Do you want to proceed with this change?"
+ ).format(from_date, to_date),
+ title="Confirm change in Frequency",
primary_action={
- 'label': frappe._('Yes, Proceed'),
- 'client_action': 'erpnext.proceed_save_with_reminders_frequency_change'
+ "label": frappe._("Yes, Proceed"),
+ "client_action": "erpnext.proceed_save_with_reminders_frequency_change",
},
- raise_exception=frappe.ValidationError
+ raise_exception=frappe.ValidationError,
)
+
@frappe.whitelist()
def set_proceed_with_frequency_change():
- '''Enables proceed with frequency change'''
+ """Enables proceed with frequency change"""
global PROCEED_WITH_FREQUENCY_CHANGE
PROCEED_WITH_FREQUENCY_CHANGE = True
diff --git a/erpnext/hr/doctype/interest/test_interest.py b/erpnext/hr/doctype/interest/test_interest.py
index d4ecd9b841..eacb57f758 100644
--- a/erpnext/hr/doctype/interest/test_interest.py
+++ b/erpnext/hr/doctype/interest/test_interest.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Interest')
+
class TestInterest(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py
index a3b111ccb1..a6e9af2679 100644
--- a/erpnext/hr/doctype/interview/interview.py
+++ b/erpnext/hr/doctype/interview/interview.py
@@ -13,6 +13,7 @@ from frappe.utils import cstr, flt, get_datetime, get_link_to_form
class DuplicateInterviewRoundError(frappe.ValidationError):
pass
+
class Interview(Document):
def validate(self):
self.validate_duplicate_interview()
@@ -21,37 +22,47 @@ class Interview(Document):
self.set_average_rating()
def on_submit(self):
- if self.status not in ['Cleared', 'Rejected']:
- frappe.throw(_('Only Interviews with Cleared or Rejected status can be submitted.'), title=_('Not Allowed'))
+ if self.status not in ["Cleared", "Rejected"]:
+ frappe.throw(
+ _("Only Interviews with Cleared or Rejected status can be submitted."), title=_("Not Allowed")
+ )
def validate_duplicate_interview(self):
- duplicate_interview = frappe.db.exists('Interview', {
- 'job_applicant': self.job_applicant,
- 'interview_round': self.interview_round,
- 'docstatus': 1
- }
+ duplicate_interview = frappe.db.exists(
+ "Interview",
+ {"job_applicant": self.job_applicant, "interview_round": self.interview_round, "docstatus": 1},
)
if duplicate_interview:
- frappe.throw(_('Job Applicants are not allowed to appear twice for the same Interview round. Interview {0} already scheduled for Job Applicant {1}').format(
- frappe.bold(get_link_to_form('Interview', duplicate_interview)),
- frappe.bold(self.job_applicant)
- ))
+ frappe.throw(
+ _(
+ "Job Applicants are not allowed to appear twice for the same Interview round. Interview {0} already scheduled for Job Applicant {1}"
+ ).format(
+ frappe.bold(get_link_to_form("Interview", duplicate_interview)),
+ frappe.bold(self.job_applicant),
+ )
+ )
def validate_designation(self):
- applicant_designation = frappe.db.get_value('Job Applicant', self.job_applicant, 'designation')
- if self.designation :
+ applicant_designation = frappe.db.get_value("Job Applicant", self.job_applicant, "designation")
+ if self.designation:
if self.designation != applicant_designation:
- frappe.throw(_('Interview Round {0} is only for Designation {1}. Job Applicant has applied for the role {2}').format(
- self.interview_round, frappe.bold(self.designation), applicant_designation),
- exc=DuplicateInterviewRoundError)
+ frappe.throw(
+ _(
+ "Interview Round {0} is only for Designation {1}. Job Applicant has applied for the role {2}"
+ ).format(
+ self.interview_round, frappe.bold(self.designation), applicant_designation
+ ),
+ exc=DuplicateInterviewRoundError,
+ )
else:
self.designation = applicant_designation
def validate_overlap(self):
- interviewers = [entry.interviewer for entry in self.interview_details] or ['']
+ interviewers = [entry.interviewer for entry in self.interview_details] or [""]
- overlaps = frappe.db.sql("""
+ overlaps = frappe.db.sql(
+ """
SELECT interview.name
FROM `tabInterview` as interview
INNER JOIN `tabInterview Detail` as detail
@@ -61,12 +72,25 @@ class Interview(Document):
((from_time < %s and to_time > %s) or
(from_time > %s and to_time < %s) or
(from_time = %s))
- """, (self.scheduled_on, self.name, self.job_applicant, interviewers,
- self.from_time, self.to_time, self.from_time, self.to_time, self.from_time))
+ """,
+ (
+ self.scheduled_on,
+ self.name,
+ self.job_applicant,
+ interviewers,
+ self.from_time,
+ self.to_time,
+ self.from_time,
+ self.to_time,
+ self.from_time,
+ ),
+ )
if overlaps:
- overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0]))
- frappe.throw(overlapping_details, title=_('Overlap'))
+ overlapping_details = _("Interview overlaps with {0}").format(
+ get_link_to_form("Interview", overlaps[0][0])
+ )
+ frappe.throw(overlapping_details, title=_("Overlap"))
def set_average_rating(self):
total_rating = 0
@@ -74,7 +98,9 @@ class Interview(Document):
if entry.average_rating:
total_rating += entry.average_rating
- self.average_rating = flt(total_rating / len(self.interview_details) if len(self.interview_details) else 0)
+ self.average_rating = flt(
+ total_rating / len(self.interview_details) if len(self.interview_details) else 0
+ )
@frappe.whitelist()
def reschedule_interview(self, scheduled_on, from_time, to_time):
@@ -82,137 +108,155 @@ class Interview(Document):
from_time = self.from_time
to_time = self.to_time
- self.db_set({
- 'scheduled_on': scheduled_on,
- 'from_time': from_time,
- 'to_time': to_time
- })
+ self.db_set({"scheduled_on": scheduled_on, "from_time": from_time, "to_time": to_time})
self.notify_update()
recipients = get_recipients(self.name)
try:
frappe.sendmail(
- recipients= recipients,
- subject=_('Interview: {0} Rescheduled').format(self.name),
- message=_('Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}').format(
- original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time),
+ recipients=recipients,
+ subject=_("Interview: {0} Rescheduled").format(self.name),
+ message=_("Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}").format(
+ original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time
+ ),
reference_doctype=self.doctype,
- reference_name=self.name
+ reference_name=self.name,
)
except Exception:
- frappe.msgprint(_('Failed to send the Interview Reschedule notification. Please configure your email account.'))
+ frappe.msgprint(
+ _("Failed to send the Interview Reschedule notification. Please configure your email account.")
+ )
- frappe.msgprint(_('Interview Rescheduled successfully'), indicator='green')
+ frappe.msgprint(_("Interview Rescheduled successfully"), indicator="green")
def get_recipients(name, for_feedback=0):
- interview = frappe.get_doc('Interview', name)
+ interview = frappe.get_doc("Interview", name)
if for_feedback:
recipients = [d.interviewer for d in interview.interview_details if not d.interview_feedback]
else:
recipients = [d.interviewer for d in interview.interview_details]
- recipients.append(frappe.db.get_value('Job Applicant', interview.job_applicant, 'email_id'))
+ recipients.append(frappe.db.get_value("Job Applicant", interview.job_applicant, "email_id"))
return recipients
@frappe.whitelist()
def get_interviewers(interview_round):
- return frappe.get_all('Interviewer', filters={'parent': interview_round}, fields=['user as interviewer'])
+ return frappe.get_all(
+ "Interviewer", filters={"parent": interview_round}, fields=["user as interviewer"]
+ )
def send_interview_reminder():
- reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings',
- ['send_interview_reminder', 'interview_reminder_template'], as_dict=True)
+ reminder_settings = frappe.db.get_value(
+ "HR Settings",
+ "HR Settings",
+ ["send_interview_reminder", "interview_reminder_template"],
+ as_dict=True,
+ )
if not reminder_settings.send_interview_reminder:
return
- remind_before = cstr(frappe.db.get_single_value('HR Settings', 'remind_before')) or '01:00:00'
- remind_before = datetime.datetime.strptime(remind_before, '%H:%M:%S')
+ remind_before = cstr(frappe.db.get_single_value("HR Settings", "remind_before")) or "01:00:00"
+ remind_before = datetime.datetime.strptime(remind_before, "%H:%M:%S")
reminder_date_time = datetime.datetime.now() + datetime.timedelta(
- hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second)
+ hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second
+ )
- interviews = frappe.get_all('Interview', filters={
- 'scheduled_on': ['between', (datetime.datetime.now(), reminder_date_time)],
- 'status': 'Pending',
- 'reminded': 0,
- 'docstatus': ['!=', 2]
- })
+ interviews = frappe.get_all(
+ "Interview",
+ filters={
+ "scheduled_on": ["between", (datetime.datetime.now(), reminder_date_time)],
+ "status": "Pending",
+ "reminded": 0,
+ "docstatus": ["!=", 2],
+ },
+ )
- interview_template = frappe.get_doc('Email Template', reminder_settings.interview_reminder_template)
+ interview_template = frappe.get_doc(
+ "Email Template", reminder_settings.interview_reminder_template
+ )
for d in interviews:
- doc = frappe.get_doc('Interview', d.name)
+ doc = frappe.get_doc("Interview", d.name)
context = doc.as_dict()
message = frappe.render_template(interview_template.response, context)
recipients = get_recipients(doc.name)
frappe.sendmail(
- recipients= recipients,
+ recipients=recipients,
subject=interview_template.subject,
message=message,
reference_doctype=doc.doctype,
- reference_name=doc.name
+ reference_name=doc.name,
)
- doc.db_set('reminded', 1)
+ doc.db_set("reminded", 1)
def send_daily_feedback_reminder():
- reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings',
- ['send_interview_feedback_reminder', 'feedback_reminder_notification_template'], as_dict=True)
+ reminder_settings = frappe.db.get_value(
+ "HR Settings",
+ "HR Settings",
+ ["send_interview_feedback_reminder", "feedback_reminder_notification_template"],
+ as_dict=True,
+ )
if not reminder_settings.send_interview_feedback_reminder:
return
- interview_feedback_template = frappe.get_doc('Email Template', reminder_settings.feedback_reminder_notification_template)
- interviews = frappe.get_all('Interview', filters={'status': ['in', ['Under Review', 'Pending']], 'docstatus': ['!=', 2]})
+ interview_feedback_template = frappe.get_doc(
+ "Email Template", reminder_settings.feedback_reminder_notification_template
+ )
+ interviews = frappe.get_all(
+ "Interview", filters={"status": ["in", ["Under Review", "Pending"]], "docstatus": ["!=", 2]}
+ )
for entry in interviews:
recipients = get_recipients(entry.name, for_feedback=1)
- doc = frappe.get_doc('Interview', entry.name)
+ doc = frappe.get_doc("Interview", entry.name)
context = doc.as_dict()
message = frappe.render_template(interview_feedback_template.response, context)
if len(recipients):
frappe.sendmail(
- recipients= recipients,
+ recipients=recipients,
subject=interview_feedback_template.subject,
message=message,
- reference_doctype='Interview',
- reference_name=entry.name
+ reference_doctype="Interview",
+ reference_name=entry.name,
)
@frappe.whitelist()
def get_expected_skill_set(interview_round):
- return frappe.get_all('Expected Skill Set', filters ={'parent': interview_round}, fields=['skill'])
+ return frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields=["skill"])
@frappe.whitelist()
def create_interview_feedback(data, interview_name, interviewer, job_applicant):
import json
-
if isinstance(data, str):
data = frappe._dict(json.loads(data))
if frappe.session.user != interviewer:
- frappe.throw(_('Only Interviewer Are allowed to submit Interview Feedback'))
+ frappe.throw(_("Only Interviewer Are allowed to submit Interview Feedback"))
- interview_feedback = frappe.new_doc('Interview Feedback')
+ interview_feedback = frappe.new_doc("Interview Feedback")
interview_feedback.interview = interview_name
interview_feedback.interviewer = interviewer
interview_feedback.job_applicant = job_applicant
for d in data.skill_set:
d = frappe._dict(d)
- interview_feedback.append('skill_assessment', {'skill': d.skill, 'rating': d.rating})
+ interview_feedback.append("skill_assessment", {"skill": d.skill, "rating": d.rating})
interview_feedback.feedback = data.feedback
interview_feedback.result = data.result
@@ -220,24 +264,33 @@ def create_interview_feedback(data, interview_name, interviewer, job_applicant):
interview_feedback.save()
interview_feedback.submit()
- frappe.msgprint(_('Interview Feedback {0} submitted successfully').format(
- get_link_to_form('Interview Feedback', interview_feedback.name)))
+ frappe.msgprint(
+ _("Interview Feedback {0} submitted successfully").format(
+ get_link_to_form("Interview Feedback", interview_feedback.name)
+ )
+ )
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_interviewer_list(doctype, txt, searchfield, start, page_len, filters):
filters = [
- ['Has Role', 'parent', 'like', '%{}%'.format(txt)],
- ['Has Role', 'role', '=', 'interviewer'],
- ['Has Role', 'parenttype', '=', 'User']
+ ["Has Role", "parent", "like", "%{}%".format(txt)],
+ ["Has Role", "role", "=", "interviewer"],
+ ["Has Role", "parenttype", "=", "User"],
]
if filters and isinstance(filters, list):
filters.extend(filters)
- return frappe.get_all('Has Role', limit_start=start, limit_page_length=page_len,
- filters=filters, fields = ['parent'], as_list=1)
+ return frappe.get_all(
+ "Has Role",
+ limit_start=start,
+ limit_page_length=page_len,
+ filters=filters,
+ fields=["parent"],
+ as_list=1,
+ )
@frappe.whitelist()
@@ -256,12 +309,13 @@ def get_events(start, end, filters=None):
"Pending": "#fff4f0",
"Under Review": "#d3e8fc",
"Cleared": "#eaf5ed",
- "Rejected": "#fce7e7"
+ "Rejected": "#fce7e7",
}
- conditions = get_event_conditions('Interview', filters)
+ conditions = get_event_conditions("Interview", filters)
- interviews = frappe.db.sql("""
+ interviews = frappe.db.sql(
+ """
SELECT DISTINCT
`tabInterview`.name, `tabInterview`.job_applicant, `tabInterview`.interview_round,
`tabInterview`.scheduled_on, `tabInterview`.status, `tabInterview`.from_time as from_time,
@@ -272,10 +326,13 @@ def get_events(start, end, filters=None):
(`tabInterview`.scheduled_on between %(start)s and %(end)s)
and docstatus != 2
{conditions}
- """.format(conditions=conditions), {
- "start": start,
- "end": end
- }, as_dict=True, update={"allDay": 0})
+ """.format(
+ conditions=conditions
+ ),
+ {"start": start, "end": end},
+ as_dict=True,
+ update={"allDay": 0},
+ )
for d in interviews:
subject_data = []
@@ -286,11 +343,11 @@ def get_events(start, end, filters=None):
color = event_color.get(d.status)
interview_data = {
- 'from': get_datetime('%s %s' % (d.scheduled_on, d.from_time or '00:00:00')),
- 'to': get_datetime('%s %s' % (d.scheduled_on, d.to_time or '00:00:00')),
- 'name': d.name,
- 'subject': '\n'.join(subject_data),
- 'color': color if color else "#89bcde"
+ "from": get_datetime("%s %s" % (d.scheduled_on, d.from_time or "00:00:00")),
+ "to": get_datetime("%s %s" % (d.scheduled_on, d.to_time or "00:00:00")),
+ "name": d.name,
+ "subject": "\n".join(subject_data),
+ "color": color if color else "#89bcde",
}
events.append(interview_data)
diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py
index fdb11afe82..ae8493ab3e 100644
--- a/erpnext/hr/doctype/interview/test_interview.py
+++ b/erpnext/hr/doctype/interview/test_interview.py
@@ -19,23 +19,30 @@ from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_appli
class TestInterview(unittest.TestCase):
def test_validations_for_designation(self):
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, designation='_Test_Sales_manager', save=0)
+ interview = create_interview_and_dependencies(
+ job_applicant.name, designation="_Test_Sales_manager", save=0
+ )
self.assertRaises(DuplicateInterviewRoundError, interview.save)
def test_notification_on_rescheduling(self):
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -4))
+ interview = create_interview_and_dependencies(
+ job_applicant.name, scheduled_on=add_days(getdate(), -4)
+ )
previous_scheduled_date = interview.scheduled_on
frappe.db.sql("DELETE FROM `tabEmail Queue`")
- interview.reschedule_interview(add_days(getdate(previous_scheduled_date), 2),
- from_time=nowtime(), to_time=nowtime())
+ interview.reschedule_interview(
+ add_days(getdate(previous_scheduled_date), 2), from_time=nowtime(), to_time=nowtime()
+ )
interview.reload()
self.assertEqual(interview.scheduled_on, add_days(getdate(previous_scheduled_date), 2))
- notification = frappe.get_all("Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")})
+ notification = frappe.get_all(
+ "Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")}
+ )
self.assertIsNotNone(notification)
def test_notification_for_scheduling(self):
@@ -76,29 +83,33 @@ class TestInterview(unittest.TestCase):
interview = create_interview_and_dependencies(job_applicant.name)
details = get_interview_details(job_applicant.name)
- self.assertEqual(details.get('stars'), 5)
- self.assertEqual(details.get('interviews').get(interview.name), {
- 'name': interview.name,
- 'interview_round': interview.interview_round,
- 'expected_average_rating': interview.expected_average_rating * 5,
- 'average_rating': interview.average_rating * 5,
- 'status': 'Pending'
- })
+ self.assertEqual(details.get("stars"), 5)
+ self.assertEqual(
+ details.get("interviews").get(interview.name),
+ {
+ "name": interview.name,
+ "interview_round": interview.interview_round,
+ "expected_average_rating": interview.expected_average_rating * 5,
+ "average_rating": interview.average_rating * 5,
+ "status": "Pending",
+ },
+ )
def tearDown(self):
frappe.db.rollback()
-def create_interview_and_dependencies(job_applicant, scheduled_on=None, from_time=None, to_time=None, designation=None, save=1):
+def create_interview_and_dependencies(
+ job_applicant, scheduled_on=None, from_time=None, to_time=None, designation=None, save=1
+):
if designation:
- designation=create_designation(designation_name = "_Test_Sales_manager").name
+ designation = create_designation(designation_name="_Test_Sales_manager").name
interviewer_1 = create_user("test_interviewer1@example.com", "Interviewer")
interviewer_2 = create_user("test_interviewer2@example.com", "Interviewer")
interview_round = create_interview_round(
- "Technical Round", ["Python", "JS"],
- designation=designation, save=True
+ "Technical Round", ["Python", "JS"], designation=designation, save=True
)
interview = frappe.new_doc("Interview")
@@ -116,6 +127,7 @@ def create_interview_and_dependencies(job_applicant, scheduled_on=None, from_tim
return interview
+
def create_interview_round(name, skill_set, interviewers=[], designation=None, save=True):
create_skill_set(skill_set)
interview_round = frappe.new_doc("Interview Round")
@@ -130,15 +142,14 @@ def create_interview_round(name, skill_set, interviewers=[], designation=None, s
interview_round.append("expected_skill_set", {"skill": skill})
for interviewer in interviewers:
- interview_round.append("interviewer", {
- "user": interviewer
- })
+ interview_round.append("interviewer", {"user": interviewer})
if save:
interview_round.save()
return interview_round
+
def create_skill_set(skill_set):
for skill in skill_set:
if not frappe.db.exists("Skill", skill):
@@ -146,6 +157,7 @@ def create_skill_set(skill_set):
doc.skill_name = skill
doc.save()
+
def create_interview_type(name="test_interview_type"):
if frappe.db.exists("Interview Type", name):
return frappe.get_doc("Interview Type", name).name
@@ -157,32 +169,41 @@ def create_interview_type(name="test_interview_type"):
return doc.name
+
def setup_reminder_settings():
- if not frappe.db.exists('Email Template', _('Interview Reminder')):
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'interview/interview_reminder_notification_template.html'))
+ if not frappe.db.exists("Email Template", _("Interview Reminder")):
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "interview/interview_reminder_notification_template.html")
+ )
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Interview Reminder'),
- 'response': response,
- 'subject': _('Interview Reminder'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Interview Reminder"),
+ "response": response,
+ "subject": _("Interview Reminder"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
- if not frappe.db.exists('Email Template', _('Interview Feedback Reminder')):
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'interview/interview_feedback_reminder_template.html'))
+ if not frappe.db.exists("Email Template", _("Interview Feedback Reminder")):
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "interview/interview_feedback_reminder_template.html")
+ )
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Interview Feedback Reminder'),
- 'response': response,
- 'subject': _('Interview Feedback Reminder'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Interview Feedback Reminder"),
+ "response": response,
+ "subject": _("Interview Feedback Reminder"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
- hr_settings = frappe.get_doc('HR Settings')
- hr_settings.interview_reminder_template = _('Interview Reminder')
- hr_settings.feedback_reminder_notification_template = _('Interview Feedback Reminder')
+ hr_settings = frappe.get_doc("HR Settings")
+ hr_settings.interview_reminder_template = _("Interview Reminder")
+ hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder")
hr_settings.save()
diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
index 2ff00c1cac..5bb498fa54 100644
--- a/erpnext/hr/doctype/interview_feedback/interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
@@ -24,28 +24,36 @@ class InterviewFeedback(Document):
def validate_interviewer(self):
applicable_interviewers = get_applicable_interviewers(self.interview)
if self.interviewer not in applicable_interviewers:
- frappe.throw(_('{0} is not allowed to submit Interview Feedback for the Interview: {1}').format(
- frappe.bold(self.interviewer), frappe.bold(self.interview)))
+ frappe.throw(
+ _("{0} is not allowed to submit Interview Feedback for the Interview: {1}").format(
+ frappe.bold(self.interviewer), frappe.bold(self.interview)
+ )
+ )
def validate_interview_date(self):
- scheduled_date = frappe.db.get_value('Interview', self.interview, 'scheduled_on')
+ scheduled_date = frappe.db.get_value("Interview", self.interview, "scheduled_on")
if getdate() < getdate(scheduled_date) and self.docstatus == 1:
- frappe.throw(_('{0} submission before {1} is not allowed').format(
- frappe.bold('Interview Feedback'),
- frappe.bold('Interview Scheduled Date')
- ))
+ frappe.throw(
+ _("{0} submission before {1} is not allowed").format(
+ frappe.bold("Interview Feedback"), frappe.bold("Interview Scheduled Date")
+ )
+ )
def validate_duplicate(self):
- duplicate_feedback = frappe.db.exists('Interview Feedback', {
- 'interviewer': self.interviewer,
- 'interview': self.interview,
- 'docstatus': 1
- })
+ duplicate_feedback = frappe.db.exists(
+ "Interview Feedback",
+ {"interviewer": self.interviewer, "interview": self.interview, "docstatus": 1},
+ )
if duplicate_feedback:
- frappe.throw(_('Feedback already submitted for the Interview {0}. Please cancel the previous Interview Feedback {1} to continue.').format(
- self.interview, get_link_to_form('Interview Feedback', duplicate_feedback)))
+ frappe.throw(
+ _(
+ "Feedback already submitted for the Interview {0}. Please cancel the previous Interview Feedback {1} to continue."
+ ).format(
+ self.interview, get_link_to_form("Interview Feedback", duplicate_feedback)
+ )
+ )
def calculate_average_rating(self):
total_rating = 0
@@ -53,10 +61,12 @@ class InterviewFeedback(Document):
if d.rating:
total_rating += d.rating
- self.average_rating = flt(total_rating / len(self.skill_assessment) if len(self.skill_assessment) else 0)
+ self.average_rating = flt(
+ total_rating / len(self.skill_assessment) if len(self.skill_assessment) else 0
+ )
def update_interview_details(self):
- doc = frappe.get_doc('Interview', self.interview)
+ doc = frappe.get_doc("Interview", self.interview)
if self.docstatus == 2:
for entry in doc.interview_details:
@@ -77,5 +87,5 @@ class InterviewFeedback(Document):
@frappe.whitelist()
def get_applicable_interviewers(interview):
- data = frappe.get_all('Interview Detail', filters={'parent': interview}, fields=['interviewer'])
+ data = frappe.get_all("Interview Detail", filters={"parent": interview}, fields=["interviewer"])
return [d.interviewer for d in data]
diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
index 19c464296a..63d4775195 100644
--- a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
@@ -17,14 +17,16 @@ class TestInterviewFeedback(unittest.TestCase):
def test_validation_for_skill_set(self):
frappe.set_user("Administrator")
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1))
+ interview = create_interview_and_dependencies(
+ job_applicant.name, scheduled_on=add_days(getdate(), -1)
+ )
skill_ratings = get_skills_rating(interview.interview_round)
interviewer = interview.interview_details[0].interviewer
- create_skill_set(['Leadership'])
+ create_skill_set(["Leadership"])
interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings)
- interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 0.8})
+ interview_feedback.append("skill_assessment", {"skill": "Leadership", "rating": 0.8})
frappe.set_user(interviewer)
self.assertRaises(frappe.ValidationError, interview_feedback.save)
@@ -33,7 +35,9 @@ class TestInterviewFeedback(unittest.TestCase):
def test_average_ratings_on_feedback_submission_and_cancellation(self):
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1))
+ interview = create_interview_and_dependencies(
+ job_applicant.name, scheduled_on=add_days(getdate(), -1)
+ )
skill_ratings = get_skills_rating(interview.interview_round)
# For First Interviewer Feedback
@@ -48,20 +52,26 @@ class TestInterviewFeedback(unittest.TestCase):
if d.rating:
total_rating += d.rating
- avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0)
+ avg_rating = flt(
+ total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0
+ )
self.assertEqual(flt(avg_rating, 2), flt(feedback_1.average_rating, 2))
- avg_on_interview_detail = frappe.db.get_value('Interview Detail', {
- 'parent': feedback_1.interview,
- 'interviewer': feedback_1.interviewer,
- 'interview_feedback': feedback_1.name
- }, 'average_rating')
+ avg_on_interview_detail = frappe.db.get_value(
+ "Interview Detail",
+ {
+ "parent": feedback_1.interview,
+ "interviewer": feedback_1.interviewer,
+ "interview_feedback": feedback_1.name,
+ },
+ "average_rating",
+ )
# 1. average should be reflected in Interview Detail.
self.assertEqual(flt(avg_on_interview_detail, 2), flt(feedback_1.average_rating, 2))
- '''For Second Interviewer Feedback'''
+ """For Second Interviewer Feedback"""
interviewer = interview.interview_details[1].interviewer
frappe.set_user(interviewer)
@@ -95,7 +105,9 @@ def create_interview_feedback(interview, interviewer, skills_ratings):
def get_skills_rating(interview_round):
import random
- skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"])
+ skills = frappe.get_all(
+ "Expected Skill Set", filters={"parent": interview_round}, fields=["skill"]
+ )
for d in skills:
d["rating"] = random.random()
return skills
diff --git a/erpnext/hr/doctype/interview_round/interview_round.py b/erpnext/hr/doctype/interview_round/interview_round.py
index 0f442c320a..83dbf0ea98 100644
--- a/erpnext/hr/doctype/interview_round/interview_round.py
+++ b/erpnext/hr/doctype/interview_round/interview_round.py
@@ -11,6 +11,7 @@ from frappe.model.document import Document
class InterviewRound(Document):
pass
+
@frappe.whitelist()
def create_interview(doc):
if isinstance(doc, str):
@@ -24,10 +25,5 @@ def create_interview(doc):
if doc.interviewers:
interview.interview_details = []
for data in doc.interviewers:
- interview.append("interview_details", {
- "interviewer": data.user
- })
+ interview.append("interview_details", {"interviewer": data.user})
return interview
-
-
-
diff --git a/erpnext/hr/doctype/interview_round/test_interview_round.py b/erpnext/hr/doctype/interview_round/test_interview_round.py
index dcec9419c0..9568165374 100644
--- a/erpnext/hr/doctype/interview_round/test_interview_round.py
+++ b/erpnext/hr/doctype/interview_round/test_interview_round.py
@@ -8,4 +8,3 @@ import unittest
class TestInterviewRound(unittest.TestCase):
pass
-
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index ccc21ced2c..5b0a4cafbf 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -13,7 +13,9 @@ from frappe.utils import validate_email_address
from erpnext.hr.doctype.interview.interview import get_interviewers
-class DuplicationError(frappe.ValidationError): pass
+class DuplicationError(frappe.ValidationError):
+ pass
+
class JobApplicant(Document):
def onload(self):
@@ -36,8 +38,8 @@ class JobApplicant(Document):
self.set_status_for_employee_referral()
if not self.applicant_name and self.email_id:
- guess = self.email_id.split('@')[0]
- self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
+ guess = self.email_id.split("@")[0]
+ self.applicant_name = " ".join([p.capitalize() for p in guess.split(".")])
def set_status_for_employee_referral(self):
emp_ref = frappe.get_doc("Employee Referral", self.employee_referral)
@@ -46,11 +48,11 @@ class JobApplicant(Document):
elif self.status in ["Accepted", "Rejected"]:
emp_ref.db_set("status", self.status)
+
@frappe.whitelist()
def create_interview(doc, interview_round):
import json
-
if isinstance(doc, str):
doc = json.loads(doc)
doc = frappe.get_doc(doc)
@@ -58,7 +60,11 @@ def create_interview(doc, interview_round):
round_designation = frappe.db.get_value("Interview Round", interview_round, "designation")
if round_designation and doc.designation and round_designation != doc.designation:
- frappe.throw(_("Interview Round {0} is only applicable for the Designation {1}").format(interview_round, round_designation))
+ frappe.throw(
+ _("Interview Round {0} is only applicable for the Designation {1}").format(
+ interview_round, round_designation
+ )
+ )
interview = frappe.new_doc("Interview")
interview.interview_round = interview_round
@@ -69,23 +75,25 @@ def create_interview(doc, interview_round):
interviewer_detail = get_interviewers(interview_round)
for d in interviewer_detail:
- interview.append("interview_details", {
- "interviewer": d.interviewer
- })
+ interview.append("interview_details", {"interviewer": d.interviewer})
return interview
+
@frappe.whitelist()
def get_interview_details(job_applicant):
- interview_details = frappe.db.get_all("Interview",
- filters={"job_applicant":job_applicant, "docstatus": ["!=", 2]},
- fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"]
+ interview_details = frappe.db.get_all(
+ "Interview",
+ filters={"job_applicant": job_applicant, "docstatus": ["!=", 2]},
+ fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"],
)
interview_detail_map = {}
meta = frappe.get_meta("Interview")
number_of_stars = meta.get_options("expected_average_rating") or 5
for detail in interview_details:
- detail.expected_average_rating = detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0
+ detail.expected_average_rating = (
+ detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0
+ )
detail.average_rating = detail.average_rating * number_of_stars if detail.average_rating else 0
interview_detail_map[detail.name] = detail
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
index 56331ac132..14b944ac61 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
@@ -1,15 +1,9 @@
def get_data():
return {
- 'fieldname': 'job_applicant',
- 'transactions': [
- {
- 'items': ['Employee', 'Employee Onboarding']
- },
- {
- 'items': ['Job Offer', 'Appointment Letter']
- },
- {
- 'items': ['Interview']
- }
+ "fieldname": "job_applicant",
+ "transactions": [
+ {"items": ["Employee", "Employee Onboarding"]},
+ {"items": ["Job Offer", "Appointment Letter"]},
+ {"items": ["Interview"]},
],
}
diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
index bf1622028d..99d1161978 100644
--- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
@@ -10,21 +10,25 @@ from erpnext.hr.doctype.designation.test_designation import create_designation
class TestJobApplicant(unittest.TestCase):
def test_job_applicant_naming(self):
- applicant = frappe.get_doc({
- "doctype": "Job Applicant",
- "status": "Open",
- "applicant_name": "_Test Applicant",
- "email_id": "job_applicant_naming@example.com"
- }).insert()
- self.assertEqual(applicant.name, 'job_applicant_naming@example.com')
+ applicant = frappe.get_doc(
+ {
+ "doctype": "Job Applicant",
+ "status": "Open",
+ "applicant_name": "_Test Applicant",
+ "email_id": "job_applicant_naming@example.com",
+ }
+ ).insert()
+ self.assertEqual(applicant.name, "job_applicant_naming@example.com")
- applicant = frappe.get_doc({
- "doctype": "Job Applicant",
- "status": "Open",
- "applicant_name": "_Test Applicant",
- "email_id": "job_applicant_naming@example.com"
- }).insert()
- self.assertEqual(applicant.name, 'job_applicant_naming@example.com-1')
+ applicant = frappe.get_doc(
+ {
+ "doctype": "Job Applicant",
+ "status": "Open",
+ "applicant_name": "_Test Applicant",
+ "email_id": "job_applicant_naming@example.com",
+ }
+ ).insert()
+ self.assertEqual(applicant.name, "job_applicant_naming@example.com-1")
def tearDown(self):
frappe.db.rollback()
@@ -41,11 +45,13 @@ def create_job_applicant(**args):
if frappe.db.exists("Job Applicant", filters):
return frappe.get_doc("Job Applicant", filters)
- job_applicant = frappe.get_doc({
- "doctype": "Job Applicant",
- "status": args.status or "Open",
- "designation": create_designation().name
- })
+ job_applicant = frappe.get_doc(
+ {
+ "doctype": "Job Applicant",
+ "status": args.status or "Open",
+ "designation": create_designation().name,
+ }
+ )
job_applicant.update(filters)
job_applicant.save()
diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py
index 072fc73271..b46930a117 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.py
+++ b/erpnext/hr/doctype/job_offer/job_offer.py
@@ -17,9 +17,15 @@ class JobOffer(Document):
def validate(self):
self.validate_vacancies()
- job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant, "docstatus": ["!=", 2]})
+ job_offer = frappe.db.exists(
+ "Job Offer", {"job_applicant": self.job_applicant, "docstatus": ["!=", 2]}
+ )
if job_offer and job_offer != self.name:
- frappe.throw(_("Job Offer: {0} is already for Job Applicant: {1}").format(frappe.bold(job_offer), frappe.bold(self.job_applicant)))
+ frappe.throw(
+ _("Job Offer: {0} is already for Job Applicant: {1}").format(
+ frappe.bold(job_offer), frappe.bold(self.job_applicant)
+ )
+ )
def validate_vacancies(self):
staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date)
@@ -27,7 +33,7 @@ class JobOffer(Document):
if staffing_plan and check_vacancies:
job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)
if not staffing_plan.get("vacancies") or cint(staffing_plan.vacancies) - len(job_offers) <= 0:
- error_variable = 'for ' + frappe.bold(self.designation)
+ error_variable = "for " + frappe.bold(self.designation)
if staffing_plan.get("parent"):
error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))
@@ -37,20 +43,27 @@ class JobOffer(Document):
update_job_applicant(self.status, self.job_applicant)
def get_job_offer(self, from_date, to_date):
- ''' Returns job offer created during a time period '''
- return frappe.get_all("Job Offer", filters={
- "offer_date": ['between', (from_date, to_date)],
+ """Returns job offer created during a time period"""
+ return frappe.get_all(
+ "Job Offer",
+ filters={
+ "offer_date": ["between", (from_date, to_date)],
"designation": self.designation,
"company": self.company,
- "docstatus": 1
- }, fields=['name'])
+ "docstatus": 1,
+ },
+ fields=["name"],
+ )
+
def update_job_applicant(status, job_applicant):
if status in ("Accepted", "Rejected"):
frappe.set_value("Job Applicant", job_applicant, "status", status)
+
def get_staffing_plan_detail(designation, company, offer_date):
- detail = frappe.db.sql("""
+ detail = frappe.db.sql(
+ """
SELECT DISTINCT spd.parent,
sp.from_date as from_date,
sp.to_date as to_date,
@@ -64,21 +77,31 @@ def get_staffing_plan_detail(designation, company, offer_date):
AND sp.company=%s
AND spd.parent = sp.name
AND %s between sp.from_date and sp.to_date
- """, (designation, company, offer_date), as_dict=1)
+ """,
+ (designation, company, offer_date),
+ as_dict=1,
+ )
return frappe._dict(detail[0]) if (detail and detail[0].parent) else None
+
@frappe.whitelist()
def make_employee(source_name, target_doc=None):
def set_missing_values(source, target):
- target.personal_email, target.first_name = frappe.db.get_value("Job Applicant", \
- source.job_applicant, ["email_id", "applicant_name"])
- doc = get_mapped_doc("Job Offer", source_name, {
+ target.personal_email, target.first_name = frappe.db.get_value(
+ "Job Applicant", source.job_applicant, ["email_id", "applicant_name"]
+ )
+
+ doc = get_mapped_doc(
+ "Job Offer",
+ source_name,
+ {
"Job Offer": {
"doctype": "Employee",
- "field_map": {
- "applicant_name": "employee_name",
- "offer_date": "scheduled_confirmation_date"
- }}
- }, target_doc, set_missing_values)
+ "field_map": {"applicant_name": "employee_name", "offer_date": "scheduled_confirmation_date"},
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doc
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py
index d94e03ca63..7d8ef115d1 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.py
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.py
@@ -12,17 +12,19 @@ from erpnext.hr.doctype.staffing_plan.test_staffing_plan import make_company
# test_records = frappe.get_test_records('Job Offer')
+
class TestJobOffer(unittest.TestCase):
def test_job_offer_creation_against_vacancies(self):
frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
job_offer = create_job_offer(job_applicant=job_applicant.name, designation="UX Designer")
- create_staffing_plan(name='Test No Vacancies', staffing_details=[{
- "designation": "UX Designer",
- "vacancies": 0,
- "estimated_cost_per_position": 5000
- }])
+ create_staffing_plan(
+ name="Test No Vacancies",
+ staffing_details=[
+ {"designation": "UX Designer", "vacancies": 0, "estimated_cost_per_position": 5000}
+ ],
+ )
self.assertRaises(frappe.ValidationError, job_offer.submit)
# test creation of job offer when vacancies are not present
@@ -49,6 +51,7 @@ class TestJobOffer(unittest.TestCase):
def tearDown(self):
frappe.db.sql("DELETE FROM `tabJob Offer` WHERE 1")
+
def create_job_offer(**args):
args = frappe._dict(args)
if not args.job_applicant:
@@ -57,32 +60,34 @@ def create_job_offer(**args):
if not frappe.db.exists("Designation", args.designation):
designation = create_designation(designation_name=args.designation)
- job_offer = frappe.get_doc({
- "doctype": "Job Offer",
- "job_applicant": args.job_applicant or job_applicant.name,
- "offer_date": args.offer_date or nowdate(),
- "designation": args.designation or "Researcher",
- "status": args.status or "Accepted"
- })
+ job_offer = frappe.get_doc(
+ {
+ "doctype": "Job Offer",
+ "job_applicant": args.job_applicant or job_applicant.name,
+ "offer_date": args.offer_date or nowdate(),
+ "designation": args.designation or "Researcher",
+ "status": args.status or "Accepted",
+ }
+ )
return job_offer
+
def create_staffing_plan(**args):
args = frappe._dict(args)
make_company()
frappe.db.set_value("Company", "_Test Company", "is_group", 1)
if frappe.db.exists("Staffing Plan", args.name or "Test"):
return
- staffing_plan = frappe.get_doc({
- "doctype": "Staffing Plan",
- "name": args.name or "Test",
- "from_date": args.from_date or nowdate(),
- "to_date": args.to_date or add_days(nowdate(), 10),
- "staffing_details": args.staffing_details or [{
- "designation": "Researcher",
- "vacancies": 1,
- "estimated_cost_per_position": 50000
- }]
- })
+ staffing_plan = frappe.get_doc(
+ {
+ "doctype": "Staffing Plan",
+ "name": args.name or "Test",
+ "from_date": args.from_date or nowdate(),
+ "to_date": args.to_date or add_days(nowdate(), 10),
+ "staffing_details": args.staffing_details
+ or [{"designation": "Researcher", "vacancies": 1, "estimated_cost_per_position": 50000}],
+ }
+ )
staffing_plan.insert()
staffing_plan.submit()
return staffing_plan
diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py
index d53daf17d8..c71407d71d 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.py
+++ b/erpnext/hr/doctype/job_opening/job_opening.py
@@ -16,67 +16,78 @@ from erpnext.hr.doctype.staffing_plan.staffing_plan import (
class JobOpening(WebsiteGenerator):
website = frappe._dict(
- template = "templates/generators/job_opening.html",
- condition_field = "publish",
- page_title_field = "job_title",
+ template="templates/generators/job_opening.html",
+ condition_field="publish",
+ page_title_field="job_title",
)
def validate(self):
if not self.route:
- self.route = frappe.scrub(self.job_title).replace('_', '-')
+ self.route = frappe.scrub(self.job_title).replace("_", "-")
self.validate_current_vacancies()
def validate_current_vacancies(self):
if not self.staffing_plan:
- staffing_plan = get_active_staffing_plan_details(self.company,
- self.designation)
+ staffing_plan = get_active_staffing_plan_details(self.company, self.designation)
if staffing_plan:
self.staffing_plan = staffing_plan[0].name
self.planned_vacancies = staffing_plan[0].vacancies
elif not self.planned_vacancies:
- planned_vacancies = frappe.db.sql("""
+ planned_vacancies = frappe.db.sql(
+ """
select vacancies from `tabStaffing Plan Detail`
- where parent=%s and designation=%s""", (self.staffing_plan, self.designation))
+ where parent=%s and designation=%s""",
+ (self.staffing_plan, self.designation),
+ )
self.planned_vacancies = planned_vacancies[0][0] if planned_vacancies else None
if self.staffing_plan and self.planned_vacancies:
staffing_plan_company = frappe.db.get_value("Staffing Plan", self.staffing_plan, "company")
- lft, rgt = frappe.get_cached_value('Company', staffing_plan_company, ["lft", "rgt"])
+ lft, rgt = frappe.get_cached_value("Company", staffing_plan_company, ["lft", "rgt"])
designation_counts = get_designation_counts(self.designation, self.company)
- current_count = designation_counts['employee_count'] + designation_counts['job_openings']
+ current_count = designation_counts["employee_count"] + designation_counts["job_openings"]
if self.planned_vacancies <= current_count:
- frappe.throw(_("Job Openings for designation {0} already open or hiring completed as per Staffing Plan {1}").format(
- self.designation, self.staffing_plan))
+ frappe.throw(
+ _(
+ "Job Openings for designation {0} already open or hiring completed as per Staffing Plan {1}"
+ ).format(self.designation, self.staffing_plan)
+ )
def get_context(self, context):
- context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
+ context.parents = [{"route": "jobs", "title": _("All Jobs")}]
+
def get_list_context(context):
context.title = _("Jobs")
- context.introduction = _('Current Job Openings')
+ context.introduction = _("Current Job Openings")
context.get_list = get_job_openings
-def get_job_openings(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
- fields = ['name', 'status', 'job_title', 'description', 'publish_salary_range',
- 'lower_range', 'upper_range', 'currency', 'job_application_route']
+
+def get_job_openings(
+ doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None
+):
+ fields = [
+ "name",
+ "status",
+ "job_title",
+ "description",
+ "publish_salary_range",
+ "lower_range",
+ "upper_range",
+ "currency",
+ "job_application_route",
+ ]
filters = filters or {}
- filters.update({
- 'status': 'Open'
- })
+ filters.update({"status": "Open"})
if txt:
- filters.update({
- 'job_title': ['like', '%{0}%'.format(txt)],
- 'description': ['like', '%{0}%'.format(txt)]
- })
+ filters.update(
+ {"job_title": ["like", "%{0}%".format(txt)], "description": ["like", "%{0}%".format(txt)]}
+ )
- return frappe.get_all(doctype,
- filters,
- fields,
- start=limit_start,
- page_length=limit_page_length,
- order_by=order_by
+ return frappe.get_all(
+ doctype, filters, fields, start=limit_start, page_length=limit_page_length, order_by=order_by
)
diff --git a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
index 67600dc20e..a30932870d 100644
--- a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
+++ b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
- return {
- 'fieldname': 'job_title',
- 'transactions': [
- {
- 'items': ['Job Applicant']
- }
- ],
- }
+ return {
+ "fieldname": "job_title",
+ "transactions": [{"items": ["Job Applicant"]}],
+ }
diff --git a/erpnext/hr/doctype/job_opening/test_job_opening.py b/erpnext/hr/doctype/job_opening/test_job_opening.py
index a1c3a1d49e..a72a6eb338 100644
--- a/erpnext/hr/doctype/job_opening/test_job_opening.py
+++ b/erpnext/hr/doctype/job_opening/test_job_opening.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Job Opening')
+
class TestJobOpening(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 232118fd67..98408afab6 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -15,11 +15,25 @@ from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import (
from erpnext.hr.utils import get_leave_period, set_employee_name
-class OverlapError(frappe.ValidationError): pass
-class BackDatedAllocationError(frappe.ValidationError): pass
-class OverAllocationError(frappe.ValidationError): pass
-class LessAllocationError(frappe.ValidationError): pass
-class ValueMultiplierError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
+
+class BackDatedAllocationError(frappe.ValidationError):
+ pass
+
+
+class OverAllocationError(frappe.ValidationError):
+ pass
+
+
+class LessAllocationError(frappe.ValidationError):
+ pass
+
+
+class ValueMultiplierError(frappe.ValidationError):
+ pass
+
class LeaveAllocation(Document):
def validate(self):
@@ -35,16 +49,22 @@ class LeaveAllocation(Document):
def validate_leave_allocation_days(self):
company = frappe.db.get_value("Employee", self.employee, "company")
leave_period = get_leave_period(self.from_date, self.to_date, company)
- max_leaves_allowed = flt(frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed"))
+ max_leaves_allowed = flt(
+ frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
+ )
if max_leaves_allowed > 0:
leave_allocated = 0
if leave_period:
- leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type,
- leave_period[0].from_date, leave_period[0].to_date)
+ leave_allocated = get_leave_allocation_for_period(
+ self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date
+ )
leave_allocated += flt(self.new_leaves_allocated)
if leave_allocated > max_leaves_allowed:
- frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")
- .format(self.leave_type, self.employee))
+ frappe.throw(
+ _(
+ "Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period"
+ ).format(self.leave_type, self.employee)
+ )
def on_submit(self):
self.create_leave_ledger_entry()
@@ -69,20 +89,22 @@ class LeaveAllocation(Document):
"leaves": leaves_to_be_added,
"from_date": self.from_date,
"to_date": self.to_date,
- "is_carry_forward": 0
+ "is_carry_forward": 0,
}
create_leave_ledger_entry(self, args, True)
def get_existing_leave_count(self):
- ledger_entries = frappe.get_all("Leave Ledger Entry",
- filters={
- "transaction_type": "Leave Allocation",
- "transaction_name": self.name,
- "employee": self.employee,
- "company": self.company,
- "leave_type": self.leave_type
- },
- pluck="leaves")
+ ledger_entries = frappe.get_all(
+ "Leave Ledger Entry",
+ filters={
+ "transaction_type": "Leave Allocation",
+ "transaction_name": self.name,
+ "employee": self.employee,
+ "company": self.company,
+ "leave_type": self.leave_type,
+ },
+ pluck="leaves",
+ )
total_existing_leaves = 0
for entry in ledger_entries:
total_existing_leaves += entry
@@ -90,21 +112,33 @@ class LeaveAllocation(Document):
return total_existing_leaves
def validate_against_leave_applications(self):
- leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
- self.from_date, self.to_date)
+ leaves_taken = get_approved_leaves_for_period(
+ self.employee, self.leave_type, self.from_date, self.to_date
+ )
if flt(leaves_taken) > flt(self.total_leaves_allocated):
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
- frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
+ frappe.msgprint(
+ _(
+ "Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period"
+ ).format(self.total_leaves_allocated, leaves_taken)
+ )
else:
- frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
+ frappe.throw(
+ _(
+ "Total allocated leaves {0} cannot be less than already approved leaves {1} for the period"
+ ).format(self.total_leaves_allocated, leaves_taken),
+ LessAllocationError,
+ )
def update_leave_policy_assignments_when_no_allocations_left(self):
- allocations = frappe.db.get_list("Leave Allocation", filters = {
- "docstatus": 1,
- "leave_policy_assignment": self.leave_policy_assignment
- })
+ allocations = frappe.db.get_list(
+ "Leave Allocation",
+ filters={"docstatus": 1, "leave_policy_assignment": self.leave_policy_assignment},
+ )
if len(allocations) == 0:
- frappe.db.set_value("Leave Policy Assignment", self.leave_policy_assignment ,"leaves_allocated", 0)
+ frappe.db.set_value(
+ "Leave Policy Assignment", self.leave_policy_assignment, "leaves_allocated", 0
+ )
def validate_period(self):
if date_diff(self.to_date, self.from_date) <= 0:
@@ -112,10 +146,13 @@ class LeaveAllocation(Document):
def validate_lwp(self):
if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"):
- frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type))
+ frappe.throw(
+ _("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)
+ )
def validate_allocation_overlap(self):
- leave_allocation = frappe.db.sql("""
+ leave_allocation = frappe.db.sql(
+ """
SELECT
name
FROM `tabLeave Allocation`
@@ -123,29 +160,44 @@ class LeaveAllocation(Document):
employee=%s AND leave_type=%s
AND name <> %s AND docstatus=1
AND to_date >= %s AND from_date <= %s""",
- (self.employee, self.leave_type, self.name, self.from_date, self.to_date))
+ (self.employee, self.leave_type, self.name, self.from_date, self.to_date),
+ )
if leave_allocation:
- frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}")
- .format(self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date)))
+ frappe.msgprint(
+ _("{0} already allocated for Employee {1} for period {2} to {3}").format(
+ self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date)
+ )
+ )
- frappe.throw(_('Reference') + ': {0}'
- .format(leave_allocation[0][0]), OverlapError)
+ frappe.throw(
+ _("Reference")
+ + ': {0}'.format(leave_allocation[0][0]),
+ OverlapError,
+ )
def validate_back_dated_allocation(self):
- future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
+ future_allocation = frappe.db.sql(
+ """select name, from_date from `tabLeave Allocation`
where employee=%s and leave_type=%s and docstatus=1 and from_date > %s
- and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1)
+ and carry_forward=1""",
+ (self.employee, self.leave_type, self.to_date),
+ as_dict=1,
+ )
if future_allocation:
- frappe.throw(_("Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}")
- .format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
- BackDatedAllocationError)
+ frappe.throw(
+ _(
+ "Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}"
+ ).format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
+ BackDatedAllocationError,
+ )
@frappe.whitelist()
def set_total_leaves_allocated(self):
- self.unused_leaves = get_carry_forwarded_leaves(self.employee,
- self.leave_type, self.from_date, self.carry_forward)
+ self.unused_leaves = get_carry_forwarded_leaves(
+ self.employee, self.leave_type, self.from_date, self.carry_forward
+ )
self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated)
@@ -154,11 +206,14 @@ class LeaveAllocation(Document):
if self.carry_forward:
self.set_carry_forwarded_leaves_in_previous_allocation()
- if not self.total_leaves_allocated \
- and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") \
- and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"):
- frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}")
- .format(self.leave_type))
+ if (
+ not self.total_leaves_allocated
+ and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave")
+ and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory")
+ ):
+ frappe.throw(
+ _("Total leaves allocated is mandatory for Leave Type {0}").format(self.leave_type)
+ )
def limit_carry_forward_based_on_max_allowed_leaves(self):
max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
@@ -167,13 +222,17 @@ class LeaveAllocation(Document):
self.unused_leaves = max_leaves_allowed - flt(self.new_leaves_allocated)
def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=False):
- ''' Set carry forwarded leaves in previous allocation '''
+ """Set carry forwarded leaves in previous allocation"""
previous_allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee)
if on_cancel:
self.unused_leaves = 0.0
if previous_allocation:
- frappe.db.set_value("Leave Allocation", previous_allocation.name,
- 'carry_forwarded_leaves_count', self.unused_leaves)
+ frappe.db.set_value(
+ "Leave Allocation",
+ previous_allocation.name,
+ "carry_forwarded_leaves_count",
+ self.unused_leaves,
+ )
def validate_total_leaves_allocated(self):
# Adding a day to include To Date in the difference
@@ -183,13 +242,15 @@ class LeaveAllocation(Document):
def create_leave_ledger_entry(self, submit=True):
if self.unused_leaves:
- expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days")
+ expiry_days = frappe.db.get_value(
+ "Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days"
+ )
end_date = add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date
args = dict(
leaves=self.unused_leaves,
from_date=self.from_date,
- to_date= min(getdate(end_date), getdate(self.to_date)),
- is_carry_forward=1
+ to_date=min(getdate(end_date), getdate(self.to_date)),
+ is_carry_forward=1,
)
create_leave_ledger_entry(self, args, submit)
@@ -197,25 +258,31 @@ class LeaveAllocation(Document):
leaves=self.new_leaves_allocated,
from_date=self.from_date,
to_date=self.to_date,
- is_carry_forward=0
+ is_carry_forward=0,
)
create_leave_ledger_entry(self, args, submit)
+
def get_previous_allocation(from_date, leave_type, employee):
- ''' Returns document properties of previous allocation '''
- return frappe.db.get_value("Leave Allocation",
+ """Returns document properties of previous allocation"""
+ return frappe.db.get_value(
+ "Leave Allocation",
filters={
- 'to_date': ("<", from_date),
- 'leave_type': leave_type,
- 'employee': employee,
- 'docstatus': 1
+ "to_date": ("<", from_date),
+ "leave_type": leave_type,
+ "employee": employee,
+ "docstatus": 1,
},
- order_by='to_date DESC',
- fieldname=['name', 'from_date', 'to_date', 'employee', 'leave_type'], as_dict=1)
+ order_by="to_date DESC",
+ fieldname=["name", "from_date", "to_date", "employee", "leave_type"],
+ as_dict=1,
+ )
+
def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
leave_allocated = 0
- leave_allocations = frappe.db.sql("""
+ leave_allocations = frappe.db.sql(
+ """
select employee, leave_type, from_date, to_date, total_leaves_allocated
from `tabLeave Allocation`
where employee=%(employee)s and leave_type=%(leave_type)s
@@ -223,12 +290,10 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
- """, {
- "from_date": from_date,
- "to_date": to_date,
- "employee": employee,
- "leave_type": leave_type
- }, as_dict=1)
+ """,
+ {"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
+ as_dict=1,
+ )
if leave_allocations:
for leave_alloc in leave_allocations:
@@ -236,35 +301,42 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
return leave_allocated
+
@frappe.whitelist()
def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
- ''' Returns carry forwarded leaves for the given employee '''
+ """Returns carry forwarded leaves for the given employee"""
unused_leaves = 0.0
previous_allocation = get_previous_allocation(date, leave_type, employee)
if carry_forward and previous_allocation:
validate_carry_forward(leave_type)
- unused_leaves = get_unused_leaves(employee, leave_type,
- previous_allocation.from_date, previous_allocation.to_date)
+ unused_leaves = get_unused_leaves(
+ employee, leave_type, previous_allocation.from_date, previous_allocation.to_date
+ )
if unused_leaves:
- max_carry_forwarded_leaves = frappe.db.get_value("Leave Type",
- leave_type, "maximum_carry_forwarded_leaves")
+ max_carry_forwarded_leaves = frappe.db.get_value(
+ "Leave Type", leave_type, "maximum_carry_forwarded_leaves"
+ )
if max_carry_forwarded_leaves and unused_leaves > flt(max_carry_forwarded_leaves):
unused_leaves = flt(max_carry_forwarded_leaves)
return unused_leaves
+
def get_unused_leaves(employee, leave_type, from_date, to_date):
- ''' Returns unused leaves between the given period while skipping leave allocation expiry '''
- leaves = frappe.get_all("Leave Ledger Entry", filters={
- 'employee': employee,
- 'leave_type': leave_type,
- 'from_date': ('>=', from_date),
- 'to_date': ('<=', to_date)
- }, or_filters={
- 'is_expired': 0,
- 'is_carry_forward': 1
- }, fields=['sum(leaves) as leaves'])
- return flt(leaves[0]['leaves'])
+ """Returns unused leaves between the given period while skipping leave allocation expiry"""
+ leaves = frappe.get_all(
+ "Leave Ledger Entry",
+ filters={
+ "employee": employee,
+ "leave_type": leave_type,
+ "from_date": (">=", from_date),
+ "to_date": ("<=", to_date),
+ },
+ or_filters={"is_expired": 0, "is_carry_forward": 1},
+ fields=["sum(leaves) as leaves"],
+ )
+ return flt(leaves[0]["leaves"])
+
def validate_carry_forward(leave_type):
if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
index 631beef435..96e81db617 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
@@ -1,17 +1,6 @@
def get_data():
- return {
- 'fieldname': 'leave_allocation',
- 'transactions': [
- {
- 'items': ['Compensatory Leave Request']
- },
- {
- 'items': ['Leave Encashment']
- }
- ],
- 'reports': [
- {
- 'items': ['Employee Leave Balance']
- }
- ]
- }
+ return {
+ "fieldname": "leave_allocation",
+ "transactions": [{"items": ["Compensatory Leave Request"]}, {"items": ["Leave Encashment"]}],
+ "reports": [{"items": ["Employee Leave Balance"]}],
+ }
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 1fe91399a0..a53d4a82ba 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -31,7 +31,7 @@ class TestLeaveAllocation(unittest.TestCase):
"from_date": getdate("2015-10-01"),
"to_date": getdate("2015-10-31"),
"new_leaves_allocated": 5,
- "docstatus": 1
+ "docstatus": 1,
},
{
"doctype": "Leave Allocation",
@@ -41,39 +41,43 @@ class TestLeaveAllocation(unittest.TestCase):
"leave_type": "_Test Leave Type",
"from_date": getdate("2015-09-01"),
"to_date": getdate("2015-11-30"),
- "new_leaves_allocated": 5
- }
+ "new_leaves_allocated": 5,
+ },
]
frappe.get_doc(leaves[0]).save()
self.assertRaises(frappe.ValidationError, frappe.get_doc(leaves[1]).save)
def test_invalid_period(self):
- doc = frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": self.employee.name,
- "employee_name": self.employee.employee_name,
- "leave_type": "_Test Leave Type",
- "from_date": getdate("2015-09-30"),
- "to_date": getdate("2015-09-1"),
- "new_leaves_allocated": 5
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": self.employee.name,
+ "employee_name": self.employee.employee_name,
+ "leave_type": "_Test Leave Type",
+ "from_date": getdate("2015-09-30"),
+ "to_date": getdate("2015-09-1"),
+ "new_leaves_allocated": 5,
+ }
+ )
# invalid period
self.assertRaises(frappe.ValidationError, doc.save)
def test_allocated_leave_days_over_period(self):
- doc = frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": self.employee.name,
- "employee_name": self.employee.employee_name,
- "leave_type": "_Test Leave Type",
- "from_date": getdate("2015-09-1"),
- "to_date": getdate("2015-09-30"),
- "new_leaves_allocated": 35
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": self.employee.name,
+ "employee_name": self.employee.employee_name,
+ "leave_type": "_Test Leave Type",
+ "from_date": getdate("2015-09-1"),
+ "to_date": getdate("2015-09-30"),
+ "new_leaves_allocated": 35,
+ }
+ )
# allocated leave more than period
self.assertRaises(frappe.ValidationError, doc.save)
@@ -91,7 +95,8 @@ class TestLeaveAllocation(unittest.TestCase):
leave_type="_Test_CF_leave",
from_date=add_months(nowdate(), -12),
to_date=add_months(nowdate(), -1),
- carry_forward=0)
+ carry_forward=0,
+ )
leave_allocation.submit()
# carry forwarded leaves considering maximum_carry_forwarded_leaves
@@ -100,7 +105,8 @@ class TestLeaveAllocation(unittest.TestCase):
employee=self.employee.name,
employee_name=self.employee.employee_name,
leave_type="_Test_CF_leave",
- carry_forward=1)
+ carry_forward=1,
+ )
leave_allocation_1.submit()
self.assertEqual(leave_allocation_1.unused_leaves, 10)
@@ -114,7 +120,8 @@ class TestLeaveAllocation(unittest.TestCase):
employee_name=self.employee.employee_name,
leave_type="_Test_CF_leave",
carry_forward=1,
- new_leaves_allocated=25)
+ new_leaves_allocated=25,
+ )
leave_allocation_2.submit()
self.assertEqual(leave_allocation_2.unused_leaves, 5)
@@ -123,7 +130,8 @@ class TestLeaveAllocation(unittest.TestCase):
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.save()
# initial leave allocation
@@ -133,7 +141,8 @@ class TestLeaveAllocation(unittest.TestCase):
leave_type="_Test_CF_leave_expiry",
from_date=add_months(nowdate(), -24),
to_date=add_months(nowdate(), -12),
- carry_forward=0)
+ carry_forward=0,
+ )
leave_allocation.submit()
leave_allocation = create_leave_allocation(
@@ -142,7 +151,8 @@ class TestLeaveAllocation(unittest.TestCase):
leave_type="_Test_CF_leave_expiry",
from_date=add_days(nowdate(), -90),
to_date=add_days(nowdate(), 100),
- carry_forward=1)
+ carry_forward=1,
+ )
leave_allocation.submit()
# expires all the carry forwarded leaves after 90 days
@@ -155,19 +165,21 @@ class TestLeaveAllocation(unittest.TestCase):
leave_type="_Test_CF_leave_expiry",
carry_forward=1,
from_date=add_months(nowdate(), 6),
- to_date=add_months(nowdate(), 12))
+ to_date=add_months(nowdate(), 12),
+ )
leave_allocation_1.submit()
self.assertEqual(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
def test_creation_of_leave_ledger_entry_on_submit(self):
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=dict(transaction_name=leave_allocation.name)
+ )
self.assertEqual(len(leave_ledger_entry), 1)
self.assertEqual(leave_ledger_entry[0].employee, leave_allocation.employee)
@@ -176,12 +188,13 @@ class TestLeaveAllocation(unittest.TestCase):
# check if leave ledger entry is deleted on cancellation
leave_allocation.cancel()
- self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
+ self.assertFalse(
+ frappe.db.exists("Leave Ledger Entry", {"transaction_name": leave_allocation.name})
+ )
def test_leave_addition_after_submit(self):
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
@@ -191,8 +204,7 @@ class TestLeaveAllocation(unittest.TestCase):
def test_leave_subtraction_after_submit(self):
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
@@ -204,26 +216,29 @@ class TestLeaveAllocation(unittest.TestCase):
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
make_holiday_list()
- frappe.db.set_value("Company", self.employee.company, "default_holiday_list", "Salary Slip Test Holiday List")
+ frappe.db.set_value(
+ "Company", self.employee.company, "default_holiday_list", "Salary Slip Test Holiday List"
+ )
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
- leave_application = frappe.get_doc({
- "doctype": 'Leave Application',
- "employee": self.employee.name,
- "leave_type": "_Test Leave Type",
- "from_date": add_months(nowdate(), 2),
- "to_date": add_months(add_days(nowdate(), 10), 2),
- "company": self.employee.company,
- "docstatus": 1,
- "status": "Approved",
- "leave_approver": 'test@example.com'
- })
+ leave_application = frappe.get_doc(
+ {
+ "doctype": "Leave Application",
+ "employee": self.employee.name,
+ "leave_type": "_Test Leave Type",
+ "from_date": add_months(nowdate(), 2),
+ "to_date": add_months(add_days(nowdate(), 10), 2),
+ "company": self.employee.company,
+ "docstatus": 1,
+ "status": "Approved",
+ "leave_approver": "test@example.com",
+ }
+ )
leave_application.submit()
leave_application.reload()
@@ -232,22 +247,26 @@ class TestLeaveAllocation(unittest.TestCase):
leave_allocation.total_leaves_allocated = leave_application.total_leave_days - 1
self.assertRaises(frappe.ValidationError, leave_allocation.submit)
+
def create_leave_allocation(**args):
args = frappe._dict(args)
emp_id = make_employee("test_emp_leave_allocation@salary.com")
employee = frappe.get_doc("Employee", emp_id)
- return frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": args.employee or employee.name,
- "employee_name": args.employee_name or employee.employee_name,
- "leave_type": args.leave_type or "_Test Leave Type",
- "from_date": args.from_date or nowdate(),
- "new_leaves_allocated": args.new_leaves_allocated or 15,
- "carry_forward": args.carry_forward or 0,
- "to_date": args.to_date or add_months(nowdate(), 12)
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": args.employee or employee.name,
+ "employee_name": args.employee_name or employee.employee_name,
+ "leave_type": args.leave_type or "_Test Leave Type",
+ "from_date": args.from_date or nowdate(),
+ "new_leaves_allocated": args.new_leaves_allocated or 15,
+ "carry_forward": args.carry_forward or 0,
+ "to_date": args.to_date or add_months(nowdate(), 12),
+ }
+ )
+
test_dependencies = ["Employee", "Leave Type"]
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 2987c1ef0e..18c69f7113 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -32,15 +32,30 @@ from erpnext.hr.utils import (
)
-class LeaveDayBlockedError(frappe.ValidationError): pass
-class OverlapError(frappe.ValidationError): pass
-class AttendanceAlreadyMarkedError(frappe.ValidationError): pass
-class NotAnOptionalHoliday(frappe.ValidationError): pass
+class LeaveDayBlockedError(frappe.ValidationError):
+ pass
+
+
+class OverlapError(frappe.ValidationError):
+ pass
+
+
+class AttendanceAlreadyMarkedError(frappe.ValidationError):
+ pass
+
+
+class NotAnOptionalHoliday(frappe.ValidationError):
+ pass
+
+
class InsufficientLeaveBalanceError(frappe.ValidationError):
pass
+
+
class LeaveAcrossAllocationsError(frappe.ValidationError):
pass
+
from frappe.model.document import Document
@@ -60,7 +75,7 @@ class LeaveApplication(Document):
self.validate_salary_processed_days()
self.validate_attendance()
self.set_half_day_date()
- if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'):
+ if frappe.db.get_value("Leave Type", self.leave_type, "is_optional_leave"):
self.validate_optional_leave()
self.validate_applicable_after()
@@ -74,7 +89,9 @@ class LeaveApplication(Document):
def on_submit(self):
if self.status == "Open":
- frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted"))
+ frappe.throw(
+ _("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted")
+ )
self.validate_back_dated_application()
self.update_attendance()
@@ -101,7 +118,9 @@ class LeaveApplication(Document):
leave_type = frappe.get_doc("Leave Type", self.leave_type)
if leave_type.applicable_after > 0:
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
- leave_days = get_approved_leaves_for_period(self.employee, False, date_of_joining, self.from_date)
+ leave_days = get_approved_leaves_for_period(
+ self.employee, False, date_of_joining, self.from_date
+ )
number_of_days = date_diff(getdate(self.from_date), date_of_joining)
if number_of_days >= 0:
holidays = 0
@@ -109,29 +128,48 @@ class LeaveApplication(Document):
holidays = get_holidays(self.employee, date_of_joining, self.from_date)
number_of_days = number_of_days - leave_days - holidays
if number_of_days < leave_type.applicable_after:
- frappe.throw(_("{0} applicable after {1} working days").format(self.leave_type, leave_type.applicable_after))
+ frappe.throw(
+ _("{0} applicable after {1} working days").format(
+ self.leave_type, leave_type.applicable_after
+ )
+ )
def validate_dates(self):
if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"):
if self.from_date and getdate(self.from_date) < getdate():
- allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application")
+ allowed_role = frappe.db.get_single_value(
+ "HR Settings", "role_allowed_to_create_backdated_leave_application"
+ )
user = frappe.get_doc("User", frappe.session.user)
user_roles = [d.role for d in user.roles]
if not allowed_role:
- frappe.throw(_("Backdated Leave Application is restricted. Please set the {} in {}").format(
- frappe.bold("Role Allowed to Create Backdated Leave Application"), get_link_to_form("HR Settings", "HR Settings")))
+ frappe.throw(
+ _("Backdated Leave Application is restricted. Please set the {} in {}").format(
+ frappe.bold("Role Allowed to Create Backdated Leave Application"),
+ get_link_to_form("HR Settings", "HR Settings"),
+ )
+ )
- if (allowed_role and allowed_role not in user_roles):
- frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role))
+ if allowed_role and allowed_role not in user_roles:
+ frappe.throw(
+ _("Only users with the {0} role can create backdated leave applications").format(
+ allowed_role
+ )
+ )
if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
frappe.throw(_("To date cannot be before from date"))
- if self.half_day and self.half_day_date \
- and (getdate(self.half_day_date) < getdate(self.from_date)
- or getdate(self.half_day_date) > getdate(self.to_date)):
+ if (
+ self.half_day
+ and self.half_day_date
+ and (
+ getdate(self.half_day_date) < getdate(self.from_date)
+ or getdate(self.half_day_date) > getdate(self.to_date)
+ )
+ ):
- frappe.throw(_("Half Day Date should be between From Date and To Date"))
+ frappe.throw(_("Half Day Date should be between From Date and To Date"))
if not is_lwp(self.leave_type):
self.validate_dates_across_allocation()
@@ -146,10 +184,14 @@ class LeaveApplication(Document):
if not (alloc_on_from_date or alloc_on_to_date):
frappe.throw(_("Application period cannot be outside leave allocation period"))
elif self.is_separate_ledger_entry_required(alloc_on_from_date, alloc_on_to_date):
- frappe.throw(_("Application period cannot be across two allocation records"), exc=LeaveAcrossAllocationsError)
+ frappe.throw(
+ _("Application period cannot be across two allocation records"),
+ exc=LeaveAcrossAllocationsError,
+ )
def get_allocation_based_on_application_dates(self) -> Tuple[Dict, Dict]:
"""Returns allocation name, from and to dates for application dates"""
+
def _get_leave_allocation_record(date):
LeaveAllocation = frappe.qb.DocType("Leave Allocation")
allocation = (
@@ -171,13 +213,20 @@ class LeaveApplication(Document):
return allocation_based_on_from_date, allocation_based_on_to_date
def validate_back_dated_application(self):
- future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
+ future_allocation = frappe.db.sql(
+ """select name, from_date from `tabLeave Allocation`
where employee=%s and leave_type=%s and docstatus=1 and from_date > %s
- and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1)
+ and carry_forward=1""",
+ (self.employee, self.leave_type, self.to_date),
+ as_dict=1,
+ )
if future_allocation:
- frappe.throw(_("Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}")
- .format(formatdate(future_allocation[0].from_date), future_allocation[0].name))
+ frappe.throw(
+ _(
+ "Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}"
+ ).format(formatdate(future_allocation[0].from_date), future_allocation[0].name)
+ )
def update_attendance(self):
if self.status != "Approved":
@@ -189,8 +238,9 @@ class LeaveApplication(Document):
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
date = dt.strftime("%Y-%m-%d")
- attendance_name = frappe.db.exists("Attendance", dict(employee = self.employee,
- attendance_date = date, docstatus = ('!=', 2)))
+ attendance_name = frappe.db.exists(
+ "Attendance", dict(employee=self.employee, attendance_date=date, docstatus=("!=", 2))
+ )
# don't mark attendance for holidays
# if leave type does not include holidays within leaves as leaves
@@ -207,17 +257,17 @@ class LeaveApplication(Document):
self.create_or_update_attendance(attendance_name, date)
def create_or_update_attendance(self, attendance_name, date):
- status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
+ status = (
+ "Half Day"
+ if self.half_day_date and getdate(date) == getdate(self.half_day_date)
+ else "On Leave"
+ )
if attendance_name:
# update existing attendance, change absent to on leave
- doc = frappe.get_doc('Attendance', attendance_name)
+ doc = frappe.get_doc("Attendance", attendance_name)
if doc.status != status:
- doc.db_set({
- 'status': status,
- 'leave_type': self.leave_type,
- 'leave_application': self.name
- })
+ doc.db_set({"status": status, "leave_type": self.leave_type, "leave_application": self.name})
else:
# make new attendance and submit it
doc = frappe.new_doc("Attendance")
@@ -234,8 +284,12 @@ class LeaveApplication(Document):
def cancel_attendance(self):
if self.docstatus == 2:
- attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s\
- and (attendance_date between %s and %s) and docstatus < 2 and status in ('On Leave', 'Half Day')""",(self.employee, self.from_date, self.to_date), as_dict=1)
+ attendance = frappe.db.sql(
+ """select name from `tabAttendance` where employee = %s\
+ and (attendance_date between %s and %s) and docstatus < 2 and status in ('On Leave', 'Half Day')""",
+ (self.employee, self.from_date, self.to_date),
+ as_dict=1,
+ )
for name in attendance:
frappe.db.set_value("Attendance", name, "docstatus", 2)
@@ -243,21 +297,29 @@ class LeaveApplication(Document):
if not frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"):
return
- last_processed_pay_slip = frappe.db.sql("""
+ last_processed_pay_slip = frappe.db.sql(
+ """
select start_date, end_date from `tabSalary Slip`
where docstatus = 1 and employee = %s
and ((%s between start_date and end_date) or (%s between start_date and end_date))
order by modified desc limit 1
- """,(self.employee, self.to_date, self.from_date))
+ """,
+ (self.employee, self.to_date, self.from_date),
+ )
if last_processed_pay_slip:
- frappe.throw(_("Salary already processed for period between {0} and {1}, Leave application period cannot be between this date range.").format(formatdate(last_processed_pay_slip[0][0]),
- formatdate(last_processed_pay_slip[0][1])))
-
+ frappe.throw(
+ _(
+ "Salary already processed for period between {0} and {1}, Leave application period cannot be between this date range."
+ ).format(
+ formatdate(last_processed_pay_slip[0][0]), formatdate(last_processed_pay_slip[0][1])
+ )
+ )
def show_block_day_warning(self):
- block_dates = get_applicable_block_dates(self.from_date, self.to_date,
- self.employee, self.company, all_lists=True)
+ block_dates = get_applicable_block_dates(
+ self.from_date, self.to_date, self.employee, self.company, all_lists=True
+ )
if block_dates:
frappe.msgprint(_("Warning: Leave application contains following block dates") + ":")
@@ -265,27 +327,41 @@ class LeaveApplication(Document):
frappe.msgprint(formatdate(d.block_date) + ": " + d.reason)
def validate_block_days(self):
- block_dates = get_applicable_block_dates(self.from_date, self.to_date,
- self.employee, self.company)
+ block_dates = get_applicable_block_dates(
+ self.from_date, self.to_date, self.employee, self.company
+ )
if block_dates and self.status == "Approved":
frappe.throw(_("You are not authorized to approve leaves on Block Dates"), LeaveDayBlockedError)
def validate_balance_leaves(self):
if self.from_date and self.to_date:
- self.total_leave_days = get_number_of_leave_days(self.employee, self.leave_type,
- self.from_date, self.to_date, self.half_day, self.half_day_date)
+ self.total_leave_days = get_number_of_leave_days(
+ self.employee, self.leave_type, self.from_date, self.to_date, self.half_day, self.half_day_date
+ )
if self.total_leave_days <= 0:
- frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave."))
+ frappe.throw(
+ _(
+ "The day(s) on which you are applying for leave are holidays. You need not apply for leave."
+ )
+ )
if not is_lwp(self.leave_type):
- leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date,
- consider_all_leaves_in_the_allocation_period=True, for_consumption=True)
+ leave_balance = get_leave_balance_on(
+ self.employee,
+ self.leave_type,
+ self.from_date,
+ self.to_date,
+ consider_all_leaves_in_the_allocation_period=True,
+ for_consumption=True,
+ )
self.leave_balance = leave_balance.get("leave_balance")
leave_balance_for_consumption = leave_balance.get("leave_balance_for_consumption")
- if self.status != "Rejected" and (leave_balance_for_consumption < self.total_leave_days or not leave_balance_for_consumption):
+ if self.status != "Rejected" and (
+ leave_balance_for_consumption < self.total_leave_days or not leave_balance_for_consumption
+ ):
self.show_insufficient_balance_message(leave_balance_for_consumption)
def show_insufficient_balance_message(self, leave_balance_for_consumption: float) -> None:
@@ -293,39 +369,57 @@ class LeaveApplication(Document):
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
if leave_balance_for_consumption != self.leave_balance:
- msg = _("Warning: Insufficient leave balance for Leave Type {0} in this allocation.").format(frappe.bold(self.leave_type))
+ msg = _("Warning: Insufficient leave balance for Leave Type {0} in this allocation.").format(
+ frappe.bold(self.leave_type)
+ )
msg += "
"
- msg += _("Actual balances aren't available because the leave application spans over different leave allocations. You can still apply for leaves which would be compensated during the next allocation.")
+ msg += _(
+ "Actual balances aren't available because the leave application spans over different leave allocations. You can still apply for leaves which would be compensated during the next allocation."
+ )
else:
- msg = _("Warning: Insufficient leave balance for Leave Type {0}.").format(frappe.bold(self.leave_type))
+ msg = _("Warning: Insufficient leave balance for Leave Type {0}.").format(
+ frappe.bold(self.leave_type)
+ )
frappe.msgprint(msg, title=_("Warning"), indicator="orange")
else:
- frappe.throw(_("Insufficient leave balance for Leave Type {0}").format(frappe.bold(self.leave_type)),
- exc=InsufficientLeaveBalanceError, title=_("Insufficient Balance"))
+ frappe.throw(
+ _("Insufficient leave balance for Leave Type {0}").format(frappe.bold(self.leave_type)),
+ exc=InsufficientLeaveBalanceError,
+ title=_("Insufficient Balance"),
+ )
def validate_leave_overlap(self):
if not self.name:
# hack! if name is null, it could cause problems with !=
self.name = "New Leave Application"
- for d in frappe.db.sql("""
+ for d in frappe.db.sql(
+ """
select
name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
from `tabLeave Application`
where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
and to_date >= %(from_date)s and from_date <= %(to_date)s
- and name != %(name)s""", {
+ and name != %(name)s""",
+ {
"employee": self.employee,
"from_date": self.from_date,
"to_date": self.to_date,
- "name": self.name
- }, as_dict = 1):
+ "name": self.name,
+ },
+ as_dict=1,
+ ):
- if cint(self.half_day)==1 and getdate(self.half_day_date) == getdate(d.half_day_date) and (
- flt(self.total_leave_days)==0.5
- or getdate(self.from_date) == getdate(d.to_date)
- or getdate(self.to_date) == getdate(d.from_date)):
+ if (
+ cint(self.half_day) == 1
+ and getdate(self.half_day_date) == getdate(d.half_day_date)
+ and (
+ flt(self.total_leave_days) == 0.5
+ or getdate(self.from_date) == getdate(d.to_date)
+ or getdate(self.to_date) == getdate(d.from_date)
+ )
+ ):
total_leaves_on_half_day = self.get_total_leaves_on_half_day()
if total_leaves_on_half_day >= 1:
@@ -335,22 +429,22 @@ class LeaveApplication(Document):
def throw_overlap_error(self, d):
form_link = get_link_to_form("Leave Application", d.name)
- msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(self.employee,
- d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date']), form_link)
+ msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(
+ self.employee, d["leave_type"], formatdate(d["from_date"]), formatdate(d["to_date"]), form_link
+ )
frappe.throw(msg, OverlapError)
def get_total_leaves_on_half_day(self):
- leave_count_on_half_day_date = frappe.db.sql("""select count(name) from `tabLeave Application`
+ leave_count_on_half_day_date = frappe.db.sql(
+ """select count(name) from `tabLeave Application`
where employee = %(employee)s
and docstatus < 2
and status in ("Open", "Approved")
and half_day = 1
and half_day_date = %(half_day_date)s
- and name != %(name)s""", {
- "employee": self.employee,
- "half_day_date": self.half_day_date,
- "name": self.name
- })[0][0]
+ and name != %(name)s""",
+ {"employee": self.employee, "half_day_date": self.half_day_date, "name": self.name},
+ )[0][0]
return leave_count_on_half_day_date * 0.5
@@ -360,24 +454,36 @@ class LeaveApplication(Document):
frappe.throw(_("Leave of type {0} cannot be longer than {1}").format(self.leave_type, max_days))
def validate_attendance(self):
- attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s and (attendance_date between %s and %s)
+ attendance = frappe.db.sql(
+ """select name from `tabAttendance` where employee = %s and (attendance_date between %s and %s)
and status = "Present" and docstatus = 1""",
- (self.employee, self.from_date, self.to_date))
+ (self.employee, self.from_date, self.to_date),
+ )
if attendance:
- frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee),
- AttendanceAlreadyMarkedError)
+ frappe.throw(
+ _("Attendance for employee {0} is already marked for this day").format(self.employee),
+ AttendanceAlreadyMarkedError,
+ )
def validate_optional_leave(self):
leave_period = get_leave_period(self.from_date, self.to_date, self.company)
if not leave_period:
frappe.throw(_("Cannot find active Leave Period"))
- optional_holiday_list = frappe.db.get_value("Leave Period", leave_period[0]["name"], "optional_holiday_list")
+ optional_holiday_list = frappe.db.get_value(
+ "Leave Period", leave_period[0]["name"], "optional_holiday_list"
+ )
if not optional_holiday_list:
- frappe.throw(_("Optional Holiday List not set for leave period {0}").format(leave_period[0]["name"]))
+ frappe.throw(
+ _("Optional Holiday List not set for leave period {0}").format(leave_period[0]["name"])
+ )
day = getdate(self.from_date)
while day <= getdate(self.to_date):
- if not frappe.db.exists({"doctype": "Holiday", "parent": optional_holiday_list, "holiday_date": day}):
- frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday)
+ if not frappe.db.exists(
+ {"doctype": "Holiday", "parent": optional_holiday_list, "holiday_date": day}
+ ):
+ frappe.throw(
+ _("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday
+ )
day = add_days(day, 1)
def set_half_day_date(self):
@@ -392,44 +498,50 @@ class LeaveApplication(Document):
if not employee.user_id:
return
- parent_doc = frappe.get_doc('Leave Application', self.name)
+ parent_doc = frappe.get_doc("Leave Application", self.name)
args = parent_doc.as_dict()
- template = frappe.db.get_single_value('HR Settings', 'leave_status_notification_template')
+ template = frappe.db.get_single_value("HR Settings", "leave_status_notification_template")
if not template:
frappe.msgprint(_("Please set default template for Leave Status Notification in HR Settings."))
return
email_template = frappe.get_doc("Email Template", template)
message = frappe.render_template(email_template.response, args)
- self.notify({
- # for post in messages
- "message": message,
- "message_to": employee.user_id,
- # for email
- "subject": email_template.subject,
- "notify": "employee"
- })
+ self.notify(
+ {
+ # for post in messages
+ "message": message,
+ "message_to": employee.user_id,
+ # for email
+ "subject": email_template.subject,
+ "notify": "employee",
+ }
+ )
def notify_leave_approver(self):
if self.leave_approver:
- parent_doc = frappe.get_doc('Leave Application', self.name)
+ parent_doc = frappe.get_doc("Leave Application", self.name)
args = parent_doc.as_dict()
- template = frappe.db.get_single_value('HR Settings', 'leave_approval_notification_template')
+ template = frappe.db.get_single_value("HR Settings", "leave_approval_notification_template")
if not template:
- frappe.msgprint(_("Please set default template for Leave Approval Notification in HR Settings."))
+ frappe.msgprint(
+ _("Please set default template for Leave Approval Notification in HR Settings.")
+ )
return
email_template = frappe.get_doc("Email Template", template)
message = frappe.render_template(email_template.response, args)
- self.notify({
- # for post in messages
- "message": message,
- "message_to": self.leave_approver,
- # for email
- "subject": email_template.subject
- })
+ self.notify(
+ {
+ # for post in messages
+ "message": message,
+ "message_to": self.leave_approver,
+ # for email
+ "subject": email_template.subject,
+ }
+ )
def notify(self, args):
args = frappe._dict(args)
@@ -438,29 +550,30 @@ class LeaveApplication(Document):
contact = args.message_to
if not isinstance(contact, list):
if not args.notify == "employee":
- contact = frappe.get_doc('User', contact).email or contact
+ contact = frappe.get_doc("User", contact).email or contact
- sender = dict()
- sender['email'] = frappe.get_doc('User', frappe.session.user).email
- sender['full_name'] = get_fullname(sender['email'])
+ sender = dict()
+ sender["email"] = frappe.get_doc("User", frappe.session.user).email
+ sender["full_name"] = get_fullname(sender["email"])
try:
frappe.sendmail(
- recipients = contact,
- sender = sender['email'],
- subject = args.subject,
- message = args.message,
+ recipients=contact,
+ sender=sender["email"],
+ subject=args.subject,
+ message=args.message,
)
frappe.msgprint(_("Email sent to {0}").format(contact))
except frappe.OutgoingEmailError:
pass
def create_leave_ledger_entry(self, submit=True):
- if self.status != 'Approved' and submit:
+ if self.status != "Approved" and submit:
return
- expiry_date = get_allocation_expiry_for_cf_leaves(self.employee, self.leave_type,
- self.to_date, self.from_date)
+ expiry_date = get_allocation_expiry_for_cf_leaves(
+ self.employee, self.leave_type, self.to_date, self.from_date
+ )
lwp = frappe.db.get_value("Leave Type", self.leave_type, "is_lwp")
if expiry_date:
@@ -478,24 +591,42 @@ class LeaveApplication(Document):
from_date=self.from_date,
to_date=self.to_date,
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception)
+ or "",
)
create_leave_ledger_entry(self, args, submit)
- def is_separate_ledger_entry_required(self, alloc_on_from_date: Optional[Dict] = None, alloc_on_to_date: Optional[Dict] = None) -> bool:
+ def is_separate_ledger_entry_required(
+ self, alloc_on_from_date: Optional[Dict] = None, alloc_on_to_date: Optional[Dict] = None
+ ) -> bool:
"""Checks if application dates fall in separate allocations"""
- if ((alloc_on_from_date and not alloc_on_to_date)
+ if (
+ (alloc_on_from_date and not alloc_on_to_date)
or (not alloc_on_from_date and alloc_on_to_date)
- or (alloc_on_from_date and alloc_on_to_date and alloc_on_from_date.name != alloc_on_to_date.name)):
+ or (
+ alloc_on_from_date and alloc_on_to_date and alloc_on_from_date.name != alloc_on_to_date.name
+ )
+ ):
return True
return False
def create_separate_ledger_entries(self, alloc_on_from_date, alloc_on_to_date, submit, lwp):
"""Creates separate ledger entries for application period falling into separate allocations"""
# for creating separate ledger entries existing allocation periods should be consecutive
- if submit and alloc_on_from_date and alloc_on_to_date and add_days(alloc_on_from_date.to_date, 1) != alloc_on_to_date.from_date:
- frappe.throw(_("Leave Application period cannot be across two non-consecutive leave allocations {0} and {1}.").format(
- get_link_to_form("Leave Allocation", alloc_on_from_date.name), get_link_to_form("Leave Allocation", alloc_on_to_date)))
+ if (
+ submit
+ and alloc_on_from_date
+ and alloc_on_to_date
+ and add_days(alloc_on_from_date.to_date, 1) != alloc_on_to_date.from_date
+ ):
+ frappe.throw(
+ _(
+ "Leave Application period cannot be across two non-consecutive leave allocations {0} and {1}."
+ ).format(
+ get_link_to_form("Leave Allocation", alloc_on_from_date.name),
+ get_link_to_form("Leave Allocation", alloc_on_to_date),
+ )
+ )
raise_exception = False if frappe.flags.in_patch else True
@@ -506,38 +637,48 @@ class LeaveApplication(Document):
first_alloc_end = add_days(alloc_on_to_date.from_date, -1)
second_alloc_start = alloc_on_to_date.from_date
- leaves_in_first_alloc = get_number_of_leave_days(self.employee, self.leave_type,
- self.from_date, first_alloc_end, self.half_day, self.half_day_date)
- leaves_in_second_alloc = get_number_of_leave_days(self.employee, self.leave_type,
- second_alloc_start, self.to_date, self.half_day, self.half_day_date)
+ leaves_in_first_alloc = get_number_of_leave_days(
+ self.employee,
+ self.leave_type,
+ self.from_date,
+ first_alloc_end,
+ self.half_day,
+ self.half_day_date,
+ )
+ leaves_in_second_alloc = get_number_of_leave_days(
+ self.employee,
+ self.leave_type,
+ second_alloc_start,
+ self.to_date,
+ self.half_day,
+ self.half_day_date,
+ )
args = dict(
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception)
+ or "",
)
if leaves_in_first_alloc:
- args.update(dict(
- from_date=self.from_date,
- to_date=first_alloc_end,
- leaves=leaves_in_first_alloc * -1
- ))
+ args.update(
+ dict(from_date=self.from_date, to_date=first_alloc_end, leaves=leaves_in_first_alloc * -1)
+ )
create_leave_ledger_entry(self, args, submit)
if leaves_in_second_alloc:
- args.update(dict(
- from_date=second_alloc_start,
- to_date=self.to_date,
- leaves=leaves_in_second_alloc * -1
- ))
+ args.update(
+ dict(from_date=second_alloc_start, to_date=self.to_date, leaves=leaves_in_second_alloc * -1)
+ )
create_leave_ledger_entry(self, args, submit)
def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp):
"""Splits leave application into two ledger entries to consider expiry of allocation"""
raise_exception = False if frappe.flags.in_patch else True
- leaves = get_number_of_leave_days(self.employee, self.leave_type,
- self.from_date, expiry_date, self.half_day, self.half_day_date)
+ leaves = get_number_of_leave_days(
+ self.employee, self.leave_type, self.from_date, expiry_date, self.half_day, self.half_day_date
+ )
if leaves:
args = dict(
@@ -545,41 +686,51 @@ class LeaveApplication(Document):
to_date=expiry_date,
leaves=leaves * -1,
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception)
+ or "",
)
create_leave_ledger_entry(self, args, submit)
if getdate(expiry_date) != getdate(self.to_date):
start_date = add_days(expiry_date, 1)
- leaves = get_number_of_leave_days(self.employee, self.leave_type,
- start_date, self.to_date, self.half_day, self.half_day_date)
+ leaves = get_number_of_leave_days(
+ self.employee, self.leave_type, start_date, self.to_date, self.half_day, self.half_day_date
+ )
if leaves:
- args.update(dict(
- from_date=start_date,
- to_date=self.to_date,
- leaves=leaves * -1
- ))
+ args.update(dict(from_date=start_date, to_date=self.to_date, leaves=leaves * -1))
create_leave_ledger_entry(self, args, submit)
-def get_allocation_expiry_for_cf_leaves(employee: str, leave_type: str, to_date: str, from_date: str) -> str:
- ''' Returns expiry of carry forward allocation in leave ledger entry '''
- expiry = frappe.get_all("Leave Ledger Entry",
+def get_allocation_expiry_for_cf_leaves(
+ employee: str, leave_type: str, to_date: str, from_date: str
+) -> str:
+ """Returns expiry of carry forward allocation in leave ledger entry"""
+ expiry = frappe.get_all(
+ "Leave Ledger Entry",
filters={
- 'employee': employee,
- 'leave_type': leave_type,
- 'is_carry_forward': 1,
- 'transaction_type': 'Leave Allocation',
- 'to_date': ['between', (from_date, to_date)],
- 'docstatus': 1
- },fields=['to_date'])
- return expiry[0]['to_date'] if expiry else ''
+ "employee": employee,
+ "leave_type": leave_type,
+ "is_carry_forward": 1,
+ "transaction_type": "Leave Allocation",
+ "to_date": ["between", (from_date, to_date)],
+ "docstatus": 1,
+ },
+ fields=["to_date"],
+ )
+ return expiry[0]["to_date"] if expiry else ""
@frappe.whitelist()
-def get_number_of_leave_days(employee: str, leave_type: str, from_date: str, to_date: str, half_day: Optional[int] = None,
- half_day_date: Optional[str] = None, holiday_list: Optional[str] = None) -> float:
+def get_number_of_leave_days(
+ employee: str,
+ leave_type: str,
+ from_date: str,
+ to_date: str,
+ half_day: Optional[int] = None,
+ half_day_date: Optional[str] = None,
+ holiday_list: Optional[str] = None,
+) -> float:
"""Returns number of leave days between 2 dates after considering half day and holidays
(Based on the include_holiday setting in Leave Type)"""
number_of_days = 0
@@ -587,14 +738,16 @@ def get_number_of_leave_days(employee: str, leave_type: str, from_date: str, to_
if from_date == to_date:
number_of_days = 0.5
elif half_day_date and half_day_date <= to_date:
- number_of_days = date_diff(to_date, from_date) + .5
+ number_of_days = date_diff(to_date, from_date) + 0.5
else:
number_of_days = date_diff(to_date, from_date) + 1
else:
number_of_days = date_diff(to_date, from_date) + 1
if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"):
- number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date, holiday_list=holiday_list))
+ number_of_days = flt(number_of_days) - flt(
+ get_holidays(employee, from_date, to_date, holiday_list=holiday_list)
+ )
return number_of_days
@@ -605,54 +758,71 @@ def get_leave_details(employee, date):
for d in allocation_records:
allocation = allocation_records.get(d, frappe._dict())
- total_allocated_leaves = frappe.db.get_value('Leave Allocation', {
- 'from_date': ('<=', date),
- 'to_date': ('>=', date),
- 'employee': employee,
- 'leave_type': allocation.leave_type,
- 'docstatus': 1
- }, 'SUM(total_leaves_allocated)') or 0
+ total_allocated_leaves = (
+ frappe.db.get_value(
+ "Leave Allocation",
+ {
+ "from_date": ("<=", date),
+ "to_date": (">=", date),
+ "employee": employee,
+ "leave_type": allocation.leave_type,
+ "docstatus": 1,
+ },
+ "SUM(total_leaves_allocated)",
+ )
+ or 0
+ )
- remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date,
- consider_all_leaves_in_the_allocation_period=True)
+ remaining_leaves = get_leave_balance_on(
+ employee, d, date, to_date=allocation.to_date, consider_all_leaves_in_the_allocation_period=True
+ )
end_date = allocation.to_date
leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, end_date) * -1
- leaves_pending = get_leaves_pending_approval_for_period(employee, d, allocation.from_date, end_date)
+ leaves_pending = get_leaves_pending_approval_for_period(
+ employee, d, allocation.from_date, end_date
+ )
leave_allocation[d] = {
"total_leaves": total_allocated_leaves,
"expired_leaves": total_allocated_leaves - (remaining_leaves + leaves_taken),
"leaves_taken": leaves_taken,
"leaves_pending_approval": leaves_pending,
- "remaining_leaves": remaining_leaves}
+ "remaining_leaves": remaining_leaves,
+ }
- #is used in set query
+ # is used in set query
lwp = frappe.get_list("Leave Type", filters={"is_lwp": 1}, pluck="name")
return {
"leave_allocation": leave_allocation,
"leave_approver": get_leave_approver(employee),
- "lwps": lwp
+ "lwps": lwp,
}
@frappe.whitelist()
-def get_leave_balance_on(employee: str, leave_type: str, date: str, to_date: str = None,
- consider_all_leaves_in_the_allocation_period: bool = False, for_consumption: bool = False):
- '''
- Returns leave balance till date
- :param employee: employee name
- :param leave_type: leave type
- :param date: date to check balance on
- :param to_date: future date to check for allocation expiry
- :param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date
- :param for_consumption: flag to check if leave balance is required for consumption or display
- eg: employee has leave balance = 10 but allocation is expiring in 1 day so employee can only consume 1 leave
- in this case leave_balance = 10 but leave_balance_for_consumption = 1
- if True, returns a dict eg: {'leave_balance': 10, 'leave_balance_for_consumption': 1}
- else, returns leave_balance (in this case 10)
- '''
+def get_leave_balance_on(
+ employee: str,
+ leave_type: str,
+ date: str,
+ to_date: str = None,
+ consider_all_leaves_in_the_allocation_period: bool = False,
+ for_consumption: bool = False,
+):
+ """
+ Returns leave balance till date
+ :param employee: employee name
+ :param leave_type: leave type
+ :param date: date to check balance on
+ :param to_date: future date to check for allocation expiry
+ :param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date
+ :param for_consumption: flag to check if leave balance is required for consumption or display
+ eg: employee has leave balance = 10 but allocation is expiring in 1 day so employee can only consume 1 leave
+ in this case leave_balance = 10 but leave_balance_for_consumption = 1
+ if True, returns a dict eg: {'leave_balance': 10, 'leave_balance_for_consumption': 1}
+ else, returns leave_balance (in this case 10)
+ """
if not to_date:
to_date = nowdate()
@@ -670,17 +840,21 @@ def get_leave_balance_on(employee: str, leave_type: str, date: str, to_date: str
if for_consumption:
return remaining_leaves
else:
- return remaining_leaves.get('leave_balance')
+ return remaining_leaves.get("leave_balance")
def get_leave_allocation_records(employee, date, leave_type=None):
"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
Ledger = frappe.qb.DocType("Leave Ledger Entry")
- cf_leave_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
+ cf_leave_case = (
+ frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
+ )
sum_cf_leaves = Sum(cf_leave_case).as_("cf_leaves")
- new_leaves_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
+ new_leaves_case = (
+ frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
+ )
sum_new_leaves = Sum(new_leaves_case).as_("new_leaves")
query = (
@@ -690,8 +864,9 @@ def get_leave_allocation_records(employee, date, leave_type=None):
sum_new_leaves,
Min(Ledger.from_date).as_("from_date"),
Max(Ledger.to_date).as_("to_date"),
- Ledger.leave_type
- ).where(
+ Ledger.leave_type,
+ )
+ .where(
(Ledger.from_date <= date)
& (Ledger.to_date >= date)
& (Ledger.docstatus == 1)
@@ -710,46 +885,57 @@ def get_leave_allocation_records(employee, date, leave_type=None):
allocated_leaves = frappe._dict()
for d in allocation_details:
- allocated_leaves.setdefault(d.leave_type, frappe._dict({
- "from_date": d.from_date,
- "to_date": d.to_date,
- "total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves),
- "unused_leaves": d.cf_leaves,
- "new_leaves_allocated": d.new_leaves,
- "leave_type": d.leave_type
- }))
+ allocated_leaves.setdefault(
+ d.leave_type,
+ frappe._dict(
+ {
+ "from_date": d.from_date,
+ "to_date": d.to_date,
+ "total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves),
+ "unused_leaves": d.cf_leaves,
+ "new_leaves_allocated": d.new_leaves,
+ "leave_type": d.leave_type,
+ }
+ ),
+ )
return allocated_leaves
-def get_leaves_pending_approval_for_period(employee: str, leave_type: str, from_date: str, to_date: str) -> float:
- ''' Returns leaves that are pending for approval '''
- leaves = frappe.get_all("Leave Application",
- filters={
- "employee": employee,
- "leave_type": leave_type,
- "status": "Open"
- },
+def get_leaves_pending_approval_for_period(
+ employee: str, leave_type: str, from_date: str, to_date: str
+) -> float:
+ """Returns leaves that are pending for approval"""
+ leaves = frappe.get_all(
+ "Leave Application",
+ filters={"employee": employee, "leave_type": leave_type, "status": "Open"},
or_filters={
"from_date": ["between", (from_date, to_date)],
- "to_date": ["between", (from_date, to_date)]
- }, fields=['SUM(total_leave_days) as leaves'])[0]
- return leaves['leaves'] if leaves['leaves'] else 0.0
+ "to_date": ["between", (from_date, to_date)],
+ },
+ fields=["SUM(total_leave_days) as leaves"],
+ )[0]
+ return leaves["leaves"] if leaves["leaves"] else 0.0
-def get_remaining_leaves(allocation: Dict, leaves_taken: float, date: str, cf_expiry: str) -> Dict[str, float]:
- '''Returns a dict of leave_balance and leave_balance_for_consumption
+def get_remaining_leaves(
+ allocation: Dict, leaves_taken: float, date: str, cf_expiry: str
+) -> Dict[str, float]:
+ """Returns a dict of leave_balance and leave_balance_for_consumption
leave_balance returns the available leave balance
leave_balance_for_consumption returns the minimum leaves remaining after comparing with remaining days for allocation expiry
- '''
+ """
+
def _get_remaining_leaves(remaining_leaves, end_date):
- ''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry '''
+ """Returns minimum leaves remaining after comparing with remaining days for allocation expiry"""
if remaining_leaves > 0:
remaining_days = date_diff(end_date, date) + 1
remaining_leaves = min(remaining_days, remaining_leaves)
return remaining_leaves
- leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(leaves_taken)
+ leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(
+ leaves_taken
+ )
# balance for carry forwarded leaves
if cf_expiry and allocation.unused_leaves:
@@ -763,21 +949,29 @@ def get_remaining_leaves(allocation: Dict, leaves_taken: float, date: str, cf_ex
return frappe._dict(leave_balance=leave_balance, leave_balance_for_consumption=remaining_leaves)
-def get_leaves_for_period(employee: str, leave_type: str, from_date: str, to_date: str, skip_expired_leaves: bool = True) -> float:
+def get_leaves_for_period(
+ employee: str, leave_type: str, from_date: str, to_date: str, skip_expired_leaves: bool = True
+) -> float:
leave_entries = get_leave_entries(employee, leave_type, from_date, to_date)
leave_days = 0
for leave_entry in leave_entries:
- inclusive_period = leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(to_date)
+ inclusive_period = leave_entry.from_date >= getdate(
+ from_date
+ ) and leave_entry.to_date <= getdate(to_date)
- if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
+ if inclusive_period and leave_entry.transaction_type == "Leave Encashment":
leave_days += leave_entry.leaves
- elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \
- and not skip_expired_leaves:
+ elif (
+ inclusive_period
+ and leave_entry.transaction_type == "Leave Allocation"
+ and leave_entry.is_expired
+ and not skip_expired_leaves
+ ):
leave_days += leave_entry.leaves
- elif leave_entry.transaction_type == 'Leave Application':
+ elif leave_entry.transaction_type == "Leave Application":
if leave_entry.from_date < getdate(from_date):
leave_entry.from_date = from_date
if leave_entry.to_date > getdate(to_date):
@@ -788,18 +982,30 @@ def get_leaves_for_period(employee: str, leave_type: str, from_date: str, to_dat
# fetch half day date for leaves with half days
if leave_entry.leaves % 1:
half_day = 1
- half_day_date = frappe.db.get_value('Leave Application',
- {'name': leave_entry.transaction_name}, ['half_day_date'])
+ half_day_date = frappe.db.get_value(
+ "Leave Application", {"name": leave_entry.transaction_name}, ["half_day_date"]
+ )
- leave_days += get_number_of_leave_days(employee, leave_type,
- leave_entry.from_date, leave_entry.to_date, half_day, half_day_date, holiday_list=leave_entry.holiday_list) * -1
+ leave_days += (
+ get_number_of_leave_days(
+ employee,
+ leave_type,
+ leave_entry.from_date,
+ leave_entry.to_date,
+ half_day,
+ half_day_date,
+ holiday_list=leave_entry.holiday_list,
+ )
+ * -1
+ )
return leave_days
def get_leave_entries(employee, leave_type, from_date, to_date):
- ''' Returns leave entries between from_date and to_date. '''
- return frappe.db.sql("""
+ """Returns leave entries between from_date and to_date."""
+ return frappe.db.sql(
+ """
SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, holiday_list,
is_carry_forward, is_expired
@@ -811,26 +1017,28 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
AND (from_date between %(from_date)s AND %(to_date)s
OR to_date between %(from_date)s AND %(to_date)s
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
- """, {
- "from_date": from_date,
- "to_date": to_date,
- "employee": employee,
- "leave_type": leave_type
- }, as_dict=1)
+ """,
+ {"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
+ as_dict=1,
+ )
@frappe.whitelist()
-def get_holidays(employee, from_date, to_date, holiday_list = None):
- '''get holidays between two dates for the given employee'''
+def get_holidays(employee, from_date, to_date, holiday_list=None):
+ """get holidays between two dates for the given employee"""
if not holiday_list:
holiday_list = get_holiday_list_for_employee(employee)
- holidays = frappe.db.sql("""select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2
+ holidays = frappe.db.sql(
+ """select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2
where h1.parent = h2.name and h1.holiday_date between %s and %s
- and h2.name = %s""", (from_date, to_date, holiday_list))[0][0]
+ and h2.name = %s""",
+ (from_date, to_date, holiday_list),
+ )[0][0]
return holidays
+
def is_lwp(leave_type):
lwp = frappe.db.sql("select is_lwp from `tabLeave Type` where name = %s", leave_type)
return lwp and cint(lwp[0][0]) or 0
@@ -839,18 +1047,17 @@ def is_lwp(leave_type):
@frappe.whitelist()
def get_events(start, end, filters=None):
from frappe.desk.reportview import get_filters_cond
+
events = []
- employee = frappe.db.get_value("Employee",
- filters={"user_id": frappe.session.user},
- fieldname=["name", "company"],
- as_dict=True
+ employee = frappe.db.get_value(
+ "Employee", filters={"user_id": frappe.session.user}, fieldname=["name", "company"], as_dict=True
)
if employee:
employee, company = employee.name, employee.company
else:
- employee = ''
+ employee = ""
company = frappe.db.get_value("Global Defaults", None, "default_company")
conditions = get_filters_cond("Leave Application", filters, [])
@@ -872,18 +1079,24 @@ def add_department_leaves(events, start, end, employee, company):
return
# department leaves
- department_employees = frappe.db.sql_list("""select name from tabEmployee where department=%s
- and company=%s""", (department, company))
+ department_employees = frappe.db.sql_list(
+ """select name from tabEmployee where department=%s
+ and company=%s""",
+ (department, company),
+ )
- filter_conditions = " and employee in (\"%s\")" % '", "'.join(department_employees)
+ filter_conditions = ' and employee in ("%s")' % '", "'.join(department_employees)
add_leaves(events, start, end, filter_conditions=filter_conditions)
def add_leaves(events, start, end, filter_conditions=None):
from frappe.desk.reportview import build_match_conditions
+
conditions = []
- if not cint(frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")):
+ if not cint(
+ frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")
+ ):
match_conditions = build_match_conditions("Leave Application")
if match_conditions:
@@ -908,12 +1121,12 @@ def add_leaves(events, start, end, filter_conditions=None):
"""
if conditions:
- query += ' AND ' + ' AND '.join(conditions)
+ query += " AND " + " AND ".join(conditions)
if filter_conditions:
query += filter_conditions
- for d in frappe.db.sql(query, {"start":start, "end": end}, as_dict=True):
+ for d in frappe.db.sql(query, {"start": start, "end": end}, as_dict=True):
e = {
"name": d.name,
"doctype": "Leave Application",
@@ -922,7 +1135,9 @@ def add_leaves(events, start, end, filter_conditions=None):
"docstatus": d.docstatus,
"color": d.color,
"all_day": int(not d.half_day),
- "title": cstr(d.employee_name) + f' ({cstr(d.leave_type)})' + (' ' + _('(Half Day)') if d.half_day else ''),
+ "title": cstr(d.employee_name)
+ + f" ({cstr(d.leave_type)})"
+ + (" " + _("(Half Day)") if d.half_day else ""),
}
if e not in events:
events.append(e)
@@ -936,14 +1151,16 @@ def add_block_dates(events, start, end, employee, company):
block_dates = get_applicable_block_dates(start, end, employee, company, all_lists=True)
for block_date in block_dates:
- events.append({
- "doctype": "Leave Block List Date",
- "from_date": block_date.block_date,
- "to_date": block_date.block_date,
- "title": _("Leave Blocked") + ": " + block_date.reason,
- "name": "_" + str(cnt),
- })
- cnt+=1
+ events.append(
+ {
+ "doctype": "Leave Block List Date",
+ "from_date": block_date.block_date,
+ "to_date": block_date.block_date,
+ "title": _("Leave Blocked") + ": " + block_date.reason,
+ "name": "_" + str(cnt),
+ }
+ )
+ cnt += 1
def add_holidays(events, start, end, employee, company):
@@ -951,27 +1168,34 @@ def add_holidays(events, start, end, employee, company):
if not applicable_holiday_list:
return
- for holiday in frappe.db.sql("""select name, holiday_date, description
+ for holiday in frappe.db.sql(
+ """select name, holiday_date, description
from `tabHoliday` where parent=%s and holiday_date between %s and %s""",
- (applicable_holiday_list, start, end), as_dict=True):
- events.append({
+ (applicable_holiday_list, start, end),
+ as_dict=True,
+ ):
+ events.append(
+ {
"doctype": "Holiday",
"from_date": holiday.holiday_date,
- "to_date": holiday.holiday_date,
+ "to_date": holiday.holiday_date,
"title": _("Holiday") + ": " + cstr(holiday.description),
- "name": holiday.name
- })
+ "name": holiday.name,
+ }
+ )
@frappe.whitelist()
def get_mandatory_approval(doctype):
mandatory = ""
if doctype == "Leave Application":
- mandatory = frappe.db.get_single_value('HR Settings',
- 'leave_approver_mandatory_in_leave_application')
+ mandatory = frappe.db.get_single_value(
+ "HR Settings", "leave_approver_mandatory_in_leave_application"
+ )
else:
- mandatory = frappe.db.get_single_value('HR Settings',
- 'expense_approver_mandatory_in_expense_claim')
+ mandatory = frappe.db.get_single_value(
+ "HR Settings", "expense_approver_mandatory_in_expense_claim"
+ )
return mandatory
@@ -989,12 +1213,11 @@ def get_approved_leaves_for_period(employee, leave_type, from_date, to_date):
if leave_type:
query += "and leave_type=%(leave_type)s"
- leave_applications = frappe.db.sql(query,{
- "from_date": from_date,
- "to_date": to_date,
- "employee": employee,
- "leave_type": leave_type
- }, as_dict=1)
+ leave_applications = frappe.db.sql(
+ query,
+ {"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
+ as_dict=1,
+ )
leave_days = 0
for leave_app in leave_applications:
@@ -1006,19 +1229,24 @@ def get_approved_leaves_for_period(employee, leave_type, from_date, to_date):
if leave_app.to_date > getdate(to_date):
leave_app.to_date = to_date
- leave_days += get_number_of_leave_days(employee, leave_type,
- leave_app.from_date, leave_app.to_date)
+ leave_days += get_number_of_leave_days(
+ employee, leave_type, leave_app.from_date, leave_app.to_date
+ )
return leave_days
@frappe.whitelist()
def get_leave_approver(employee):
- leave_approver, department = frappe.db.get_value("Employee",
- employee, ["leave_approver", "department"])
+ leave_approver, department = frappe.db.get_value(
+ "Employee", employee, ["leave_approver", "department"]
+ )
if not leave_approver and department:
- leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
- 'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
+ leave_approver = frappe.db.get_value(
+ "Department Approver",
+ {"parent": department, "parentfield": "leave_approvers", "idx": 1},
+ "approver",
+ )
return leave_approver
diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
index 8b0b98ddc0..ee5cbe99f3 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
+++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
@@ -3,16 +3,7 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'leave_application',
- 'transactions': [
- {
- 'items': ['Attendance']
- }
- ],
- 'reports': [
- {
- 'label': _('Reports'),
- 'items': ['Employee Leave Balance']
- }
- ]
- }
+ "fieldname": "leave_application",
+ "transactions": [{"items": ["Attendance"]}],
+ "reports": [{"label": _("Reports"), "items": ["Employee Leave Balance"]}],
+ }
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 3a30990268..f33d0afa4e 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -49,7 +49,7 @@ _test_records = [
"description": "_Test Reason",
"leave_type": "_Test Leave Type",
"posting_date": "2013-01-02",
- "to_date": "2013-05-05"
+ "to_date": "2013-05-05",
},
{
"company": "_Test Company",
@@ -59,7 +59,7 @@ _test_records = [
"description": "_Test Reason",
"leave_type": "_Test Leave Type",
"posting_date": "2013-01-02",
- "to_date": "2013-05-05"
+ "to_date": "2013-05-05",
},
{
"company": "_Test Company",
@@ -69,8 +69,8 @@ _test_records = [
"description": "_Test Reason",
"leave_type": "_Test Leave Type LWP",
"posting_date": "2013-01-02",
- "to_date": "2013-01-15"
- }
+ "to_date": "2013-01-15",
+ },
]
@@ -90,19 +90,19 @@ class TestLeaveApplication(unittest.TestCase):
self.holiday_list = make_holiday_list(from_date=from_date, to_date=to_date)
if not frappe.db.exists("Leave Type", "_Test Leave Type"):
- frappe.get_doc(dict(
- leave_type_name="_Test Leave Type",
- doctype="Leave Type",
- include_holiday=True
- )).insert()
+ frappe.get_doc(
+ dict(leave_type_name="_Test Leave Type", doctype="Leave Type", include_holiday=True)
+ ).insert()
def tearDown(self):
frappe.db.rollback()
frappe.set_user("Administrator")
def _clear_roles(self):
- frappe.db.sql("""delete from `tabHas Role` where parent in
- ("test@example.com", "test1@example.com", "test2@example.com")""")
+ frappe.db.sql(
+ """delete from `tabHas Role` where parent in
+ ("test@example.com", "test1@example.com", "test2@example.com")"""
+ )
def _clear_applications(self):
frappe.db.sql("""delete from `tabLeave Application`""")
@@ -113,91 +113,100 @@ class TestLeaveApplication(unittest.TestCase):
application.to_date = "2013-01-05"
return application
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_validate_application_across_allocations(self):
# Test validation for application dates when negative balance is disabled
frappe.delete_doc_if_exists("Leave Type", "Test Leave Validation", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Leave Validation",
- doctype="Leave Type",
- allow_negative=False
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Validation", doctype="Leave Type", allow_negative=False)
+ ).insert()
employee = get_employee()
date = getdate()
first_sunday = get_first_sunday(self.holiday_list, for_date=get_year_start(date))
- leave_application = frappe.get_doc(dict(
- doctype='Leave Application',
- employee=employee.name,
- leave_type=leave_type.name,
- from_date=add_days(first_sunday, 1),
- to_date=add_days(first_sunday, 4),
- company="_Test Company",
- status="Approved",
- leave_approver = 'test@example.com'
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(first_sunday, 1),
+ to_date=add_days(first_sunday, 4),
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
# Application period cannot be outside leave allocation period
self.assertRaises(frappe.ValidationError, leave_application.insert)
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+ make_allocation_record(
+ leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)
+ )
- leave_application = frappe.get_doc(dict(
- doctype='Leave Application',
- employee=employee.name,
- leave_type=leave_type.name,
- from_date=add_days(first_sunday, -10),
- to_date=add_days(first_sunday, 1),
- company="_Test Company",
- status="Approved",
- leave_approver = 'test@example.com'
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(first_sunday, -10),
+ to_date=add_days(first_sunday, 1),
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
# Application period cannot be across two allocation records
self.assertRaises(LeaveAcrossAllocationsError, leave_application.insert)
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_insufficient_leave_balance_validation(self):
# CASE 1: Validation when allow negative is disabled
frappe.delete_doc_if_exists("Leave Type", "Test Leave Validation", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Leave Validation",
- doctype="Leave Type",
- allow_negative=False
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Validation", doctype="Leave Type", allow_negative=False)
+ ).insert()
employee = get_employee()
date = getdate()
first_sunday = get_first_sunday(self.holiday_list, for_date=get_year_start(date))
# allocate 2 leaves, apply for more
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date), leaves=2)
- leave_application = frappe.get_doc(dict(
- doctype='Leave Application',
- employee=employee.name,
+ make_allocation_record(
leave_type=leave_type.name,
- from_date=add_days(first_sunday, 1),
- to_date=add_days(first_sunday, 3),
- company="_Test Company",
- status="Approved",
- leave_approver = 'test@example.com'
- ))
+ from_date=get_year_start(date),
+ to_date=get_year_ending(date),
+ leaves=2,
+ )
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(first_sunday, 1),
+ to_date=add_days(first_sunday, 3),
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
self.assertRaises(InsufficientLeaveBalanceError, leave_application.insert)
# CASE 2: Allows creating application with a warning message when allow negative is enabled
frappe.db.set_value("Leave Type", "Test Leave Validation", "allow_negative", True)
- make_leave_application(employee.name, add_days(first_sunday, 1), add_days(first_sunday, 3), leave_type.name)
+ make_leave_application(
+ employee.name, add_days(first_sunday, 1), add_days(first_sunday, 3), leave_type.name
+ )
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_separate_leave_ledger_entry_for_boundary_applications(self):
# When application falls in 2 different allocations and Allow Negative is enabled
# creates separate leave ledger entries
frappe.delete_doc_if_exists("Leave Type", "Test Leave Validation", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Leave Validation",
- doctype="Leave Type",
- allow_negative=True
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Validation", doctype="Leave Type", allow_negative=True)
+ ).insert()
employee = get_employee()
date = getdate()
@@ -208,13 +217,17 @@ class TestLeaveApplication(unittest.TestCase):
# application across allocations
# CASE 1: from date has no allocation, to date has an allocation / both dates have allocation
- application = make_leave_application(employee.name, add_days(year_start, -10), add_days(year_start, 3), leave_type.name)
+ application = make_leave_application(
+ employee.name, add_days(year_start, -10), add_days(year_start, 3), leave_type.name
+ )
# 2 separate leave ledger entries
- ledgers = frappe.db.get_all("Leave Ledger Entry", {
- "transaction_type": "Leave Application",
- "transaction_name": application.name
- }, ["leaves", "from_date", "to_date"], order_by="from_date")
+ ledgers = frappe.db.get_all(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Application", "transaction_name": application.name},
+ ["leaves", "from_date", "to_date"],
+ order_by="from_date",
+ )
self.assertEqual(len(ledgers), 2)
self.assertEqual(ledgers[0].from_date, application.from_date)
@@ -224,13 +237,17 @@ class TestLeaveApplication(unittest.TestCase):
self.assertEqual(ledgers[1].to_date, application.to_date)
# CASE 2: from date has an allocation, to date has no allocation
- application = make_leave_application(employee.name, add_days(year_end, -3), add_days(year_end, 5), leave_type.name)
+ application = make_leave_application(
+ employee.name, add_days(year_end, -3), add_days(year_end, 5), leave_type.name
+ )
# 2 separate leave ledger entries
- ledgers = frappe.db.get_all("Leave Ledger Entry", {
- "transaction_type": "Leave Application",
- "transaction_name": application.name
- }, ["leaves", "from_date", "to_date"], order_by="from_date")
+ ledgers = frappe.db.get_all(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Application", "transaction_name": application.name},
+ ["leaves", "from_date", "to_date"],
+ order_by="from_date",
+ )
self.assertEqual(len(ledgers), 2)
self.assertEqual(ledgers[0].from_date, application.from_date)
@@ -240,74 +257,77 @@ class TestLeaveApplication(unittest.TestCase):
self.assertEqual(ledgers[1].to_date, application.to_date)
def test_overwrite_attendance(self):
- '''check attendance is automatically created on leave approval'''
+ """check attendance is automatically created on leave approval"""
make_allocation_record()
application = self.get_application(_test_records[0])
- application.status = 'Approved'
- application.from_date = '2018-01-01'
- application.to_date = '2018-01-03'
+ application.status = "Approved"
+ application.from_date = "2018-01-01"
+ application.to_date = "2018-01-03"
application.insert()
application.submit()
- attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'],
- dict(attendance_date=('between', ['2018-01-01', '2018-01-03']), docstatus=("!=", 2)))
+ attendance = frappe.get_all(
+ "Attendance",
+ ["name", "status", "attendance_date"],
+ dict(attendance_date=("between", ["2018-01-01", "2018-01-03"]), docstatus=("!=", 2)),
+ )
# attendance created for all 3 days
self.assertEqual(len(attendance), 3)
# all on leave
- self.assertTrue(all([d.status == 'On Leave' for d in attendance]))
+ self.assertTrue(all([d.status == "On Leave" for d in attendance]))
# dates
dates = [d.attendance_date for d in attendance]
- for d in ('2018-01-01', '2018-01-02', '2018-01-03'):
+ for d in ("2018-01-01", "2018-01-02", "2018-01-03"):
self.assertTrue(getdate(d) in dates)
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_attendance_for_include_holidays(self):
# Case 1: leave type with 'Include holidays within leaves as leaves' enabled
frappe.delete_doc_if_exists("Leave Type", "Test Include Holidays", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Include Holidays",
- doctype="Leave Type",
- include_holiday=True
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Include Holidays", doctype="Leave Type", include_holiday=True)
+ ).insert()
date = getdate()
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+ make_allocation_record(
+ leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)
+ )
employee = get_employee()
first_sunday = get_first_sunday(self.holiday_list)
- leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
+ leave_application = make_leave_application(
+ employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name
+ )
leave_application.reload()
self.assertEqual(leave_application.total_leave_days, 4)
- self.assertEqual(frappe.db.count('Attendance', {'leave_application': leave_application.name}), 4)
+ self.assertEqual(frappe.db.count("Attendance", {"leave_application": leave_application.name}), 4)
leave_application.cancel()
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_attendance_update_for_exclude_holidays(self):
# Case 2: leave type with 'Include holidays within leaves as leaves' disabled
frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Do Not Include Holidays",
- doctype="Leave Type",
- include_holiday=False
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(
+ leave_type_name="Test Do Not Include Holidays", doctype="Leave Type", include_holiday=False
+ )
+ ).insert()
date = getdate()
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+ make_allocation_record(
+ leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)
+ )
employee = get_employee()
first_sunday = get_first_sunday(self.holiday_list)
# already marked attendance on a holiday should be deleted in this case
- config = {
- "doctype": "Attendance",
- "employee": employee.name,
- "status": "Present"
- }
+ config = {"doctype": "Attendance", "employee": employee.name, "status": "Present"}
attendance_on_holiday = frappe.get_doc(config)
attendance_on_holiday.attendance_date = first_sunday
attendance_on_holiday.flags.ignore_validate = True
@@ -319,7 +339,9 @@ class TestLeaveApplication(unittest.TestCase):
attendance.flags.ignore_validate = True
attendance.save()
- leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name, employee.company)
+ leave_application = make_leave_application(
+ employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name, employee.company
+ )
leave_application.reload()
# holiday should be excluded while marking attendance
@@ -336,11 +358,13 @@ class TestLeaveApplication(unittest.TestCase):
self._clear_roles()
from frappe.utils.user import add_role
+
add_role("test@example.com", "HR User")
clear_user_permissions_for_doctype("Employee")
- frappe.db.set_value("Department", "_Test Department - _TC",
- "leave_block_list", "_Test Leave Block List")
+ frappe.db.set_value(
+ "Department", "_Test Department - _TC", "leave_block_list", "_Test Leave Block List"
+ )
make_allocation_record()
@@ -363,6 +387,7 @@ class TestLeaveApplication(unittest.TestCase):
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -379,6 +404,7 @@ class TestLeaveApplication(unittest.TestCase):
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -412,6 +438,7 @@ class TestLeaveApplication(unittest.TestCase):
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -434,6 +461,7 @@ class TestLeaveApplication(unittest.TestCase):
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -463,49 +491,49 @@ class TestLeaveApplication(unittest.TestCase):
application.half_day_date = "2013-01-05"
application.insert()
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_optional_leave(self):
leave_period = get_leave_period()
today = nowdate()
- holiday_list = 'Test Holiday List for Optional Holiday'
+ holiday_list = "Test Holiday List for Optional Holiday"
employee = get_employee()
first_sunday = get_first_sunday(self.holiday_list)
optional_leave_date = add_days(first_sunday, 1)
- if not frappe.db.exists('Holiday List', holiday_list):
- frappe.get_doc(dict(
- doctype = 'Holiday List',
- holiday_list_name = holiday_list,
- from_date = add_months(today, -6),
- to_date = add_months(today, 6),
- holidays = [
- dict(holiday_date = optional_leave_date, description = 'Test')
- ]
- )).insert()
+ if not frappe.db.exists("Holiday List", holiday_list):
+ frappe.get_doc(
+ dict(
+ doctype="Holiday List",
+ holiday_list_name=holiday_list,
+ from_date=add_months(today, -6),
+ to_date=add_months(today, 6),
+ holidays=[dict(holiday_date=optional_leave_date, description="Test")],
+ )
+ ).insert()
- frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list)
- leave_type = 'Test Optional Type'
- if not frappe.db.exists('Leave Type', leave_type):
- frappe.get_doc(dict(
- leave_type_name = leave_type,
- doctype = 'Leave Type',
- is_optional_leave = 1
- )).insert()
+ frappe.db.set_value("Leave Period", leave_period.name, "optional_holiday_list", holiday_list)
+ leave_type = "Test Optional Type"
+ if not frappe.db.exists("Leave Type", leave_type):
+ frappe.get_doc(
+ dict(leave_type_name=leave_type, doctype="Leave Type", is_optional_leave=1)
+ ).insert()
allocate_leaves(employee, leave_period, leave_type, 10)
date = add_days(first_sunday, 2)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- company = '_Test Company',
- description = "_Test Reason",
- leave_type = leave_type,
- from_date = date,
- to_date = date,
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ company="_Test Company",
+ description="_Test Reason",
+ leave_type=leave_type,
+ from_date=date,
+ to_date=date,
+ )
+ )
# can only apply on optional holidays
self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
@@ -523,118 +551,125 @@ class TestLeaveApplication(unittest.TestCase):
employee = get_employee()
leave_period = get_leave_period()
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type',
- doctype = 'Leave Type',
- max_leaves_allowed = 5
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Type", doctype="Leave Type", max_leaves_allowed=5)
+ ).insert()
date = add_days(nowdate(), -7)
allocate_leaves(employee, leave_period, leave_type.name, 5)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 2),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 2),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
leave_application.submit()
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = add_days(date, 4),
- to_date = add_days(date, 8),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=add_days(date, 4),
+ to_date=add_days(date, 8),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertRaises(frappe.ValidationError, leave_application.insert)
def test_applicable_after(self):
employee = get_employee()
leave_period = get_leave_period()
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type',
- doctype = 'Leave Type',
- applicable_after = 15
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Type", doctype="Leave Type", applicable_after=15)
+ ).insert()
date = add_days(nowdate(), -7)
- frappe.db.set_value('Employee', employee.name, "date_of_joining", date)
+ frappe.db.set_value("Employee", employee.name, "date_of_joining", date)
allocate_leaves(employee, leave_period, leave_type.name, 10)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 4),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 4),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertRaises(frappe.ValidationError, leave_application.insert)
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type 1", force=1)
- leave_type_1 = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type 1',
- doctype = 'Leave Type'
- )).insert()
+ leave_type_1 = frappe.get_doc(
+ dict(leave_type_name="Test Leave Type 1", doctype="Leave Type")
+ ).insert()
allocate_leaves(employee, leave_period, leave_type_1.name, 10)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type_1.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 4),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type_1.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 4),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertTrue(leave_application.insert())
- frappe.db.set_value('Employee', employee.name, "date_of_joining", "2010-01-01")
+ frappe.db.set_value("Employee", employee.name, "date_of_joining", "2010-01-01")
def test_max_continuous_leaves(self):
employee = get_employee()
leave_period = get_leave_period()
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type',
- doctype = 'Leave Type',
- max_leaves_allowed = 15,
- max_continuous_days_allowed = 3
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(
+ leave_type_name="Test Leave Type",
+ doctype="Leave Type",
+ max_leaves_allowed=15,
+ max_continuous_days_allowed=3,
+ )
+ ).insert()
date = add_days(nowdate(), -7)
allocate_leaves(employee, leave_period, leave_type.name, 10)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 4),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 4),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertRaises(frappe.ValidationError, leave_application.insert)
@@ -643,60 +678,70 @@ class TestLeaveApplication(unittest.TestCase):
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.insert()
create_carry_forwarded_allocation(employee, leave_type)
- details = get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8), for_consumption=True)
+ details = get_leave_balance_on(
+ employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8), for_consumption=True
+ )
self.assertEqual(details.leave_balance_for_consumption, 21)
self.assertEqual(details.leave_balance, 30)
def test_earned_leaves_creation(self):
- frappe.db.sql('''delete from `tabLeave Period`''')
- frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
- frappe.db.sql('''delete from `tabLeave Allocation`''')
- frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
+ frappe.db.sql("""delete from `tabLeave Period`""")
+ frappe.db.sql("""delete from `tabLeave Policy Assignment`""")
+ frappe.db.sql("""delete from `tabLeave Allocation`""")
+ frappe.db.sql("""delete from `tabLeave Ledger Entry`""")
leave_period = get_leave_period()
employee = get_employee()
- leave_type = 'Test Earned Leave Type'
- frappe.delete_doc_if_exists("Leave Type", 'Test Earned Leave Type', force=1)
- frappe.get_doc(dict(
- leave_type_name = leave_type,
- doctype = 'Leave Type',
- is_earned_leave = 1,
- earned_leave_frequency = 'Monthly',
- rounding = 0.5,
- max_leaves_allowed = 6
- )).insert()
+ leave_type = "Test Earned Leave Type"
+ frappe.delete_doc_if_exists("Leave Type", "Test Earned Leave Type", force=1)
+ frappe.get_doc(
+ dict(
+ leave_type_name=leave_type,
+ doctype="Leave Type",
+ is_earned_leave=1,
+ earned_leave_frequency="Monthly",
+ rounding=0.5,
+ max_leaves_allowed=6,
+ )
+ ).insert()
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
- }).insert()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}],
+ }
+ ).insert()
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [employee.name], frappe._dict(data)
+ )
from erpnext.hr.utils import allocate_earned_leaves
+
i = 0
- while(i<14):
+ while i < 14:
allocate_earned_leaves(ignore_duplicates=True)
i += 1
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 6)
# validate earned leaves creation without maximum leaves
- frappe.db.set_value('Leave Type', leave_type, 'max_leaves_allowed', 0)
+ frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 0)
i = 0
- while(i<6):
+ while i < 6:
allocate_earned_leaves(ignore_duplicates=True)
i += 1
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 9)
@@ -705,34 +750,35 @@ class TestLeaveApplication(unittest.TestCase):
def test_current_leave_on_submit(self):
employee = get_employee()
- leave_type = 'Sick Leave'
- if not frappe.db.exists('Leave Type', leave_type):
- frappe.get_doc(dict(
- leave_type_name=leave_type,
- doctype='Leave Type'
- )).insert()
+ leave_type = "Sick Leave"
+ if not frappe.db.exists("Leave Type", leave_type):
+ frappe.get_doc(dict(leave_type_name=leave_type, doctype="Leave Type")).insert()
- allocation = frappe.get_doc(dict(
- doctype = 'Leave Allocation',
- employee = employee.name,
- leave_type = leave_type,
- from_date = '2018-10-01',
- to_date = '2018-10-10',
- new_leaves_allocated = 1
- ))
+ allocation = frappe.get_doc(
+ dict(
+ doctype="Leave Allocation",
+ employee=employee.name,
+ leave_type=leave_type,
+ from_date="2018-10-01",
+ to_date="2018-10-10",
+ new_leaves_allocated=1,
+ )
+ )
allocation.insert(ignore_permissions=True)
allocation.submit()
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type,
- description = "_Test Reason",
- from_date = '2018-10-02',
- to_date = '2018-10-02',
- company = '_Test Company',
- status = 'Approved',
- leave_approver = 'test@example.com'
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type,
+ description="_Test Reason",
+ from_date="2018-10-02",
+ to_date="2018-10-02",
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
self.assertTrue(leave_application.insert())
leave_application.submit()
self.assertEqual(leave_application.docstatus, 1)
@@ -740,26 +786,31 @@ class TestLeaveApplication(unittest.TestCase):
def test_creation_of_leave_ledger_entry_on_submit(self):
employee = get_employee()
- leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1')
+ leave_type = create_leave_type(leave_type_name="Test Leave Type 1")
leave_type.save()
- leave_allocation = create_leave_allocation(employee=employee.name, employee_name=employee.employee_name,
- leave_type=leave_type.name)
+ leave_allocation = create_leave_allocation(
+ employee=employee.name, employee_name=employee.employee_name, leave_type=leave_type.name
+ )
leave_allocation.submit()
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- from_date = add_days(nowdate(), 1),
- to_date = add_days(nowdate(), 4),
- description = "_Test Reason",
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(nowdate(), 1),
+ to_date=add_days(nowdate(), 4),
+ description="_Test Reason",
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
leave_application.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=dict(transaction_name=leave_application.name)
+ )
self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type)
@@ -767,32 +818,39 @@ class TestLeaveApplication(unittest.TestCase):
# check if leave ledger entry is deleted on cancellation
leave_application.cancel()
- self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name}))
+ self.assertFalse(
+ frappe.db.exists("Leave Ledger Entry", {"transaction_name": leave_application.name})
+ )
def test_ledger_entry_creation_on_intermediate_allocation_expiry(self):
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- from_date = add_days(nowdate(), -3),
- to_date = add_days(nowdate(), 7),
- description = "_Test Reason",
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(nowdate(), -3),
+ to_date=add_days(nowdate(), 7),
+ description="_Test Reason",
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
leave_application.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", "*", filters=dict(transaction_name=leave_application.name)
+ )
self.assertEqual(len(leave_ledger_entry), 2)
self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
@@ -806,12 +864,18 @@ class TestLeaveApplication(unittest.TestCase):
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
- self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
+ self.assertEqual(
+ get_leave_balance_on(
+ employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)
+ ),
+ 0,
+ )
def test_leave_approver_perms(self):
employee = get_employee()
@@ -827,8 +891,8 @@ class TestLeaveApplication(unittest.TestCase):
make_allocation_record(employee.name)
application = self.get_application(_test_records[0])
- application.from_date = '2018-01-01'
- application.to_date = '2018-01-03'
+ application.from_date = "2018-01-01"
+ application.to_date = "2018-01-03"
application.leave_approver = user
application.insert()
self.assertTrue(application.name in frappe.share.get_shared("Leave Application", user))
@@ -854,7 +918,7 @@ class TestLeaveApplication(unittest.TestCase):
employee.leave_approver = ""
employee.save()
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_get_leave_details_for_dashboard(self):
employee = get_employee()
date = getdate()
@@ -862,34 +926,44 @@ class TestLeaveApplication(unittest.TestCase):
year_end = getdate(get_year_ending(date))
# ALLOCATION = 30
- allocation = make_allocation_record(employee=employee.name, from_date=year_start, to_date=year_end)
+ allocation = make_allocation_record(
+ employee=employee.name, from_date=year_start, to_date=year_end
+ )
# USED LEAVES = 4
first_sunday = get_first_sunday(self.holiday_list)
- leave_application = make_leave_application(employee.name, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ employee.name, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
# LEAVES PENDING APPROVAL = 1
- leave_application = make_leave_application(employee.name, add_days(first_sunday, 5), add_days(first_sunday, 5),
- '_Test Leave Type', submit=False)
- leave_application.status = 'Open'
+ leave_application = make_leave_application(
+ employee.name,
+ add_days(first_sunday, 5),
+ add_days(first_sunday, 5),
+ "_Test Leave Type",
+ submit=False,
+ )
+ leave_application.status = "Open"
leave_application.save()
details = get_leave_details(employee.name, allocation.from_date)
- leave_allocation = details['leave_allocation']['_Test Leave Type']
- self.assertEqual(leave_allocation['total_leaves'], 30)
- self.assertEqual(leave_allocation['leaves_taken'], 4)
- self.assertEqual(leave_allocation['expired_leaves'], 0)
- self.assertEqual(leave_allocation['leaves_pending_approval'], 1)
- self.assertEqual(leave_allocation['remaining_leaves'], 26)
+ leave_allocation = details["leave_allocation"]["_Test Leave Type"]
+ self.assertEqual(leave_allocation["total_leaves"], 30)
+ self.assertEqual(leave_allocation["leaves_taken"], 4)
+ self.assertEqual(leave_allocation["expired_leaves"], 0)
+ self.assertEqual(leave_allocation["leaves_pending_approval"], 1)
+ self.assertEqual(leave_allocation["remaining_leaves"], 26)
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_get_leave_allocation_records(self):
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.insert()
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
@@ -900,86 +974,99 @@ class TestLeaveApplication(unittest.TestCase):
"total_leaves_allocated": 30.0,
"unused_leaves": 15.0,
"new_leaves_allocated": 15.0,
- "leave_type": leave_type.name
+ "leave_type": leave_type.name,
}
self.assertEqual(details.get(leave_type.name), expected_data)
def create_carry_forwarded_allocation(employee, leave_type):
- # initial leave allocation
- leave_allocation = create_leave_allocation(
- leave_type="_Test_CF_leave_expiry",
- employee=employee.name,
- employee_name=employee.employee_name,
- from_date=add_months(nowdate(), -24),
- to_date=add_months(nowdate(), -12),
- carry_forward=0)
- leave_allocation.submit()
+ # initial leave allocation
+ leave_allocation = create_leave_allocation(
+ leave_type="_Test_CF_leave_expiry",
+ employee=employee.name,
+ employee_name=employee.employee_name,
+ from_date=add_months(nowdate(), -24),
+ to_date=add_months(nowdate(), -12),
+ carry_forward=0,
+ )
+ leave_allocation.submit()
- leave_allocation = create_leave_allocation(
- leave_type="_Test_CF_leave_expiry",
- employee=employee.name,
- employee_name=employee.employee_name,
- from_date=add_days(nowdate(), -84),
- to_date=add_days(nowdate(), 100),
- carry_forward=1)
- leave_allocation.submit()
+ leave_allocation = create_leave_allocation(
+ leave_type="_Test_CF_leave_expiry",
+ employee=employee.name,
+ employee_name=employee.employee_name,
+ from_date=add_days(nowdate(), -84),
+ to_date=add_days(nowdate(), 100),
+ carry_forward=1,
+ )
+ leave_allocation.submit()
- return leave_allocation
+ return leave_allocation
-def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None, carry_forward=False, leaves=None):
- allocation = frappe.get_doc({
- "doctype": "Leave Allocation",
- "employee": employee or "_T-Employee-00001",
- "leave_type": leave_type or "_Test Leave Type",
- "from_date": from_date or "2013-01-01",
- "to_date": to_date or "2019-12-31",
- "new_leaves_allocated": leaves or 30,
- "carry_forward": carry_forward
- })
+
+def make_allocation_record(
+ employee=None, leave_type=None, from_date=None, to_date=None, carry_forward=False, leaves=None
+):
+ allocation = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "employee": employee or "_T-Employee-00001",
+ "leave_type": leave_type or "_Test Leave Type",
+ "from_date": from_date or "2013-01-01",
+ "to_date": to_date or "2019-12-31",
+ "new_leaves_allocated": leaves or 30,
+ "carry_forward": carry_forward,
+ }
+ )
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation
+
def get_employee():
return frappe.get_doc("Employee", "_T-Employee-00001")
+
def set_leave_approver():
employee = get_employee()
dept_doc = frappe.get_doc("Department", employee.department)
- dept_doc.append('leave_approvers', {
- 'approver': 'test@example.com'
- })
+ dept_doc.append("leave_approvers", {"approver": "test@example.com"})
dept_doc.save(ignore_permissions=True)
+
def get_leave_period():
leave_period_name = frappe.db.get_value("Leave Period", {"company": "_Test Company"})
if leave_period_name:
return frappe.get_doc("Leave Period", leave_period_name)
else:
- return frappe.get_doc(dict(
- name = 'Test Leave Period',
- doctype = 'Leave Period',
- from_date = add_months(nowdate(), -6),
- to_date = add_months(nowdate(), 6),
- company = "_Test Company",
- is_active = 1
- )).insert()
+ return frappe.get_doc(
+ dict(
+ name="Test Leave Period",
+ doctype="Leave Period",
+ from_date=add_months(nowdate(), -6),
+ to_date=add_months(nowdate(), 6),
+ company="_Test Company",
+ is_active=1,
+ )
+ ).insert()
+
def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, eligible_leaves=0):
- allocate_leave = frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": employee.name,
- "employee_name": employee.employee_name,
- "leave_type": leave_type,
- "from_date": leave_period.from_date,
- "to_date": leave_period.to_date,
- "new_leaves_allocated": new_leaves_allocated,
- "docstatus": 1
- }).insert()
+ allocate_leave = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": employee.name,
+ "employee_name": employee.employee_name,
+ "leave_type": leave_type,
+ "from_date": leave_period.from_date,
+ "to_date": leave_period.to_date,
+ "new_leaves_allocated": new_leaves_allocated,
+ "docstatus": 1,
+ }
+ ).insert()
allocate_leave.submit()
@@ -988,11 +1075,14 @@ def get_first_sunday(holiday_list, for_date=None):
date = for_date or getdate()
month_start_date = get_first_day(date)
month_end_date = get_last_day(date)
- first_sunday = frappe.db.sql("""
+ first_sunday = frappe.db.sql(
+ """
select holiday_date from `tabHoliday`
where parent = %s
and holiday_date between %s and %s
order by holiday_date
- """, (holiday_list, month_start_date, month_end_date))[0][0]
+ """,
+ (holiday_list, month_start_date, month_end_date),
+ )[0][0]
return first_sunday
diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list.py b/erpnext/hr/doctype/leave_block_list/leave_block_list.py
index d6b77f984c..a57ba84e38 100644
--- a/erpnext/hr/doctype/leave_block_list/leave_block_list.py
+++ b/erpnext/hr/doctype/leave_block_list/leave_block_list.py
@@ -10,7 +10,6 @@ from frappe.model.document import Document
class LeaveBlockList(Document):
-
def validate(self):
dates = []
for d in self.get("leave_block_list_dates"):
@@ -20,23 +19,29 @@ class LeaveBlockList(Document):
frappe.msgprint(_("Date is repeated") + ":" + d.block_date, raise_exception=1)
dates.append(d.block_date)
+
@frappe.whitelist()
-def get_applicable_block_dates(from_date, to_date, employee=None,
- company=None, all_lists=False):
+def get_applicable_block_dates(from_date, to_date, employee=None, company=None, all_lists=False):
block_dates = []
for block_list in get_applicable_block_lists(employee, company, all_lists):
- block_dates.extend(frappe.db.sql("""select block_date, reason
+ block_dates.extend(
+ frappe.db.sql(
+ """select block_date, reason
from `tabLeave Block List Date` where parent=%s
- and block_date between %s and %s""", (block_list, from_date, to_date),
- as_dict=1))
+ and block_date between %s and %s""",
+ (block_list, from_date, to_date),
+ as_dict=1,
+ )
+ )
return block_dates
+
def get_applicable_block_lists(employee=None, company=None, all_lists=False):
block_lists = []
if not employee:
- employee = frappe.db.get_value("Employee", {"user_id":frappe.session.user})
+ employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user})
if not employee:
return []
@@ -49,18 +54,25 @@ def get_applicable_block_lists(employee=None, company=None, all_lists=False):
block_lists.append(block_list)
# per department
- department = frappe.db.get_value("Employee",employee, "department")
+ department = frappe.db.get_value("Employee", employee, "department")
if department:
block_list = frappe.db.get_value("Department", department, "leave_block_list")
add_block_list(block_list)
# global
- for block_list in frappe.db.sql_list("""select name from `tabLeave Block List`
- where applies_to_all_departments=1 and company=%s""", company):
+ for block_list in frappe.db.sql_list(
+ """select name from `tabLeave Block List`
+ where applies_to_all_departments=1 and company=%s""",
+ company,
+ ):
add_block_list(block_list)
return list(set(block_lists))
+
def is_user_in_allow_list(block_list):
- return frappe.session.user in frappe.db.sql_list("""select allow_user
- from `tabLeave Block List Allow` where parent=%s""", block_list)
+ return frappe.session.user in frappe.db.sql_list(
+ """select allow_user
+ from `tabLeave Block List Allow` where parent=%s""",
+ block_list,
+ )
diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
index 7cca62e0f5..afeb5ded39 100644
--- a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
+++ b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'leave_block_list',
- 'transactions': [
- {
- 'items': ['Department']
- }
- ]
- }
+ return {"fieldname": "leave_block_list", "transactions": [{"items": ["Department"]}]}
diff --git a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py
index afbabb66a4..be85a35414 100644
--- a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py
+++ b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py
@@ -15,24 +15,36 @@ class TestLeaveBlockList(unittest.TestCase):
def test_get_applicable_block_dates(self):
frappe.set_user("test@example.com")
- frappe.db.set_value("Department", "_Test Department - _TC", "leave_block_list",
- "_Test Leave Block List")
- self.assertTrue(getdate("2013-01-02") in
- [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")])
+ frappe.db.set_value(
+ "Department", "_Test Department - _TC", "leave_block_list", "_Test Leave Block List"
+ )
+ self.assertTrue(
+ getdate("2013-01-02")
+ in [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")]
+ )
def test_get_applicable_block_dates_for_allowed_user(self):
frappe.set_user("test1@example.com")
- frappe.db.set_value("Department", "_Test Department 1 - _TC", "leave_block_list",
- "_Test Leave Block List")
- self.assertEqual([], [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")])
+ frappe.db.set_value(
+ "Department", "_Test Department 1 - _TC", "leave_block_list", "_Test Leave Block List"
+ )
+ self.assertEqual(
+ [], [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")]
+ )
def test_get_applicable_block_dates_all_lists(self):
frappe.set_user("test1@example.com")
- frappe.db.set_value("Department", "_Test Department 1 - _TC", "leave_block_list",
- "_Test Leave Block List")
- self.assertTrue(getdate("2013-01-02") in
- [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03", all_lists=True)])
+ frappe.db.set_value(
+ "Department", "_Test Department 1 - _TC", "leave_block_list", "_Test Leave Block List"
+ )
+ self.assertTrue(
+ getdate("2013-01-02")
+ in [
+ d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03", all_lists=True)
+ ]
+ )
+
test_dependencies = ["Employee"]
-test_records = frappe.get_test_records('Leave Block List')
+test_records = frappe.get_test_records("Leave Block List")
diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
index 19f97b83d4..c57f8ae72b 100644
--- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
+++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
@@ -18,8 +18,12 @@ class LeaveControlPanel(Document):
condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
- e = frappe.db.sql("select name from tabEmployee where status='Active' {condition}"
- .format(condition=condition_str), tuple(values))
+ e = frappe.db.sql(
+ "select name from tabEmployee where status='Active' {condition}".format(
+ condition=condition_str
+ ),
+ tuple(values),
+ )
return e
@@ -27,7 +31,7 @@ class LeaveControlPanel(Document):
for f in ["from_date", "to_date", "leave_type", "no_of_days"]:
if not self.get(f):
frappe.throw(_("{0} is required").format(self.meta.get_label(f)))
- self.validate_from_to_dates('from_date', 'to_date')
+ self.validate_from_to_dates("from_date", "to_date")
@frappe.whitelist()
def allocate_leave(self):
@@ -39,10 +43,10 @@ class LeaveControlPanel(Document):
for d in self.get_employees():
try:
- la = frappe.new_doc('Leave Allocation')
+ la = frappe.new_doc("Leave Allocation")
la.set("__islocal", 1)
la.employee = cstr(d[0])
- la.employee_name = frappe.db.get_value('Employee',cstr(d[0]),'employee_name')
+ la.employee_name = frappe.db.get_value("Employee", cstr(d[0]), "employee_name")
la.leave_type = self.leave_type
la.from_date = self.from_date
la.to_date = self.to_date
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index 8ef0e36fb8..0f655e3e0f 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -26,9 +26,12 @@ class LeaveEncashment(Document):
self.encashment_date = getdate(nowdate())
def validate_salary_structure(self):
- if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
- frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
-
+ if not frappe.db.exists("Salary Structure Assignment", {"employee": self.employee}):
+ frappe.throw(
+ _("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(
+ self.employee
+ )
+ )
def before_submit(self):
if self.encashment_amount <= 0:
@@ -36,7 +39,7 @@ class LeaveEncashment(Document):
def on_submit(self):
if not self.leave_allocation:
- self.leave_allocation = self.get_leave_allocation().get('name')
+ self.leave_allocation = self.get_leave_allocation().get("name")
additional_salary = frappe.new_doc("Additional Salary")
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
additional_salary.employee = self.employee
@@ -52,8 +55,13 @@ class LeaveEncashment(Document):
additional_salary.submit()
# Set encashed leaves in Allocation
- frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
- frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days)
+ frappe.db.set_value(
+ "Leave Allocation",
+ self.leave_allocation,
+ "total_leaves_encashed",
+ frappe.db.get_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed")
+ + self.encashable_days,
+ )
self.create_leave_ledger_entry()
@@ -63,40 +71,69 @@ class LeaveEncashment(Document):
self.db_set("additional_salary", "")
if self.leave_allocation:
- frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
- frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days)
+ frappe.db.set_value(
+ "Leave Allocation",
+ self.leave_allocation,
+ "total_leaves_encashed",
+ frappe.db.get_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed")
+ - self.encashable_days,
+ )
self.create_leave_ledger_entry(submit=False)
@frappe.whitelist()
def get_leave_details_for_encashment(self):
- salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate()))
+ salary_structure = get_assigned_salary_structure(
+ self.employee, self.encashment_date or getdate(nowdate())
+ )
if not salary_structure:
- frappe.throw(_("No Salary Structure assigned for Employee {0} on given date {1}").format(self.employee, self.encashment_date))
+ frappe.throw(
+ _("No Salary Structure assigned for Employee {0} on given date {1}").format(
+ self.employee, self.encashment_date
+ )
+ )
- if not frappe.db.get_value("Leave Type", self.leave_type, 'allow_encashment'):
+ if not frappe.db.get_value("Leave Type", self.leave_type, "allow_encashment"):
frappe.throw(_("Leave Type {0} is not encashable").format(self.leave_type))
allocation = self.get_leave_allocation()
if not allocation:
- frappe.throw(_("No Leaves Allocated to Employee: {0} for Leave Type: {1}").format(self.employee, self.leave_type))
+ frappe.throw(
+ _("No Leaves Allocated to Employee: {0} for Leave Type: {1}").format(
+ self.employee, self.leave_type
+ )
+ )
- self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\
+ self.leave_balance = (
+ allocation.total_leaves_allocated
+ - allocation.carry_forwarded_leaves_count
- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date)
+ )
- encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days')
+ encashable_days = self.leave_balance - frappe.db.get_value(
+ "Leave Type", self.leave_type, "encashment_threshold_days"
+ )
self.encashable_days = encashable_days if encashable_days > 0 else 0
- per_day_encashment = frappe.db.get_value('Salary Structure', salary_structure , 'leave_encashment_amount_per_day')
- self.encashment_amount = self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0
+ per_day_encashment = frappe.db.get_value(
+ "Salary Structure", salary_structure, "leave_encashment_amount_per_day"
+ )
+ self.encashment_amount = (
+ self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0
+ )
self.leave_allocation = allocation.name
return True
def get_leave_allocation(self):
- leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
+ leave_allocation = frappe.db.sql(
+ """select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
between from_date and to_date and docstatus=1 and leave_type='{1}'
- and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec
+ and employee= '{2}'""".format(
+ self.encashment_date or getdate(nowdate()), self.leave_type, self.employee
+ ),
+ as_dict=1,
+ ) # nosec
return leave_allocation[0] if leave_allocation else None
@@ -105,7 +142,7 @@ class LeaveEncashment(Document):
leaves=self.encashable_days * -1,
from_date=self.encashment_date,
to_date=self.encashment_date,
- is_carry_forward=0
+ is_carry_forward=0,
)
create_leave_ledger_entry(self, args, submit)
@@ -114,27 +151,26 @@ class LeaveEncashment(Document):
if not leave_allocation:
return
- to_date = leave_allocation.get('to_date')
+ to_date = leave_allocation.get("to_date")
if to_date < getdate(nowdate()):
args = frappe._dict(
- leaves=self.encashable_days,
- from_date=to_date,
- to_date=to_date,
- is_carry_forward=0
+ leaves=self.encashable_days, from_date=to_date, to_date=to_date, is_carry_forward=0
)
create_leave_ledger_entry(self, args, submit)
def create_leave_encashment(leave_allocation):
- ''' Creates leave encashment for the given allocations '''
+ """Creates leave encashment for the given allocations"""
for allocation in leave_allocation:
if not get_assigned_salary_structure(allocation.employee, allocation.to_date):
continue
- leave_encashment = frappe.get_doc(dict(
- doctype="Leave Encashment",
- leave_period=allocation.leave_period,
- employee=allocation.employee,
- leave_type=allocation.leave_type,
- encashment_date=allocation.to_date
- ))
+ leave_encashment = frappe.get_doc(
+ dict(
+ doctype="Leave Encashment",
+ leave_period=allocation.leave_period,
+ employee=allocation.employee,
+ leave_type=allocation.leave_type,
+ encashment_date=allocation.to_date,
+ )
+ )
leave_encashment.insert(ignore_permissions=True)
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index 99a479d3e5..83eb969feb 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -16,18 +16,19 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_
test_dependencies = ["Leave Type"]
+
class TestLeaveEncashment(unittest.TestCase):
def setUp(self):
- frappe.db.sql('''delete from `tabLeave Period`''')
- frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
- frappe.db.sql('''delete from `tabLeave Allocation`''')
- frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
- frappe.db.sql('''delete from `tabAdditional Salary`''')
+ frappe.db.sql("""delete from `tabLeave Period`""")
+ frappe.db.sql("""delete from `tabLeave Policy Assignment`""")
+ frappe.db.sql("""delete from `tabLeave Allocation`""")
+ frappe.db.sql("""delete from `tabLeave Ledger Entry`""")
+ frappe.db.sql("""delete from `tabAdditional Salary`""")
# create the leave policy
leave_policy = create_leave_policy(
- leave_type="_Test Leave Type Encashment",
- annual_allocation=10)
+ leave_type="_Test Leave Type Encashment", annual_allocation=10
+ )
leave_policy.submit()
# create employee, salary structure and assignment
@@ -38,28 +39,44 @@ class TestLeaveEncashment(unittest.TestCase):
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": self.leave_period.name
+ "leave_period": self.leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee], frappe._dict(data)
+ )
- salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
- other_details={"leave_encashment_amount_per_day": 50})
+ salary_structure = make_salary_structure(
+ "Salary Structure for Encashment",
+ "Monthly",
+ self.employee,
+ other_details={"leave_encashment_amount_per_day": 50},
+ )
def tearDown(self):
- for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
+ for dt in [
+ "Leave Period",
+ "Leave Allocation",
+ "Leave Ledger Entry",
+ "Additional Salary",
+ "Leave Encashment",
+ "Salary Structure",
+ "Leave Policy",
+ ]:
frappe.db.sql("delete from `tab%s`" % dt)
def test_leave_balance_value_and_amount(self):
- frappe.db.sql('''delete from `tabLeave Encashment`''')
- leave_encashment = frappe.get_doc(dict(
- doctype='Leave Encashment',
- employee=self.employee,
- leave_type="_Test Leave Type Encashment",
- leave_period=self.leave_period.name,
- payroll_date=today(),
- currency="INR"
- )).insert()
+ frappe.db.sql("""delete from `tabLeave Encashment`""")
+ leave_encashment = frappe.get_doc(
+ dict(
+ doctype="Leave Encashment",
+ employee=self.employee,
+ leave_type="_Test Leave Type Encashment",
+ leave_period=self.leave_period.name,
+ payroll_date=today(),
+ currency="INR",
+ )
+ ).insert()
self.assertEqual(leave_encashment.leave_balance, 10)
self.assertEqual(leave_encashment.encashable_days, 5)
@@ -68,23 +85,27 @@ class TestLeaveEncashment(unittest.TestCase):
leave_encashment.submit()
# assert links
- add_sal = frappe.get_all("Additional Salary", filters = {"ref_docname": leave_encashment.name})[0]
+ add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0]
self.assertTrue(add_sal)
def test_creation_of_leave_ledger_entry_on_submit(self):
- frappe.db.sql('''delete from `tabLeave Encashment`''')
- leave_encashment = frappe.get_doc(dict(
- doctype='Leave Encashment',
- employee=self.employee,
- leave_type="_Test Leave Type Encashment",
- leave_period=self.leave_period.name,
- payroll_date=today(),
- currency="INR"
- )).insert()
+ frappe.db.sql("""delete from `tabLeave Encashment`""")
+ leave_encashment = frappe.get_doc(
+ dict(
+ doctype="Leave Encashment",
+ employee=self.employee,
+ leave_type="_Test Leave Type Encashment",
+ leave_period=self.leave_period.name,
+ payroll_date=today(),
+ currency="INR",
+ )
+ ).insert()
leave_encashment.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=dict(transaction_name=leave_encashment.name)
+ )
self.assertEqual(len(leave_ledger_entry), 1)
self.assertEqual(leave_ledger_entry[0].employee, leave_encashment.employee)
@@ -93,7 +114,11 @@ class TestLeaveEncashment(unittest.TestCase):
# check if leave ledger entry is deleted on cancellation
- frappe.db.sql("Delete from `tabAdditional Salary` WHERE ref_docname = %s", (leave_encashment.name) )
+ frappe.db.sql(
+ "Delete from `tabAdditional Salary` WHERE ref_docname = %s", (leave_encashment.name)
+ )
leave_encashment.cancel()
- self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name}))
+ self.assertFalse(
+ frappe.db.exists("Leave Ledger Entry", {"transaction_name": leave_encashment.name})
+ )
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
index a5923e0021..fed9f770df 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
@@ -20,9 +20,11 @@ class LeaveLedgerEntry(Document):
else:
frappe.throw(_("Only expired allocation can be cancelled"))
+
def validate_leave_allocation_against_leave_application(ledger):
- ''' Checks that leave allocation has no leave application against it '''
- leave_application_records = frappe.db.sql_list("""
+ """Checks that leave allocation has no leave application against it"""
+ leave_application_records = frappe.db.sql_list(
+ """
SELECT transaction_name
FROM `tabLeave Ledger Entry`
WHERE
@@ -31,15 +33,21 @@ def validate_leave_allocation_against_leave_application(ledger):
AND transaction_type='Leave Application'
AND from_date>=%s
AND to_date<=%s
- """, (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
+ """,
+ (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date),
+ )
if leave_application_records:
- frappe.throw(_("Leave allocation {0} is linked with the Leave Application {1}").format(
- ledger.transaction_name, ', '.join(leave_application_records)))
+ frappe.throw(
+ _("Leave allocation {0} is linked with the Leave Application {1}").format(
+ ledger.transaction_name, ", ".join(leave_application_records)
+ )
+ )
+
def create_leave_ledger_entry(ref_doc, args, submit=True):
ledger = frappe._dict(
- doctype='Leave Ledger Entry',
+ doctype="Leave Ledger Entry",
employee=ref_doc.employee,
employee_name=ref_doc.employee_name,
leave_type=ref_doc.leave_type,
@@ -47,7 +55,7 @@ def create_leave_ledger_entry(ref_doc, args, submit=True):
transaction_name=ref_doc.name,
is_carry_forward=0,
is_expired=0,
- is_lwp=0
+ is_lwp=0,
)
ledger.update(args)
@@ -58,54 +66,69 @@ def create_leave_ledger_entry(ref_doc, args, submit=True):
else:
delete_ledger_entry(ledger)
+
def delete_ledger_entry(ledger):
- ''' Delete ledger entry on cancel of leave application/allocation/encashment '''
+ """Delete ledger entry on cancel of leave application/allocation/encashment"""
if ledger.transaction_type == "Leave Allocation":
validate_leave_allocation_against_leave_application(ledger)
expired_entry = get_previous_expiry_ledger_entry(ledger)
- frappe.db.sql("""DELETE
+ frappe.db.sql(
+ """DELETE
FROM `tabLeave Ledger Entry`
WHERE
`transaction_name`=%s
- OR `name`=%s""", (ledger.transaction_name, expired_entry))
+ OR `name`=%s""",
+ (ledger.transaction_name, expired_entry),
+ )
+
def get_previous_expiry_ledger_entry(ledger):
- ''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled '''
- creation_date = frappe.db.get_value("Leave Ledger Entry", filters={
- 'transaction_name': ledger.transaction_name,
- 'is_expired': 0,
- 'transaction_type': 'Leave Allocation'
- }, fieldname=['creation'])
+ """Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled"""
+ creation_date = frappe.db.get_value(
+ "Leave Ledger Entry",
+ filters={
+ "transaction_name": ledger.transaction_name,
+ "is_expired": 0,
+ "transaction_type": "Leave Allocation",
+ },
+ fieldname=["creation"],
+ )
- creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ''
+ creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ""
+
+ return frappe.db.get_value(
+ "Leave Ledger Entry",
+ filters={
+ "creation": ("like", creation_date + "%"),
+ "employee": ledger.employee,
+ "leave_type": ledger.leave_type,
+ "is_expired": 1,
+ "docstatus": 1,
+ "is_carry_forward": 0,
+ },
+ fieldname=["name"],
+ )
- return frappe.db.get_value("Leave Ledger Entry", filters={
- 'creation': ('like', creation_date+"%"),
- 'employee': ledger.employee,
- 'leave_type': ledger.leave_type,
- 'is_expired': 1,
- 'docstatus': 1,
- 'is_carry_forward': 0
- }, fieldname=['name'])
def process_expired_allocation():
- ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry
- Case 1: carry forwarded expiry period is set for the leave type,
- create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
- Case 2: leave type has no specific expiry period for carry forwarded leaves
- and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
- '''
+ """Check if a carry forwarded allocation has expired and create a expiry ledger entry
+ Case 1: carry forwarded expiry period is set for the leave type,
+ create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
+ Case 2: leave type has no specific expiry period for carry forwarded leaves
+ and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
+ """
# fetch leave type records that has carry forwarded leaves expiry
- leave_type_records = frappe.db.get_values("Leave Type", filters={
- 'expire_carry_forwarded_leaves_after_days': (">", 0)
- }, fieldname=['name'])
+ leave_type_records = frappe.db.get_values(
+ "Leave Type", filters={"expire_carry_forwarded_leaves_after_days": (">", 0)}, fieldname=["name"]
+ )
- leave_type = [record[0] for record in leave_type_records] or ['']
+ leave_type = [record[0] for record in leave_type_records] or [""]
# fetch non expired leave ledger entry of transaction_type allocation
- expire_allocation = frappe.db.sql("""
+ expire_allocation = frappe.db.sql(
+ """
SELECT
leaves, to_date, employee, leave_type,
is_carry_forward, transaction_name as name, transaction_type
@@ -123,32 +146,41 @@ def process_expired_allocation():
OR (is_carry_forward = 0 AND leave_type not in %s)
)))
AND transaction_type = 'Leave Allocation'
- AND to_date < %s""", (leave_type, today()), as_dict=1)
+ AND to_date < %s""",
+ (leave_type, today()),
+ as_dict=1,
+ )
if expire_allocation:
create_expiry_ledger_entry(expire_allocation)
+
def create_expiry_ledger_entry(allocations):
- ''' Create ledger entry for expired allocation '''
+ """Create ledger entry for expired allocation"""
for allocation in allocations:
if allocation.is_carry_forward:
expire_carried_forward_allocation(allocation)
else:
expire_allocation(allocation)
+
def get_remaining_leaves(allocation):
- ''' Returns remaining leaves from the given allocation '''
- return frappe.db.get_value("Leave Ledger Entry",
+ """Returns remaining leaves from the given allocation"""
+ return frappe.db.get_value(
+ "Leave Ledger Entry",
filters={
- 'employee': allocation.employee,
- 'leave_type': allocation.leave_type,
- 'to_date': ('<=', allocation.to_date),
- 'docstatus': 1
- }, fieldname=['SUM(leaves)'])
+ "employee": allocation.employee,
+ "leave_type": allocation.leave_type,
+ "to_date": ("<=", allocation.to_date),
+ "docstatus": 1,
+ },
+ fieldname=["SUM(leaves)"],
+ )
+
@frappe.whitelist()
def expire_allocation(allocation, expiry_date=None):
- ''' expires non-carry forwarded allocation '''
+ """expires non-carry forwarded allocation"""
leaves = get_remaining_leaves(allocation)
expiry_date = expiry_date if expiry_date else allocation.to_date
@@ -157,21 +189,28 @@ def expire_allocation(allocation, expiry_date=None):
args = dict(
leaves=flt(leaves) * -1,
transaction_name=allocation.name,
- transaction_type='Leave Allocation',
+ transaction_type="Leave Allocation",
from_date=expiry_date,
to_date=expiry_date,
is_carry_forward=0,
- is_expired=1
+ is_expired=1,
)
create_leave_ledger_entry(allocation, args)
frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1)
+
def expire_carried_forward_allocation(allocation):
- ''' Expires remaining leaves in the on carried forward allocation '''
+ """Expires remaining leaves in the on carried forward allocation"""
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
- leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type,
- allocation.from_date, allocation.to_date, skip_expired_leaves=False)
+
+ leaves_taken = get_leaves_for_period(
+ allocation.employee,
+ allocation.leave_type,
+ allocation.from_date,
+ allocation.to_date,
+ skip_expired_leaves=False,
+ )
leaves = flt(allocation.leaves) + flt(leaves_taken)
# allow expired leaves entry to be created
@@ -183,6 +222,6 @@ def expire_carried_forward_allocation(allocation):
is_carry_forward=allocation.is_carry_forward,
is_expired=1,
from_date=allocation.to_date,
- to_date=allocation.to_date
+ to_date=allocation.to_date,
)
create_leave_ledger_entry(allocation, args)
diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py
index b1cb6887d9..6e62bb5876 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.py
+++ b/erpnext/hr/doctype/leave_period/leave_period.py
@@ -11,7 +11,6 @@ from erpnext.hr.utils import validate_overlap
class LeavePeriod(Document):
-
def validate(self):
self.validate_dates()
validate_overlap(self, self.from_date, self.to_date, self.company)
diff --git a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
index 1adae0fda3..854f988f35 100644
--- a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
+++ b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
@@ -3,11 +3,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'leave_period',
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Leave Allocation']
- }
- ]
+ "fieldname": "leave_period",
+ "transactions": [{"label": _("Transactions"), "items": ["Leave Allocation"]}],
}
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py
index 10936dddc9..09235741b6 100644
--- a/erpnext/hr/doctype/leave_period/test_leave_period.py
+++ b/erpnext/hr/doctype/leave_period/test_leave_period.py
@@ -9,23 +9,32 @@ import erpnext
test_dependencies = ["Employee", "Leave Type", "Leave Policy"]
+
class TestLeavePeriod(unittest.TestCase):
pass
+
def create_leave_period(from_date, to_date, company=None):
- leave_period = frappe.db.get_value('Leave Period',
- dict(company=company or erpnext.get_default_company(),
+ leave_period = frappe.db.get_value(
+ "Leave Period",
+ dict(
+ company=company or erpnext.get_default_company(),
from_date=from_date,
to_date=to_date,
- is_active=1), 'name')
+ is_active=1,
+ ),
+ "name",
+ )
if leave_period:
return frappe.get_doc("Leave Period", leave_period)
- leave_period = frappe.get_doc({
- "doctype": "Leave Period",
- "company": company or erpnext.get_default_company(),
- "from_date": from_date,
- "to_date": to_date,
- "is_active": 1
- }).insert()
+ leave_period = frappe.get_doc(
+ {
+ "doctype": "Leave Period",
+ "company": company or erpnext.get_default_company(),
+ "from_date": from_date,
+ "to_date": to_date,
+ "is_active": 1,
+ }
+ ).insert()
return leave_period
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy.py b/erpnext/hr/doctype/leave_policy/leave_policy.py
index 80450d5d6e..33c949354c 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy.py
@@ -11,6 +11,12 @@ class LeavePolicy(Document):
def validate(self):
if self.leave_policy_details:
for lp_detail in self.leave_policy_details:
- max_leaves_allowed = frappe.db.get_value("Leave Type", lp_detail.leave_type, "max_leaves_allowed")
+ max_leaves_allowed = frappe.db.get_value(
+ "Leave Type", lp_detail.leave_type, "max_leaves_allowed"
+ )
if max_leaves_allowed > 0 and lp_detail.annual_allocation > max_leaves_allowed:
- frappe.throw(_("Maximum leave allowed in the leave type {0} is {1}").format(lp_detail.leave_type, max_leaves_allowed))
+ frappe.throw(
+ _("Maximum leave allowed in the leave type {0} is {1}").format(
+ lp_detail.leave_type, max_leaves_allowed
+ )
+ )
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
index 73782d6c81..57ea93ee46 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
@@ -3,11 +3,8 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'leave_policy',
- 'transactions': [
- {
- 'label': _('Leaves'),
- 'items': ['Leave Policy Assignment', 'Leave Allocation']
- },
- ]
+ "fieldname": "leave_policy",
+ "transactions": [
+ {"label": _("Leaves"), "items": ["Leave Policy Assignment", "Leave Allocation"]},
+ ],
}
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
index a4b8af759e..33d5508705 100644
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
@@ -15,18 +15,25 @@ class TestLeavePolicy(unittest.TestCase):
leave_type.max_leaves_allowed = 2
leave_type.save()
- leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1)
+ leave_policy = create_leave_policy(
+ leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1
+ )
self.assertRaises(frappe.ValidationError, leave_policy.insert)
+
def create_leave_policy(**args):
- ''' Returns an object of leave policy '''
+ """Returns an object of leave policy"""
args = frappe._dict(args)
- return frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{
- "leave_type": args.leave_type or "_Test Leave Type",
- "annual_allocation": args.annual_allocation or 10
- }]
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [
+ {
+ "leave_type": args.leave_type or "_Test Leave Type",
+ "annual_allocation": args.annual_allocation or 10,
+ }
+ ],
+ }
+ )
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
index ae5ac7b689..2ed86f301c 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -22,22 +22,33 @@ class LeavePolicyAssignment(Document):
def set_dates(self):
if self.assignment_based_on == "Leave Period":
- self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
+ self.effective_from, self.effective_to = frappe.db.get_value(
+ "Leave Period", self.leave_period, ["from_date", "to_date"]
+ )
elif self.assignment_based_on == "Joining Date":
self.effective_from = frappe.db.get_value("Employee", self.employee, "date_of_joining")
def validate_policy_assignment_overlap(self):
- leave_policy_assignments = frappe.get_all("Leave Policy Assignment", filters = {
- "employee": self.employee,
- "name": ("!=", self.name),
- "docstatus": 1,
- "effective_to": (">=", self.effective_from),
- "effective_from": ("<=", self.effective_to)
- })
+ leave_policy_assignments = frappe.get_all(
+ "Leave Policy Assignment",
+ filters={
+ "employee": self.employee,
+ "name": ("!=", self.name),
+ "docstatus": 1,
+ "effective_to": (">=", self.effective_from),
+ "effective_from": ("<=", self.effective_to),
+ },
+ )
if len(leave_policy_assignments):
- frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
- .format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
+ frappe.throw(
+ _("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}").format(
+ bold(self.leave_policy),
+ bold(self.employee),
+ bold(formatdate(self.effective_from)),
+ bold(formatdate(self.effective_to)),
+ )
+ )
def warn_about_carry_forwarding(self):
if not self.carry_forward:
@@ -49,8 +60,9 @@ class LeavePolicyAssignment(Document):
for policy in leave_policy.leave_policy_details:
leave_type = leave_types.get(policy.leave_type)
if not leave_type.is_carry_forward:
- msg = _("Leaves for the Leave Type {0} won't be carry-forwarded since carry-forwarding is disabled.").format(
- frappe.bold(get_link_to_form("Leave Type", leave_type.name)))
+ msg = _(
+ "Leaves for the Leave Type {0} won't be carry-forwarded since carry-forwarding is disabled."
+ ).format(frappe.bold(get_link_to_form("Leave Type", leave_type.name)))
frappe.msgprint(msg, indicator="orange", alert=True)
@frappe.whitelist()
@@ -67,41 +79,54 @@ class LeavePolicyAssignment(Document):
for leave_policy_detail in leave_policy.leave_policy_details:
if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
leave_allocation, new_leaves_allocated = self.create_leave_allocation(
- leave_policy_detail.leave_type, leave_policy_detail.annual_allocation,
- leave_type_details, date_of_joining
+ leave_policy_detail.leave_type,
+ leave_policy_detail.annual_allocation,
+ leave_type_details,
+ date_of_joining,
)
- leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated}
+ leave_allocations[leave_policy_detail.leave_type] = {
+ "name": leave_allocation,
+ "leaves": new_leaves_allocated,
+ }
self.db_set("leaves_allocated", 1)
return leave_allocations
- def create_leave_allocation(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+ def create_leave_allocation(
+ self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ ):
# Creates leave allocation for the given employee in the provided leave period
carry_forward = self.carry_forward
if self.carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
carry_forward = 0
- new_leaves_allocated = self.get_new_leaves(leave_type, new_leaves_allocated,
- leave_type_details, date_of_joining)
+ new_leaves_allocated = self.get_new_leaves(
+ leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ )
- allocation = frappe.get_doc(dict(
- doctype="Leave Allocation",
- employee=self.employee,
- leave_type=leave_type,
- from_date=self.effective_from,
- to_date=self.effective_to,
- new_leaves_allocated=new_leaves_allocated,
- leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else '',
- leave_policy_assignment = self.name,
- leave_policy = self.leave_policy,
- carry_forward=carry_forward
- ))
- allocation.save(ignore_permissions = True)
+ allocation = frappe.get_doc(
+ dict(
+ doctype="Leave Allocation",
+ employee=self.employee,
+ leave_type=leave_type,
+ from_date=self.effective_from,
+ to_date=self.effective_to,
+ new_leaves_allocated=new_leaves_allocated,
+ leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else "",
+ leave_policy_assignment=self.name,
+ leave_policy=self.leave_policy,
+ carry_forward=carry_forward,
+ )
+ )
+ allocation.save(ignore_permissions=True)
allocation.submit()
return allocation.name, new_leaves_allocated
def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
from frappe.model.meta import get_field_precision
- precision = get_field_precision(frappe.get_meta("Leave Allocation").get_field("new_leaves_allocated"))
+
+ precision = get_field_precision(
+ frappe.get_meta("Leave Allocation").get_field("new_leaves_allocated")
+ )
# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
if leave_type_details.get(leave_type).is_compensatory == 1:
@@ -112,16 +137,22 @@ class LeavePolicyAssignment(Document):
new_leaves_allocated = 0
else:
# get leaves for past months if assignment is based on Leave Period / Joining Date
- new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining)
+ new_leaves_allocated = self.get_leaves_for_passed_months(
+ leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ )
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
elif getdate(date_of_joining) > getdate(self.effective_from):
- remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
+ remaining_period = (date_diff(self.effective_to, date_of_joining) + 1) / (
+ date_diff(self.effective_to, self.effective_from) + 1
+ )
new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
return flt(new_leaves_allocated, precision)
- def get_leaves_for_passed_months(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+ def get_leaves_for_passed_months(
+ self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ ):
from erpnext.hr.utils import get_monthly_earned_leave
current_date = frappe.flags.current_date or getdate()
@@ -144,8 +175,11 @@ class LeavePolicyAssignment(Document):
months_passed = add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj)
if months_passed > 0:
- monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated,
- leave_type_details.get(leave_type).earned_leave_frequency, leave_type_details.get(leave_type).rounding)
+ monthly_earned_leave = get_monthly_earned_leave(
+ new_leaves_allocated,
+ leave_type_details.get(leave_type).earned_leave_frequency,
+ leave_type_details.get(leave_type).rounding,
+ )
new_leaves_allocated = monthly_earned_leave * months_passed
else:
new_leaves_allocated = 0
@@ -174,7 +208,7 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj
def create_assignment_for_multiple_employees(employees, data):
if isinstance(employees, str):
- employees= json.loads(employees)
+ employees = json.loads(employees)
if isinstance(data, str):
data = frappe._dict(json.loads(data))
@@ -201,11 +235,23 @@ def create_assignment_for_multiple_employees(employees, data):
return docs_name
+
def get_leave_type_details():
leave_type_details = frappe._dict()
- leave_types = frappe.get_all("Leave Type",
- fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "based_on_date_of_joining",
- "is_carry_forward", "expire_carry_forwarded_leaves_after_days", "earned_leave_frequency", "rounding"])
+ leave_types = frappe.get_all(
+ "Leave Type",
+ fields=[
+ "name",
+ "is_lwp",
+ "is_earned_leave",
+ "is_compensatory",
+ "based_on_date_of_joining",
+ "is_carry_forward",
+ "expire_carry_forwarded_leaves_after_days",
+ "earned_leave_frequency",
+ "rounding",
+ ],
+ )
for d in leave_types:
leave_type_details.setdefault(d.name, d)
return leave_type_details
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
index 4363439b7c..13b39c7ee6 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
@@ -3,11 +3,8 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'leave_policy_assignment',
- 'transactions': [
- {
- 'label': _('Leaves'),
- 'items': ['Leave Allocation']
- },
- ]
+ "fieldname": "leave_policy_assignment",
+ "transactions": [
+ {"label": _("Leaves"), "items": ["Leave Allocation"]},
+ ],
}
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 27e4f148a6..9780828557 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -17,9 +17,16 @@ from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
test_dependencies = ["Employee"]
+
class TestLeavePolicyAssignment(unittest.TestCase):
def setUp(self):
- for doctype in ["Leave Period", "Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+ for doctype in [
+ "Leave Period",
+ "Leave Application",
+ "Leave Allocation",
+ "Leave Policy Assignment",
+ "Leave Ledger Entry",
+ ]:
frappe.db.delete(doctype)
employee = get_employee()
@@ -35,16 +42,25 @@ class TestLeavePolicyAssignment(unittest.TestCase):
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
- self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
+ self.assertEqual(
+ frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"),
+ 1,
+ )
- leave_allocation = frappe.get_list("Leave Allocation", filters={
- "employee": self.employee.name,
- "leave_policy":leave_policy.name,
- "leave_policy_assignment": leave_policy_assignments[0],
- "docstatus": 1})[0]
+ leave_allocation = frappe.get_list(
+ "Leave Allocation",
+ filters={
+ "employee": self.employee.name,
+ "leave_policy": leave_policy.name,
+ "leave_policy_assignment": leave_policy_assignments[0],
+ "docstatus": 1,
+ },
+ )[0]
leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
@@ -63,68 +79,94 @@ class TestLeavePolicyAssignment(unittest.TestCase):
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
# every leave is allocated no more leave can be granted now
- self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
- leave_allocation = frappe.get_list("Leave Allocation", filters={
- "employee": self.employee.name,
- "leave_policy":leave_policy.name,
- "leave_policy_assignment": leave_policy_assignments[0],
- "docstatus": 1})[0]
+ self.assertEqual(
+ frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"),
+ 1,
+ )
+ leave_allocation = frappe.get_list(
+ "Leave Allocation",
+ filters={
+ "employee": self.employee.name,
+ "leave_policy": leave_policy.name,
+ "leave_policy_assignment": leave_policy_assignments[0],
+ "docstatus": 1,
+ },
+ )[0]
leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
leave_alloc_doc.cancel()
leave_alloc_doc.delete()
- self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 0)
+ self.assertEqual(
+ frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"),
+ 0,
+ )
def test_earned_leave_allocation(self):
leave_period = create_leave_period("Test Earned Leave Period")
leave_type = create_earned_leave_type("Test Earned Leave")
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}]
- }).submit()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}],
+ }
+ ).submit()
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
# second last day of the month
# leaves allocated should be 0 since it is an earned leave and allocation happens via scheduler based on set frequency
frappe.flags.current_date = add_days(get_last_day(getdate()), -1)
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 0)
def test_earned_leave_alloc_for_passed_months_based_on_leave_period(self):
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -1)))
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -1))
+ )
# Case 1: assignment created one month after the leave period, should allocate 1 leave
frappe.flags.current_date = get_first_day(getdate())
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 1)
def test_earned_leave_alloc_for_passed_months_on_month_end_based_on_leave_period(self):
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -2))
+ )
# Case 2: assignment created on the last day of the leave period's latter month
# should allocate 1 leave for current month even though the month has not ended
# since the daily job might have already executed
@@ -133,32 +175,48 @@ class TestLeavePolicyAssignment(unittest.TestCase):
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
# if the daily job is not completed yet, there is another check present
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def test_earned_leave_alloc_for_passed_months_with_cf_leaves_based_on_leave_period(self):
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -2))
+ )
# initial leave allocation = 5
- leave_allocation = create_leave_allocation(employee=self.employee.name, employee_name=self.employee.employee_name, leave_type="Test Earned Leave",
- from_date=add_months(getdate(), -12), to_date=add_months(getdate(), -3), new_leaves_allocated=5, carry_forward=0)
+ leave_allocation = create_leave_allocation(
+ employee=self.employee.name,
+ employee_name=self.employee.employee_name,
+ leave_type="Test Earned Leave",
+ from_date=add_months(getdate(), -12),
+ to_date=add_months(getdate(), -3),
+ new_leaves_allocated=5,
+ carry_forward=0,
+ )
leave_allocation.submit()
# Case 3: assignment created on the last day of the leave period's latter month with carry forwarding
@@ -167,14 +225,19 @@ class TestLeavePolicyAssignment(unittest.TestCase):
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
"leave_period": leave_period.name,
- "carry_forward": 1
+ "carry_forward": 1,
}
# carry forwarded leaves = 5, 3 leaves allocated for passed months
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- details = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, ["total_leaves_allocated", "new_leaves_allocated", "unused_leaves", "name"], as_dict=True)
+ details = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ ["total_leaves_allocated", "new_leaves_allocated", "unused_leaves", "name"],
+ as_dict=True,
+ )
self.assertEqual(details.new_leaves_allocated, 2)
self.assertEqual(details.unused_leaves, 5)
self.assertEqual(details.total_leaves_allocated, 7)
@@ -182,20 +245,27 @@ class TestLeavePolicyAssignment(unittest.TestCase):
# if the daily job is not completed yet, there is another check present
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import is_earned_leave_already_allocated
+
frappe.flags.current_date = get_last_day(getdate())
allocation = frappe.get_doc("Leave Allocation", details.name)
# 1 leave is still pending to be allocated, irrespective of carry forwarded leaves
- self.assertFalse(is_earned_leave_already_allocated(allocation, leave_policy.leave_policy_details[0].annual_allocation))
+ self.assertFalse(
+ is_earned_leave_already_allocated(
+ allocation, leave_policy.leave_policy_details[0].annual_allocation
+ )
+ )
def test_earned_leave_alloc_for_passed_months_based_on_joining_date(self):
# tests leave alloc for earned leaves for assignment based on joining date in policy assignment
leave_type = create_earned_leave_type("Test Earned Leave")
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
- }).submit()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}],
+ }
+ ).submit()
# joining date set to 2 months back
self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
@@ -203,29 +273,39 @@ class TestLeavePolicyAssignment(unittest.TestCase):
# assignment created on the last day of the current month
frappe.flags.current_date = get_last_day(getdate())
- data = {
- "assignment_based_on": "Joining Date",
- "leave_policy": leave_policy.name
- }
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
- effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+ data = {"assignment_based_on": "Joining Date", "leave_policy": leave_policy.name}
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
+ effective_from = frappe.db.get_value(
+ "Leave Policy Assignment", leave_policy_assignments[0], "effective_from"
+ )
self.assertEqual(effective_from, self.employee.date_of_joining)
self.assertEqual(leaves_allocated, 3)
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
frappe.flags.current_date = get_last_day(getdate())
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def test_grant_leaves_on_doj_for_earned_leaves_based_on_leave_period(self):
# tests leave alloc based on leave period for earned leaves with "based on doj" configuration in leave type
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)), based_on_doj=True)
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -2)), based_on_doj=True
+ )
# joining date set to 2 months back
self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
@@ -237,34 +317,43 @@ class TestLeavePolicyAssignment(unittest.TestCase):
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
# if the daily job is not completed yet, there is another check present
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
frappe.flags.current_date = get_first_day(getdate())
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def test_grant_leaves_on_doj_for_earned_leaves_based_on_joining_date(self):
# tests leave alloc based on joining date for earned leaves with "based on doj" configuration in leave type
leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj=True)
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
- }).submit()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}],
+ }
+ ).submit()
# joining date set to 2 months back
# leave should be allocated for current month too since this day is same as the joining day
@@ -273,24 +362,32 @@ class TestLeavePolicyAssignment(unittest.TestCase):
# assignment created on the first day of the current month
frappe.flags.current_date = get_first_day(getdate())
- data = {
- "assignment_based_on": "Joining Date",
- "leave_policy": leave_policy.name
- }
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
- effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+ data = {"assignment_based_on": "Joining Date", "leave_policy": leave_policy.name}
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
+ effective_from = frappe.db.get_value(
+ "Leave Policy Assignment", leave_policy_assignments[0], "effective_from"
+ )
self.assertEqual(effective_from, self.employee.date_of_joining)
self.assertEqual(leaves_allocated, 3)
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
frappe.flags.current_date = get_first_day(getdate())
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def tearDown(self):
@@ -302,15 +399,17 @@ class TestLeavePolicyAssignment(unittest.TestCase):
def create_earned_leave_type(leave_type, based_on_doj=False):
frappe.delete_doc_if_exists("Leave Type", leave_type, force=1)
- return frappe.get_doc(dict(
- leave_type_name=leave_type,
- doctype="Leave Type",
- is_earned_leave=1,
- earned_leave_frequency="Monthly",
- rounding=0.5,
- is_carry_forward=1,
- based_on_date_of_joining=based_on_doj
- )).insert()
+ return frappe.get_doc(
+ dict(
+ leave_type_name=leave_type,
+ doctype="Leave Type",
+ is_earned_leave=1,
+ earned_leave_frequency="Monthly",
+ rounding=0.5,
+ is_carry_forward=1,
+ based_on_date_of_joining=based_on_doj,
+ )
+ ).insert()
def create_leave_period(name, start_date=None):
@@ -318,24 +417,27 @@ def create_leave_period(name, start_date=None):
if not start_date:
start_date = get_first_day(getdate())
- return frappe.get_doc(dict(
- name=name,
- doctype="Leave Period",
- from_date=start_date,
- to_date=add_months(start_date, 12),
- company="_Test Company",
- is_active=1
- )).insert()
+ return frappe.get_doc(
+ dict(
+ name=name,
+ doctype="Leave Period",
+ from_date=start_date,
+ to_date=add_months(start_date, 12),
+ company="_Test Company",
+ is_active=1,
+ )
+ ).insert()
def setup_leave_period_and_policy(start_date, based_on_doj=False):
leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj)
- leave_period = create_leave_period("Test Earned Leave Period",
- start_date=start_date)
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
- }).insert()
+ leave_period = create_leave_period("Test Earned Leave Period", start_date=start_date)
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}],
+ }
+ ).insert()
- return leave_period, leave_policy
\ No newline at end of file
+ return leave_period, leave_policy
diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py
index 4b59c2c09b..82b9bd6575 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.py
+++ b/erpnext/hr/doctype/leave_type/leave_type.py
@@ -11,17 +11,23 @@ from frappe.utils import today
class LeaveType(Document):
def validate(self):
if self.is_lwp:
- leave_allocation = frappe.get_all("Leave Allocation", filters={
- 'leave_type': self.name,
- 'from_date': ("<=", today()),
- 'to_date': (">=", today())
- }, fields=['name'])
- leave_allocation = [l['name'] for l in leave_allocation]
+ leave_allocation = frappe.get_all(
+ "Leave Allocation",
+ filters={"leave_type": self.name, "from_date": ("<=", today()), "to_date": (">=", today())},
+ fields=["name"],
+ )
+ leave_allocation = [l["name"] for l in leave_allocation]
if leave_allocation:
- frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec
+ frappe.throw(
+ _(
+ "Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay"
+ ).format(", ".join(leave_allocation))
+ ) # nosec
if self.is_lwp and self.is_ppl:
frappe.throw(_("Leave Type can be either without pay or partial pay"))
- if self.is_ppl and (self.fraction_of_daily_salary_per_leave < 0 or self.fraction_of_daily_salary_per_leave > 1):
+ if self.is_ppl and (
+ self.fraction_of_daily_salary_per_leave < 0 or self.fraction_of_daily_salary_per_leave > 1
+ ):
frappe.throw(_("The fraction of Daily Salary per Leave should be between 0 and 1"))
diff --git a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
index 074d3e4e52..269a1ecc69 100644
--- a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
+++ b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
@@ -1,12 +1,10 @@
def get_data():
return {
- 'fieldname': 'leave_type',
- 'transactions': [
+ "fieldname": "leave_type",
+ "transactions": [
{
- 'items': ['Leave Allocation', 'Leave Application'],
+ "items": ["Leave Allocation", "Leave Application"],
},
- {
- 'items': ['Attendance', 'Leave Encashment']
- }
- ]
+ {"items": ["Attendance", "Leave Encashment"]},
+ ],
}
diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py
index c1b64e99ef..69f9e12520 100644
--- a/erpnext/hr/doctype/leave_type/test_leave_type.py
+++ b/erpnext/hr/doctype/leave_type/test_leave_type.py
@@ -3,27 +3,30 @@
import frappe
-test_records = frappe.get_test_records('Leave Type')
+test_records = frappe.get_test_records("Leave Type")
+
def create_leave_type(**args):
- args = frappe._dict(args)
- if frappe.db.exists("Leave Type", args.leave_type_name):
- return frappe.get_doc("Leave Type", args.leave_type_name)
- leave_type = frappe.get_doc({
- "doctype": "Leave Type",
- "leave_type_name": args.leave_type_name or "_Test Leave Type",
- "include_holiday": args.include_holidays or 1,
- "allow_encashment": args.allow_encashment or 0,
- "is_earned_leave": args.is_earned_leave or 0,
- "is_lwp": args.is_lwp or 0,
- "is_ppl":args.is_ppl or 0,
- "is_carry_forward": args.is_carry_forward or 0,
- "expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
- "encashment_threshold_days": args.encashment_threshold_days or 5,
- "earning_component": "Leave Encashment"
- })
+ args = frappe._dict(args)
+ if frappe.db.exists("Leave Type", args.leave_type_name):
+ return frappe.get_doc("Leave Type", args.leave_type_name)
+ leave_type = frappe.get_doc(
+ {
+ "doctype": "Leave Type",
+ "leave_type_name": args.leave_type_name or "_Test Leave Type",
+ "include_holiday": args.include_holidays or 1,
+ "allow_encashment": args.allow_encashment or 0,
+ "is_earned_leave": args.is_earned_leave or 0,
+ "is_lwp": args.is_lwp or 0,
+ "is_ppl": args.is_ppl or 0,
+ "is_carry_forward": args.is_carry_forward or 0,
+ "expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
+ "encashment_threshold_days": args.encashment_threshold_days or 5,
+ "earning_component": "Leave Encashment",
+ }
+ )
- if leave_type.is_ppl:
- leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
+ if leave_type.is_ppl:
+ leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
- return leave_type
+ return leave_type
diff --git a/erpnext/hr/doctype/offer_term/test_offer_term.py b/erpnext/hr/doctype/offer_term/test_offer_term.py
index 2e5ed75438..2bea7b2597 100644
--- a/erpnext/hr/doctype/offer_term/test_offer_term.py
+++ b/erpnext/hr/doctype/offer_term/test_offer_term.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Offer Term')
+
class TestOfferTerm(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index 517730281f..5a1248698c 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -20,7 +20,7 @@ class ShiftAssignment(Document):
self.validate_overlapping_dates()
if self.end_date:
- self.validate_from_to_dates('start_date', 'end_date')
+ self.validate_from_to_dates("start_date", "end_date")
def validate_overlapping_dates(self):
if not self.name:
@@ -33,7 +33,7 @@ class ShiftAssignment(Document):
"""
if self.end_date:
- condition += """ or
+ condition += """ or
%(end_date)s between start_date and end_date
or
start_date between %(start_date)s and %(end_date)s
@@ -41,7 +41,8 @@ class ShiftAssignment(Document):
else:
condition += """ ) """
- assigned_shifts = frappe.db.sql("""
+ assigned_shifts = frappe.db.sql(
+ """
select name, shift_type, start_date ,end_date, docstatus, status
from `tabShift Assignment`
where
@@ -49,13 +50,18 @@ class ShiftAssignment(Document):
and name != %(name)s
and status = "Active"
{0}
- """.format(condition), {
- "employee": self.employee,
- "shift_type": self.shift_type,
- "start_date": self.start_date,
- "end_date": self.end_date,
- "name": self.name
- }, as_dict = 1)
+ """.format(
+ condition
+ ),
+ {
+ "employee": self.employee,
+ "shift_type": self.shift_type,
+ "start_date": self.start_date,
+ "end_date": self.end_date,
+ "name": self.name,
+ },
+ as_dict=1,
+ )
if len(assigned_shifts):
self.throw_overlap_error(assigned_shifts[0])
@@ -63,7 +69,9 @@ class ShiftAssignment(Document):
def throw_overlap_error(self, shift_details):
shift_details = frappe._dict(shift_details)
if shift_details.docstatus == 1 and shift_details.status == "Active":
- msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name))
+ msg = _("Employee {0} already has Active Shift {1}: {2}").format(
+ frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)
+ )
if shift_details.start_date:
msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y"))
title = "Ongoing Shift"
@@ -73,23 +81,27 @@ class ShiftAssignment(Document):
if msg:
frappe.throw(msg, title=title)
+
@frappe.whitelist()
def get_events(start, end, filters=None):
events = []
- employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"],
- as_dict=True)
+ employee = frappe.db.get_value(
+ "Employee", {"user_id": frappe.session.user}, ["name", "company"], as_dict=True
+ )
if employee:
employee, company = employee.name, employee.company
else:
- employee=''
- company=frappe.db.get_value("Global Defaults", None, "default_company")
+ employee = ""
+ company = frappe.db.get_value("Global Defaults", None, "default_company")
from frappe.desk.reportview import get_filters_cond
+
conditions = get_filters_cond("Shift Assignment", filters, [])
add_assignments(events, start, end, conditions=conditions)
return events
+
def add_assignments(events, start, end, conditions=None):
query = """select name, start_date, end_date, employee_name,
employee, docstatus, shift_type
@@ -101,7 +113,7 @@ def add_assignments(events, start, end, conditions=None):
if conditions:
query += conditions
- records = frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True)
+ records = frappe.db.sql(query, {"start_date": start, "end_date": end}, as_dict=True)
shift_timing_map = get_shift_type_timing([d.shift_type for d in records])
for d in records:
@@ -109,27 +121,33 @@ def add_assignments(events, start, end, conditions=None):
daily_event_end = d.end_date if d.end_date else getdate()
delta = timedelta(days=1)
while daily_event_start <= daily_event_end:
- start_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['start_time']
- end_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['end_time']
+ start_timing = (
+ frappe.utils.get_datetime(daily_event_start) + shift_timing_map[d.shift_type]["start_time"]
+ )
+ end_timing = (
+ frappe.utils.get_datetime(daily_event_start) + shift_timing_map[d.shift_type]["end_time"]
+ )
daily_event_start += delta
e = {
"name": d.name,
"doctype": "Shift Assignment",
"start_date": start_timing,
"end_date": end_timing,
- "title": cstr(d.employee_name) + ": "+ \
- cstr(d.shift_type),
+ "title": cstr(d.employee_name) + ": " + cstr(d.shift_type),
"docstatus": d.docstatus,
- "allDay": 0
+ "allDay": 0,
}
if e not in events:
events.append(e)
return events
+
def get_shift_type_timing(shift_types):
shift_timing_map = {}
- data = frappe.get_all("Shift Type", filters = {"name": ("IN", shift_types)}, fields = ['name', 'start_time', 'end_time'])
+ data = frappe.get_all(
+ "Shift Type", filters={"name": ("IN", shift_types)}, fields=["name", "start_time", "end_time"]
+ )
for d in data:
shift_timing_map[d.name] = d
@@ -137,7 +155,9 @@ def get_shift_type_timing(shift_types):
return shift_timing_map
-def get_employee_shift(employee, for_date=None, consider_default_shift=False, next_shift_direction=None):
+def get_employee_shift(
+ employee, for_date=None, consider_default_shift=False, next_shift_direction=None
+):
"""Returns a Shift Type for the given employee on the given date. (excluding the holidays)
:param employee: Employee for which shift is required.
@@ -147,21 +167,25 @@ def get_employee_shift(employee, for_date=None, consider_default_shift=False, ne
"""
if for_date is None:
for_date = nowdate()
- default_shift = frappe.db.get_value('Employee', employee, 'default_shift')
+ default_shift = frappe.db.get_value("Employee", employee, "default_shift")
shift_type_name = None
- shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date'])
+ shift_assignment_details = frappe.db.get_value(
+ "Shift Assignment",
+ {"employee": employee, "start_date": ("<=", for_date), "docstatus": "1", "status": "Active"},
+ ["shift_type", "end_date"],
+ )
if shift_assignment_details:
shift_type_name = shift_assignment_details[0]
# if end_date present means that shift is over after end_date else it is a ongoing shift.
- if shift_assignment_details[1] and for_date >= shift_assignment_details[1] :
+ if shift_assignment_details[1] and for_date >= shift_assignment_details[1]:
shift_type_name = None
if not shift_type_name and consider_default_shift:
shift_type_name = default_shift
if shift_type_name:
- holiday_list_name = frappe.db.get_value('Shift Type', shift_type_name, 'holiday_list')
+ holiday_list_name = frappe.db.get_value("Shift Type", shift_type_name, "holiday_list")
if not holiday_list_name:
holiday_list_name = get_holiday_list_for_employee(employee, False)
if holiday_list_name and is_holiday(holiday_list_name, for_date):
@@ -170,22 +194,30 @@ def get_employee_shift(employee, for_date=None, consider_default_shift=False, ne
if not shift_type_name and next_shift_direction:
MAX_DAYS = 366
if consider_default_shift and default_shift:
- direction = -1 if next_shift_direction == 'reverse' else +1
+ direction = -1 if next_shift_direction == "reverse" else +1
for i in range(MAX_DAYS):
- date = for_date+timedelta(days=direction*(i+1))
+ date = for_date + timedelta(days=direction * (i + 1))
shift_details = get_employee_shift(employee, date, consider_default_shift, None)
if shift_details:
shift_type_name = shift_details.shift_type.name
for_date = date
break
else:
- direction = '<' if next_shift_direction == 'reverse' else '>'
- sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc'
- dates = frappe.db.get_all('Shift Assignment',
- ['start_date', 'end_date'],
- {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"},
+ direction = "<" if next_shift_direction == "reverse" else ">"
+ sort_order = "desc" if next_shift_direction == "reverse" else "asc"
+ dates = frappe.db.get_all(
+ "Shift Assignment",
+ ["start_date", "end_date"],
+ {
+ "employee": employee,
+ "start_date": (direction, for_date),
+ "docstatus": "1",
+ "status": "Active",
+ },
as_list=True,
- limit=MAX_DAYS, order_by="start_date "+sort_order)
+ limit=MAX_DAYS,
+ order_by="start_date " + sort_order,
+ )
if dates:
for date in dates:
@@ -201,35 +233,57 @@ def get_employee_shift(employee, for_date=None, consider_default_shift=False, ne
def get_employee_shift_timings(employee, for_timestamp=None, consider_default_shift=False):
- """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee
- """
+ """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee"""
if for_timestamp is None:
for_timestamp = now_datetime()
# write and verify a test case for midnight shift.
prev_shift = curr_shift = next_shift = None
- curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward')
+ curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, "forward")
if curr_shift:
- next_shift = get_employee_shift(employee, curr_shift.start_datetime.date()+timedelta(days=1), consider_default_shift, 'forward')
- prev_shift = get_employee_shift(employee, for_timestamp.date()+timedelta(days=-1), consider_default_shift, 'reverse')
+ next_shift = get_employee_shift(
+ employee,
+ curr_shift.start_datetime.date() + timedelta(days=1),
+ consider_default_shift,
+ "forward",
+ )
+ prev_shift = get_employee_shift(
+ employee, for_timestamp.date() + timedelta(days=-1), consider_default_shift, "reverse"
+ )
if curr_shift:
if prev_shift:
- curr_shift.actual_start = prev_shift.end_datetime if curr_shift.actual_start < prev_shift.end_datetime else curr_shift.actual_start
- prev_shift.actual_end = curr_shift.actual_start if prev_shift.actual_end > curr_shift.actual_start else prev_shift.actual_end
+ curr_shift.actual_start = (
+ prev_shift.end_datetime
+ if curr_shift.actual_start < prev_shift.end_datetime
+ else curr_shift.actual_start
+ )
+ prev_shift.actual_end = (
+ curr_shift.actual_start
+ if prev_shift.actual_end > curr_shift.actual_start
+ else prev_shift.actual_end
+ )
if next_shift:
- next_shift.actual_start = curr_shift.end_datetime if next_shift.actual_start < curr_shift.end_datetime else next_shift.actual_start
- curr_shift.actual_end = next_shift.actual_start if curr_shift.actual_end > next_shift.actual_start else curr_shift.actual_end
+ next_shift.actual_start = (
+ curr_shift.end_datetime
+ if next_shift.actual_start < curr_shift.end_datetime
+ else next_shift.actual_start
+ )
+ curr_shift.actual_end = (
+ next_shift.actual_start
+ if curr_shift.actual_end > next_shift.actual_start
+ else curr_shift.actual_end
+ )
return prev_shift, curr_shift, next_shift
def get_shift_details(shift_type_name, for_date=None):
"""Returns Shift Details which contain some additional information as described below.
'shift_details' contains the following keys:
- 'shift_type' - Object of DocType Shift Type,
- 'start_datetime' - Date and Time of shift start on given date,
- 'end_datetime' - Date and Time of shift end on given date,
- 'actual_start' - datetime of shift start after adding 'begin_check_in_before_shift_start_time',
- 'actual_end' - datetime of shift end after adding 'allow_check_out_after_shift_end_time'(None is returned if this is zero)
+ 'shift_type' - Object of DocType Shift Type,
+ 'start_datetime' - Date and Time of shift start on given date,
+ 'end_datetime' - Date and Time of shift end on given date,
+ 'actual_start' - datetime of shift start after adding 'begin_check_in_before_shift_start_time',
+ 'actual_end' - datetime of shift end after adding 'allow_check_out_after_shift_end_time'(None is returned if this is zero)
:param shift_type_name: shift type name for which shift_details is required.
:param for_date: Date on which shift_details are required
@@ -238,30 +292,38 @@ def get_shift_details(shift_type_name, for_date=None):
return None
if not for_date:
for_date = nowdate()
- shift_type = frappe.get_doc('Shift Type', shift_type_name)
+ shift_type = frappe.get_doc("Shift Type", shift_type_name)
start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time
- for_date = for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date
+ for_date = (
+ for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date
+ )
end_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.end_time
- actual_start = start_datetime - timedelta(minutes=shift_type.begin_check_in_before_shift_start_time)
+ actual_start = start_datetime - timedelta(
+ minutes=shift_type.begin_check_in_before_shift_start_time
+ )
actual_end = end_datetime + timedelta(minutes=shift_type.allow_check_out_after_shift_end_time)
- return frappe._dict({
- 'shift_type': shift_type,
- 'start_datetime': start_datetime,
- 'end_datetime': end_datetime,
- 'actual_start': actual_start,
- 'actual_end': actual_end
- })
+ return frappe._dict(
+ {
+ "shift_type": shift_type,
+ "start_datetime": start_datetime,
+ "end_datetime": end_datetime,
+ "actual_start": actual_start,
+ "actual_end": actual_end,
+ }
+ )
def get_actual_start_end_datetime_of_shift(employee, for_datetime, consider_default_shift=False):
"""Takes a datetime and returns the 'actual' start datetime and end datetime of the shift in which the timestamp belongs.
- Here 'actual' means - taking in to account the "begin_check_in_before_shift_start_time" and "allow_check_out_after_shift_end_time".
- None is returned if the timestamp is outside any actual shift timings.
- Shift Details is also returned(current/upcoming i.e. if timestamp not in any actual shift then details of next shift returned)
+ Here 'actual' means - taking in to account the "begin_check_in_before_shift_start_time" and "allow_check_out_after_shift_end_time".
+ None is returned if the timestamp is outside any actual shift timings.
+ Shift Details is also returned(current/upcoming i.e. if timestamp not in any actual shift then details of next shift returned)
"""
actual_shift_start = actual_shift_end = shift_details = None
- shift_timings_as_per_timestamp = get_employee_shift_timings(employee, for_datetime, consider_default_shift)
+ shift_timings_as_per_timestamp = get_employee_shift_timings(
+ employee, for_datetime, consider_default_shift
+ )
timestamp_list = []
for shift in shift_timings_as_per_timestamp:
if shift:
@@ -273,11 +335,11 @@ def get_actual_start_end_datetime_of_shift(employee, for_datetime, consider_defa
if timestamp and for_datetime <= timestamp:
timestamp_index = index
break
- if timestamp_index and timestamp_index%2 == 1:
- shift_details = shift_timings_as_per_timestamp[int((timestamp_index-1)/2)]
+ if timestamp_index and timestamp_index % 2 == 1:
+ shift_details = shift_timings_as_per_timestamp[int((timestamp_index - 1) / 2)]
actual_shift_start = shift_details.actual_start
actual_shift_end = shift_details.actual_end
elif timestamp_index:
- shift_details = shift_timings_as_per_timestamp[int(timestamp_index/2)]
+ shift_details = shift_timings_as_per_timestamp[int(timestamp_index / 2)]
return actual_shift_start, actual_shift_end, shift_details
diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
index d4900814ff..4a1ec293bd 100644
--- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
@@ -8,19 +8,21 @@ from frappe.utils import add_days, nowdate
test_dependencies = ["Shift Type"]
-class TestShiftAssignment(unittest.TestCase):
+class TestShiftAssignment(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabShift Assignment`")
def test_make_shift_assignment(self):
- shift_assignment = frappe.get_doc({
- "doctype": "Shift Assignment",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "start_date": nowdate()
- }).insert()
+ shift_assignment = frappe.get_doc(
+ {
+ "doctype": "Shift Assignment",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "start_date": nowdate(),
+ }
+ ).insert()
shift_assignment.submit()
self.assertEqual(shift_assignment.docstatus, 1)
@@ -28,52 +30,59 @@ class TestShiftAssignment(unittest.TestCase):
def test_overlapping_for_ongoing_shift(self):
# shift should be Ongoing if Only start_date is present and status = Active
- shift_assignment_1 = frappe.get_doc({
- "doctype": "Shift Assignment",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "start_date": nowdate(),
- "status": 'Active'
- }).insert()
+ shift_assignment_1 = frappe.get_doc(
+ {
+ "doctype": "Shift Assignment",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "start_date": nowdate(),
+ "status": "Active",
+ }
+ ).insert()
shift_assignment_1.submit()
self.assertEqual(shift_assignment_1.docstatus, 1)
- shift_assignment = frappe.get_doc({
- "doctype": "Shift Assignment",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "start_date": add_days(nowdate(), 2)
- })
+ shift_assignment = frappe.get_doc(
+ {
+ "doctype": "Shift Assignment",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "start_date": add_days(nowdate(), 2),
+ }
+ )
self.assertRaises(frappe.ValidationError, shift_assignment.save)
def test_overlapping_for_fixed_period_shift(self):
# shift should is for Fixed period if Only start_date and end_date both are present and status = Active
- shift_assignment_1 = frappe.get_doc({
+ shift_assignment_1 = frappe.get_doc(
+ {
"doctype": "Shift Assignment",
"shift_type": "Day Shift",
"company": "_Test Company",
"employee": "_T-Employee-00001",
"start_date": nowdate(),
"end_date": add_days(nowdate(), 30),
- "status": 'Active'
- }).insert()
- shift_assignment_1.submit()
+ "status": "Active",
+ }
+ ).insert()
+ shift_assignment_1.submit()
-
- # it should not allowed within period of any shift.
- shift_assignment_3 = frappe.get_doc({
+ # it should not allowed within period of any shift.
+ shift_assignment_3 = frappe.get_doc(
+ {
"doctype": "Shift Assignment",
"shift_type": "Day Shift",
"company": "_Test Company",
"employee": "_T-Employee-00001",
- "start_date":add_days(nowdate(), 10),
+ "start_date": add_days(nowdate(), 10),
"end_date": add_days(nowdate(), 35),
- "status": 'Active'
- })
+ "status": "Active",
+ }
+ )
- self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
+ self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index d4fcf99d7d..1e3e8ff646 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -10,7 +10,9 @@ from frappe.utils import formatdate, getdate
from erpnext.hr.utils import share_doc_with_approver, validate_active_employee
-class OverlapError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
class ShiftRequest(Document):
def validate(self):
@@ -39,24 +41,35 @@ class ShiftRequest(Document):
assignment_doc.insert()
assignment_doc.submit()
- frappe.msgprint(_("Shift Assignment: {0} created for Employee: {1}").format(frappe.bold(assignment_doc.name), frappe.bold(self.employee)))
+ frappe.msgprint(
+ _("Shift Assignment: {0} created for Employee: {1}").format(
+ frappe.bold(assignment_doc.name), frappe.bold(self.employee)
+ )
+ )
def on_cancel(self):
- shift_assignment_list = frappe.get_list("Shift Assignment", {'employee': self.employee, 'shift_request': self.name})
+ shift_assignment_list = frappe.get_list(
+ "Shift Assignment", {"employee": self.employee, "shift_request": self.name}
+ )
if shift_assignment_list:
for shift in shift_assignment_list:
- shift_assignment_doc = frappe.get_doc("Shift Assignment", shift['name'])
+ shift_assignment_doc = frappe.get_doc("Shift Assignment", shift["name"])
shift_assignment_doc.cancel()
def validate_default_shift(self):
default_shift = frappe.get_value("Employee", self.employee, "default_shift")
if self.shift_type == default_shift:
- frappe.throw(_("You can not request for your Default Shift: {0}").format(frappe.bold(self.shift_type)))
+ frappe.throw(
+ _("You can not request for your Default Shift: {0}").format(frappe.bold(self.shift_type))
+ )
def validate_approver(self):
department = frappe.get_value("Employee", self.employee, "department")
shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver")
- approvers = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))
+ approvers = frappe.db.sql(
+ """select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""",
+ (department),
+ )
approvers = [approver[0] for approver in approvers]
approvers.append(shift_approver)
if self.approver not in approvers:
@@ -67,10 +80,11 @@ class ShiftRequest(Document):
frappe.throw(_("To date cannot be before from date"))
def validate_shift_request_overlap_dates(self):
- if not self.name:
- self.name = "New Shift Request"
+ if not self.name:
+ self.name = "New Shift Request"
- d = frappe.db.sql("""
+ d = frappe.db.sql(
+ """
select
name, shift_type, from_date, to_date
from `tabShift Request`
@@ -79,20 +93,23 @@ class ShiftRequest(Document):
and %(from_date)s <= to_date) or
( %(to_date)s >= from_date
and %(to_date)s <= to_date ))
- and name != %(name)s""", {
- "employee": self.employee,
- "shift_type": self.shift_type,
- "from_date": self.from_date,
- "to_date": self.to_date,
- "name": self.name
- }, as_dict=1)
+ and name != %(name)s""",
+ {
+ "employee": self.employee,
+ "shift_type": self.shift_type,
+ "from_date": self.from_date,
+ "to_date": self.to_date,
+ "name": self.name,
+ },
+ as_dict=1,
+ )
- for date_overlap in d:
- if date_overlap ['name']:
- self.throw_overlap_error(date_overlap)
+ for date_overlap in d:
+ if date_overlap["name"]:
+ self.throw_overlap_error(date_overlap)
def throw_overlap_error(self, d):
- msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
- d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
- + """ {0}""".format(d["name"])
+ msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(
+ self.employee, d["shift_type"], formatdate(d["from_date"]), formatdate(d["to_date"])
+ ) + """ {0}""".format(d["name"])
frappe.throw(msg, OverlapError)
diff --git a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
index 531c98db5f..2859b8f771 100644
--- a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
+++ b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'shift_request',
- 'transactions': [
- {
- 'items': ['Shift Assignment']
- },
- ],
- }
+ return {
+ "fieldname": "shift_request",
+ "transactions": [
+ {"items": ["Shift Assignment"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py
index 3633c9b300..b4f5177215 100644
--- a/erpnext/hr/doctype/shift_request/test_shift_request.py
+++ b/erpnext/hr/doctype/shift_request/test_shift_request.py
@@ -10,6 +10,7 @@ from erpnext.hr.doctype.employee.test_employee import make_employee
test_dependencies = ["Shift Type"]
+
class TestShiftRequest(unittest.TestCase):
def setUp(self):
for doctype in ["Shift Request", "Shift Assignment"]:
@@ -20,9 +21,12 @@ class TestShiftRequest(unittest.TestCase):
def test_make_shift_request(self):
"Test creation/updation of Shift Assignment from Shift Request."
- department = frappe.get_value("Employee", "_T-Employee-00001", 'department')
+ department = frappe.get_value("Employee", "_T-Employee-00001", "department")
set_shift_approver(department)
- approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
+ approver = frappe.db.sql(
+ """select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""",
+ (department),
+ )[0][0]
shift_request = make_shift_request(approver)
@@ -31,7 +35,7 @@ class TestShiftRequest(unittest.TestCase):
"Shift Assignment",
filters={"shift_request": shift_request.name},
fieldname=["employee", "docstatus"],
- as_dict=True
+ as_dict=True,
)
self.assertEqual(shift_request.employee, shift_assignment.employee)
self.assertEqual(shift_assignment.docstatus, 1)
@@ -39,9 +43,7 @@ class TestShiftRequest(unittest.TestCase):
shift_request.cancel()
shift_assignment_docstatus = frappe.db.get_value(
- "Shift Assignment",
- filters={"shift_request": shift_request.name},
- fieldname="docstatus"
+ "Shift Assignment", filters={"shift_request": shift_request.name}, fieldname="docstatus"
)
self.assertEqual(shift_assignment_docstatus, 2)
@@ -62,7 +64,10 @@ class TestShiftRequest(unittest.TestCase):
shift_request.reload()
department = frappe.get_value("Employee", "_T-Employee-00001", "department")
set_shift_approver(department)
- department_approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
+ department_approver = frappe.db.sql(
+ """select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""",
+ (department),
+ )[0][0]
shift_request.approver = department_approver
shift_request.save()
self.assertTrue(shift_request.name not in frappe.share.get_shared("Shift Request", user))
@@ -85,22 +90,25 @@ class TestShiftRequest(unittest.TestCase):
def set_shift_approver(department):
department_doc = frappe.get_doc("Department", department)
- department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
+ department_doc.append("shift_request_approver", {"approver": "test1@example.com"})
department_doc.save()
department_doc.reload()
+
def make_shift_request(approver, do_not_submit=0):
- shift_request = frappe.get_doc({
- "doctype": "Shift Request",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "employee_name": "_Test Employee",
- "from_date": nowdate(),
- "to_date": add_days(nowdate(), 10),
- "approver": approver,
- "status": "Approved"
- }).insert()
+ shift_request = frappe.get_doc(
+ {
+ "doctype": "Shift Request",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "employee_name": "_Test Employee",
+ "from_date": nowdate(),
+ "to_date": add_days(nowdate(), 10),
+ "approver": approver,
+ "status": "Approved",
+ }
+ ).insert()
if do_not_submit:
return shift_request
diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py
index 562a5739d6..3f5cb222bf 100644
--- a/erpnext/hr/doctype/shift_type/shift_type.py
+++ b/erpnext/hr/doctype/shift_type/shift_type.py
@@ -24,20 +24,45 @@ from erpnext.hr.doctype.shift_assignment.shift_assignment import (
class ShiftType(Document):
@frappe.whitelist()
def process_auto_attendance(self):
- if not cint(self.enable_auto_attendance) or not self.process_attendance_after or not self.last_sync_of_checkin:
+ if (
+ not cint(self.enable_auto_attendance)
+ or not self.process_attendance_after
+ or not self.last_sync_of_checkin
+ ):
return
filters = {
- 'skip_auto_attendance':'0',
- 'attendance':('is', 'not set'),
- 'time':('>=', self.process_attendance_after),
- 'shift_actual_end': ('<', self.last_sync_of_checkin),
- 'shift': self.name
+ "skip_auto_attendance": "0",
+ "attendance": ("is", "not set"),
+ "time": (">=", self.process_attendance_after),
+ "shift_actual_end": ("<", self.last_sync_of_checkin),
+ "shift": self.name,
}
- logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
- for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
+ logs = frappe.db.get_list(
+ "Employee Checkin", fields="*", filters=filters, order_by="employee,time"
+ )
+ for key, group in itertools.groupby(
+ logs, key=lambda x: (x["employee"], x["shift_actual_start"])
+ ):
single_shift_logs = list(group)
- attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs)
- mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name)
+ (
+ attendance_status,
+ working_hours,
+ late_entry,
+ early_exit,
+ in_time,
+ out_time,
+ ) = self.get_attendance(single_shift_logs)
+ mark_attendance_and_link_log(
+ single_shift_logs,
+ attendance_status,
+ key[1].date(),
+ working_hours,
+ late_entry,
+ early_exit,
+ in_time,
+ out_time,
+ self.name,
+ )
for employee in self.get_assigned_employee(self.process_attendance_after, True):
self.mark_absent_for_dates_with_no_attendance(employee)
@@ -45,36 +70,66 @@ class ShiftType(Document):
"""Return attendance_status, working_hours, late_entry, early_exit, in_time, out_time
for a set of logs belonging to a single shift.
Assumtion:
- 1. These logs belongs to an single shift, single employee and is not in a holiday date.
- 2. Logs are in chronological order
+ 1. These logs belongs to an single shift, single employee and is not in a holiday date.
+ 2. Logs are in chronological order
"""
late_entry = early_exit = False
- total_working_hours, in_time, out_time = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on)
- if cint(self.enable_entry_grace_period) and in_time and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period)):
+ total_working_hours, in_time, out_time = calculate_working_hours(
+ logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on
+ )
+ if (
+ cint(self.enable_entry_grace_period)
+ and in_time
+ and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period))
+ ):
late_entry = True
- if cint(self.enable_exit_grace_period) and out_time and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period)):
+ if (
+ cint(self.enable_exit_grace_period)
+ and out_time
+ and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period))
+ ):
early_exit = True
- if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
- return 'Absent', total_working_hours, late_entry, early_exit, in_time, out_time
- if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
- return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time
- return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time
+ if (
+ self.working_hours_threshold_for_absent
+ and total_working_hours < self.working_hours_threshold_for_absent
+ ):
+ return "Absent", total_working_hours, late_entry, early_exit, in_time, out_time
+ if (
+ self.working_hours_threshold_for_half_day
+ and total_working_hours < self.working_hours_threshold_for_half_day
+ ):
+ return "Half Day", total_working_hours, late_entry, early_exit, in_time, out_time
+ return "Present", total_working_hours, late_entry, early_exit, in_time, out_time
def mark_absent_for_dates_with_no_attendance(self, employee):
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.
The Absent is marked starting from 'process_attendance_after' or employee creation date.
"""
- date_of_joining, relieving_date, employee_creation = frappe.db.get_value("Employee", employee, ["date_of_joining", "relieving_date", "creation"])
+ date_of_joining, relieving_date, employee_creation = frappe.db.get_value(
+ "Employee", employee, ["date_of_joining", "relieving_date", "creation"]
+ )
if not date_of_joining:
date_of_joining = employee_creation.date()
start_date = max(getdate(self.process_attendance_after), date_of_joining)
- actual_shift_datetime = get_actual_start_end_datetime_of_shift(employee, get_datetime(self.last_sync_of_checkin), True)
- last_shift_time = actual_shift_datetime[0] if actual_shift_datetime[0] else get_datetime(self.last_sync_of_checkin)
- prev_shift = get_employee_shift(employee, last_shift_time.date()-timedelta(days=1), True, 'reverse')
+ actual_shift_datetime = get_actual_start_end_datetime_of_shift(
+ employee, get_datetime(self.last_sync_of_checkin), True
+ )
+ last_shift_time = (
+ actual_shift_datetime[0]
+ if actual_shift_datetime[0]
+ else get_datetime(self.last_sync_of_checkin)
+ )
+ prev_shift = get_employee_shift(
+ employee, last_shift_time.date() - timedelta(days=1), True, "reverse"
+ )
if prev_shift:
- end_date = min(prev_shift.start_datetime.date(), relieving_date) if relieving_date else prev_shift.start_datetime.date()
+ end_date = (
+ min(prev_shift.start_datetime.date(), relieving_date)
+ if relieving_date
+ else prev_shift.start_datetime.date()
+ )
else:
return
holiday_list_name = self.holiday_list
@@ -84,37 +139,40 @@ class ShiftType(Document):
for date in dates:
shift_details = get_employee_shift(employee, date, True)
if shift_details and shift_details.shift_type.name == self.name:
- mark_attendance(employee, date, 'Absent', self.name)
+ mark_attendance(employee, date, "Absent", self.name)
def get_assigned_employee(self, from_date=None, consider_default_shift=False):
- filters = {'start_date':('>', from_date), 'shift_type': self.name, 'docstatus': '1'}
+ filters = {"start_date": (">", from_date), "shift_type": self.name, "docstatus": "1"}
if not from_date:
del filters["start_date"]
- assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True)
+ assigned_employees = frappe.get_all("Shift Assignment", "employee", filters, as_list=True)
assigned_employees = [x[0] for x in assigned_employees]
if consider_default_shift:
- filters = {'default_shift': self.name, 'status': ['!=', 'Inactive']}
- default_shift_employees = frappe.get_all('Employee', 'name', filters, as_list=True)
+ filters = {"default_shift": self.name, "status": ["!=", "Inactive"]}
+ default_shift_employees = frappe.get_all("Employee", "name", filters, as_list=True)
default_shift_employees = [x[0] for x in default_shift_employees]
- return list(set(assigned_employees+default_shift_employees))
+ return list(set(assigned_employees + default_shift_employees))
return assigned_employees
+
def process_auto_attendance_for_all_shifts():
- shift_list = frappe.get_all('Shift Type', 'name', {'enable_auto_attendance':'1'}, as_list=True)
+ shift_list = frappe.get_all("Shift Type", "name", {"enable_auto_attendance": "1"}, as_list=True)
for shift in shift_list:
- doc = frappe.get_doc('Shift Type', shift[0])
+ doc = frappe.get_doc("Shift Type", shift[0])
doc.process_auto_attendance()
-def get_filtered_date_list(employee, start_date, end_date, filter_attendance=True, holiday_list=None):
- """Returns a list of dates after removing the dates with attendance and holidays
- """
+
+def get_filtered_date_list(
+ employee, start_date, end_date, filter_attendance=True, holiday_list=None
+):
+ """Returns a list of dates after removing the dates with attendance and holidays"""
base_dates_query = """select adddate(%(start_date)s, t2.i*100 + t1.i*10 + t0.i) selected_date from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2"""
- condition_query = ''
+ condition_query = ""
if filter_attendance:
condition_query += """ and a.selected_date not in (
select attendance_date from `tabAttendance`
@@ -126,10 +184,20 @@ def get_filtered_date_list(employee, start_date, end_date, filter_attendance=Tru
parentfield = 'holidays' and parent = %(holiday_list)s
and holiday_date between %(start_date)s and %(end_date)s)"""
- dates = frappe.db.sql("""select * from
+ dates = frappe.db.sql(
+ """select * from
({base_dates_query}) as a
where a.selected_date <= %(end_date)s {condition_query}
- """.format(base_dates_query=base_dates_query, condition_query=condition_query),
- {"employee":employee, "start_date":start_date, "end_date":end_date, "holiday_list":holiday_list}, as_list=True)
+ """.format(
+ base_dates_query=base_dates_query, condition_query=condition_query
+ ),
+ {
+ "employee": employee,
+ "start_date": start_date,
+ "end_date": end_date,
+ "holiday_list": holiday_list,
+ },
+ as_list=True,
+ )
return [getdate(date[0]) for date in dates]
diff --git a/erpnext/hr/doctype/shift_type/shift_type_dashboard.py b/erpnext/hr/doctype/shift_type/shift_type_dashboard.py
index 919da2db27..920d8fd547 100644
--- a/erpnext/hr/doctype/shift_type/shift_type_dashboard.py
+++ b/erpnext/hr/doctype/shift_type/shift_type_dashboard.py
@@ -1,13 +1,8 @@
def get_data():
return {
- 'fieldname': 'shift',
- 'non_standard_fieldnames': {
- 'Shift Request': 'shift_type',
- 'Shift Assignment': 'shift_type'
- },
- 'transactions': [
- {
- 'items': ['Attendance', 'Employee Checkin', 'Shift Request', 'Shift Assignment']
- }
- ]
+ "fieldname": "shift",
+ "non_standard_fieldnames": {"Shift Request": "shift_type", "Shift Assignment": "shift_type"},
+ "transactions": [
+ {"items": ["Attendance", "Employee Checkin", "Shift Request", "Shift Assignment"]}
+ ],
}
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 7b2ea215ad..93a493c9d2 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -9,8 +9,13 @@ from frappe.utils import cint, flt, getdate, nowdate
from frappe.utils.nestedset import get_descendants_of
-class SubsidiaryCompanyError(frappe.ValidationError): pass
-class ParentCompanyError(frappe.ValidationError): pass
+class SubsidiaryCompanyError(frappe.ValidationError):
+ pass
+
+
+class ParentCompanyError(frappe.ValidationError):
+ pass
+
class StaffingPlan(Document):
def validate(self):
@@ -33,11 +38,11 @@ class StaffingPlan(Document):
self.total_estimated_budget = 0
for detail in self.get("staffing_details"):
- #Set readonly fields
+ # Set readonly fields
self.set_number_of_positions(detail)
designation_counts = get_designation_counts(detail.designation, self.company)
- detail.current_count = designation_counts['employee_count']
- detail.current_openings = designation_counts['job_openings']
+ detail.current_count = designation_counts["employee_count"]
+ detail.current_openings = designation_counts["job_openings"]
detail.total_estimated_cost = 0
if detail.number_of_positions > 0:
@@ -52,80 +57,122 @@ class StaffingPlan(Document):
def validate_overlap(self, staffing_plan_detail):
# Validate if any submitted Staffing Plan exist for any Designations in this plan
# and spd.vacancies>0 ?
- overlap = frappe.db.sql("""select spd.parent
+ overlap = frappe.db.sql(
+ """select spd.parent
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <= %s and sp.company = %s
- """, (staffing_plan_detail.designation, self.from_date, self.to_date, self.company))
- if overlap and overlap [0][0]:
- frappe.throw(_("Staffing Plan {0} already exist for designation {1}")
- .format(overlap[0][0], staffing_plan_detail.designation))
+ """,
+ (staffing_plan_detail.designation, self.from_date, self.to_date, self.company),
+ )
+ if overlap and overlap[0][0]:
+ frappe.throw(
+ _("Staffing Plan {0} already exist for designation {1}").format(
+ overlap[0][0], staffing_plan_detail.designation
+ )
+ )
def validate_with_parent_plan(self, staffing_plan_detail):
- if not frappe.get_cached_value('Company', self.company, "parent_company"):
- return # No parent, nothing to validate
+ if not frappe.get_cached_value("Company", self.company, "parent_company"):
+ return # No parent, nothing to validate
# Get staffing plan applicable for the company (Parent Company)
- parent_plan_details = get_active_staffing_plan_details(self.company, staffing_plan_detail.designation, self.from_date, self.to_date)
+ parent_plan_details = get_active_staffing_plan_details(
+ self.company, staffing_plan_detail.designation, self.from_date, self.to_date
+ )
if not parent_plan_details:
- return #no staffing plan for any parent Company in hierarchy
+ return # no staffing plan for any parent Company in hierarchy
# Fetch parent company which owns the staffing plan. NOTE: Parent could be higher up in the hierarchy
parent_company = frappe.db.get_value("Staffing Plan", parent_plan_details[0].name, "company")
# Parent plan available, validate with parent, siblings as well as children of staffing plan Company
- if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or \
- flt(staffing_plan_detail.total_estimated_cost) > flt(parent_plan_details[0].total_estimated_cost):
- frappe.throw(_("You can only plan for upto {0} vacancies and budget {1} \
- for {2} as per staffing plan {3} for parent company {4}.").format(
+ if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or flt(
+ staffing_plan_detail.total_estimated_cost
+ ) > flt(parent_plan_details[0].total_estimated_cost):
+ frappe.throw(
+ _(
+ "You can only plan for upto {0} vacancies and budget {1} \
+ for {2} as per staffing plan {3} for parent company {4}."
+ ).format(
cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_plan_details[0].name,
- parent_company), ParentCompanyError)
+ parent_company,
+ ),
+ ParentCompanyError,
+ )
- #Get vacanices already planned for all companies down the hierarchy of Parent Company
- lft, rgt = frappe.get_cached_value('Company', parent_company, ["lft", "rgt"])
- all_sibling_details = frappe.db.sql("""select sum(spd.vacancies) as vacancies,
+ # Get vacanices already planned for all companies down the hierarchy of Parent Company
+ lft, rgt = frappe.get_cached_value("Company", parent_company, ["lft", "rgt"])
+ all_sibling_details = frappe.db.sql(
+ """select sum(spd.vacancies) as vacancies,
sum(spd.total_estimated_cost) as total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <=%s
and sp.company in (select name from tabCompany where lft > %s and rgt < %s)
- """, (staffing_plan_detail.designation, self.from_date, self.to_date, lft, rgt), as_dict = 1)[0]
+ """,
+ (staffing_plan_detail.designation, self.from_date, self.to_date, lft, rgt),
+ as_dict=1,
+ )[0]
- if (cint(parent_plan_details[0].vacancies) < \
- (cint(staffing_plan_detail.vacancies) + cint(all_sibling_details.vacancies))) or \
- (flt(parent_plan_details[0].total_estimated_cost) < \
- (flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost))):
- frappe.throw(_("{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
- You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}.").format(
+ if (
+ cint(parent_plan_details[0].vacancies)
+ < (cint(staffing_plan_detail.vacancies) + cint(all_sibling_details.vacancies))
+ ) or (
+ flt(parent_plan_details[0].total_estimated_cost)
+ < (
+ flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost)
+ )
+ ):
+ frappe.throw(
+ _(
+ "{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
+ You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}."
+ ).format(
cint(all_sibling_details.vacancies),
all_sibling_details.total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_company,
cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
- parent_plan_details[0].name))
+ parent_plan_details[0].name,
+ )
+ )
def validate_with_subsidiary_plans(self, staffing_plan_detail):
- #Valdate this plan with all child company plan
- children_details = frappe.db.sql("""select sum(spd.vacancies) as vacancies,
+ # Valdate this plan with all child company plan
+ children_details = frappe.db.sql(
+ """select sum(spd.vacancies) as vacancies,
sum(spd.total_estimated_cost) as total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <=%s
and sp.company in (select name from tabCompany where parent_company = %s)
- """, (staffing_plan_detail.designation, self.from_date, self.to_date, self.company), as_dict = 1)[0]
+ """,
+ (staffing_plan_detail.designation, self.from_date, self.to_date, self.company),
+ as_dict=1,
+ )[0]
- if children_details and \
- cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies) or \
- flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost):
- frappe.throw(_("Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
- Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies").format(
+ if (
+ children_details
+ and cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies)
+ or flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost)
+ ):
+ frappe.throw(
+ _(
+ "Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
+ Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
+ ).format(
self.company,
cint(children_details.vacancies),
children_details.total_estimated_cost,
- frappe.bold(staffing_plan_detail.designation)), SubsidiaryCompanyError)
+ frappe.bold(staffing_plan_detail.designation),
+ ),
+ SubsidiaryCompanyError,
+ )
+
@frappe.whitelist()
def get_designation_counts(designation, company):
@@ -133,25 +180,24 @@ def get_designation_counts(designation, company):
return False
employee_counts = {}
- company_set = get_descendants_of('Company', company)
+ company_set = get_descendants_of("Company", company)
company_set.append(company)
- employee_counts["employee_count"] = frappe.db.get_value("Employee",
- filters={
- 'designation': designation,
- 'status': 'Active',
- 'company': ('in', company_set)
- }, fieldname=['count(name)'])
+ employee_counts["employee_count"] = frappe.db.get_value(
+ "Employee",
+ filters={"designation": designation, "status": "Active", "company": ("in", company_set)},
+ fieldname=["count(name)"],
+ )
- employee_counts['job_openings'] = frappe.db.get_value("Job Opening",
- filters={
- 'designation': designation,
- 'status': 'Open',
- 'company': ('in', company_set)
- }, fieldname=['count(name)'])
+ employee_counts["job_openings"] = frappe.db.get_value(
+ "Job Opening",
+ filters={"designation": designation, "status": "Open", "company": ("in", company_set)},
+ fieldname=["count(name)"],
+ )
return employee_counts
+
@frappe.whitelist()
def get_active_staffing_plan_details(company, designation, from_date=None, to_date=None):
if from_date is None:
@@ -161,17 +207,22 @@ def get_active_staffing_plan_details(company, designation, from_date=None, to_da
if not company or not designation:
frappe.throw(_("Please select Company and Designation"))
- staffing_plan = frappe.db.sql("""
+ staffing_plan = frappe.db.sql(
+ """
select sp.name, spd.vacancies, spd.total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where company=%s and spd.designation=%s and sp.docstatus=1
- and to_date >= %s and from_date <= %s """, (company, designation, from_date, to_date), as_dict = 1)
+ and to_date >= %s and from_date <= %s """,
+ (company, designation, from_date, to_date),
+ as_dict=1,
+ )
if not staffing_plan:
- parent_company = frappe.get_cached_value('Company', company, "parent_company")
+ parent_company = frappe.get_cached_value("Company", company, "parent_company")
if parent_company:
- staffing_plan = get_active_staffing_plan_details(parent_company,
- designation, from_date, to_date)
+ staffing_plan = get_active_staffing_plan_details(
+ parent_company, designation, from_date, to_date
+ )
# Only a single staffing plan can be active for a designation on given date
return staffing_plan if staffing_plan else None
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
index abde0d5a51..0f555d9db2 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
- return {
- 'fieldname': 'staffing_plan',
- 'transactions': [
- {
- 'items': ['Job Opening']
- }
- ],
- }
+ return {
+ "fieldname": "staffing_plan",
+ "transactions": [{"items": ["Job Opening"]}],
+ }
diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
index 8ff0dbbc28..a3adbbd56a 100644
--- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
@@ -13,6 +13,7 @@ from erpnext.hr.doctype.staffing_plan.staffing_plan import (
test_dependencies = ["Designation"]
+
class TestStaffingPlan(unittest.TestCase):
def test_staffing_plan(self):
_set_up()
@@ -24,11 +25,10 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.name = "Test"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 6,
- "estimated_cost_per_position": 50000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 6, "estimated_cost_per_position": 50000},
+ )
staffing_plan.insert()
staffing_plan.submit()
self.assertEqual(staffing_plan.total_estimated_budget, 300000.00)
@@ -42,11 +42,10 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.name = "Test 1"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 3,
- "estimated_cost_per_position": 45000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 3, "estimated_cost_per_position": 45000},
+ )
self.assertRaises(SubsidiaryCompanyError, staffing_plan.insert)
def test_staffing_plan_parent_company(self):
@@ -58,11 +57,10 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.name = "Test"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 7,
- "estimated_cost_per_position": 50000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 7, "estimated_cost_per_position": 50000},
+ )
staffing_plan.insert()
staffing_plan.submit()
self.assertEqual(staffing_plan.total_estimated_budget, 350000.00)
@@ -73,19 +71,20 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.name = "Test 1"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 7,
- "estimated_cost_per_position": 60000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 7, "estimated_cost_per_position": 60000},
+ )
staffing_plan.insert()
self.assertRaises(ParentCompanyError, staffing_plan.submit)
+
def _set_up():
for doctype in ["Staffing Plan", "Staffing Plan Detail"]:
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
make_company()
+
def make_company():
if frappe.db.exists("Company", "_Test Company 10"):
return
diff --git a/erpnext/hr/doctype/training_event/test_training_event.py b/erpnext/hr/doctype/training_event/test_training_event.py
index f4329c9fe7..ec7eb74da9 100644
--- a/erpnext/hr/doctype/training_event/test_training_event.py
+++ b/erpnext/hr/doctype/training_event/test_training_event.py
@@ -14,10 +14,7 @@ class TestTrainingEvent(unittest.TestCase):
create_training_program("Basic Training")
employee = make_employee("robert_loan@trainig.com")
employee2 = make_employee("suzie.tan@trainig.com")
- self.attendees = [
- {"employee": employee},
- {"employee": employee2}
- ]
+ self.attendees = [{"employee": employee}, {"employee": employee2}]
def test_training_event_status_update(self):
training_event = create_training_event(self.attendees)
@@ -43,20 +40,25 @@ class TestTrainingEvent(unittest.TestCase):
def create_training_program(training_program):
if not frappe.db.get_value("Training Program", training_program):
- frappe.get_doc({
- "doctype": "Training Program",
- "training_program": training_program,
- "description": training_program
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Training Program",
+ "training_program": training_program,
+ "description": training_program,
+ }
+ ).insert()
+
def create_training_event(attendees):
- return frappe.get_doc({
- "doctype": "Training Event",
- "event_name": "Basic Training Event",
- "training_program": "Basic Training",
- "location": "Union Square",
- "start_time": add_days(today(), 5),
- "end_time": add_days(today(), 6),
- "introduction": "Welcome to the Basic Training Event",
- "employees": attendees
- }).insert()
+ return frappe.get_doc(
+ {
+ "doctype": "Training Event",
+ "event_name": "Basic Training Event",
+ "training_program": "Basic Training",
+ "location": "Union Square",
+ "start_time": add_days(today(), 5),
+ "end_time": add_days(today(), 6),
+ "introduction": "Welcome to the Basic Training Event",
+ "employees": attendees,
+ }
+ ).insert()
diff --git a/erpnext/hr/doctype/training_event/training_event.py b/erpnext/hr/doctype/training_event/training_event.py
index c8c8bbe733..59972bb2f3 100644
--- a/erpnext/hr/doctype/training_event/training_event.py
+++ b/erpnext/hr/doctype/training_event/training_event.py
@@ -19,21 +19,20 @@ class TrainingEvent(Document):
self.set_status_for_attendees()
def set_employee_emails(self):
- self.employee_emails = ', '.join(get_employee_emails([d.employee
- for d in self.employees]))
+ self.employee_emails = ", ".join(get_employee_emails([d.employee for d in self.employees]))
def validate_period(self):
if time_diff_in_seconds(self.end_time, self.start_time) <= 0:
- frappe.throw(_('End time cannot be before start time'))
+ frappe.throw(_("End time cannot be before start time"))
def set_status_for_attendees(self):
- if self.event_status == 'Completed':
+ if self.event_status == "Completed":
for employee in self.employees:
- if employee.attendance == 'Present' and employee.status != 'Feedback Submitted':
- employee.status = 'Completed'
+ if employee.attendance == "Present" and employee.status != "Feedback Submitted":
+ employee.status = "Completed"
- elif self.event_status == 'Scheduled':
+ elif self.event_status == "Scheduled":
for employee in self.employees:
- employee.status = 'Open'
+ employee.status = "Open"
self.db_update_all()
diff --git a/erpnext/hr/doctype/training_event/training_event_dashboard.py b/erpnext/hr/doctype/training_event/training_event_dashboard.py
index 141fffc282..ca13938e58 100644
--- a/erpnext/hr/doctype/training_event/training_event_dashboard.py
+++ b/erpnext/hr/doctype/training_event/training_event_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'training_event',
- 'transactions': [
- {
- 'items': ['Training Result', 'Training Feedback']
- },
- ],
- }
+ return {
+ "fieldname": "training_event",
+ "transactions": [
+ {"items": ["Training Result", "Training Feedback"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.py b/erpnext/hr/doctype/training_feedback/test_training_feedback.py
index 58ed623100..c787b7038f 100644
--- a/erpnext/hr/doctype/training_feedback/test_training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.py
@@ -32,10 +32,9 @@ class TestTrainingFeedback(unittest.TestCase):
self.assertRaises(frappe.ValidationError, feedback.save)
# cannot record feedback for absent employee
- employee = frappe.db.get_value("Training Event Employee", {
- "parent": training_event.name,
- "employee": self.employee
- }, "name")
+ employee = frappe.db.get_value(
+ "Training Event Employee", {"parent": training_event.name, "employee": self.employee}, "name"
+ )
frappe.db.set_value("Training Event Employee", employee, "attendance", "Absent")
feedback = create_training_feedback(training_event.name, self.employee)
@@ -52,10 +51,9 @@ class TestTrainingFeedback(unittest.TestCase):
feedback = create_training_feedback(training_event.name, self.employee)
feedback.submit()
- status = frappe.db.get_value("Training Event Employee", {
- "parent": training_event.name,
- "employee": self.employee
- }, "status")
+ status = frappe.db.get_value(
+ "Training Event Employee", {"parent": training_event.name, "employee": self.employee}, "status"
+ )
self.assertEqual(status, "Feedback Submitted")
@@ -64,9 +62,11 @@ class TestTrainingFeedback(unittest.TestCase):
def create_training_feedback(event, employee):
- return frappe.get_doc({
- "doctype": "Training Feedback",
- "training_event": event,
- "employee": employee,
- "feedback": "Test"
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Training Feedback",
+ "training_event": event,
+ "employee": employee,
+ "feedback": "Test",
+ }
+ )
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.py b/erpnext/hr/doctype/training_feedback/training_feedback.py
index 1f9ec3b0b8..d5de28ed2d 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.py
@@ -13,32 +13,35 @@ class TrainingFeedback(Document):
if training_event.docstatus != 1:
frappe.throw(_("{0} must be submitted").format(_("Training Event")))
- emp_event_details = frappe.db.get_value("Training Event Employee", {
- "parent": self.training_event,
- "employee": self.employee
- }, ["name", "attendance"], as_dict=True)
+ emp_event_details = frappe.db.get_value(
+ "Training Event Employee",
+ {"parent": self.training_event, "employee": self.employee},
+ ["name", "attendance"],
+ as_dict=True,
+ )
if not emp_event_details:
- frappe.throw(_("Employee {0} not found in Training Event Participants.").format(
- frappe.bold(self.employee_name)))
+ frappe.throw(
+ _("Employee {0} not found in Training Event Participants.").format(
+ frappe.bold(self.employee_name)
+ )
+ )
if emp_event_details.attendance == "Absent":
frappe.throw(_("Feedback cannot be recorded for an absent Employee."))
def on_submit(self):
- employee = frappe.db.get_value("Training Event Employee", {
- "parent": self.training_event,
- "employee": self.employee
- })
+ employee = frappe.db.get_value(
+ "Training Event Employee", {"parent": self.training_event, "employee": self.employee}
+ )
if employee:
frappe.db.set_value("Training Event Employee", employee, "status", "Feedback Submitted")
def on_cancel(self):
- employee = frappe.db.get_value("Training Event Employee", {
- "parent": self.training_event,
- "employee": self.employee
- })
+ employee = frappe.db.get_value(
+ "Training Event Employee", {"parent": self.training_event, "employee": self.employee}
+ )
if employee:
frappe.db.set_value("Training Event Employee", employee, "status", "Completed")
diff --git a/erpnext/hr/doctype/training_program/training_program_dashboard.py b/erpnext/hr/doctype/training_program/training_program_dashboard.py
index 374c1e8913..1735db18e1 100644
--- a/erpnext/hr/doctype/training_program/training_program_dashboard.py
+++ b/erpnext/hr/doctype/training_program/training_program_dashboard.py
@@ -3,11 +3,8 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'training_program',
- 'transactions': [
- {
- 'label': _('Training Events'),
- 'items': ['Training Event']
- },
- ]
+ "fieldname": "training_program",
+ "transactions": [
+ {"label": _("Training Events"), "items": ["Training Event"]},
+ ],
}
diff --git a/erpnext/hr/doctype/training_result/test_training_result.py b/erpnext/hr/doctype/training_result/test_training_result.py
index 1735ff4e34..136543cbe1 100644
--- a/erpnext/hr/doctype/training_result/test_training_result.py
+++ b/erpnext/hr/doctype/training_result/test_training_result.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Training Result')
+
class TestTrainingResult(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/training_result/training_result.py b/erpnext/hr/doctype/training_result/training_result.py
index bb5c71e7a1..48a5b2c2e9 100644
--- a/erpnext/hr/doctype/training_result/training_result.py
+++ b/erpnext/hr/doctype/training_result/training_result.py
@@ -13,22 +13,22 @@ class TrainingResult(Document):
def validate(self):
training_event = frappe.get_doc("Training Event", self.training_event)
if training_event.docstatus != 1:
- frappe.throw(_('{0} must be submitted').format(_('Training Event')))
+ frappe.throw(_("{0} must be submitted").format(_("Training Event")))
- self.employee_emails = ', '.join(get_employee_emails([d.employee
- for d in self.employees]))
+ self.employee_emails = ", ".join(get_employee_emails([d.employee for d in self.employees]))
def on_submit(self):
training_event = frappe.get_doc("Training Event", self.training_event)
- training_event.status = 'Completed'
+ training_event.status = "Completed"
for e in self.employees:
for e1 in training_event.employees:
if e1.employee == e.employee:
- e1.status = 'Completed'
+ e1.status = "Completed"
break
training_event.save()
+
@frappe.whitelist()
def get_employees(training_event):
return frappe.get_doc("Training Event", training_event).employees
diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
index 4c7bd805f9..537c20633b 100644
--- a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
@@ -10,12 +10,15 @@ import erpnext
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data
-test_dependencies = ['Holiday List']
+test_dependencies = ["Holiday List"]
+
class TestUploadAttendance(unittest.TestCase):
@classmethod
def setUpClass(cls):
- frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List')
+ frappe.db.set_value(
+ "Company", erpnext.get_default_company(), "default_holiday_list", "_Test Holiday List"
+ )
def test_date_range(self):
employee = make_employee("test_employee@company.com")
@@ -27,14 +30,13 @@ class TestUploadAttendance(unittest.TestCase):
employee_doc.date_of_joining = date_of_joining
employee_doc.relieving_date = relieving_date
employee_doc.save()
- args = {
- "from_date": from_date,
- "to_date": to_date
- }
+ args = {"from_date": from_date, "to_date": to_date}
data = get_data(args)
filtered_data = []
for row in data:
if row[1] == employee:
filtered_data.append(row)
for row in filtered_data:
- self.assertTrue(getdate(row[3]) >= getdate(date_of_joining) and getdate(row[3]) <= getdate(relieving_date))
+ self.assertTrue(
+ getdate(row[3]) >= getdate(date_of_joining) and getdate(row[3]) <= getdate(relieving_date)
+ )
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
index 94eb300100..a66a48124d 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
@@ -17,6 +17,7 @@ from erpnext.hr.utils import get_holiday_dates_for_employee
class UploadAttendance(Document):
pass
+
@frappe.whitelist()
def get_template():
if not frappe.has_permission("Attendance", "create"):
@@ -38,29 +39,37 @@ def get_template():
return
# write out response as a type csv
- frappe.response['result'] = cstr(w.getvalue())
- frappe.response['type'] = 'csv'
- frappe.response['doctype'] = "Attendance"
+ frappe.response["result"] = cstr(w.getvalue())
+ frappe.response["type"] = "csv"
+ frappe.response["doctype"] = "Attendance"
+
def add_header(w):
- status = ", ".join((frappe.get_meta("Attendance").get_field("status").options or "").strip().split("\n"))
+ status = ", ".join(
+ (frappe.get_meta("Attendance").get_field("status").options or "").strip().split("\n")
+ )
w.writerow(["Notes:"])
w.writerow(["Please do not change the template headings"])
w.writerow(["Status should be one of these values: " + status])
w.writerow(["If you are overwriting existing attendance records, 'ID' column mandatory"])
- w.writerow(["ID", "Employee", "Employee Name", "Date", "Status", "Leave Type",
- "Company", "Naming Series"])
+ w.writerow(
+ ["ID", "Employee", "Employee Name", "Date", "Status", "Leave Type", "Company", "Naming Series"]
+ )
return w
+
def add_data(w, args):
data = get_data(args)
writedata(w, data)
return w
+
def get_data(args):
dates = get_dates(args)
employees = get_active_employees()
- holidays = get_holidays_for_employees([employee.name for employee in employees], args["from_date"], args["to_date"])
+ holidays = get_holidays_for_employees(
+ [employee.name for employee in employees], args["from_date"], args["to_date"]
+ )
existing_attendance_records = get_existing_attendance_records(args)
data = []
for date in dates:
@@ -71,27 +80,33 @@ def get_data(args):
if getdate(date) > getdate(employee.relieving_date):
continue
existing_attendance = {}
- if existing_attendance_records \
- and tuple([getdate(date), employee.name]) in existing_attendance_records \
- and getdate(employee.date_of_joining) <= getdate(date) \
- and getdate(employee.relieving_date) >= getdate(date):
- existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])]
+ if (
+ existing_attendance_records
+ and tuple([getdate(date), employee.name]) in existing_attendance_records
+ and getdate(employee.date_of_joining) <= getdate(date)
+ and getdate(employee.relieving_date) >= getdate(date)
+ ):
+ existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])]
employee_holiday_list = get_holiday_list_for_employee(employee.name)
row = [
existing_attendance and existing_attendance.name or "",
- employee.name, employee.employee_name, date,
+ employee.name,
+ employee.employee_name,
+ date,
existing_attendance and existing_attendance.status or "",
- existing_attendance and existing_attendance.leave_type or "", employee.company,
+ existing_attendance and existing_attendance.leave_type or "",
+ employee.company,
existing_attendance and existing_attendance.naming_series or get_naming_series(),
]
if date in holidays[employee_holiday_list]:
- row[4] = "Holiday"
+ row[4] = "Holiday"
data.append(row)
return data
+
def get_holidays_for_employees(employees, from_date, to_date):
holidays = {}
for employee in employees:
@@ -102,30 +117,35 @@ def get_holidays_for_employees(employees, from_date, to_date):
return holidays
+
def writedata(w, data):
for row in data:
w.writerow(row)
+
def get_dates(args):
"""get list of dates in between from date and to date"""
no_of_days = date_diff(add_days(args["to_date"], 1), args["from_date"])
dates = [add_days(args["from_date"], i) for i in range(0, no_of_days)]
return dates
+
def get_active_employees():
- employees = frappe.db.get_all('Employee',
- fields=['name', 'employee_name', 'date_of_joining', 'company', 'relieving_date'],
- filters={
- 'docstatus': ['<', 2],
- 'status': 'Active'
- }
+ employees = frappe.db.get_all(
+ "Employee",
+ fields=["name", "employee_name", "date_of_joining", "company", "relieving_date"],
+ filters={"docstatus": ["<", 2], "status": "Active"},
)
return employees
+
def get_existing_attendance_records(args):
- attendance = frappe.db.sql("""select name, attendance_date, employee, status, leave_type, naming_series
+ attendance = frappe.db.sql(
+ """select name, attendance_date, employee, status, leave_type, naming_series
from `tabAttendance` where attendance_date between %s and %s and docstatus < 2""",
- (args["from_date"], args["to_date"]), as_dict=1)
+ (args["from_date"], args["to_date"]),
+ as_dict=1,
+ )
existing_attendance = {}
for att in attendance:
@@ -133,6 +153,7 @@ def get_existing_attendance_records(args):
return existing_attendance
+
def get_naming_series():
series = frappe.get_meta("Attendance").get_field("naming_series").options.strip().split("\n")
if not series:
@@ -146,15 +167,16 @@ def upload():
raise frappe.PermissionError
from frappe.utils.csvutils import read_csv_content
+
rows = read_csv_content(frappe.local.uploaded_file)
if not rows:
frappe.throw(_("Please select a csv file"))
frappe.enqueue(import_attendances, rows=rows, now=True if len(rows) < 200 else False)
-def import_attendances(rows):
+def import_attendances(rows):
def remove_holidays(rows):
- rows = [ row for row in rows if row[4] != "Holiday"]
+ rows = [row for row in rows if row[4] != "Holiday"]
return rows
from frappe.modules import scrub
@@ -172,7 +194,8 @@ def import_attendances(rows):
from frappe.utils.csvutils import check_record, import_doc
for i, row in enumerate(rows):
- if not row: continue
+ if not row:
+ continue
row_idx = i + 5
d = frappe._dict(zip(columns, row))
@@ -183,16 +206,12 @@ def import_attendances(rows):
try:
check_record(d)
ret.append(import_doc(d, "Attendance", 1, row_idx, submit=True))
- frappe.publish_realtime('import_attendance', dict(
- progress=i,
- total=len(rows)
- ))
+ frappe.publish_realtime("import_attendance", dict(progress=i, total=len(rows)))
except AttributeError:
pass
except Exception as e:
error = True
- ret.append('Error for row (#%d) %s : %s' % (row_idx,
- len(row)>1 and row[1] or "", cstr(e)))
+ ret.append("Error for row (#%d) %s : %s" % (row_idx, len(row) > 1 and row[1] or "", cstr(e)))
frappe.errprint(frappe.get_traceback())
if error:
@@ -200,7 +219,4 @@ def import_attendances(rows):
else:
frappe.db.commit()
- frappe.publish_realtime('import_attendance', dict(
- messages=ret,
- error=error
- ))
+ frappe.publish_realtime("import_attendance", dict(messages=ret, error=error))
diff --git a/erpnext/hr/doctype/vehicle/test_vehicle.py b/erpnext/hr/doctype/vehicle/test_vehicle.py
index c5ea5a38c8..97fe651122 100644
--- a/erpnext/hr/doctype/vehicle/test_vehicle.py
+++ b/erpnext/hr/doctype/vehicle/test_vehicle.py
@@ -8,18 +8,21 @@ from frappe.utils import random_string
# test_records = frappe.get_test_records('Vehicle')
+
class TestVehicle(unittest.TestCase):
def test_make_vehicle(self):
- vehicle = frappe.get_doc({
- "doctype": "Vehicle",
- "license_plate": random_string(10).upper(),
- "make": "Maruti",
- "model": "PCM",
- "last_odometer":5000,
- "acquisition_date":frappe.utils.nowdate(),
- "location": "Mumbai",
- "chassis_no": "1234ABCD",
- "uom": "Litre",
- "vehicle_value":frappe.utils.flt(500000)
- })
+ vehicle = frappe.get_doc(
+ {
+ "doctype": "Vehicle",
+ "license_plate": random_string(10).upper(),
+ "make": "Maruti",
+ "model": "PCM",
+ "last_odometer": 5000,
+ "acquisition_date": frappe.utils.nowdate(),
+ "location": "Mumbai",
+ "chassis_no": "1234ABCD",
+ "uom": "Litre",
+ "vehicle_value": frappe.utils.flt(500000),
+ }
+ )
vehicle.insert()
diff --git a/erpnext/hr/doctype/vehicle/vehicle.py b/erpnext/hr/doctype/vehicle/vehicle.py
index 946233b548..22c14c3727 100644
--- a/erpnext/hr/doctype/vehicle/vehicle.py
+++ b/erpnext/hr/doctype/vehicle/vehicle.py
@@ -15,9 +15,15 @@ class Vehicle(Document):
if getdate(self.carbon_check_date) > getdate():
frappe.throw(_("Last carbon check date cannot be a future date"))
+
def get_timeline_data(doctype, name):
- '''Return timeline for vehicle log'''
- return dict(frappe.db.sql('''select unix_timestamp(date), count(*)
+ """Return timeline for vehicle log"""
+ return dict(
+ frappe.db.sql(
+ """select unix_timestamp(date), count(*)
from `tabVehicle Log` where license_plate=%s
and date > date_sub(curdate(), interval 1 year)
- group by date''', name))
+ group by date""",
+ name,
+ )
+ )
diff --git a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
index f6e5f06d8c..758dfbd60a 100644
--- a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
+++ b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
@@ -3,18 +3,11 @@ from frappe import _
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on logs against this Vehicle. See timeline below for details'),
- 'fieldname': 'license_plate',
- 'non_standard_fieldnames':{
- 'Delivery Trip': 'vehicle'
- },
- 'transactions': [
- {
- 'items': ['Vehicle Log']
- },
- {
- 'items': ['Delivery Trip']
- }
- ]
+ "heatmap": True,
+ "heatmap_message": _(
+ "This is based on logs against this Vehicle. See timeline below for details"
+ ),
+ "fieldname": "license_plate",
+ "non_standard_fieldnames": {"Delivery Trip": "vehicle"},
+ "transactions": [{"items": ["Vehicle Log"]}, {"items": ["Delivery Trip"]}],
}
diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
index abb288723c..bb29670d39 100644
--- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
@@ -12,7 +12,9 @@ from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
class TestVehicleLog(unittest.TestCase):
def setUp(self):
- employee_id = frappe.db.sql("""select name from `tabEmployee` where name='testdriver@example.com'""")
+ employee_id = frappe.db.sql(
+ """select name from `tabEmployee` where name='testdriver@example.com'"""
+ )
self.employee_id = employee_id[0][0] if employee_id else None
if not self.employee_id:
@@ -27,11 +29,11 @@ class TestVehicleLog(unittest.TestCase):
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
- #checking value of vehicle odometer value on submit.
+ # checking value of vehicle odometer value on submit.
vehicle = frappe.get_doc("Vehicle", self.license_plate)
self.assertEqual(vehicle.last_odometer, vehicle_log.odometer)
- #checking value vehicle odometer on vehicle log cancellation.
+ # checking value vehicle odometer on vehicle log cancellation.
last_odometer = vehicle_log.last_odometer
current_odometer = vehicle_log.odometer
distance_travelled = current_odometer - last_odometer
@@ -48,7 +50,7 @@ class TestVehicleLog(unittest.TestCase):
expense_claim = make_expense_claim(vehicle_log.name)
fuel_expense = expense_claim.expenses[0].amount
- self.assertEqual(fuel_expense, 50*500)
+ self.assertEqual(fuel_expense, 50 * 500)
vehicle_log.cancel()
frappe.delete_doc("Expense Claim", expense_claim.name)
@@ -67,8 +69,9 @@ class TestVehicleLog(unittest.TestCase):
def get_vehicle(employee_id):
- license_plate=random_string(10).upper()
- vehicle = frappe.get_doc({
+ license_plate = random_string(10).upper()
+ vehicle = frappe.get_doc(
+ {
"doctype": "Vehicle",
"license_plate": cstr(license_plate),
"make": "Maruti",
@@ -79,8 +82,9 @@ def get_vehicle(employee_id):
"location": "Mumbai",
"chassis_no": "1234ABCD",
"uom": "Litre",
- "vehicle_value": flt(500000)
- })
+ "vehicle_value": flt(500000),
+ }
+ )
try:
vehicle.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
@@ -89,29 +93,37 @@ def get_vehicle(employee_id):
def make_vehicle_log(license_plate, employee_id, with_services=False):
- vehicle_log = frappe.get_doc({
- "doctype": "Vehicle Log",
- "license_plate": cstr(license_plate),
- "employee": employee_id,
- "date": nowdate(),
- "odometer": 5010,
- "fuel_qty": flt(50),
- "price": flt(500)
- })
+ vehicle_log = frappe.get_doc(
+ {
+ "doctype": "Vehicle Log",
+ "license_plate": cstr(license_plate),
+ "employee": employee_id,
+ "date": nowdate(),
+ "odometer": 5010,
+ "fuel_qty": flt(50),
+ "price": flt(500),
+ }
+ )
if with_services:
- vehicle_log.append("service_detail", {
- "service_item": "Oil Change",
- "type": "Inspection",
- "frequency": "Mileage",
- "expense_amount": flt(500)
- })
- vehicle_log.append("service_detail", {
- "service_item": "Wheels",
- "type": "Change",
- "frequency": "Half Yearly",
- "expense_amount": flt(1500)
- })
+ vehicle_log.append(
+ "service_detail",
+ {
+ "service_item": "Oil Change",
+ "type": "Inspection",
+ "frequency": "Mileage",
+ "expense_amount": flt(500),
+ },
+ )
+ vehicle_log.append(
+ "service_detail",
+ {
+ "service_item": "Wheels",
+ "type": "Change",
+ "frequency": "Half Yearly",
+ "expense_amount": flt(1500),
+ },
+ )
vehicle_log.save()
vehicle_log.submit()
diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.py b/erpnext/hr/doctype/vehicle_log/vehicle_log.py
index e414141efb..2c1d9a4efe 100644
--- a/erpnext/hr/doctype/vehicle_log/vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.py
@@ -11,17 +11,24 @@ from frappe.utils import flt
class VehicleLog(Document):
def validate(self):
if flt(self.odometer) < flt(self.last_odometer):
- frappe.throw(_("Current Odometer Value should be greater than Last Odometer Value {0}").format(self.last_odometer))
+ frappe.throw(
+ _("Current Odometer Value should be greater than Last Odometer Value {0}").format(
+ self.last_odometer
+ )
+ )
def on_submit(self):
frappe.db.set_value("Vehicle", self.license_plate, "last_odometer", self.odometer)
def on_cancel(self):
distance_travelled = self.odometer - self.last_odometer
- if(distance_travelled > 0):
- updated_odometer_value = int(frappe.db.get_value("Vehicle", self.license_plate, "last_odometer")) - distance_travelled
+ if distance_travelled > 0:
+ updated_odometer_value = (
+ int(frappe.db.get_value("Vehicle", self.license_plate, "last_odometer")) - distance_travelled
+ )
frappe.db.set_value("Vehicle", self.license_plate, "last_odometer", updated_odometer_value)
+
@frappe.whitelist()
def make_expense_claim(docname):
expense_claim = frappe.db.exists("Expense Claim", {"vehicle_log": docname})
@@ -39,9 +46,8 @@ def make_expense_claim(docname):
exp_claim.employee = vehicle_log.employee
exp_claim.vehicle_log = vehicle_log.name
exp_claim.remark = _("Expense Claim for Vehicle Log {0}").format(vehicle_log.name)
- exp_claim.append("expenses", {
- "expense_date": vehicle_log.date,
- "description": _("Vehicle Expenses"),
- "amount": claim_amount
- })
+ exp_claim.append(
+ "expenses",
+ {"expense_date": vehicle_log.date, "description": _("Vehicle Expenses"), "amount": claim_amount},
+ )
return exp_claim.as_dict()
diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.py b/erpnext/hr/page/organizational_chart/organizational_chart.py
index 1e2d758179..3674912dc0 100644
--- a/erpnext/hr/page/organizational_chart/organizational_chart.py
+++ b/erpnext/hr/page/organizational_chart/organizational_chart.py
@@ -3,26 +3,27 @@ import frappe
@frappe.whitelist()
def get_children(parent=None, company=None, exclude_node=None):
- filters = [['status', '!=', 'Left']]
- if company and company != 'All Companies':
- filters.append(['company', '=', company])
+ filters = [["status", "!=", "Left"]]
+ if company and company != "All Companies":
+ filters.append(["company", "=", company])
if parent and company and parent != company:
- filters.append(['reports_to', '=', parent])
+ filters.append(["reports_to", "=", parent])
else:
- filters.append(['reports_to', '=', ''])
+ filters.append(["reports_to", "=", ""])
if exclude_node:
- filters.append(['name', '!=', exclude_node])
+ filters.append(["name", "!=", exclude_node])
- employees = frappe.get_list('Employee',
- fields=['employee_name as name', 'name as id', 'reports_to', 'image', 'designation as title'],
+ employees = frappe.get_list(
+ "Employee",
+ fields=["employee_name as name", "name as id", "reports_to", "image", "designation as title"],
filters=filters,
- order_by='name'
+ order_by="name",
)
for employee in employees:
- is_expandable = frappe.db.count('Employee', filters={'reports_to': employee.get('id')})
+ is_expandable = frappe.db.count("Employee", filters={"reports_to": employee.get("id")})
employee.connections = get_connections(employee.id)
employee.expandable = 1 if is_expandable else 0
@@ -32,16 +33,12 @@ def get_children(parent=None, company=None, exclude_node=None):
def get_connections(employee):
num_connections = 0
- nodes_to_expand = frappe.get_list('Employee', filters=[
- ['reports_to', '=', employee]
- ])
+ nodes_to_expand = frappe.get_list("Employee", filters=[["reports_to", "=", employee]])
num_connections += len(nodes_to_expand)
while nodes_to_expand:
parent = nodes_to_expand.pop(0)
- descendants = frappe.get_list('Employee', filters=[
- ['reports_to', '=', parent.name]
- ])
+ descendants = frappe.get_list("Employee", filters=[["reports_to", "=", parent.name]])
num_connections += len(descendants)
nodes_to_expand.extend(descendants)
diff --git a/erpnext/hr/page/team_updates/team_updates.py b/erpnext/hr/page/team_updates/team_updates.py
index 0a4624c531..c1fcb73585 100644
--- a/erpnext/hr/page/team_updates/team_updates.py
+++ b/erpnext/hr/page/team_updates/team_updates.py
@@ -4,15 +4,20 @@ from email_reply_parser import EmailReplyParser
@frappe.whitelist()
def get_data(start=0):
- #frappe.only_for('Employee', 'System Manager')
- data = frappe.get_all('Communication',
- fields=('content', 'text_content', 'sender', 'creation'),
- filters=dict(reference_doctype='Daily Work Summary'),
- order_by='creation desc', limit=40, start=start)
+ # frappe.only_for('Employee', 'System Manager')
+ data = frappe.get_all(
+ "Communication",
+ fields=("content", "text_content", "sender", "creation"),
+ filters=dict(reference_doctype="Daily Work Summary"),
+ order_by="creation desc",
+ limit=40,
+ start=start,
+ )
for d in data:
- d.sender_name = frappe.db.get_value("Employee", {"user_id": d.sender},
- "employee_name") or d.sender
+ d.sender_name = (
+ frappe.db.get_value("Employee", {"user_id": d.sender}, "employee_name") or d.sender
+ )
if d.text_content:
d.content = frappe.utils.md_to_html(EmailReplyParser.parse_reply(d.text_content))
diff --git a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py
index 63764bb8a9..d93688a492 100644
--- a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py
+++ b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py
@@ -9,51 +9,53 @@ from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_user_em
def execute(filters=None):
- if not filters.group: return [], []
+ if not filters.group:
+ return [], []
columns, data = get_columns(), get_data(filters)
return columns, data
+
def get_columns(filters=None):
columns = [
- {
- "label": _("User"),
- "fieldname": "user",
- "fieldtype": "Data",
- "width": 300
- },
+ {"label": _("User"), "fieldname": "user", "fieldtype": "Data", "width": 300},
{
"label": _("Replies"),
"fieldname": "count",
"fieldtype": "data",
"width": 100,
- "align": 'right',
+ "align": "right",
},
{
"label": _("Total"),
"fieldname": "total",
"fieldtype": "data",
"width": 100,
- "align": 'right',
- }
+ "align": "right",
+ },
]
return columns
+
def get_data(filters):
- daily_summary_emails = frappe.get_all('Daily Work Summary',
- fields=["name"],
- filters=[["creation","Between", filters.range]])
- daily_summary_emails = [d.get('name') for d in daily_summary_emails]
- replies = frappe.get_all('Communication',
- fields=['content', 'text_content', 'sender'],
- filters=[['reference_doctype','=', 'Daily Work Summary'],
- ['reference_name', 'in', daily_summary_emails],
- ['communication_type', '=', 'Communication'],
- ['sent_or_received', '=', 'Received']],
- order_by='creation asc')
+ daily_summary_emails = frappe.get_all(
+ "Daily Work Summary", fields=["name"], filters=[["creation", "Between", filters.range]]
+ )
+ daily_summary_emails = [d.get("name") for d in daily_summary_emails]
+ replies = frappe.get_all(
+ "Communication",
+ fields=["content", "text_content", "sender"],
+ filters=[
+ ["reference_doctype", "=", "Daily Work Summary"],
+ ["reference_name", "in", daily_summary_emails],
+ ["communication_type", "=", "Communication"],
+ ["sent_or_received", "=", "Received"],
+ ],
+ order_by="creation asc",
+ )
data = []
total = len(daily_summary_emails)
for user in get_user_emails_from_group(filters.group):
- user_name = frappe.get_value('User', user, 'full_name')
+ user_name = frappe.get_value("User", user, "full_name")
count = len([d for d in replies if d.sender == user])
data.append([user_name, count, total])
return data
diff --git a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py
index 62b83f26a6..29532f7680 100644
--- a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py
+++ b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py
@@ -7,7 +7,8 @@ from frappe import _, msgprint
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
advances_list = get_advances(filters)
columns = get_columns()
@@ -18,8 +19,16 @@ def execute(filters=None):
data = []
for advance in advances_list:
- row = [advance.name, advance.employee, advance.company, advance.posting_date,
- advance.advance_amount, advance.paid_amount, advance.claimed_amount, advance.status]
+ row = [
+ advance.name,
+ advance.employee,
+ advance.company,
+ advance.posting_date,
+ advance.advance_amount,
+ advance.paid_amount,
+ advance.claimed_amount,
+ advance.status,
+ ]
data.append(row)
return columns, data
@@ -32,54 +41,40 @@ def get_columns():
"fieldname": "title",
"fieldtype": "Link",
"options": "Employee Advance",
- "width": 120
+ "width": 120,
},
{
"label": _("Employee"),
"fieldname": "employee",
"fieldtype": "Link",
"options": "Employee",
- "width": 120
+ "width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
- "width": 120
- },
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 120
+ "width": 120,
},
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
{
"label": _("Advance Amount"),
"fieldname": "advance_amount",
"fieldtype": "Currency",
- "width": 120
- },
- {
- "label": _("Paid Amount"),
- "fieldname": "paid_amount",
- "fieldtype": "Currency",
- "width": 120
+ "width": 120,
},
+ {"label": _("Paid Amount"), "fieldname": "paid_amount", "fieldtype": "Currency", "width": 120},
{
"label": _("Claimed Amount"),
"fieldname": "claimed_amount",
"fieldtype": "Currency",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Status"),
- "fieldname": "status",
- "fieldtype": "Data",
- "width": 120
- }
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
]
+
def get_conditions(filters):
conditions = ""
@@ -96,10 +91,15 @@ def get_conditions(filters):
return conditions
+
def get_advances(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select name, employee, paid_amount, status, advance_amount, claimed_amount, company,
+ return frappe.db.sql(
+ """select name, employee, paid_amount, status, advance_amount, claimed_amount, company,
posting_date, purpose
from `tabEmployee Advance`
- where docstatus<2 %s order by posting_date, name desc""" %
- conditions, filters, as_dict=1)
+ where docstatus<2 %s order by posting_date, name desc"""
+ % conditions,
+ filters,
+ as_dict=1,
+ )
diff --git a/erpnext/hr/report/employee_analytics/employee_analytics.py b/erpnext/hr/report/employee_analytics/employee_analytics.py
index 3a75276cb0..12be156ab9 100644
--- a/erpnext/hr/report/employee_analytics/employee_analytics.py
+++ b/erpnext/hr/report/employee_analytics/employee_analytics.py
@@ -7,10 +7,11 @@ from frappe import _
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if not filters["company"]:
- frappe.throw(_('{0} is mandatory').format(_('Company')))
+ frappe.throw(_("{0} is mandatory").format(_("Company")))
columns = get_columns()
employees = get_employees(filters)
@@ -20,28 +21,41 @@ def execute(filters=None):
for department in parameters_result:
parameters.append(department)
- chart = get_chart_data(parameters,employees, filters)
+ chart = get_chart_data(parameters, employees, filters)
return columns, employees, None, chart
+
def get_columns():
return [
- _("Employee") + ":Link/Employee:120", _("Name") + ":Data:200", _("Date of Birth")+ ":Date:100",
- _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
- _("Designation") + ":Link/Designation:120", _("Gender") + "::100", _("Company") + ":Link/Company:120"
+ _("Employee") + ":Link/Employee:120",
+ _("Name") + ":Data:200",
+ _("Date of Birth") + ":Date:100",
+ _("Branch") + ":Link/Branch:120",
+ _("Department") + ":Link/Department:120",
+ _("Designation") + ":Link/Designation:120",
+ _("Gender") + "::100",
+ _("Company") + ":Link/Company:120",
]
-def get_conditions(filters):
- conditions = " and "+filters.get("parameter").lower().replace(" ","_")+" IS NOT NULL "
- if filters.get("company"): conditions += " and company = '%s'" % \
- filters["company"].replace("'", "\\'")
+def get_conditions(filters):
+ conditions = " and " + filters.get("parameter").lower().replace(" ", "_") + " IS NOT NULL "
+
+ if filters.get("company"):
+ conditions += " and company = '%s'" % filters["company"].replace("'", "\\'")
return conditions
+
def get_employees(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select name, employee_name, date_of_birth,
+ return frappe.db.sql(
+ """select name, employee_name, date_of_birth,
branch, department, designation,
- gender, company from `tabEmployee` where status = 'Active' %s""" % conditions, as_list=1)
+ gender, company from `tabEmployee` where status = 'Active' %s"""
+ % conditions,
+ as_list=1,
+ )
+
def get_parameters(filters):
if filters.get("parameter") == "Grade":
@@ -49,36 +63,37 @@ def get_parameters(filters):
else:
parameter = filters.get("parameter")
- return frappe.db.sql("""select name from `tab"""+ parameter +"""` """, as_list=1)
+ return frappe.db.sql("""select name from `tab""" + parameter + """` """, as_list=1)
-def get_chart_data(parameters,employees, filters):
+
+def get_chart_data(parameters, employees, filters):
if not parameters:
parameters = []
datasets = []
- parameter_field_name = filters.get("parameter").lower().replace(" ","_")
+ parameter_field_name = filters.get("parameter").lower().replace(" ", "_")
label = []
for parameter in parameters:
if parameter:
- total_employee = frappe.db.sql("""select count(*) from
- `tabEmployee` where """+
- parameter_field_name + """ = %s and company = %s""" ,( parameter[0], filters.get("company")), as_list=1)
+ total_employee = frappe.db.sql(
+ """select count(*) from
+ `tabEmployee` where """
+ + parameter_field_name
+ + """ = %s and company = %s""",
+ (parameter[0], filters.get("company")),
+ as_list=1,
+ )
if total_employee[0][0]:
label.append(parameter)
datasets.append(total_employee[0][0])
- values = [ value for value in datasets if value !=0]
+ values = [value for value in datasets if value != 0]
- total_employee = frappe.db.count('Employee', {'status':'Active'})
+ total_employee = frappe.db.count("Employee", {"status": "Active"})
others = total_employee - sum(values)
label.append(["Not Set"])
values.append(others)
- chart = {
- "data": {
- 'labels': label,
- 'datasets': [{'name': 'Employees','values': values}]
- }
- }
+ chart = {"data": {"labels": label, "datasets": [{"name": "Employees", "values": values}]}}
chart["type"] = "donut"
return chart
diff --git a/erpnext/hr/report/employee_birthday/employee_birthday.py b/erpnext/hr/report/employee_birthday/employee_birthday.py
index cec5a48c19..a6a13d8a4d 100644
--- a/erpnext/hr/report/employee_birthday/employee_birthday.py
+++ b/erpnext/hr/report/employee_birthday/employee_birthday.py
@@ -7,34 +7,59 @@ from frappe import _
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns()
data = get_employees(filters)
return columns, data
+
def get_columns():
return [
- _("Employee") + ":Link/Employee:120", _("Name") + ":Data:200", _("Date of Birth")+ ":Date:100",
- _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
- _("Designation") + ":Link/Designation:120", _("Gender") + "::60", _("Company") + ":Link/Company:120"
+ _("Employee") + ":Link/Employee:120",
+ _("Name") + ":Data:200",
+ _("Date of Birth") + ":Date:100",
+ _("Branch") + ":Link/Branch:120",
+ _("Department") + ":Link/Department:120",
+ _("Designation") + ":Link/Designation:120",
+ _("Gender") + "::60",
+ _("Company") + ":Link/Company:120",
]
+
def get_employees(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select name, employee_name, date_of_birth,
+ return frappe.db.sql(
+ """select name, employee_name, date_of_birth,
branch, department, designation,
- gender, company from tabEmployee where status = 'Active' %s""" % conditions, as_list=1)
+ gender, company from tabEmployee where status = 'Active' %s"""
+ % conditions,
+ as_list=1,
+ )
+
def get_conditions(filters):
conditions = ""
if filters.get("month"):
- month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
- "Dec"].index(filters["month"]) + 1
+ month = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ].index(filters["month"]) + 1
conditions += " and month(date_of_birth) = '%s'" % month
- if filters.get("company"): conditions += " and company = '%s'" % \
- filters["company"].replace("'", "\\'")
+ if filters.get("company"):
+ conditions += " and company = '%s'" % filters["company"].replace("'", "\\'")
return conditions
diff --git a/erpnext/hr/report/employee_exits/employee_exits.py b/erpnext/hr/report/employee_exits/employee_exits.py
index bde5a89926..9cd9ff0a6b 100644
--- a/erpnext/hr/report/employee_exits/employee_exits.py
+++ b/erpnext/hr/report/employee_exits/employee_exits.py
@@ -15,104 +15,106 @@ def execute(filters=None):
return columns, data, None, chart, report_summary
+
def get_columns():
return [
{
- 'label': _('Employee'),
- 'fieldname': 'employee',
- 'fieldtype': 'Link',
- 'options': 'Employee',
- 'width': 150
+ "label": _("Employee"),
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 150,
+ },
+ {"label": _("Employee Name"), "fieldname": "employee_name", "fieldtype": "Data", "width": 150},
+ {
+ "label": _("Date of Joining"),
+ "fieldname": "date_of_joining",
+ "fieldtype": "Date",
+ "width": 120,
+ },
+ {"label": _("Relieving Date"), "fieldname": "relieving_date", "fieldtype": "Date", "width": 120},
+ {
+ "label": _("Exit Interview"),
+ "fieldname": "exit_interview",
+ "fieldtype": "Link",
+ "options": "Exit Interview",
+ "width": 150,
},
{
- 'label': _('Employee Name'),
- 'fieldname': 'employee_name',
- 'fieldtype': 'Data',
- 'width': 150
+ "label": _("Interview Status"),
+ "fieldname": "interview_status",
+ "fieldtype": "Data",
+ "width": 130,
},
{
- 'label': _('Date of Joining'),
- 'fieldname': 'date_of_joining',
- 'fieldtype': 'Date',
- 'width': 120
+ "label": _("Final Decision"),
+ "fieldname": "employee_status",
+ "fieldtype": "Data",
+ "width": 150,
},
{
- 'label': _('Relieving Date'),
- 'fieldname': 'relieving_date',
- 'fieldtype': 'Date',
- 'width': 120
+ "label": _("Full and Final Statement"),
+ "fieldname": "full_and_final_statement",
+ "fieldtype": "Link",
+ "options": "Full and Final Statement",
+ "width": 180,
},
{
- 'label': _('Exit Interview'),
- 'fieldname': 'exit_interview',
- 'fieldtype': 'Link',
- 'options': 'Exit Interview',
- 'width': 150
+ "label": _("Department"),
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "options": "Department",
+ "width": 120,
},
{
- 'label': _('Interview Status'),
- 'fieldname': 'interview_status',
- 'fieldtype': 'Data',
- 'width': 130
+ "label": _("Designation"),
+ "fieldname": "designation",
+ "fieldtype": "Link",
+ "options": "Designation",
+ "width": 120,
},
{
- 'label': _('Final Decision'),
- 'fieldname': 'employee_status',
- 'fieldtype': 'Data',
- 'width': 150
+ "label": _("Reports To"),
+ "fieldname": "reports_to",
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 120,
},
- {
- 'label': _('Full and Final Statement'),
- 'fieldname': 'full_and_final_statement',
- 'fieldtype': 'Link',
- 'options': 'Full and Final Statement',
- 'width': 180
- },
- {
- 'label': _('Department'),
- 'fieldname': 'department',
- 'fieldtype': 'Link',
- 'options': 'Department',
- 'width': 120
- },
- {
- 'label': _('Designation'),
- 'fieldname': 'designation',
- 'fieldtype': 'Link',
- 'options': 'Designation',
- 'width': 120
- },
- {
- 'label': _('Reports To'),
- 'fieldname': 'reports_to',
- 'fieldtype': 'Link',
- 'options': 'Employee',
- 'width': 120
- }
]
+
def get_data(filters):
- employee = frappe.qb.DocType('Employee')
- interview = frappe.qb.DocType('Exit Interview')
- fnf = frappe.qb.DocType('Full and Final Statement')
+ employee = frappe.qb.DocType("Employee")
+ interview = frappe.qb.DocType("Exit Interview")
+ fnf = frappe.qb.DocType("Full and Final Statement")
query = (
frappe.qb.from_(employee)
- .left_join(interview).on(interview.employee == employee.name)
- .left_join(fnf).on(fnf.employee == employee.name)
- .select(
- employee.name.as_('employee'), employee.employee_name.as_('employee_name'),
- employee.date_of_joining.as_('date_of_joining'), employee.relieving_date.as_('relieving_date'),
- employee.department.as_('department'), employee.designation.as_('designation'),
- employee.reports_to.as_('reports_to'), interview.name.as_('exit_interview'),
- interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'),
- interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement'))
- .distinct()
- .where(
- ((employee.relieving_date.isnotnull()) | (employee.relieving_date != ''))
- & ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
- & ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
- ).orderby(employee.relieving_date, order=Order.asc)
+ .left_join(interview)
+ .on(interview.employee == employee.name)
+ .left_join(fnf)
+ .on(fnf.employee == employee.name)
+ .select(
+ employee.name.as_("employee"),
+ employee.employee_name.as_("employee_name"),
+ employee.date_of_joining.as_("date_of_joining"),
+ employee.relieving_date.as_("relieving_date"),
+ employee.department.as_("department"),
+ employee.designation.as_("designation"),
+ employee.reports_to.as_("reports_to"),
+ interview.name.as_("exit_interview"),
+ interview.status.as_("interview_status"),
+ interview.employee_status.as_("employee_status"),
+ interview.reference_document_name.as_("questionnaire"),
+ fnf.name.as_("full_and_final_statement"),
+ )
+ .distinct()
+ .where(
+ ((employee.relieving_date.isnotnull()) | (employee.relieving_date != ""))
+ & ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
+ & ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
+ )
+ .orderby(employee.relieving_date, order=Order.asc)
)
query = get_conditions(filters, query, employee, interview, fnf)
@@ -122,44 +124,48 @@ def get_data(filters):
def get_conditions(filters, query, employee, interview, fnf):
- if filters.get('from_date') and filters.get('to_date'):
- query = query.where(employee.relieving_date[getdate(filters.get('from_date')):getdate(filters.get('to_date'))])
+ if filters.get("from_date") and filters.get("to_date"):
+ query = query.where(
+ employee.relieving_date[getdate(filters.get("from_date")) : getdate(filters.get("to_date"))]
+ )
- elif filters.get('from_date'):
- query = query.where(employee.relieving_date >= filters.get('from_date'))
+ elif filters.get("from_date"):
+ query = query.where(employee.relieving_date >= filters.get("from_date"))
- elif filters.get('to_date'):
- query = query.where(employee.relieving_date <= filters.get('to_date'))
+ elif filters.get("to_date"):
+ query = query.where(employee.relieving_date <= filters.get("to_date"))
- if filters.get('company'):
- query = query.where(employee.company == filters.get('company'))
+ if filters.get("company"):
+ query = query.where(employee.company == filters.get("company"))
- if filters.get('department'):
- query = query.where(employee.department == filters.get('department'))
+ if filters.get("department"):
+ query = query.where(employee.department == filters.get("department"))
- if filters.get('designation'):
- query = query.where(employee.designation == filters.get('designation'))
+ if filters.get("designation"):
+ query = query.where(employee.designation == filters.get("designation"))
- if filters.get('employee'):
- query = query.where(employee.name == filters.get('employee'))
+ if filters.get("employee"):
+ query = query.where(employee.name == filters.get("employee"))
- if filters.get('reports_to'):
- query = query.where(employee.reports_to == filters.get('reports_to'))
+ if filters.get("reports_to"):
+ query = query.where(employee.reports_to == filters.get("reports_to"))
- if filters.get('interview_status'):
- query = query.where(interview.status == filters.get('interview_status'))
+ if filters.get("interview_status"):
+ query = query.where(interview.status == filters.get("interview_status"))
- if filters.get('final_decision'):
- query = query.where(interview.employee_status == filters.get('final_decision'))
+ if filters.get("final_decision"):
+ query = query.where(interview.employee_status == filters.get("final_decision"))
- if filters.get('exit_interview_pending'):
- query = query.where((interview.name == '') | (interview.name.isnull()))
+ if filters.get("exit_interview_pending"):
+ query = query.where((interview.name == "") | (interview.name.isnull()))
- if filters.get('questionnaire_pending'):
- query = query.where((interview.reference_document_name == '') | (interview.reference_document_name.isnull()))
+ if filters.get("questionnaire_pending"):
+ query = query.where(
+ (interview.reference_document_name == "") | (interview.reference_document_name.isnull())
+ )
- if filters.get('fnf_pending'):
- query = query.where((fnf.name == '') | (fnf.name.isnull()))
+ if filters.get("fnf_pending"):
+ query = query.where((fnf.name == "") | (fnf.name.isnull()))
return query
@@ -173,20 +179,20 @@ def get_chart_data(data):
pending = 0
for entry in data:
- if entry.employee_status == 'Employee Retained':
+ if entry.employee_status == "Employee Retained":
retained += 1
- elif entry.employee_status == 'Exit Confirmed':
+ elif entry.employee_status == "Exit Confirmed":
exit_confirmed += 1
else:
pending += 1
chart = {
- 'data': {
- 'labels': [_('Retained'), _('Exit Confirmed'), _('Decision Pending')],
- 'datasets': [{'name': _('Employee Status'), 'values': [retained, exit_confirmed, pending]}]
+ "data": {
+ "labels": [_("Retained"), _("Exit Confirmed"), _("Decision Pending")],
+ "datasets": [{"name": _("Employee Status"), "values": [retained, exit_confirmed, pending]}],
},
- 'type': 'donut',
- 'colors': ['green', 'red', 'blue'],
+ "type": "donut",
+ "colors": ["green", "red", "blue"],
}
return chart
@@ -203,28 +209,27 @@ def get_report_summary(data):
return [
{
- 'value': total_resignations,
- 'label': _('Total Resignations'),
- 'indicator': 'Red' if total_resignations > 0 else 'Green',
- 'datatype': 'Int',
+ "value": total_resignations,
+ "label": _("Total Resignations"),
+ "indicator": "Red" if total_resignations > 0 else "Green",
+ "datatype": "Int",
},
{
- 'value': interviews_pending,
- 'label': _('Pending Interviews'),
- 'indicator': 'Blue' if interviews_pending > 0 else 'Green',
- 'datatype': 'Int',
+ "value": interviews_pending,
+ "label": _("Pending Interviews"),
+ "indicator": "Blue" if interviews_pending > 0 else "Green",
+ "datatype": "Int",
},
{
- 'value': fnf_pending,
- 'label': _('Pending FnF'),
- 'indicator': 'Blue' if fnf_pending > 0 else 'Green',
- 'datatype': 'Int',
+ "value": fnf_pending,
+ "label": _("Pending FnF"),
+ "indicator": "Blue" if fnf_pending > 0 else "Green",
+ "datatype": "Int",
},
{
- 'value': questionnaires_pending,
- 'label': _('Pending Questionnaires'),
- 'indicator': 'Blue' if questionnaires_pending > 0 else 'Green',
- 'datatype': 'Int'
+ "value": questionnaires_pending,
+ "label": _("Pending Questionnaires"),
+ "indicator": "Blue" if questionnaires_pending > 0 else "Green",
+ "datatype": "Int",
},
]
-
diff --git a/erpnext/hr/report/employee_exits/test_employee_exits.py b/erpnext/hr/report/employee_exits/test_employee_exits.py
index d7e95a60d0..a9a30de554 100644
--- a/erpnext/hr/report/employee_exits/test_employee_exits.py
+++ b/erpnext/hr/report/employee_exits/test_employee_exits.py
@@ -28,33 +28,33 @@ class TestEmployeeExits(unittest.TestCase):
@classmethod
def create_records(cls):
cls.emp1 = make_employee(
- 'employeeexit1@example.com',
- company='Test Company',
- date_of_joining=getdate('01-10-2021'),
+ "employeeexit1@example.com",
+ company="Test Company",
+ date_of_joining=getdate("01-10-2021"),
relieving_date=add_days(getdate(), 14),
- designation='Accountant'
+ designation="Accountant",
)
cls.emp2 = make_employee(
- 'employeeexit2@example.com',
- company='Test Company',
- date_of_joining=getdate('01-12-2021'),
+ "employeeexit2@example.com",
+ company="Test Company",
+ date_of_joining=getdate("01-12-2021"),
relieving_date=add_days(getdate(), 15),
- designation='Accountant'
+ designation="Accountant",
)
cls.emp3 = make_employee(
- 'employeeexit3@example.com',
- company='Test Company',
- date_of_joining=getdate('02-12-2021'),
+ "employeeexit3@example.com",
+ company="Test Company",
+ date_of_joining=getdate("02-12-2021"),
relieving_date=add_days(getdate(), 29),
- designation='Engineer'
+ designation="Engineer",
)
cls.emp4 = make_employee(
- 'employeeexit4@example.com',
- company='Test Company',
- date_of_joining=getdate('01-12-2021'),
+ "employeeexit4@example.com",
+ company="Test Company",
+ date_of_joining=getdate("01-12-2021"),
relieving_date=add_days(getdate(), 30),
- designation='Engineer'
+ designation="Engineer",
)
# exit interview for 3 employees only
@@ -69,174 +69,177 @@ class TestEmployeeExits(unittest.TestCase):
# link questionnaire for a few records
# setting employee doctype as reference instead of creating a questionnaire
# since this is just for a test
- frappe.db.set_value('Exit Interview', cls.interview1.name, {
- 'ref_doctype': 'Employee',
- 'reference_document_name': cls.emp1
- })
+ frappe.db.set_value(
+ "Exit Interview",
+ cls.interview1.name,
+ {"ref_doctype": "Employee", "reference_document_name": cls.emp1},
+ )
- frappe.db.set_value('Exit Interview', cls.interview2.name, {
- 'ref_doctype': 'Employee',
- 'reference_document_name': cls.emp2
- })
-
- frappe.db.set_value('Exit Interview', cls.interview3.name, {
- 'ref_doctype': 'Employee',
- 'reference_document_name': cls.emp3
- })
+ frappe.db.set_value(
+ "Exit Interview",
+ cls.interview2.name,
+ {"ref_doctype": "Employee", "reference_document_name": cls.emp2},
+ )
+ frappe.db.set_value(
+ "Exit Interview",
+ cls.interview3.name,
+ {"ref_doctype": "Employee", "reference_document_name": cls.emp3},
+ )
def test_employee_exits_summary(self):
filters = {
- 'company': 'Test Company',
- 'from_date': getdate(),
- 'to_date': add_days(getdate(), 15),
- 'designation': 'Accountant'
+ "company": "Test Company",
+ "from_date": getdate(),
+ "to_date": add_days(getdate(), 15),
+ "designation": "Accountant",
}
report = execute(filters)
- employee1 = frappe.get_doc('Employee', self.emp1)
- employee2 = frappe.get_doc('Employee', self.emp2)
+ employee1 = frappe.get_doc("Employee", self.emp1)
+ employee2 = frappe.get_doc("Employee", self.emp2)
expected_data = [
{
- 'employee': employee1.name,
- 'employee_name': employee1.employee_name,
- 'date_of_joining': employee1.date_of_joining,
- 'relieving_date': employee1.relieving_date,
- 'department': employee1.department,
- 'designation': employee1.designation,
- 'reports_to': None,
- 'exit_interview': self.interview1.name,
- 'interview_status': self.interview1.status,
- 'employee_status': '',
- 'questionnaire': employee1.name,
- 'full_and_final_statement': self.fnf1.name
+ "employee": employee1.name,
+ "employee_name": employee1.employee_name,
+ "date_of_joining": employee1.date_of_joining,
+ "relieving_date": employee1.relieving_date,
+ "department": employee1.department,
+ "designation": employee1.designation,
+ "reports_to": None,
+ "exit_interview": self.interview1.name,
+ "interview_status": self.interview1.status,
+ "employee_status": "",
+ "questionnaire": employee1.name,
+ "full_and_final_statement": self.fnf1.name,
},
{
- 'employee': employee2.name,
- 'employee_name': employee2.employee_name,
- 'date_of_joining': employee2.date_of_joining,
- 'relieving_date': employee2.relieving_date,
- 'department': employee2.department,
- 'designation': employee2.designation,
- 'reports_to': None,
- 'exit_interview': self.interview2.name,
- 'interview_status': self.interview2.status,
- 'employee_status': '',
- 'questionnaire': employee2.name,
- 'full_and_final_statement': self.fnf2.name
- }
+ "employee": employee2.name,
+ "employee_name": employee2.employee_name,
+ "date_of_joining": employee2.date_of_joining,
+ "relieving_date": employee2.relieving_date,
+ "department": employee2.department,
+ "designation": employee2.designation,
+ "reports_to": None,
+ "exit_interview": self.interview2.name,
+ "interview_status": self.interview2.status,
+ "employee_status": "",
+ "questionnaire": employee2.name,
+ "full_and_final_statement": self.fnf2.name,
+ },
]
- self.assertEqual(expected_data, report[1]) # rows
-
+ self.assertEqual(expected_data, report[1]) # rows
def test_pending_exit_interviews_summary(self):
filters = {
- 'company': 'Test Company',
- 'from_date': getdate(),
- 'to_date': add_days(getdate(), 30),
- 'exit_interview_pending': 1
+ "company": "Test Company",
+ "from_date": getdate(),
+ "to_date": add_days(getdate(), 30),
+ "exit_interview_pending": 1,
}
report = execute(filters)
- employee4 = frappe.get_doc('Employee', self.emp4)
- expected_data = [{
- 'employee': employee4.name,
- 'employee_name': employee4.employee_name,
- 'date_of_joining': employee4.date_of_joining,
- 'relieving_date': employee4.relieving_date,
- 'department': employee4.department,
- 'designation': employee4.designation,
- 'reports_to': None,
- 'exit_interview': None,
- 'interview_status': None,
- 'employee_status': None,
- 'questionnaire': None,
- 'full_and_final_statement': None
- }]
-
- self.assertEqual(expected_data, report[1]) # rows
-
- def test_pending_exit_questionnaire_summary(self):
- filters = {
- 'company': 'Test Company',
- 'from_date': getdate(),
- 'to_date': add_days(getdate(), 30),
- 'questionnaire_pending': 1
- }
-
- report = execute(filters)
-
- employee4 = frappe.get_doc('Employee', self.emp4)
- expected_data = [{
- 'employee': employee4.name,
- 'employee_name': employee4.employee_name,
- 'date_of_joining': employee4.date_of_joining,
- 'relieving_date': employee4.relieving_date,
- 'department': employee4.department,
- 'designation': employee4.designation,
- 'reports_to': None,
- 'exit_interview': None,
- 'interview_status': None,
- 'employee_status': None,
- 'questionnaire': None,
- 'full_and_final_statement': None
- }]
-
- self.assertEqual(expected_data, report[1]) # rows
-
-
- def test_pending_fnf_summary(self):
- filters = {
- 'company': 'Test Company',
- 'fnf_pending': 1
- }
-
- report = execute(filters)
-
- employee3 = frappe.get_doc('Employee', self.emp3)
- employee4 = frappe.get_doc('Employee', self.emp4)
+ employee4 = frappe.get_doc("Employee", self.emp4)
expected_data = [
{
- 'employee': employee3.name,
- 'employee_name': employee3.employee_name,
- 'date_of_joining': employee3.date_of_joining,
- 'relieving_date': employee3.relieving_date,
- 'department': employee3.department,
- 'designation': employee3.designation,
- 'reports_to': None,
- 'exit_interview': self.interview3.name,
- 'interview_status': self.interview3.status,
- 'employee_status': '',
- 'questionnaire': employee3.name,
- 'full_and_final_statement': None
- },
- {
- 'employee': employee4.name,
- 'employee_name': employee4.employee_name,
- 'date_of_joining': employee4.date_of_joining,
- 'relieving_date': employee4.relieving_date,
- 'department': employee4.department,
- 'designation': employee4.designation,
- 'reports_to': None,
- 'exit_interview': None,
- 'interview_status': None,
- 'employee_status': None,
- 'questionnaire': None,
- 'full_and_final_statement': None
+ "employee": employee4.name,
+ "employee_name": employee4.employee_name,
+ "date_of_joining": employee4.date_of_joining,
+ "relieving_date": employee4.relieving_date,
+ "department": employee4.department,
+ "designation": employee4.designation,
+ "reports_to": None,
+ "exit_interview": None,
+ "interview_status": None,
+ "employee_status": None,
+ "questionnaire": None,
+ "full_and_final_statement": None,
}
]
- self.assertEqual(expected_data, report[1]) # rows
+ self.assertEqual(expected_data, report[1]) # rows
+
+ def test_pending_exit_questionnaire_summary(self):
+ filters = {
+ "company": "Test Company",
+ "from_date": getdate(),
+ "to_date": add_days(getdate(), 30),
+ "questionnaire_pending": 1,
+ }
+
+ report = execute(filters)
+
+ employee4 = frappe.get_doc("Employee", self.emp4)
+ expected_data = [
+ {
+ "employee": employee4.name,
+ "employee_name": employee4.employee_name,
+ "date_of_joining": employee4.date_of_joining,
+ "relieving_date": employee4.relieving_date,
+ "department": employee4.department,
+ "designation": employee4.designation,
+ "reports_to": None,
+ "exit_interview": None,
+ "interview_status": None,
+ "employee_status": None,
+ "questionnaire": None,
+ "full_and_final_statement": None,
+ }
+ ]
+
+ self.assertEqual(expected_data, report[1]) # rows
+
+ def test_pending_fnf_summary(self):
+ filters = {"company": "Test Company", "fnf_pending": 1}
+
+ report = execute(filters)
+
+ employee3 = frappe.get_doc("Employee", self.emp3)
+ employee4 = frappe.get_doc("Employee", self.emp4)
+ expected_data = [
+ {
+ "employee": employee3.name,
+ "employee_name": employee3.employee_name,
+ "date_of_joining": employee3.date_of_joining,
+ "relieving_date": employee3.relieving_date,
+ "department": employee3.department,
+ "designation": employee3.designation,
+ "reports_to": None,
+ "exit_interview": self.interview3.name,
+ "interview_status": self.interview3.status,
+ "employee_status": "",
+ "questionnaire": employee3.name,
+ "full_and_final_statement": None,
+ },
+ {
+ "employee": employee4.name,
+ "employee_name": employee4.employee_name,
+ "date_of_joining": employee4.date_of_joining,
+ "relieving_date": employee4.relieving_date,
+ "department": employee4.department,
+ "designation": employee4.designation,
+ "reports_to": None,
+ "exit_interview": None,
+ "interview_status": None,
+ "employee_status": None,
+ "questionnaire": None,
+ "full_and_final_statement": None,
+ },
+ ]
+
+ self.assertEqual(expected_data, report[1]) # rows
def create_company():
- if not frappe.db.exists('Company', 'Test Company'):
- frappe.get_doc({
- 'doctype': 'Company',
- 'company_name': 'Test Company',
- 'default_currency': 'INR',
- 'country': 'India'
- }).insert()
\ No newline at end of file
+ if not frappe.db.exists("Company", "Test Company"):
+ frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": "Test Company",
+ "default_currency": "INR",
+ "country": "India",
+ }
+ ).insert()
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 66c1d25d59..ca352f197d 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -17,7 +17,8 @@ from erpnext.hr.doctype.leave_application.leave_application import (
Filters = frappe._dict
-def execute(filters: Optional[Filters] = None) -> Tuple:
+
+def execute(filters: Optional[Filters] = None) -> Tuple:
if filters.to_date <= filters.from_date:
frappe.throw(_('"From Date" can not be greater than or equal to "To Date"'))
@@ -28,91 +29,105 @@ def execute(filters: Optional[Filters] = None) -> Tuple:
def get_columns() -> List[Dict]:
- return [{
- 'label': _('Leave Type'),
- 'fieldtype': 'Link',
- 'fieldname': 'leave_type',
- 'width': 200,
- 'options': 'Leave Type'
- }, {
- 'label': _('Employee'),
- 'fieldtype': 'Link',
- 'fieldname': 'employee',
- 'width': 100,
- 'options': 'Employee'
- }, {
- 'label': _('Employee Name'),
- 'fieldtype': 'Dynamic Link',
- 'fieldname': 'employee_name',
- 'width': 100,
- 'options': 'employee'
- }, {
- 'label': _('Opening Balance'),
- 'fieldtype': 'float',
- 'fieldname': 'opening_balance',
- 'width': 150,
- }, {
- 'label': _('New Leave(s) Allocated'),
- 'fieldtype': 'float',
- 'fieldname': 'leaves_allocated',
- 'width': 200,
- }, {
- 'label': _('Leave(s) Taken'),
- 'fieldtype': 'float',
- 'fieldname': 'leaves_taken',
- 'width': 150,
- }, {
- 'label': _('Leave(s) Expired'),
- 'fieldtype': 'float',
- 'fieldname': 'leaves_expired',
- 'width': 150,
- }, {
- 'label': _('Closing Balance'),
- 'fieldtype': 'float',
- 'fieldname': 'closing_balance',
- 'width': 150,
- }]
+ return [
+ {
+ "label": _("Leave Type"),
+ "fieldtype": "Link",
+ "fieldname": "leave_type",
+ "width": 200,
+ "options": "Leave Type",
+ },
+ {
+ "label": _("Employee"),
+ "fieldtype": "Link",
+ "fieldname": "employee",
+ "width": 100,
+ "options": "Employee",
+ },
+ {
+ "label": _("Employee Name"),
+ "fieldtype": "Dynamic Link",
+ "fieldname": "employee_name",
+ "width": 100,
+ "options": "employee",
+ },
+ {
+ "label": _("Opening Balance"),
+ "fieldtype": "float",
+ "fieldname": "opening_balance",
+ "width": 150,
+ },
+ {
+ "label": _("New Leave(s) Allocated"),
+ "fieldtype": "float",
+ "fieldname": "leaves_allocated",
+ "width": 200,
+ },
+ {
+ "label": _("Leave(s) Taken"),
+ "fieldtype": "float",
+ "fieldname": "leaves_taken",
+ "width": 150,
+ },
+ {
+ "label": _("Leave(s) Expired"),
+ "fieldtype": "float",
+ "fieldname": "leaves_expired",
+ "width": 150,
+ },
+ {
+ "label": _("Closing Balance"),
+ "fieldtype": "float",
+ "fieldname": "closing_balance",
+ "width": 150,
+ },
+ ]
def get_data(filters: Filters) -> List:
- leave_types = frappe.db.get_list('Leave Type', pluck='name', order_by='name')
+ leave_types = frappe.db.get_list("Leave Type", pluck="name", order_by="name")
conditions = get_conditions(filters)
user = frappe.session.user
- department_approver_map = get_department_leave_approver_map(filters.get('department'))
+ department_approver_map = get_department_leave_approver_map(filters.get("department"))
- active_employees = frappe.get_list('Employee',
+ active_employees = frappe.get_list(
+ "Employee",
filters=conditions,
- fields=['name', 'employee_name', 'department', 'user_id', 'leave_approver'])
+ fields=["name", "employee_name", "department", "user_id", "leave_approver"],
+ )
data = []
for leave_type in leave_types:
if len(active_employees) > 1:
- data.append({
- 'leave_type': leave_type
- })
+ data.append({"leave_type": leave_type})
else:
- row = frappe._dict({
- 'leave_type': leave_type
- })
+ row = frappe._dict({"leave_type": leave_type})
for employee in active_employees:
- leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver)
+ leave_approvers = department_approver_map.get(employee.department_name, []).append(
+ employee.leave_approver
+ )
- if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
- or ("HR Manager" in frappe.get_roles(user)):
+ if (
+ (leave_approvers and len(leave_approvers) and user in leave_approvers)
+ or (user in ["Administrator", employee.user_id])
+ or ("HR Manager" in frappe.get_roles(user))
+ ):
if len(active_employees) > 1:
row = frappe._dict()
row.employee = employee.name
row.employee_name = employee.employee_name
- leaves_taken = get_leaves_for_period(employee.name, leave_type,
- filters.from_date, filters.to_date) * -1
+ leaves_taken = (
+ get_leaves_for_period(employee.name, leave_type, filters.from_date, filters.to_date) * -1
+ )
new_allocation, expired_leaves, carry_forwarded_leaves = get_allocated_and_expired_leaves(
- filters.from_date, filters.to_date, employee.name, leave_type)
+ filters.from_date, filters.to_date, employee.name, leave_type
+ )
opening = get_opening_balance(employee.name, leave_type, filters, carry_forwarded_leaves)
row.leaves_allocated = new_allocation
@@ -121,21 +136,27 @@ def get_data(filters: Filters) -> List:
row.leaves_taken = leaves_taken
# not be shown on the basis of days left it create in user mind for carry_forward leave
- row.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken))
+ row.closing_balance = new_allocation + opening - (row.leaves_expired + leaves_taken)
row.indent = 1
data.append(row)
return data
-def get_opening_balance(employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float) -> float:
+def get_opening_balance(
+ employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float
+) -> float:
# allocation boundary condition
# opening balance is the closing leave balance 1 day before the filter start date
opening_balance_date = add_days(filters.from_date, -1)
allocation = get_previous_allocation(filters.from_date, leave_type, employee)
- if allocation and allocation.get("to_date") and opening_balance_date and \
- getdate(allocation.get("to_date")) == getdate(opening_balance_date):
+ if (
+ allocation
+ and allocation.get("to_date")
+ and opening_balance_date
+ and getdate(allocation.get("to_date")) == getdate(opening_balance_date)
+ ):
# if opening balance date is same as the previous allocation's expiry
# then opening balance should only consider carry forwarded leaves
opening_balance = carry_forwarded_leaves
@@ -147,39 +168,35 @@ def get_opening_balance(employee: str, leave_type: str, filters: Filters, carry_
def get_conditions(filters: Filters) -> Dict:
- conditions={
- 'status': 'Active',
+ conditions = {
+ "status": "Active",
}
- if filters.get('employee'):
- conditions['name'] = filters.get('employee')
+ if filters.get("employee"):
+ conditions["name"] = filters.get("employee")
- if filters.get('company'):
- conditions['company'] = filters.get('company')
+ if filters.get("company"):
+ conditions["company"] = filters.get("company")
- if filters.get('department'):
- conditions['department'] = filters.get('department')
+ if filters.get("department"):
+ conditions["department"] = filters.get("department")
return conditions
def get_department_leave_approver_map(department: Optional[str] = None):
# get current department and all its child
- department_list = frappe.get_list('Department',
- filters={'disabled': 0},
- or_filters={
- 'name': department,
- 'parent_department': department
- },
- pluck='name'
+ department_list = frappe.get_list(
+ "Department",
+ filters={"disabled": 0},
+ or_filters={"name": department, "parent_department": department},
+ pluck="name",
)
# retrieve approvers list from current department and from its subsequent child departments
- approver_list = frappe.get_all('Department Approver',
- filters={
- 'parentfield': 'leave_approvers',
- 'parent': ('in', department_list)
- },
- fields=['parent', 'approver'],
- as_list=True
+ approver_list = frappe.get_all(
+ "Department Approver",
+ filters={"parentfield": "leave_approvers", "parent": ("in", department_list)},
+ fields=["parent", "approver"],
+ as_list=True,
)
approvers = {}
@@ -190,7 +207,9 @@ def get_department_leave_approver_map(department: Optional[str] = None):
return approvers
-def get_allocated_and_expired_leaves(from_date: str, to_date: str, employee: str, leave_type: str) -> Tuple[float, float, float]:
+def get_allocated_and_expired_leaves(
+ from_date: str, to_date: str, employee: str, leave_type: str
+) -> Tuple[float, float, float]:
new_allocation = 0
expired_leaves = 0
carry_forwarded_leaves = 0
@@ -207,8 +226,7 @@ def get_allocated_and_expired_leaves(from_date: str, to_date: str, employee: str
# leave allocations ending before to_date, reduce leaves taken within that period
# since they are already used, they won't expire
expired_leaves += record.leaves
- expired_leaves += get_leaves_for_period(employee, leave_type,
- record.from_date, record.to_date)
+ expired_leaves += get_leaves_for_period(employee, leave_type, record.from_date, record.to_date)
if record.from_date >= getdate(from_date):
if record.is_carry_forward:
@@ -219,22 +237,31 @@ def get_allocated_and_expired_leaves(from_date: str, to_date: str, employee: str
return new_allocation, expired_leaves, carry_forwarded_leaves
-def get_leave_ledger_entries(from_date: str, to_date: str, employee: str, leave_type: str) -> List[Dict]:
- ledger = frappe.qb.DocType('Leave Ledger Entry')
+def get_leave_ledger_entries(
+ from_date: str, to_date: str, employee: str, leave_type: str
+) -> List[Dict]:
+ ledger = frappe.qb.DocType("Leave Ledger Entry")
records = (
frappe.qb.from_(ledger)
.select(
- ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date,
- ledger.leaves, ledger.transaction_name, ledger.transaction_type,
- ledger.is_carry_forward, ledger.is_expired
- ).where(
+ ledger.employee,
+ ledger.leave_type,
+ ledger.from_date,
+ ledger.to_date,
+ ledger.leaves,
+ ledger.transaction_name,
+ ledger.transaction_type,
+ ledger.is_carry_forward,
+ ledger.is_expired,
+ )
+ .where(
(ledger.docstatus == 1)
- & (ledger.transaction_type == 'Leave Allocation')
+ & (ledger.transaction_type == "Leave Allocation")
& (ledger.employee == employee)
& (ledger.leave_type == leave_type)
& (
- (ledger.from_date[from_date: to_date])
- | (ledger.to_date[from_date: to_date])
+ (ledger.from_date[from_date:to_date])
+ | (ledger.to_date[from_date:to_date])
| ((ledger.from_date < from_date) & (ledger.to_date > to_date))
)
)
@@ -248,16 +275,13 @@ def get_chart_data(data: List) -> Dict:
datasets = []
employee_data = data
- if data and data[0].get('employee_name'):
+ if data and data[0].get("employee_name"):
get_dataset_for_chart(employee_data, datasets, labels)
chart = {
- 'data': {
- 'labels': labels,
- 'datasets': datasets
- },
- 'type': 'bar',
- 'colors': ['#456789', '#EE8888', '#7E77BF']
+ "data": {"labels": labels, "datasets": datasets},
+ "type": "bar",
+ "colors": ["#456789", "#EE8888", "#7E77BF"],
}
return chart
@@ -265,18 +289,17 @@ def get_chart_data(data: List) -> Dict:
def get_dataset_for_chart(employee_data: List, datasets: List, labels: List) -> List:
leaves = []
- employee_data = sorted(employee_data, key=lambda k: k['employee_name'])
+ employee_data = sorted(employee_data, key=lambda k: k["employee_name"])
- for key, group in groupby(employee_data, lambda x: x['employee_name']):
+ for key, group in groupby(employee_data, lambda x: x["employee_name"]):
for grp in group:
if grp.closing_balance:
- leaves.append(frappe._dict({
- 'leave_type': grp.leave_type,
- 'closing_balance': grp.closing_balance
- }))
+ leaves.append(
+ frappe._dict({"leave_type": grp.leave_type, "closing_balance": grp.closing_balance})
+ )
if leaves:
labels.append(key)
for leave in leaves:
- datasets.append({'name': leave.leave_type, 'values': [leave.closing_balance]})
+ datasets.append({"name": leave.leave_type, "values": [leave.closing_balance]})
diff --git a/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py
index b2ed72c04d..dc0f4d2c94 100644
--- a/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py
@@ -21,141 +21,189 @@ from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_leave_application,
)
-test_records = frappe.get_test_records('Leave Type')
+test_records = frappe.get_test_records("Leave Type")
+
class TestEmployeeLeaveBalance(unittest.TestCase):
def setUp(self):
- for dt in ['Leave Application', 'Leave Allocation', 'Salary Slip', 'Leave Ledger Entry', 'Leave Type']:
+ for dt in [
+ "Leave Application",
+ "Leave Allocation",
+ "Salary Slip",
+ "Leave Ledger Entry",
+ "Leave Type",
+ ]:
frappe.db.delete(dt)
- frappe.set_user('Administrator')
+ frappe.set_user("Administrator")
- self.employee_id = make_employee('test_emp_leave_balance@example.com', company='_Test Company')
+ self.employee_id = make_employee("test_emp_leave_balance@example.com", company="_Test Company")
self.date = getdate()
self.year_start = getdate(get_year_start(self.date))
self.mid_year = add_months(self.year_start, 6)
self.year_end = getdate(get_year_ending(self.date))
- self.holiday_list = make_holiday_list('_Test Emp Balance Holiday List', self.year_start, self.year_end)
+ self.holiday_list = make_holiday_list(
+ "_Test Emp Balance Holiday List", self.year_start, self.year_end
+ )
def tearDown(self):
frappe.db.rollback()
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_employee_leave_balance(self):
frappe.get_doc(test_records[0]).insert()
# 5 leaves
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.year_start, -11),
- to_date=add_days(self.year_start, -1), leaves=5)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=add_days(self.year_start, -11),
+ to_date=add_days(self.year_start, -1),
+ leaves=5,
+ )
# 30 leaves
- allocation2 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation2 = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# expires 5 leaves
process_expired_allocation()
# 4 days leave
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
- filters = frappe._dict({
- 'from_date': allocation1.from_date,
- 'to_date': allocation2.to_date,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": allocation1.from_date,
+ "to_date": allocation2.to_date,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- expected_data = [{
- 'leave_type': '_Test Leave Type',
- 'employee': self.employee_id,
- 'employee_name': 'test_emp_leave_balance@example.com',
- 'leaves_allocated': flt(allocation1.new_leaves_allocated + allocation2.new_leaves_allocated),
- 'leaves_expired': flt(allocation1.new_leaves_allocated),
- 'opening_balance': flt(0),
- 'leaves_taken': flt(leave_application.total_leave_days),
- 'closing_balance': flt(allocation2.new_leaves_allocated - leave_application.total_leave_days),
- 'indent': 1
- }]
+ expected_data = [
+ {
+ "leave_type": "_Test Leave Type",
+ "employee": self.employee_id,
+ "employee_name": "test_emp_leave_balance@example.com",
+ "leaves_allocated": flt(allocation1.new_leaves_allocated + allocation2.new_leaves_allocated),
+ "leaves_expired": flt(allocation1.new_leaves_allocated),
+ "opening_balance": flt(0),
+ "leaves_taken": flt(leave_application.total_leave_days),
+ "closing_balance": flt(allocation2.new_leaves_allocated - leave_application.total_leave_days),
+ "indent": 1,
+ }
+ ]
self.assertEqual(report[1], expected_data)
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_opening_balance_on_alloc_boundary_dates(self):
frappe.get_doc(test_records[0]).insert()
# 30 leaves allocated
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
# Case 1: opening balance for first alloc boundary
- filters = frappe._dict({
- 'from_date': self.year_start,
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {"from_date": self.year_start, "to_date": self.year_end, "employee": self.employee_id}
+ )
report = execute(filters)
self.assertEqual(report[1][0].opening_balance, 0)
# Case 2: opening balance after leave application date
- filters = frappe._dict({
- 'from_date': add_days(leave_application.to_date, 1),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(leave_application.to_date, 1),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- self.assertEqual(report[1][0].opening_balance, (allocation1.new_leaves_allocated - leave_application.total_leave_days))
+ self.assertEqual(
+ report[1][0].opening_balance,
+ (allocation1.new_leaves_allocated - leave_application.total_leave_days),
+ )
# Case 3: leave balance shows actual balance and not consumption balance as per remaining days near alloc end date
# eg: 3 days left for alloc to end, leave balance should still be 26 and not 3
- filters = frappe._dict({
- 'from_date': add_days(self.year_end, -3),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(self.year_end, -3),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- self.assertEqual(report[1][0].opening_balance, (allocation1.new_leaves_allocated - leave_application.total_leave_days))
+ self.assertEqual(
+ report[1][0].opening_balance,
+ (allocation1.new_leaves_allocated - leave_application.total_leave_days),
+ )
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_opening_balance_considers_carry_forwarded_leaves(self):
- leave_type = create_leave_type(
- leave_type_name="_Test_CF_leave_expiry",
- is_carry_forward=1)
+ leave_type = create_leave_type(leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1)
leave_type.insert()
# 30 leaves allocated for first half of the year
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=self.year_start,
- to_date=self.mid_year, leave_type=leave_type.name)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=self.year_start,
+ to_date=self.mid_year,
+ leave_type=leave_type.name,
+ )
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, first_sunday, add_days(first_sunday, 3), leave_type.name)
+ leave_application = make_leave_application(
+ self.employee_id, first_sunday, add_days(first_sunday, 3), leave_type.name
+ )
leave_application.reload()
# 30 leaves allocated for second half of the year + carry forward leaves (26) from the previous allocation
- allocation2 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.mid_year, 1), to_date=self.year_end,
- carry_forward=True, leave_type=leave_type.name)
+ allocation2 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=add_days(self.mid_year, 1),
+ to_date=self.year_end,
+ carry_forward=True,
+ leave_type=leave_type.name,
+ )
# Case 1: carry forwarded leaves considered in opening balance for second alloc
- filters = frappe._dict({
- 'from_date': add_days(self.mid_year, 1),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(self.mid_year, 1),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
# available leaves from old alloc
opening_balance = allocation1.new_leaves_allocated - leave_application.total_leave_days
self.assertEqual(report[1][0].opening_balance, opening_balance)
# Case 2: opening balance one day after alloc boundary = carry forwarded leaves + new leaves alloc
- filters = frappe._dict({
- 'from_date': add_days(self.mid_year, 2),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(self.mid_year, 2),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
# available leaves from old alloc
- opening_balance = allocation2.new_leaves_allocated + (allocation1.new_leaves_allocated - leave_application.total_leave_days)
+ opening_balance = allocation2.new_leaves_allocated + (
+ allocation1.new_leaves_allocated - leave_application.total_leave_days
+ )
self.assertEqual(report[1][0].opening_balance, opening_balance)
diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
index 71c18bb51f..2a16dc41d6 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
@@ -19,11 +19,12 @@ def execute(filters=None):
return columns, data
+
def get_columns(leave_types):
columns = [
_("Employee") + ":Link.Employee:150",
_("Employee Name") + "::200",
- _("Department") +"::150"
+ _("Department") + "::150",
]
for leave_type in leave_types:
@@ -31,6 +32,7 @@ def get_columns(leave_types):
return columns
+
def get_conditions(filters):
conditions = {
"status": "Active",
@@ -43,15 +45,18 @@ def get_conditions(filters):
return conditions
+
def get_data(filters, leave_types):
user = frappe.session.user
conditions = get_conditions(filters)
- active_employees = frappe.get_list("Employee",
+ active_employees = frappe.get_list(
+ "Employee",
filters=conditions,
- fields=["name", "employee_name", "department", "user_id", "leave_approver"])
+ fields=["name", "employee_name", "department", "user_id", "leave_approver"],
+ )
- department_approver_map = get_department_leave_approver_map(filters.get('department'))
+ department_approver_map = get_department_leave_approver_map(filters.get("department"))
data = []
for employee in active_employees:
@@ -59,14 +64,18 @@ def get_data(filters, leave_types):
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
- if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)):
+ if (
+ (len(leave_approvers) and user in leave_approvers)
+ or (user in ["Administrator", employee.user_id])
+ or ("HR Manager" in frappe.get_roles(user))
+ ):
row = [employee.name, employee.employee_name, employee.department]
available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types:
remaining = 0
if leave_type in available_leave["leave_allocation"]:
# opening balance
- remaining = available_leave["leave_allocation"][leave_type]['remaining_leaves']
+ remaining = available_leave["leave_allocation"][leave_type]["remaining_leaves"]
row += [remaining]
diff --git a/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py
index 6f16a8d58c..34b665fa9f 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py
@@ -20,40 +20,59 @@ from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_leave_application,
)
-test_records = frappe.get_test_records('Leave Type')
+test_records = frappe.get_test_records("Leave Type")
+
class TestEmployeeLeaveBalance(unittest.TestCase):
def setUp(self):
- for dt in ['Leave Application', 'Leave Allocation', 'Salary Slip', 'Leave Ledger Entry', 'Leave Type']:
+ for dt in [
+ "Leave Application",
+ "Leave Allocation",
+ "Salary Slip",
+ "Leave Ledger Entry",
+ "Leave Type",
+ ]:
frappe.db.delete(dt)
- frappe.set_user('Administrator')
+ frappe.set_user("Administrator")
- self.employee_id = make_employee('test_emp_leave_balance@example.com', company='_Test Company')
- self.employee_id = make_employee('test_emp_leave_balance@example.com', company='_Test Company')
+ self.employee_id = make_employee("test_emp_leave_balance@example.com", company="_Test Company")
+ self.employee_id = make_employee("test_emp_leave_balance@example.com", company="_Test Company")
self.date = getdate()
self.year_start = getdate(get_year_start(self.date))
self.year_end = getdate(get_year_ending(self.date))
- self.holiday_list = make_holiday_list('_Test Emp Balance Holiday List', self.year_start, self.year_end)
+ self.holiday_list = make_holiday_list(
+ "_Test Emp Balance Holiday List", self.year_start, self.year_end
+ )
def tearDown(self):
frappe.db.rollback()
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_employee_leave_balance_summary(self):
frappe.get_doc(test_records[0]).insert()
# 5 leaves
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.year_start, -11),
- to_date=add_days(self.year_start, -1), leaves=5)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=add_days(self.year_start, -11),
+ to_date=add_days(self.year_start, -1),
+ leaves=5,
+ )
# 30 leaves
- allocation2 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation2 = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# 2 days leave within the first allocation
- leave_application1 = make_leave_application(self.employee_id, add_days(self.year_start, -11), add_days(self.year_start, -10),
- '_Test Leave Type')
+ leave_application1 = make_leave_application(
+ self.employee_id,
+ add_days(self.year_start, -11),
+ add_days(self.year_start, -10),
+ "_Test Leave Type",
+ )
leave_application1.reload()
# expires 3 leaves
@@ -61,57 +80,69 @@ class TestEmployeeLeaveBalance(unittest.TestCase):
# 4 days leave within the second allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application2 = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application2 = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application2.reload()
- filters = frappe._dict({
- 'date': add_days(leave_application2.to_date, 1),
- 'company': '_Test Company',
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "date": add_days(leave_application2.to_date, 1),
+ "company": "_Test Company",
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- expected_data = [[
- self.employee_id,
- 'test_emp_leave_balance@example.com',
- frappe.db.get_value('Employee', self.employee_id, 'department'),
- flt(
- allocation1.new_leaves_allocated # allocated = 5
- + allocation2.new_leaves_allocated # allocated = 30
- - leave_application1.total_leave_days # leaves taken in the 1st alloc = 2
- - (allocation1.new_leaves_allocated - leave_application1.total_leave_days) # leaves expired from 1st alloc = 3
- - leave_application2.total_leave_days # leaves taken in the 2nd alloc = 4
- )
- ]]
+ expected_data = [
+ [
+ self.employee_id,
+ "test_emp_leave_balance@example.com",
+ frappe.db.get_value("Employee", self.employee_id, "department"),
+ flt(
+ allocation1.new_leaves_allocated # allocated = 5
+ + allocation2.new_leaves_allocated # allocated = 30
+ - leave_application1.total_leave_days # leaves taken in the 1st alloc = 2
+ - (
+ allocation1.new_leaves_allocated - leave_application1.total_leave_days
+ ) # leaves expired from 1st alloc = 3
+ - leave_application2.total_leave_days # leaves taken in the 2nd alloc = 4
+ ),
+ ]
+ ]
self.assertEqual(report[1], expected_data)
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_get_leave_balance_near_alloc_expiry(self):
frappe.get_doc(test_records[0]).insert()
# 30 leaves allocated
- allocation = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
# Leave balance should show actual balance, and not "consumption balance as per remaining days", near alloc end date
# eg: 3 days left for alloc to end, leave balance should still be 26 and not 3
- filters = frappe._dict({
- 'date': add_days(self.year_end, -3),
- 'company': '_Test Company',
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {"date": add_days(self.year_end, -3), "company": "_Test Company", "employee": self.employee_id}
+ )
report = execute(filters)
- expected_data = [[
- self.employee_id,
- 'test_emp_leave_balance@example.com',
- frappe.db.get_value('Employee', self.employee_id, 'department'),
- flt(allocation.new_leaves_allocated - leave_application.total_leave_days)
- ]]
+ expected_data = [
+ [
+ self.employee_id,
+ "test_emp_leave_balance@example.com",
+ frappe.db.get_value("Employee", self.employee_id, "department"),
+ flt(allocation.new_leaves_allocated - leave_application.total_leave_days),
+ ]
+ ]
self.assertEqual(report[1], expected_data)
diff --git a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py
index 00a4a7c29f..f13fabf06e 100644
--- a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py
+++ b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py
@@ -21,16 +21,21 @@ def get_columns():
_("Name") + ":Data:200",
_("Date") + ":Date:100",
_("Status") + ":Data:70",
- _("Holiday") + ":Data:200"
+ _("Holiday") + ":Data:200",
]
+
def get_employees(filters):
- holiday_filter = [["holiday_date", ">=", filters.from_date], ["holiday_date", "<=", filters.to_date]]
+ holiday_filter = [
+ ["holiday_date", ">=", filters.from_date],
+ ["holiday_date", "<=", filters.to_date],
+ ]
if filters.holiday_list:
holiday_filter.append(["parent", "=", filters.holiday_list])
- holidays = frappe.get_all("Holiday", fields=["holiday_date", "description"],
- filters=holiday_filter)
+ holidays = frappe.get_all(
+ "Holiday", fields=["holiday_date", "description"], filters=holiday_filter
+ )
holiday_names = {}
holidays_list = []
@@ -39,18 +44,23 @@ def get_employees(filters):
holidays_list.append(holiday.holiday_date)
holiday_names[holiday.holiday_date] = holiday.description
- if(holidays_list):
+ if holidays_list:
cond = " attendance_date in %(holidays_list)s"
if filters.holiday_list:
- cond += """ and (employee in (select employee from tabEmployee where holiday_list = %(holidays)s))"""
+ cond += (
+ """ and (employee in (select employee from tabEmployee where holiday_list = %(holidays)s))"""
+ )
- employee_list = frappe.db.sql("""select
+ employee_list = frappe.db.sql(
+ """select
employee, employee_name, attendance_date, status
from tabAttendance
- where %s"""% cond.format(', '.join(["%s"] * len(holidays_list))),
- {'holidays_list':holidays_list,
- 'holidays':filters.holiday_list}, as_list=True)
+ where %s"""
+ % cond.format(", ".join(["%s"] * len(holidays_list))),
+ {"holidays_list": holidays_list, "holidays": filters.holiday_list},
+ as_list=True,
+ )
for employee_data in employee_list:
employee_data.append(holiday_names[employee_data[2]])
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index 9a993e52e2..8ea49899f2 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -15,21 +15,15 @@ status_map = {
"Weekly Off": "WO",
"On Leave": "L",
"Present": "P",
- "Work From Home": "WFH"
- }
+ "Work From Home": "WFH",
+}
+
+day_abbr = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
-day_abbr = [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
-]
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if filters.hide_year_field == 1:
filters.year = 2020
@@ -44,14 +38,19 @@ def execute(filters=None):
emp_map, group_by_parameters = get_employee_details(filters.group_by, filters.company)
holiday_list = []
for parameter in group_by_parameters:
- h_list = [emp_map[parameter][d]["holiday_list"] for d in emp_map[parameter] if emp_map[parameter][d]["holiday_list"]]
+ h_list = [
+ emp_map[parameter][d]["holiday_list"]
+ for d in emp_map[parameter]
+ if emp_map[parameter][d]["holiday_list"]
+ ]
holiday_list += h_list
else:
emp_map = get_employee_details(filters.group_by, filters.company)
holiday_list = [emp_map[d]["holiday_list"] for d in emp_map if emp_map[d]["holiday_list"]]
-
- default_holiday_list = frappe.get_cached_value('Company', filters.get("company"), "default_holiday_list")
+ default_holiday_list = frappe.get_cached_value(
+ "Company", filters.get("company"), "default_holiday_list"
+ )
holiday_list.append(default_holiday_list)
holiday_list = list(set(holiday_list))
holiday_map = get_holiday(holiday_list, filters["month"])
@@ -70,20 +69,39 @@ def execute(filters=None):
for parameter in group_by_parameters:
emp_map_set = set([key for key in emp_map[parameter].keys()])
att_map_set = set([key for key in att_map.keys()])
- if (att_map_set & emp_map_set):
- parameter_row = [""+ parameter + ""] + ['' for day in range(filters["total_days_in_month"] + 2)]
+ if att_map_set & emp_map_set:
+ parameter_row = ["" + parameter + ""] + [
+ "" for day in range(filters["total_days_in_month"] + 2)
+ ]
data.append(parameter_row)
- record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
+ record, emp_att_data = add_data(
+ emp_map[parameter],
+ att_map,
+ filters,
+ holiday_map,
+ conditions,
+ default_holiday_list,
+ leave_types=leave_types,
+ )
emp_att_map.update(emp_att_data)
data += record
else:
- record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
+ record, emp_att_map = add_data(
+ emp_map,
+ att_map,
+ filters,
+ holiday_map,
+ conditions,
+ default_holiday_list,
+ leave_types=leave_types,
+ )
data += record
chart_data = get_chart_data(emp_att_map, days)
return columns, data, None, chart_data
+
def get_chart_data(emp_att_map, days):
labels = []
datasets = [
@@ -110,24 +128,20 @@ def get_chart_data(emp_att_map, days):
if emp_att_map[emp][idx] == "L":
total_leave_on_day += 1
-
datasets[0]["values"].append(total_absent_on_day)
datasets[1]["values"].append(total_present_on_day)
datasets[2]["values"].append(total_leave_on_day)
-
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- }
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}}
chart["type"] = "line"
return chart
-def add_data(employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=None):
+
+def add_data(
+ employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=None
+):
record = []
emp_att_map = {}
@@ -141,7 +155,7 @@ def add_data(employee_map, att_map, filters, holiday_map, conditions, default_ho
row += [" "]
row += [emp, emp_det.employee_name]
- total_p = total_a = total_l = total_h = total_um= 0.0
+ total_p = total_a = total_l = total_h = total_um = 0.0
emp_status_map = []
for day in range(filters["total_days_in_month"]):
status = None
@@ -152,7 +166,7 @@ def add_data(employee_map, att_map, filters, holiday_map, conditions, default_ho
if emp_holiday_list in holiday_map:
for idx, ele in enumerate(holiday_map[emp_holiday_list]):
- if day+1 == holiday_map[emp_holiday_list][idx][0]:
+ if day + 1 == holiday_map[emp_holiday_list][idx][0]:
if holiday_map[emp_holiday_list][idx][1]:
status = "Weekly Off"
else:
@@ -162,7 +176,7 @@ def add_data(employee_map, att_map, filters, holiday_map, conditions, default_ho
abbr = status_map.get(status, "")
emp_status_map.append(abbr)
- if filters.summarized_view:
+ if filters.summarized_view:
if status == "Present" or status == "Work From Home":
total_p += 1
elif status == "Absent":
@@ -189,12 +203,21 @@ def add_data(employee_map, att_map, filters, holiday_map, conditions, default_ho
filters.update({"employee": emp})
if filters.summarized_view:
- leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\
- where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1)
+ leave_details = frappe.db.sql(
+ """select leave_type, status, count(*) as count from `tabAttendance`\
+ where leave_type is not NULL %s group by leave_type, status"""
+ % conditions,
+ filters,
+ as_dict=1,
+ )
- time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \
+ time_default_counts = frappe.db.sql(
+ """select (select count(*) from `tabAttendance` where \
late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \
- early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters)
+ early_exit = 1 %s) as early_exit_count"""
+ % (conditions, conditions),
+ filters,
+ )
leaves = {}
for d in leave_details:
@@ -211,38 +234,48 @@ def add_data(employee_map, att_map, filters, holiday_map, conditions, default_ho
else:
row.append("0.0")
- row.extend([time_default_counts[0][0],time_default_counts[0][1]])
+ row.extend([time_default_counts[0][0], time_default_counts[0][1]])
emp_att_map[emp] = emp_status_map
record.append(row)
return record, emp_att_map
+
def get_columns(filters):
columns = []
if filters.group_by:
- columns = [_(filters.group_by)+ ":Link/Branch:120"]
+ columns = [_(filters.group_by) + ":Link/Branch:120"]
- columns += [
- _("Employee") + ":Link/Employee:120", _("Employee Name") + ":Data/:120"
- ]
+ columns += [_("Employee") + ":Link/Employee:120", _("Employee Name") + ":Data/:120"]
days = []
for day in range(filters["total_days_in_month"]):
- date = str(filters.year) + "-" + str(filters.month)+ "-" + str(day+1)
+ date = str(filters.year) + "-" + str(filters.month) + "-" + str(day + 1)
day_name = day_abbr[getdate(date).weekday()]
- days.append(cstr(day+1)+ " " +day_name +"::65")
+ days.append(cstr(day + 1) + " " + day_name + "::65")
if not filters.summarized_view:
columns += days
if filters.summarized_view:
- columns += [_("Total Present") + ":Float:120", _("Total Leaves") + ":Float:120", _("Total Absent") + ":Float:120", _("Total Holidays") + ":Float:120", _("Unmarked Days")+ ":Float:120"]
+ columns += [
+ _("Total Present") + ":Float:120",
+ _("Total Leaves") + ":Float:120",
+ _("Total Absent") + ":Float:120",
+ _("Total Holidays") + ":Float:120",
+ _("Unmarked Days") + ":Float:120",
+ ]
return columns, days
+
def get_attendance_list(conditions, filters):
- attendance_list = frappe.db.sql("""select employee, day(attendance_date) as day_of_month,
- status from tabAttendance where docstatus = 1 %s order by employee, attendance_date""" %
- conditions, filters, as_dict=1)
+ attendance_list = frappe.db.sql(
+ """select employee, day(attendance_date) as day_of_month,
+ status from tabAttendance where docstatus = 1 %s order by employee, attendance_date"""
+ % conditions,
+ filters,
+ as_dict=1,
+ )
if not attendance_list:
msgprint(_("No attendance record found"), alert=True, indicator="orange")
@@ -254,6 +287,7 @@ def get_attendance_list(conditions, filters):
return att_map
+
def get_conditions(filters):
if not (filters.get("month") and filters.get("year")):
msgprint(_("Please select month and year"), raise_exception=1)
@@ -262,29 +296,35 @@ def get_conditions(filters):
conditions = " and month(attendance_date) = %(month)s and year(attendance_date) = %(year)s"
- if filters.get("company"): conditions += " and company = %(company)s"
- if filters.get("employee"): conditions += " and employee = %(employee)s"
+ if filters.get("company"):
+ conditions += " and company = %(company)s"
+ if filters.get("employee"):
+ conditions += " and employee = %(employee)s"
return conditions, filters
+
def get_employee_details(group_by, company):
emp_map = {}
query = """select name, employee_name, designation, department, branch, company,
- holiday_list from `tabEmployee` where company = %s """ % frappe.db.escape(company)
+ holiday_list from `tabEmployee` where company = %s """ % frappe.db.escape(
+ company
+ )
if group_by:
group_by = group_by.lower()
query += " order by " + group_by + " ASC"
- employee_details = frappe.db.sql(query , as_dict=1)
+ employee_details = frappe.db.sql(query, as_dict=1)
group_by_parameters = []
if group_by:
- group_by_parameters = list(set(detail.get(group_by, "") for detail in employee_details if detail.get(group_by, "")))
+ group_by_parameters = list(
+ set(detail.get(group_by, "") for detail in employee_details if detail.get(group_by, ""))
+ )
for parameter in group_by_parameters:
- emp_map[parameter] = {}
-
+ emp_map[parameter] = {}
for d in employee_details:
if group_by and len(group_by_parameters):
@@ -299,18 +339,28 @@ def get_employee_details(group_by, company):
else:
return emp_map, group_by_parameters
+
def get_holiday(holiday_list, month):
holiday_map = frappe._dict()
for d in holiday_list:
if d:
- holiday_map.setdefault(d, frappe.db.sql('''select day(holiday_date), weekly_off from `tabHoliday`
- where parent=%s and month(holiday_date)=%s''', (d, month)))
+ holiday_map.setdefault(
+ d,
+ frappe.db.sql(
+ """select day(holiday_date), weekly_off from `tabHoliday`
+ where parent=%s and month(holiday_date)=%s""",
+ (d, month),
+ ),
+ )
return holiday_map
+
@frappe.whitelist()
def get_attendance_years():
- year_list = frappe.db.sql_list("""select distinct YEAR(attendance_date) from tabAttendance ORDER BY YEAR(attendance_date) DESC""")
+ year_list = frappe.db.sql_list(
+ """select distinct YEAR(attendance_date) from tabAttendance ORDER BY YEAR(attendance_date) DESC"""
+ )
if not year_list:
year_list = [getdate().year]
diff --git a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
index 952af8117e..91da08eee5 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
@@ -11,31 +11,33 @@ from erpnext.hr.report.monthly_attendance_sheet.monthly_attendance_sheet import
class TestMonthlyAttendanceSheet(FrappeTestCase):
def setUp(self):
self.employee = make_employee("test_employee@example.com")
- frappe.db.delete('Attendance', {'employee': self.employee})
+ frappe.db.delete("Attendance", {"employee": self.employee})
def test_monthly_attendance_sheet_report(self):
now = now_datetime()
previous_month = now.month - 1
previous_month_first = now.replace(day=1).replace(month=previous_month).date()
- company = frappe.db.get_value('Employee', self.employee, 'company')
+ company = frappe.db.get_value("Employee", self.employee, "company")
# mark different attendance status on first 3 days of previous month
- mark_attendance(self.employee, previous_month_first, 'Absent')
- mark_attendance(self.employee, previous_month_first + relativedelta(days=1), 'Present')
- mark_attendance(self.employee, previous_month_first + relativedelta(days=2), 'On Leave')
+ mark_attendance(self.employee, previous_month_first, "Absent")
+ mark_attendance(self.employee, previous_month_first + relativedelta(days=1), "Present")
+ mark_attendance(self.employee, previous_month_first + relativedelta(days=2), "On Leave")
- filters = frappe._dict({
- 'month': previous_month,
- 'year': now.year,
- 'company': company,
- })
+ filters = frappe._dict(
+ {
+ "month": previous_month,
+ "year": now.year,
+ "company": company,
+ }
+ )
report = execute(filters=filters)
employees = report[1][0]
- datasets = report[3]['data']['datasets']
- absent = datasets[0]['values']
- present = datasets[1]['values']
- leaves = datasets[2]['values']
+ datasets = report[3]["data"]["datasets"]
+ absent = datasets[0]["values"]
+ present = datasets[1]["values"]
+ leaves = datasets[2]["values"]
# ensure correct attendance is reflect on the report
self.assertIn(self.employee, employees)
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
index 6383a9bbac..b6caf400dd 100644
--- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
@@ -8,7 +8,8 @@ from frappe import _
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
filters = frappe._dict(filters)
columns = get_columns()
@@ -25,67 +26,53 @@ def get_columns():
"fieldtype": "Link",
"fieldname": "staffing_plan",
"options": "Staffing Plan",
- "width": 150
+ "width": 150,
},
{
"label": _("Job Opening"),
"fieldtype": "Link",
"fieldname": "job_opening",
"options": "Job Opening",
- "width": 105
+ "width": 105,
},
{
"label": _("Job Applicant"),
"fieldtype": "Link",
"fieldname": "job_applicant",
"options": "Job Applicant",
- "width": 150
- },
- {
- "label": _("Applicant name"),
- "fieldtype": "data",
- "fieldname": "applicant_name",
- "width": 130
+ "width": 150,
},
+ {"label": _("Applicant name"), "fieldtype": "data", "fieldname": "applicant_name", "width": 130},
{
"label": _("Application Status"),
"fieldtype": "Data",
"fieldname": "application_status",
- "width": 150
+ "width": 150,
},
{
"label": _("Job Offer"),
"fieldtype": "Link",
"fieldname": "job_offer",
"options": "job Offer",
- "width": 150
- },
- {
- "label": _("Designation"),
- "fieldtype": "Data",
- "fieldname": "designation",
- "width": 100
- },
- {
- "label": _("Offer Date"),
- "fieldtype": "date",
- "fieldname": "offer_date",
- "width": 100
+ "width": 150,
},
+ {"label": _("Designation"), "fieldtype": "Data", "fieldname": "designation", "width": 100},
+ {"label": _("Offer Date"), "fieldtype": "date", "fieldname": "offer_date", "width": 100},
{
"label": _("Job Offer status"),
"fieldtype": "Data",
"fieldname": "job_offer_status",
- "width": 150
- }
+ "width": 150,
+ },
]
+
def get_data(filters):
data = []
staffing_plan_details = get_staffing_plan(filters)
- staffing_plan_list = list(set([details["name"] for details in staffing_plan_details]))
- sp_jo_map , jo_list = get_job_opening(staffing_plan_list)
- jo_ja_map , ja_list = get_job_applicant(jo_list)
+ staffing_plan_list = list(set([details["name"] for details in staffing_plan_details]))
+ sp_jo_map, jo_list = get_job_opening(staffing_plan_list)
+ jo_ja_map, ja_list = get_job_applicant(jo_list)
ja_joff_map = get_job_offer(ja_list)
for sp in sp_jo_map.keys():
@@ -100,37 +87,40 @@ def get_parent_row(sp_jo_map, sp, jo_ja_map, ja_joff_map):
if sp in sp_jo_map.keys():
for jo in sp_jo_map[sp]:
row = {
- "staffing_plan" : sp,
- "job_opening" : jo["name"],
+ "staffing_plan": sp,
+ "job_opening": jo["name"],
}
data.append(row)
- child_row = get_child_row( jo["name"], jo_ja_map, ja_joff_map)
+ child_row = get_child_row(jo["name"], jo_ja_map, ja_joff_map)
data += child_row
return data
+
def get_child_row(jo, jo_ja_map, ja_joff_map):
data = []
if jo in jo_ja_map.keys():
for ja in jo_ja_map[jo]:
row = {
- "indent":1,
+ "indent": 1,
"job_applicant": ja.name,
"applicant_name": ja.applicant_name,
"application_status": ja.status,
}
if ja.name in ja_joff_map.keys():
- jo_detail =ja_joff_map[ja.name][0]
+ jo_detail = ja_joff_map[ja.name][0]
row["job_offer"] = jo_detail.name
row["job_offer_status"] = jo_detail.status
- row["offer_date"]= jo_detail.offer_date.strftime("%d-%m-%Y")
+ row["offer_date"] = jo_detail.offer_date.strftime("%d-%m-%Y")
row["designation"] = jo_detail.designation
data.append(row)
return data
+
def get_staffing_plan(filters):
- staffing_plan = frappe.db.sql("""
+ staffing_plan = frappe.db.sql(
+ """
select
sp.name, sp.department, spd.designation, spd.vacancies, spd.current_count, spd.parent, sp.to_date
from
@@ -139,13 +129,20 @@ def get_staffing_plan(filters):
spd.parent = sp.name
And
sp.to_date > '{0}'
- """.format(filters.on_date), as_dict = 1)
+ """.format(
+ filters.on_date
+ ),
+ as_dict=1,
+ )
return staffing_plan
+
def get_job_opening(sp_list):
- job_openings = frappe.get_all("Job Opening", filters = [["staffing_plan", "IN", sp_list]], fields =["name", "staffing_plan"])
+ job_openings = frappe.get_all(
+ "Job Opening", filters=[["staffing_plan", "IN", sp_list]], fields=["name", "staffing_plan"]
+ )
sp_jo_map = {}
jo_list = []
@@ -160,12 +157,17 @@ def get_job_opening(sp_list):
return sp_jo_map, jo_list
+
def get_job_applicant(jo_list):
jo_ja_map = {}
- ja_list =[]
+ ja_list = []
- applicants = frappe.get_all("Job Applicant", filters = [["job_title", "IN", jo_list]], fields =["name", "job_title","applicant_name", 'status'])
+ applicants = frappe.get_all(
+ "Job Applicant",
+ filters=[["job_title", "IN", jo_list]],
+ fields=["name", "job_title", "applicant_name", "status"],
+ )
for applicant in applicants:
if applicant.job_title not in jo_ja_map.keys():
@@ -175,12 +177,17 @@ def get_job_applicant(jo_list):
ja_list.append(applicant.name)
- return jo_ja_map , ja_list
+ return jo_ja_map, ja_list
+
def get_job_offer(ja_list):
ja_joff_map = {}
- offers = frappe.get_all("Job Offer", filters = [["job_applicant", "IN", ja_list]], fields =["name", "job_applicant", "status", 'offer_date', 'designation'])
+ offers = frappe.get_all(
+ "Job Offer",
+ filters=[["job_applicant", "IN", ja_list]],
+ fields=["name", "job_applicant", "status", "offer_date", "designation"],
+ )
for offer in offers:
if offer.job_applicant not in ja_joff_map.keys():
diff --git a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
index 8672e68cf4..da6dace72b 100644
--- a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
+++ b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
@@ -17,12 +17,14 @@ from erpnext.hr.report.vehicle_expenses.vehicle_expenses import execute
class TestVehicleExpenses(unittest.TestCase):
@classmethod
def setUpClass(self):
- frappe.db.sql('delete from `tabVehicle Log`')
+ frappe.db.sql("delete from `tabVehicle Log`")
- employee_id = frappe.db.sql('''select name from `tabEmployee` where name="testdriver@example.com"''')
+ employee_id = frappe.db.sql(
+ '''select name from `tabEmployee` where name="testdriver@example.com"'''
+ )
self.employee_id = employee_id[0][0] if employee_id else None
if not self.employee_id:
- self.employee_id = make_employee('testdriver@example.com', company='_Test Company')
+ self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
self.license_plate = get_vehicle(self.employee_id)
@@ -31,36 +33,35 @@ class TestVehicleExpenses(unittest.TestCase):
expense_claim = make_expense_claim(vehicle_log.name)
# Based on Fiscal Year
- filters = {
- 'filter_based_on': 'Fiscal Year',
- 'fiscal_year': get_fiscal_year(getdate())[0]
- }
+ filters = {"filter_based_on": "Fiscal Year", "fiscal_year": get_fiscal_year(getdate())[0]}
report = execute(filters)
- expected_data = [{
- 'vehicle': self.license_plate,
- 'make': 'Maruti',
- 'model': 'PCM',
- 'location': 'Mumbai',
- 'log_name': vehicle_log.name,
- 'odometer': 5010,
- 'date': getdate(),
- 'fuel_qty': 50.0,
- 'fuel_price': 500.0,
- 'fuel_expense': 25000.0,
- 'service_expense': 2000.0,
- 'employee': self.employee_id
- }]
+ expected_data = [
+ {
+ "vehicle": self.license_plate,
+ "make": "Maruti",
+ "model": "PCM",
+ "location": "Mumbai",
+ "log_name": vehicle_log.name,
+ "odometer": 5010,
+ "date": getdate(),
+ "fuel_qty": 50.0,
+ "fuel_price": 500.0,
+ "fuel_expense": 25000.0,
+ "service_expense": 2000.0,
+ "employee": self.employee_id,
+ }
+ ]
self.assertEqual(report[1], expected_data)
# Based on Date Range
fiscal_year = get_fiscal_year(getdate(), as_dict=True)
filters = {
- 'filter_based_on': 'Date Range',
- 'from_date': fiscal_year.year_start_date,
- 'to_date': fiscal_year.year_end_date
+ "filter_based_on": "Date Range",
+ "from_date": fiscal_year.year_start_date,
+ "to_date": fiscal_year.year_end_date,
}
report = execute(filters)
@@ -68,9 +69,9 @@ class TestVehicleExpenses(unittest.TestCase):
# clean up
vehicle_log.cancel()
- frappe.delete_doc('Expense Claim', expense_claim.name)
- frappe.delete_doc('Vehicle Log', vehicle_log.name)
+ frappe.delete_doc("Expense Claim", expense_claim.name)
+ frappe.delete_doc("Vehicle Log", vehicle_log.name)
def tearDown(self):
- frappe.delete_doc('Vehicle', self.license_plate, force=1)
- frappe.delete_doc('Employee', self.employee_id, force=1)
+ frappe.delete_doc("Vehicle", self.license_plate, force=1)
+ frappe.delete_doc("Employee", self.employee_id, force=1)
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
index 17d1e9d46a..fc5510ddad 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
@@ -18,83 +18,44 @@ def execute(filters=None):
return columns, data, None, chart
+
def get_columns():
return [
{
- 'fieldname': 'vehicle',
- 'fieldtype': 'Link',
- 'label': _('Vehicle'),
- 'options': 'Vehicle',
- 'width': 150
+ "fieldname": "vehicle",
+ "fieldtype": "Link",
+ "label": _("Vehicle"),
+ "options": "Vehicle",
+ "width": 150,
+ },
+ {"fieldname": "make", "fieldtype": "Data", "label": _("Make"), "width": 100},
+ {"fieldname": "model", "fieldtype": "Data", "label": _("Model"), "width": 80},
+ {"fieldname": "location", "fieldtype": "Data", "label": _("Location"), "width": 100},
+ {
+ "fieldname": "log_name",
+ "fieldtype": "Link",
+ "label": _("Vehicle Log"),
+ "options": "Vehicle Log",
+ "width": 100,
+ },
+ {"fieldname": "odometer", "fieldtype": "Int", "label": _("Odometer Value"), "width": 120},
+ {"fieldname": "date", "fieldtype": "Date", "label": _("Date"), "width": 100},
+ {"fieldname": "fuel_qty", "fieldtype": "Float", "label": _("Fuel Qty"), "width": 80},
+ {"fieldname": "fuel_price", "fieldtype": "Float", "label": _("Fuel Price"), "width": 100},
+ {"fieldname": "fuel_expense", "fieldtype": "Currency", "label": _("Fuel Expense"), "width": 150},
+ {
+ "fieldname": "service_expense",
+ "fieldtype": "Currency",
+ "label": _("Service Expense"),
+ "width": 150,
},
{
- 'fieldname': 'make',
- 'fieldtype': 'Data',
- 'label': _('Make'),
- 'width': 100
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "label": _("Employee"),
+ "options": "Employee",
+ "width": 150,
},
- {
- 'fieldname': 'model',
- 'fieldtype': 'Data',
- 'label': _('Model'),
- 'width': 80
- },
- {
- 'fieldname': 'location',
- 'fieldtype': 'Data',
- 'label': _('Location'),
- 'width': 100
- },
- {
- 'fieldname': 'log_name',
- 'fieldtype': 'Link',
- 'label': _('Vehicle Log'),
- 'options': 'Vehicle Log',
- 'width': 100
- },
- {
- 'fieldname': 'odometer',
- 'fieldtype': 'Int',
- 'label': _('Odometer Value'),
- 'width': 120
- },
- {
- 'fieldname': 'date',
- 'fieldtype': 'Date',
- 'label': _('Date'),
- 'width': 100
- },
- {
- 'fieldname': 'fuel_qty',
- 'fieldtype': 'Float',
- 'label': _('Fuel Qty'),
- 'width': 80
- },
- {
- 'fieldname': 'fuel_price',
- 'fieldtype': 'Float',
- 'label': _('Fuel Price'),
- 'width': 100
- },
- {
- 'fieldname': 'fuel_expense',
- 'fieldtype': 'Currency',
- 'label': _('Fuel Expense'),
- 'width': 150
- },
- {
- 'fieldname': 'service_expense',
- 'fieldtype': 'Currency',
- 'label': _('Service Expense'),
- 'width': 150
- },
- {
- 'fieldname': 'employee',
- 'fieldtype': 'Link',
- 'label': _('Employee'),
- 'options': 'Employee',
- 'width': 150
- }
]
@@ -102,7 +63,8 @@ def get_vehicle_log_data(filters):
start_date, end_date = get_period_dates(filters)
conditions, values = get_conditions(filters)
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
vhcl.license_plate as vehicle, vhcl.make, vhcl.model,
vhcl.location, log.name as log_name, log.odometer,
@@ -116,58 +78,70 @@ def get_vehicle_log_data(filters):
and log.docstatus = 1
and date between %(start_date)s and %(end_date)s
{0}
- ORDER BY date""".format(conditions), values, as_dict=1)
+ ORDER BY date""".format(
+ conditions
+ ),
+ values,
+ as_dict=1,
+ )
for row in data:
- row['service_expense'] = get_service_expense(row.log_name)
+ row["service_expense"] = get_service_expense(row.log_name)
return data
def get_conditions(filters):
- conditions = ''
+ conditions = ""
start_date, end_date = get_period_dates(filters)
- values = {
- 'start_date': start_date,
- 'end_date': end_date
- }
+ values = {"start_date": start_date, "end_date": end_date}
if filters.employee:
- conditions += ' and log.employee = %(employee)s'
- values['employee'] = filters.employee
+ conditions += " and log.employee = %(employee)s"
+ values["employee"] = filters.employee
if filters.vehicle:
- conditions += ' and vhcl.license_plate = %(vehicle)s'
- values['vehicle'] = filters.vehicle
+ conditions += " and vhcl.license_plate = %(vehicle)s"
+ values["vehicle"] = filters.vehicle
return conditions, values
def get_period_dates(filters):
- if filters.filter_based_on == 'Fiscal Year' and filters.fiscal_year:
- fy = frappe.db.get_value('Fiscal Year', filters.fiscal_year,
- ['year_start_date', 'year_end_date'], as_dict=True)
+ if filters.filter_based_on == "Fiscal Year" and filters.fiscal_year:
+ fy = frappe.db.get_value(
+ "Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
+ )
return fy.year_start_date, fy.year_end_date
else:
return filters.from_date, filters.to_date
def get_service_expense(logname):
- expense_amount = frappe.db.sql("""
+ expense_amount = frappe.db.sql(
+ """
SELECT sum(expense_amount)
FROM
`tabVehicle Log` log, `tabVehicle Service` service
WHERE
service.parent=log.name and log.name=%s
- """, logname)
+ """,
+ logname,
+ )
return flt(expense_amount[0][0]) if expense_amount else 0.0
def get_chart_data(data, filters):
- period_list = get_period_list(filters.fiscal_year, filters.fiscal_year,
- filters.from_date, filters.to_date, filters.filter_based_on, 'Monthly')
+ period_list = get_period_list(
+ filters.fiscal_year,
+ filters.fiscal_year,
+ filters.from_date,
+ filters.to_date,
+ filters.filter_based_on,
+ "Monthly",
+ )
fuel_data, service_data = [], []
@@ -184,29 +158,20 @@ def get_chart_data(data, filters):
service_data.append([period.key, total_service_exp])
labels = [period.label for period in period_list]
- fuel_exp_data= [row[1] for row in fuel_data]
- service_exp_data= [row[1] for row in service_data]
+ fuel_exp_data = [row[1] for row in fuel_data]
+ service_exp_data = [row[1] for row in service_data]
datasets = []
if fuel_exp_data:
- datasets.append({
- 'name': _('Fuel Expenses'),
- 'values': fuel_exp_data
- })
+ datasets.append({"name": _("Fuel Expenses"), "values": fuel_exp_data})
if service_exp_data:
- datasets.append({
- 'name': _('Service Expenses'),
- 'values': service_exp_data
- })
+ datasets.append({"name": _("Service Expenses"), "values": service_exp_data})
chart = {
- 'data': {
- 'labels': labels,
- 'datasets': datasets
- },
- 'type': 'line',
- 'fieldtype': 'Currency'
+ "data": {"labels": labels, "datasets": datasets},
+ "type": "line",
+ "fieldtype": "Currency",
}
return chart
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index c1740471e2..fd69a9b4f1 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -23,20 +23,26 @@ from erpnext.hr.doctype.employee.employee import (
)
-class DuplicateDeclarationError(frappe.ValidationError): pass
+class DuplicateDeclarationError(frappe.ValidationError):
+ pass
+
def set_employee_name(doc):
if doc.employee and not doc.employee_name:
doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
+
def update_employee_work_history(employee, details, date=None, cancel=False):
if not employee.internal_work_history and not cancel:
- employee.append("internal_work_history", {
- "branch": employee.branch,
- "designation": employee.designation,
- "department": employee.department,
- "from_date": employee.date_of_joining
- })
+ employee.append(
+ "internal_work_history",
+ {
+ "branch": employee.branch,
+ "designation": employee.designation,
+ "department": employee.department,
+ "from_date": employee.date_of_joining,
+ },
+ )
internal_work_history = {}
for item in details:
@@ -47,7 +53,7 @@ def update_employee_work_history(employee, details, date=None, cancel=False):
new_data = item.new if not cancel else item.current
if fieldtype == "Date" and new_data:
new_data = getdate(new_data)
- elif fieldtype =="Datetime" and new_data:
+ elif fieldtype == "Datetime" and new_data:
new_data = get_datetime(new_data)
setattr(employee, item.fieldname, new_data)
if item.fieldname in ["department", "designation", "branch"]:
@@ -62,6 +68,7 @@ def update_employee_work_history(employee, details, date=None, cancel=False):
return employee
+
def delete_employee_work_history(details, employee, date):
filters = {}
for d in details:
@@ -85,12 +92,25 @@ def delete_employee_work_history(details, employee, date):
def get_employee_fields_label():
fields = []
for df in frappe.get_meta("Employee").get("fields"):
- if df.fieldname in ["salutation", "user_id", "employee_number", "employment_type",
- "holiday_list", "branch", "department", "designation", "grade",
- "notice_number_of_days", "reports_to", "leave_policy", "company_email"]:
- fields.append({"value": df.fieldname, "label": df.label})
+ if df.fieldname in [
+ "salutation",
+ "user_id",
+ "employee_number",
+ "employment_type",
+ "holiday_list",
+ "branch",
+ "department",
+ "designation",
+ "grade",
+ "notice_number_of_days",
+ "reports_to",
+ "leave_policy",
+ "company_email",
+ ]:
+ fields.append({"value": df.fieldname, "label": df.label})
return fields
+
@frappe.whitelist()
def get_employee_field_property(employee, fieldname):
if employee and fieldname:
@@ -101,17 +121,15 @@ def get_employee_field_property(employee, fieldname):
value = formatdate(value)
elif field.fieldtype == "Datetime":
value = format_datetime(value)
- return {
- "value" : value,
- "datatype" : field.fieldtype,
- "label" : field.label,
- "options" : options
- }
+ return {"value": value, "datatype": field.fieldtype, "label": field.label, "options": options}
else:
return False
+
def validate_dates(doc, from_date, to_date):
- date_of_joining, relieving_date = frappe.db.get_value("Employee", doc.employee, ["date_of_joining", "relieving_date"])
+ date_of_joining, relieving_date = frappe.db.get_value(
+ "Employee", doc.employee, ["date_of_joining", "relieving_date"]
+ )
if getdate(from_date) > getdate(to_date):
frappe.throw(_("To date can not be less than from date"))
elif getdate(from_date) > getdate(nowdate()):
@@ -121,7 +139,8 @@ def validate_dates(doc, from_date, to_date):
elif relieving_date and getdate(to_date) > getdate(relieving_date):
frappe.throw(_("To date can not greater than employee's relieving date"))
-def validate_overlap(doc, from_date, to_date, company = None):
+
+def validate_overlap(doc, from_date, to_date, company=None):
query = """
select name
from `tab{0}`
@@ -131,15 +150,19 @@ def validate_overlap(doc, from_date, to_date, company = None):
if not doc.name:
# hack! if name is null, it could cause problems with !=
- doc.name = "New "+doc.doctype
+ doc.name = "New " + doc.doctype
- overlap_doc = frappe.db.sql(query.format(doc.doctype),{
+ overlap_doc = frappe.db.sql(
+ query.format(doc.doctype),
+ {
"employee": doc.get("employee"),
"from_date": from_date,
"to_date": to_date,
"name": doc.name,
- "company": company
- }, as_dict = 1)
+ "company": company,
+ },
+ as_dict=1,
+ )
if overlap_doc:
if doc.get("employee"):
@@ -148,6 +171,7 @@ def validate_overlap(doc, from_date, to_date, company = None):
exists_for = company
throw_overlap_error(doc, exists_for, overlap_doc[0].name, from_date, to_date)
+
def get_doc_condition(doctype):
if doctype == "Compensatory Leave Request":
return "and employee = %(employee)s and docstatus < 2 \
@@ -159,23 +183,36 @@ def get_doc_condition(doctype):
or to_date between %(from_date)s and %(to_date)s \
or (from_date < %(from_date)s and to_date > %(to_date)s))"
+
def throw_overlap_error(doc, exists_for, overlap_doc, from_date, to_date):
- msg = _("A {0} exists between {1} and {2} (").format(doc.doctype,
- formatdate(from_date), formatdate(to_date)) \
- + """ {1}""".format(doc.doctype, overlap_doc) \
+ msg = (
+ _("A {0} exists between {1} and {2} (").format(
+ doc.doctype, formatdate(from_date), formatdate(to_date)
+ )
+ + """ {1}""".format(doc.doctype, overlap_doc)
+ _(") for {0}").format(exists_for)
+ )
frappe.throw(msg)
+
def validate_duplicate_exemption_for_payroll_period(doctype, docname, payroll_period, employee):
- existing_record = frappe.db.exists(doctype, {
- "payroll_period": payroll_period,
- "employee": employee,
- 'docstatus': ['<', 2],
- 'name': ['!=', docname]
- })
+ existing_record = frappe.db.exists(
+ doctype,
+ {
+ "payroll_period": payroll_period,
+ "employee": employee,
+ "docstatus": ["<", 2],
+ "name": ["!=", docname],
+ },
+ )
if existing_record:
- frappe.throw(_("{0} already exists for employee {1} and period {2}")
- .format(doctype, employee, payroll_period), DuplicateDeclarationError)
+ frappe.throw(
+ _("{0} already exists for employee {1} and period {2}").format(
+ doctype, employee, payroll_period
+ ),
+ DuplicateDeclarationError,
+ )
+
def validate_tax_declaration(declarations):
subcategories = []
@@ -184,61 +221,79 @@ def validate_tax_declaration(declarations):
frappe.throw(_("More than one selection for {0} not allowed").format(d.exemption_sub_category))
subcategories.append(d.exemption_sub_category)
+
def get_total_exemption_amount(declarations):
exemptions = frappe._dict()
for d in declarations:
exemptions.setdefault(d.exemption_category, frappe._dict())
category_max_amount = exemptions.get(d.exemption_category).max_amount
if not category_max_amount:
- category_max_amount = frappe.db.get_value("Employee Tax Exemption Category", d.exemption_category, "max_amount")
+ category_max_amount = frappe.db.get_value(
+ "Employee Tax Exemption Category", d.exemption_category, "max_amount"
+ )
exemptions.get(d.exemption_category).max_amount = category_max_amount
- sub_category_exemption_amount = d.max_amount \
- if (d.max_amount and flt(d.amount) > flt(d.max_amount)) else d.amount
+ sub_category_exemption_amount = (
+ d.max_amount if (d.max_amount and flt(d.amount) > flt(d.max_amount)) else d.amount
+ )
exemptions.get(d.exemption_category).setdefault("total_exemption_amount", 0.0)
exemptions.get(d.exemption_category).total_exemption_amount += flt(sub_category_exemption_amount)
- if category_max_amount and exemptions.get(d.exemption_category).total_exemption_amount > category_max_amount:
+ if (
+ category_max_amount
+ and exemptions.get(d.exemption_category).total_exemption_amount > category_max_amount
+ ):
exemptions.get(d.exemption_category).total_exemption_amount = category_max_amount
total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()])
return total_exemption_amount
+
@frappe.whitelist()
def get_leave_period(from_date, to_date, company):
- leave_period = frappe.db.sql("""
+ leave_period = frappe.db.sql(
+ """
select name, from_date, to_date
from `tabLeave Period`
where company=%(company)s and is_active=1
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
- """, {
- "from_date": from_date,
- "to_date": to_date,
- "company": company
- }, as_dict=1)
+ """,
+ {"from_date": from_date, "to_date": to_date, "company": company},
+ as_dict=1,
+ )
if leave_period:
return leave_period
+
def generate_leave_encashment():
- ''' Generates a draft leave encashment on allocation expiry '''
+ """Generates a draft leave encashment on allocation expiry"""
from erpnext.hr.doctype.leave_encashment.leave_encashment import create_leave_encashment
- if frappe.db.get_single_value('HR Settings', 'auto_leave_encashment'):
- leave_type = frappe.get_all('Leave Type', filters={'allow_encashment': 1}, fields=['name'])
- leave_type=[l['name'] for l in leave_type]
+ if frappe.db.get_single_value("HR Settings", "auto_leave_encashment"):
+ leave_type = frappe.get_all("Leave Type", filters={"allow_encashment": 1}, fields=["name"])
+ leave_type = [l["name"] for l in leave_type]
- leave_allocation = frappe.get_all("Leave Allocation", filters={
- 'to_date': add_days(today(), -1),
- 'leave_type': ('in', leave_type)
- }, fields=['employee', 'leave_period', 'leave_type', 'to_date', 'total_leaves_allocated', 'new_leaves_allocated'])
+ leave_allocation = frappe.get_all(
+ "Leave Allocation",
+ filters={"to_date": add_days(today(), -1), "leave_type": ("in", leave_type)},
+ fields=[
+ "employee",
+ "leave_period",
+ "leave_type",
+ "to_date",
+ "total_leaves_allocated",
+ "new_leaves_allocated",
+ ],
+ )
create_leave_encashment(leave_allocation=leave_allocation)
+
def allocate_earned_leaves(ignore_duplicates=False):
- '''Allocate earned leaves to Employees'''
+ """Allocate earned leaves to Employees"""
e_leave_types = get_earned_leaves()
today = getdate()
@@ -251,37 +306,52 @@ def allocate_earned_leaves(ignore_duplicates=False):
if not allocation.leave_policy_assignment and not allocation.leave_policy:
continue
- leave_policy = allocation.leave_policy if allocation.leave_policy else frappe.db.get_value(
- "Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"])
+ leave_policy = (
+ allocation.leave_policy
+ if allocation.leave_policy
+ else frappe.db.get_value(
+ "Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"]
+ )
+ )
- annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
- 'parent': leave_policy,
- 'leave_type': e_leave_type.name
- }, fieldname=['annual_allocation'])
+ annual_allocation = frappe.db.get_value(
+ "Leave Policy Detail",
+ filters={"parent": leave_policy, "leave_type": e_leave_type.name},
+ fieldname=["annual_allocation"],
+ )
- from_date=allocation.from_date
+ from_date = allocation.from_date
if e_leave_type.based_on_date_of_joining:
- from_date = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
+ from_date = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
- if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining):
- update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates)
+ if check_effective_date(
+ from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining
+ ):
+ update_previous_leave_allocation(
+ allocation, annual_allocation, e_leave_type, ignore_duplicates
+ )
-def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates=False):
- earned_leaves = get_monthly_earned_leave(annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding)
- allocation = frappe.get_doc('Leave Allocation', allocation.name)
- new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+def update_previous_leave_allocation(
+ allocation, annual_allocation, e_leave_type, ignore_duplicates=False
+):
+ earned_leaves = get_monthly_earned_leave(
+ annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding
+ )
- if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
- new_allocation = e_leave_type.max_leaves_allowed
+ allocation = frappe.get_doc("Leave Allocation", allocation.name)
+ new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
- if new_allocation != allocation.total_leaves_allocated:
- today_date = today()
+ if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
+ new_allocation = e_leave_type.max_leaves_allowed
- if ignore_duplicates or not is_earned_leave_already_allocated(allocation, annual_allocation):
- allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
- create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+ if new_allocation != allocation.total_leaves_allocated:
+ today_date = today()
+
+ if ignore_duplicates or not is_earned_leave_already_allocated(allocation, annual_allocation):
+ allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
+ create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
def get_monthly_earned_leave(annual_leaves, frequency, rounding):
@@ -309,8 +379,9 @@ def is_earned_leave_already_allocated(allocation, annual_allocation):
date_of_joining = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
assignment = frappe.get_doc("Leave Policy Assignment", allocation.leave_policy_assignment)
- leaves_for_passed_months = assignment.get_leaves_for_passed_months(allocation.leave_type,
- annual_allocation, leave_type_details, date_of_joining)
+ leaves_for_passed_months = assignment.get_leaves_for_passed_months(
+ allocation.leave_type, annual_allocation, leave_type_details, date_of_joining
+ )
# exclude carry-forwarded leaves while checking for leave allocation for passed months
num_allocations = allocation.total_leaves_allocated
@@ -323,26 +394,39 @@ def is_earned_leave_already_allocated(allocation, annual_allocation):
def get_leave_allocations(date, leave_type):
- return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
+ return frappe.db.sql(
+ """select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
from `tabLeave Allocation`
where
%s between from_date and to_date and docstatus=1
and leave_type=%s""",
- (date, leave_type), as_dict=1)
+ (date, leave_type),
+ as_dict=1,
+ )
def get_earned_leaves():
- return frappe.get_all("Leave Type",
- fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding", "based_on_date_of_joining"],
- filters={'is_earned_leave' : 1})
+ return frappe.get_all(
+ "Leave Type",
+ fields=[
+ "name",
+ "max_leaves_allowed",
+ "earned_leave_frequency",
+ "rounding",
+ "based_on_date_of_joining",
+ ],
+ filters={"is_earned_leave": 1},
+ )
+
def create_additional_leave_ledger_entry(allocation, leaves, date):
- ''' Create leave ledger entry for leave types '''
+ """Create leave ledger entry for leave types"""
allocation.new_leaves_allocated = leaves
allocation.from_date = date
allocation.unused_leaves = 0
allocation.create_leave_ledger_entry()
+
def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining):
import calendar
@@ -351,10 +435,12 @@ def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining
from_date = get_datetime(from_date)
to_date = get_datetime(to_date)
rd = relativedelta.relativedelta(to_date, from_date)
- #last day of month
- last_day = calendar.monthrange(to_date.year, to_date.month)[1]
+ # last day of month
+ last_day = calendar.monthrange(to_date.year, to_date.month)[1]
- if (from_date.day == to_date.day and based_on_date_of_joining) or (not based_on_date_of_joining and to_date.day == last_day):
+ if (from_date.day == to_date.day and based_on_date_of_joining) or (
+ not based_on_date_of_joining and to_date.day == last_day
+ ):
if frequency == "Monthly":
return True
elif frequency == "Quarterly" and rd.months % 3:
@@ -371,16 +457,21 @@ def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining
def get_salary_assignment(employee, date):
- assignment = frappe.db.sql("""
+ assignment = frappe.db.sql(
+ """
select * from `tabSalary Structure Assignment`
where employee=%(employee)s
and docstatus = 1
- and %(on_date)s >= from_date order by from_date desc limit 1""", {
- 'employee': employee,
- 'on_date': date,
- }, as_dict=1)
+ and %(on_date)s >= from_date order by from_date desc limit 1""",
+ {
+ "employee": employee,
+ "on_date": date,
+ },
+ as_dict=1,
+ )
return assignment[0] if assignment else None
+
def get_sal_slip_total_benefit_given(employee, payroll_period, component=False):
total_given_benefit_amount = 0
query = """
@@ -398,17 +489,22 @@ def get_sal_slip_total_benefit_given(employee, payroll_period, component=False):
if component:
query += "and sd.salary_component = %(component)s"
- sum_of_given_benefit = frappe.db.sql(query, {
- 'employee': employee,
- 'start_date': payroll_period.start_date,
- 'end_date': payroll_period.end_date,
- 'component': component
- }, as_dict=True)
+ sum_of_given_benefit = frappe.db.sql(
+ query,
+ {
+ "employee": employee,
+ "start_date": payroll_period.start_date,
+ "end_date": payroll_period.end_date,
+ "component": component,
+ },
+ as_dict=True,
+ )
if sum_of_given_benefit and flt(sum_of_given_benefit[0].total_amount) > 0:
total_given_benefit_amount = sum_of_given_benefit[0].total_amount
return total_given_benefit_amount
+
def get_holiday_dates_for_employee(employee, start_date, end_date):
"""return a list of holiday dates for the given employee between start_date and end_date"""
# return only date
@@ -417,50 +513,48 @@ def get_holiday_dates_for_employee(employee, start_date, end_date):
return [cstr(h.holiday_date) for h in holidays]
-def get_holidays_for_employee(employee, start_date, end_date, raise_exception=True, only_non_weekly=False):
+def get_holidays_for_employee(
+ employee, start_date, end_date, raise_exception=True, only_non_weekly=False
+):
"""Get Holidays for a given employee
- `employee` (str)
- `start_date` (str or datetime)
- `end_date` (str or datetime)
- `raise_exception` (bool)
- `only_non_weekly` (bool)
+ `employee` (str)
+ `start_date` (str or datetime)
+ `end_date` (str or datetime)
+ `raise_exception` (bool)
+ `only_non_weekly` (bool)
- return: list of dicts with `holiday_date` and `description`
+ return: list of dicts with `holiday_date` and `description`
"""
holiday_list = get_holiday_list_for_employee(employee, raise_exception=raise_exception)
if not holiday_list:
return []
- filters = {
- 'parent': holiday_list,
- 'holiday_date': ('between', [start_date, end_date])
- }
+ filters = {"parent": holiday_list, "holiday_date": ("between", [start_date, end_date])}
if only_non_weekly:
- filters['weekly_off'] = False
+ filters["weekly_off"] = False
- holidays = frappe.get_all(
- 'Holiday',
- fields=['description', 'holiday_date'],
- filters=filters
- )
+ holidays = frappe.get_all("Holiday", fields=["description", "holiday_date"], filters=filters)
return holidays
+
@erpnext.allow_regional
def calculate_annual_eligible_hra_exemption(doc):
# Don't delete this method, used for localization
# Indian HRA Exemption Calculation
return {}
+
@erpnext.allow_regional
def calculate_hra_exemption_for_period(doc):
# Don't delete this method, used for localization
# Indian HRA Exemption Calculation
return {}
+
def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, component=False):
total_claimed_amount = 0
query = """
@@ -475,24 +569,29 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co
if component:
query += "and earning_component = %(component)s"
- sum_of_claimed_amount = frappe.db.sql(query, {
- 'employee': employee,
- 'start_date': payroll_period.start_date,
- 'end_date': payroll_period.end_date,
- 'component': component
- }, as_dict=True)
+ sum_of_claimed_amount = frappe.db.sql(
+ query,
+ {
+ "employee": employee,
+ "start_date": payroll_period.start_date,
+ "end_date": payroll_period.end_date,
+ "component": component,
+ },
+ as_dict=True,
+ )
if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
total_claimed_amount = sum_of_claimed_amount[0].total_amount
return total_claimed_amount
+
def share_doc_with_approver(doc, user):
# if approver does not have permissions, share
if not frappe.has_permission(doc=doc, ptype="submit", user=user):
- frappe.share.add(doc.doctype, doc.name, user, submit=1,
- flags={"ignore_share_permission": True})
+ frappe.share.add(doc.doctype, doc.name, user, submit=1, flags={"ignore_share_permission": True})
- frappe.msgprint(_("Shared with the user {0} with {1} access").format(
- user, frappe.bold("submit"), alert=True))
+ frappe.msgprint(
+ _("Shared with the user {0} with {1} access").format(user, frappe.bold("submit"), alert=True)
+ )
# remove shared doc if approver changes
doc_before_save = doc.get_doc_before_save()
@@ -500,14 +599,19 @@ def share_doc_with_approver(doc, user):
approvers = {
"Leave Application": "leave_approver",
"Expense Claim": "expense_approver",
- "Shift Request": "approver"
+ "Shift Request": "approver",
}
approver = approvers.get(doc.doctype)
if doc_before_save.get(approver) != doc.get(approver):
frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
+
def validate_active_employee(employee):
if frappe.db.get_value("Employee", employee, "status") == "Inactive":
- frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format(
- get_link_to_form("Employee", employee)), InactiveEmployeeStatusError)
+ frappe.throw(
+ _("Transactions cannot be created for an Inactive Employee {0}.").format(
+ get_link_to_form("Employee", employee)
+ ),
+ InactiveEmployeeStatusError,
+ )
diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
index 6144d9d39a..aab3d8ccb5 100644
--- a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
+++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
@@ -12,10 +12,19 @@ from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applic
@frappe.whitelist()
@cache_source
-def get_data(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
- to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+def get_data(
+ chart_name=None,
+ chart=None,
+ no_cache=None,
+ filters=None,
+ from_date=None,
+ to_date=None,
+ timespan=None,
+ time_interval=None,
+ heatmap_year=None,
+):
if chart_name:
- chart = frappe.get_doc('Dashboard Chart', chart_name)
+ chart = frappe.get_doc("Dashboard Chart", chart_name)
else:
chart = frappe._dict(frappe.parse_json(chart))
@@ -29,28 +38,44 @@ def get_data(chart_name = None, chart = None, no_cache = None, filters = None, f
labels = []
values = []
- if filters.get('company'):
+ if filters.get("company"):
conditions = "AND company = %(company)s"
loan_security_details = get_loan_security_details()
- unpledges = frappe._dict(frappe.db.sql("""
+ unpledges = frappe._dict(
+ frappe.db.sql(
+ """
SELECT u.loan_security, sum(u.qty) as qty
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
WHERE u.parent = up.name
AND up.status = 'Approved'
{conditions}
GROUP BY u.loan_security
- """.format(conditions=conditions), filters, as_list=1))
+ """.format(
+ conditions=conditions
+ ),
+ filters,
+ as_list=1,
+ )
+ )
- pledges = frappe._dict(frappe.db.sql("""
+ pledges = frappe._dict(
+ frappe.db.sql(
+ """
SELECT p.loan_security, sum(p.qty) as qty
FROM `tabLoan Security Pledge` lp, `tabPledge`p
WHERE p.parent = lp.name
AND lp.status = 'Pledged'
{conditions}
GROUP BY p.loan_security
- """.format(conditions=conditions), filters, as_list=1))
+ """.format(
+ conditions=conditions
+ ),
+ filters,
+ as_list=1,
+ )
+ )
for security, qty in pledges.items():
current_pledges.setdefault(security, qty)
@@ -60,19 +85,15 @@ def get_data(chart_name = None, chart = None, no_cache = None, filters = None, f
count = 0
for security, qty in sorted_pledges.items():
- values.append(qty * loan_security_details.get(security, {}).get('latest_price', 0))
+ values.append(qty * loan_security_details.get(security, {}).get("latest_price", 0))
labels.append(security)
- count +=1
+ count += 1
## Just need top 10 securities
if count == 10:
break
return {
- 'labels': labels,
- 'datasets': [{
- 'name': 'Top 10 Securities',
- 'chartType': 'bar',
- 'values': values
- }]
+ "labels": labels,
+ "datasets": [{"name": "Top 10 Securities", "chartType": "bar", "values": values}],
}
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 0fe9947472..a0ef1b971c 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -20,7 +20,7 @@ from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpled
class Loan(AccountsController):
def validate(self):
- if self.applicant_type == 'Employee' and self.repay_from_salary:
+ if self.applicant_type == "Employee" and self.repay_from_salary:
validate_employee_currency_with_company_currency(self.applicant, self.company)
self.set_loan_amount()
self.validate_loan_amount()
@@ -31,34 +31,47 @@ class Loan(AccountsController):
self.validate_repay_from_salary()
if self.is_term_loan:
- validate_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount,
- self.repayment_periods, self.is_term_loan)
+ validate_repayment_method(
+ self.repayment_method,
+ self.loan_amount,
+ self.monthly_repayment_amount,
+ self.repayment_periods,
+ self.is_term_loan,
+ )
self.make_repayment_schedule()
self.set_repayment_period()
self.calculate_totals()
def validate_accounts(self):
- for fieldname in ['payment_account', 'loan_account', 'interest_income_account', 'penalty_income_account']:
- company = frappe.get_value("Account", self.get(fieldname), 'company')
+ for fieldname in [
+ "payment_account",
+ "loan_account",
+ "interest_income_account",
+ "penalty_income_account",
+ ]:
+ company = frappe.get_value("Account", self.get(fieldname), "company")
if company != self.company:
- frappe.throw(_("Account {0} does not belongs to company {1}").format(frappe.bold(self.get(fieldname)),
- frappe.bold(self.company)))
+ frappe.throw(
+ _("Account {0} does not belongs to company {1}").format(
+ frappe.bold(self.get(fieldname)), frappe.bold(self.company)
+ )
+ )
def validate_cost_center(self):
if not self.cost_center and self.rate_of_interest != 0:
- self.cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
+ self.cost_center = frappe.db.get_value("Company", self.company, "cost_center")
if not self.cost_center:
- frappe.throw(_('Cost center is mandatory for loans having rate of interest greater than 0'))
+ frappe.throw(_("Cost center is mandatory for loans having rate of interest greater than 0"))
def on_submit(self):
self.link_loan_security_pledge()
def on_cancel(self):
self.unlink_loan_security_pledge()
- self.ignore_linked_doctypes = ['GL Entry']
+ self.ignore_linked_doctypes = ["GL Entry"]
def set_missing_fields(self):
if not self.company:
@@ -71,15 +84,25 @@ class Loan(AccountsController):
self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest")
if self.repayment_method == "Repay Over Number of Periods":
- self.monthly_repayment_amount = get_monthly_repayment_amount(self.loan_amount, self.rate_of_interest, self.repayment_periods)
+ self.monthly_repayment_amount = get_monthly_repayment_amount(
+ self.loan_amount, self.rate_of_interest, self.repayment_periods
+ )
def check_sanctioned_amount_limit(self):
- sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
+ sanctioned_amount_limit = get_sanctioned_amount_limit(
+ self.applicant_type, self.applicant, self.company
+ )
if sanctioned_amount_limit:
total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
- if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
- frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
+ if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(
+ sanctioned_amount_limit
+ ):
+ frappe.throw(
+ _("Sanctioned Amount limit crossed for {0} {1}").format(
+ self.applicant_type, frappe.bold(self.applicant)
+ )
+ )
def validate_repay_from_salary(self):
if not self.is_term_loan and self.repay_from_salary:
@@ -92,8 +115,8 @@ class Loan(AccountsController):
self.repayment_schedule = []
payment_date = self.repayment_start_date
balance_amount = self.loan_amount
- while(balance_amount > 0):
- interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100))
+ while balance_amount > 0:
+ interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12 * 100))
principal_amount = self.monthly_repayment_amount - interest_amount
balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
if balance_amount < 0:
@@ -101,13 +124,16 @@ class Loan(AccountsController):
balance_amount = 0.0
total_payment = principal_amount + interest_amount
- self.append("repayment_schedule", {
- "payment_date": payment_date,
- "principal_amount": principal_amount,
- "interest_amount": interest_amount,
- "total_payment": total_payment,
- "balance_loan_amount": balance_amount
- })
+ self.append(
+ "repayment_schedule",
+ {
+ "payment_date": payment_date,
+ "principal_amount": principal_amount,
+ "interest_amount": interest_amount,
+ "total_payment": total_payment,
+ "balance_loan_amount": balance_amount,
+ },
+ )
next_payment_date = add_single_month(payment_date)
payment_date = next_payment_date
@@ -125,14 +151,13 @@ class Loan(AccountsController):
if self.is_term_loan:
for data in self.repayment_schedule:
self.total_payment += data.total_payment
- self.total_interest_payable +=data.interest_amount
+ self.total_interest_payable += data.interest_amount
else:
self.total_payment = self.loan_amount
def set_loan_amount(self):
if self.loan_application and not self.loan_amount:
- self.loan_amount = frappe.db.get_value('Loan Application', self.loan_application, 'loan_amount')
-
+ self.loan_amount = frappe.db.get_value("Loan Application", self.loan_application, "loan_amount")
def validate_loan_amount(self):
if self.maximum_loan_amount and self.loan_amount > self.maximum_loan_amount:
@@ -144,30 +169,36 @@ class Loan(AccountsController):
def link_loan_security_pledge(self):
if self.is_secured_loan and self.loan_application:
- maximum_loan_value = frappe.db.get_value('Loan Security Pledge',
- {
- 'loan_application': self.loan_application,
- 'status': 'Requested'
- },
- 'sum(maximum_loan_value)'
+ maximum_loan_value = frappe.db.get_value(
+ "Loan Security Pledge",
+ {"loan_application": self.loan_application, "status": "Requested"},
+ "sum(maximum_loan_value)",
)
if maximum_loan_value:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabLoan Security Pledge`
SET loan = %s, pledge_time = %s, status = 'Pledged'
WHERE status = 'Requested' and loan_application = %s
- """, (self.name, now_datetime(), self.loan_application))
+ """,
+ (self.name, now_datetime(), self.loan_application),
+ )
- self.db_set('maximum_loan_amount', maximum_loan_value)
+ self.db_set("maximum_loan_amount", maximum_loan_value)
def unlink_loan_security_pledge(self):
- pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name})
+ pledges = frappe.get_all("Loan Security Pledge", fields=["name"], filters={"loan": self.name})
pledge_list = [d.name for d in pledges]
if pledge_list:
- frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET
+ frappe.db.sql(
+ """UPDATE `tabLoan Security Pledge` SET
loan = '', status = 'Unpledged'
- where name in (%s) """ % (', '.join(['%s']*len(pledge_list))), tuple(pledge_list)) #nosec
+ where name in (%s) """
+ % (", ".join(["%s"] * len(pledge_list))),
+ tuple(pledge_list),
+ ) # nosec
+
def update_total_amount_paid(doc):
total_amount_paid = 0
@@ -176,24 +207,51 @@ def update_total_amount_paid(doc):
total_amount_paid += data.total_payment
frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
+
def get_total_loan_amount(applicant_type, applicant, company):
pending_amount = 0
- loan_details = frappe.db.get_all("Loan",
- filters={"applicant_type": applicant_type, "company": company, "applicant": applicant, "docstatus": 1,
- "status": ("!=", "Closed")},
- fields=["status", "total_payment", "disbursed_amount", "total_interest_payable", "total_principal_paid",
- "written_off_amount"])
+ loan_details = frappe.db.get_all(
+ "Loan",
+ filters={
+ "applicant_type": applicant_type,
+ "company": company,
+ "applicant": applicant,
+ "docstatus": 1,
+ "status": ("!=", "Closed"),
+ },
+ fields=[
+ "status",
+ "total_payment",
+ "disbursed_amount",
+ "total_interest_payable",
+ "total_principal_paid",
+ "written_off_amount",
+ ],
+ )
- interest_amount = flt(frappe.db.get_value("Loan Interest Accrual", {"applicant_type": applicant_type,
- "company": company, "applicant": applicant, "docstatus": 1}, "sum(interest_amount - paid_interest_amount)"))
+ interest_amount = flt(
+ frappe.db.get_value(
+ "Loan Interest Accrual",
+ {"applicant_type": applicant_type, "company": company, "applicant": applicant, "docstatus": 1},
+ "sum(interest_amount - paid_interest_amount)",
+ )
+ )
for loan in loan_details:
if loan.status in ("Disbursed", "Loan Closure Requested"):
- pending_amount += flt(loan.total_payment) - flt(loan.total_interest_payable) \
- - flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+ pending_amount += (
+ flt(loan.total_payment)
+ - flt(loan.total_interest_payable)
+ - flt(loan.total_principal_paid)
+ - flt(loan.written_off_amount)
+ )
elif loan.status == "Partially Disbursed":
- pending_amount += flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
- - flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+ pending_amount += (
+ flt(loan.disbursed_amount)
+ - flt(loan.total_interest_payable)
+ - flt(loan.total_principal_paid)
+ - flt(loan.written_off_amount)
+ )
elif loan.status == "Sanctioned":
pending_amount += flt(loan.total_payment)
@@ -201,12 +259,18 @@ def get_total_loan_amount(applicant_type, applicant, company):
return pending_amount
-def get_sanctioned_amount_limit(applicant_type, applicant, company):
- return frappe.db.get_value('Sanctioned Loan Amount',
- {'applicant_type': applicant_type, 'company': company, 'applicant': applicant},
- 'sanctioned_amount_limit')
-def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods, is_term_loan):
+def get_sanctioned_amount_limit(applicant_type, applicant, company):
+ return frappe.db.get_value(
+ "Sanctioned Loan Amount",
+ {"applicant_type": applicant_type, "company": company, "applicant": applicant},
+ "sanctioned_amount_limit",
+ )
+
+
+def validate_repayment_method(
+ repayment_method, loan_amount, monthly_repayment_amount, repayment_periods, is_term_loan
+):
if is_term_loan and not repayment_method:
frappe.throw(_("Repayment Method is mandatory for term loans"))
@@ -220,27 +284,34 @@ def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_a
if monthly_repayment_amount > loan_amount:
frappe.throw(_("Monthly Repayment Amount cannot be greater than Loan Amount"))
+
def get_monthly_repayment_amount(loan_amount, rate_of_interest, repayment_periods):
if rate_of_interest:
- monthly_interest_rate = flt(rate_of_interest) / (12 *100)
- monthly_repayment_amount = math.ceil((loan_amount * monthly_interest_rate *
- (1 + monthly_interest_rate)**repayment_periods) \
- / ((1 + monthly_interest_rate)**repayment_periods - 1))
+ monthly_interest_rate = flt(rate_of_interest) / (12 * 100)
+ monthly_repayment_amount = math.ceil(
+ (loan_amount * monthly_interest_rate * (1 + monthly_interest_rate) ** repayment_periods)
+ / ((1 + monthly_interest_rate) ** repayment_periods - 1)
+ )
else:
monthly_repayment_amount = math.ceil(flt(loan_amount) / repayment_periods)
return monthly_repayment_amount
+
@frappe.whitelist()
def request_loan_closure(loan, posting_date=None):
if not posting_date:
posting_date = getdate()
amounts = calculate_amounts(loan, posting_date)
- pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \
- amounts['interest_amount'] + amounts['penalty_amount']
+ pending_amount = (
+ amounts["pending_principal_amount"]
+ + amounts["unaccrued_interest"]
+ + amounts["interest_amount"]
+ + amounts["penalty_amount"]
+ )
- loan_type = frappe.get_value('Loan', loan, 'loan_type')
- write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
+ loan_type = frappe.get_value("Loan", loan, "loan_type")
+ write_off_limit = frappe.get_value("Loan Type", loan_type, "write_off_amount")
if pending_amount and abs(pending_amount) < write_off_limit:
# Auto create loan write off and update status as loan closure requested
@@ -249,7 +320,8 @@ def request_loan_closure(loan, posting_date=None):
elif pending_amount > 0:
frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount))
- frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
+ frappe.db.set_value("Loan", loan, "status", "Loan Closure Requested")
+
@frappe.whitelist()
def get_loan_application(loan_application):
@@ -257,10 +329,12 @@ def get_loan_application(loan_application):
if loan:
return loan.as_dict()
+
def close_loan(loan, total_amount_paid):
frappe.db.set_value("Loan", loan, "total_amount_paid", total_amount_paid)
frappe.db.set_value("Loan", loan, "status", "Closed")
+
@frappe.whitelist()
def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amount=0, as_dict=0):
disbursement_entry = frappe.new_doc("Loan Disbursement")
@@ -277,6 +351,7 @@ def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amo
else:
return disbursement_entry
+
@frappe.whitelist()
def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as_dict=0):
repayment_entry = frappe.new_doc("Loan Repayment")
@@ -292,27 +367,28 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as
else:
return repayment_entry
+
@frappe.whitelist()
def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict=0):
if not company:
- company = frappe.get_value('Loan', loan, 'company')
+ company = frappe.get_value("Loan", loan, "company")
if not posting_date:
posting_date = getdate()
amounts = calculate_amounts(loan, posting_date)
- pending_amount = amounts['pending_principal_amount']
+ pending_amount = amounts["pending_principal_amount"]
if amount and (amount > pending_amount):
- frappe.throw(_('Write Off amount cannot be greater than pending loan amount'))
+ frappe.throw(_("Write Off amount cannot be greater than pending loan amount"))
if not amount:
amount = pending_amount
# get default write off account from company master
- write_off_account = frappe.get_value('Company', company, 'write_off_account')
+ write_off_account = frappe.get_value("Company", company, "write_off_account")
- write_off = frappe.new_doc('Loan Write Off')
+ write_off = frappe.new_doc("Loan Write Off")
write_off.loan = loan
write_off.posting_date = posting_date
write_off.write_off_account = write_off_account
@@ -324,26 +400,35 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict
else:
return write_off
+
@frappe.whitelist()
-def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0):
+def unpledge_security(
+ loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0
+):
# if no security_map is passed it will be considered as full unpledge
if security_map and isinstance(security_map, str):
security_map = json.loads(security_map)
if loan:
pledge_qty_map = security_map or get_pledged_security_qty(loan)
- loan_doc = frappe.get_doc('Loan', loan)
- unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
- loan_doc.applicant_type, loan_doc.applicant)
+ loan_doc = frappe.get_doc("Loan", loan)
+ unpledge_request = create_loan_security_unpledge(
+ pledge_qty_map, loan_doc.name, loan_doc.company, loan_doc.applicant_type, loan_doc.applicant
+ )
# will unpledge qty based on loan security pledge
elif loan_security_pledge:
security_map = {}
- pledge_doc = frappe.get_doc('Loan Security Pledge', loan_security_pledge)
+ pledge_doc = frappe.get_doc("Loan Security Pledge", loan_security_pledge)
for security in pledge_doc.securities:
security_map.setdefault(security.loan_security, security.qty)
- unpledge_request = create_loan_security_unpledge(security_map, pledge_doc.loan,
- pledge_doc.company, pledge_doc.applicant_type, pledge_doc.applicant)
+ unpledge_request = create_loan_security_unpledge(
+ security_map,
+ pledge_doc.loan,
+ pledge_doc.company,
+ pledge_doc.applicant_type,
+ pledge_doc.applicant,
+ )
if save:
unpledge_request.save()
@@ -353,16 +438,17 @@ def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, a
if approve:
if unpledge_request.docstatus == 1:
- unpledge_request.status = 'Approved'
+ unpledge_request.status = "Approved"
unpledge_request.save()
else:
- frappe.throw(_('Only submittted unpledge requests can be approved'))
+ frappe.throw(_("Only submittted unpledge requests can be approved"))
if as_dict:
return unpledge_request
else:
return unpledge_request
+
def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, applicant):
unpledge_request = frappe.new_doc("Loan Security Unpledge")
unpledge_request.applicant_type = applicant_type
@@ -372,17 +458,16 @@ def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, a
for security, qty in unpledge_map.items():
if qty:
- unpledge_request.append('securities', {
- "loan_security": security,
- "qty": qty
- })
+ unpledge_request.append("securities", {"loan_security": security, "qty": qty})
return unpledge_request
+
def validate_employee_currency_with_company_currency(applicant, company):
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import (
get_employee_currency,
)
+
if not applicant:
frappe.throw(_("Please select Applicant"))
if not company:
@@ -390,18 +475,20 @@ def validate_employee_currency_with_company_currency(applicant, company):
employee_currency = get_employee_currency(applicant)
company_currency = erpnext.get_company_currency(company)
if employee_currency != company_currency:
- frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
- .format(applicant, employee_currency))
+ frappe.throw(
+ _(
+ "Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}"
+ ).format(applicant, employee_currency)
+ )
+
@frappe.whitelist()
def get_shortfall_applicants():
- loans = frappe.get_all('Loan Security Shortfall', {'status': 'Pending'}, pluck='loan')
- applicants = set(frappe.get_all('Loan', {'name': ('in', loans)}, pluck='name'))
+ loans = frappe.get_all("Loan Security Shortfall", {"status": "Pending"}, pluck="loan")
+ applicants = set(frappe.get_all("Loan", {"name": ("in", loans)}, pluck="name"))
+
+ return {"value": len(applicants), "fieldtype": "Int"}
- return {
- "value": len(applicants),
- "fieldtype": "Int"
- }
def add_single_month(date):
if getdate(date) == get_last_day(date):
@@ -409,29 +496,46 @@ def add_single_month(date):
else:
return add_months(date, 1)
+
@frappe.whitelist()
def make_refund_jv(loan, amount=0, reference_number=None, reference_date=None, submit=0):
- loan_details = frappe.db.get_value('Loan', loan, ['applicant_type', 'applicant',
- 'loan_account', 'payment_account', 'posting_date', 'company', 'name',
- 'total_payment', 'total_principal_paid'], as_dict=1)
+ loan_details = frappe.db.get_value(
+ "Loan",
+ loan,
+ [
+ "applicant_type",
+ "applicant",
+ "loan_account",
+ "payment_account",
+ "posting_date",
+ "company",
+ "name",
+ "total_payment",
+ "total_principal_paid",
+ ],
+ as_dict=1,
+ )
- loan_details.doctype = 'Loan'
+ loan_details.doctype = "Loan"
loan_details[loan_details.applicant_type.lower()] = loan_details.applicant
if not amount:
amount = flt(loan_details.total_principal_paid - loan_details.total_payment)
if amount < 0:
- frappe.throw(_('No excess amount pending for refund'))
+ frappe.throw(_("No excess amount pending for refund"))
- refund_jv = get_payment_entry(loan_details, {
- "party_type": loan_details.applicant_type,
- "party_account": loan_details.loan_account,
- "amount_field_party": 'debit_in_account_currency',
- "amount_field_bank": 'credit_in_account_currency',
- "amount": amount,
- "bank_account": loan_details.payment_account
- })
+ refund_jv = get_payment_entry(
+ loan_details,
+ {
+ "party_type": loan_details.applicant_type,
+ "party_account": loan_details.loan_account,
+ "amount_field_party": "debit_in_account_currency",
+ "amount_field_bank": "credit_in_account_currency",
+ "amount": amount,
+ "bank_account": loan_details.payment_account,
+ },
+ )
if reference_number:
refund_jv.cheque_no = reference_number
@@ -442,4 +546,4 @@ def make_refund_jv(loan, amount=0, reference_number=None, reference_date=None, s
if submit:
refund_jv.submit()
- return refund_jv
\ No newline at end of file
+ return refund_jv
diff --git a/erpnext/loan_management/doctype/loan/loan_dashboard.py b/erpnext/loan_management/doctype/loan/loan_dashboard.py
index c8a9e64f5e..971d5450ea 100644
--- a/erpnext/loan_management/doctype/loan/loan_dashboard.py
+++ b/erpnext/loan_management/doctype/loan/loan_dashboard.py
@@ -1,16 +1,19 @@
def get_data():
return {
- 'fieldname': 'loan',
- 'non_standard_fieldnames': {
- 'Loan Disbursement': 'against_loan',
- 'Loan Repayment': 'against_loan',
+ "fieldname": "loan",
+ "non_standard_fieldnames": {
+ "Loan Disbursement": "against_loan",
+ "Loan Repayment": "against_loan",
},
- 'transactions': [
+ "transactions": [
+ {"items": ["Loan Security Pledge", "Loan Security Shortfall", "Loan Disbursement"]},
{
- 'items': ['Loan Security Pledge', 'Loan Security Shortfall', 'Loan Disbursement']
+ "items": [
+ "Loan Repayment",
+ "Loan Interest Accrual",
+ "Loan Write Off",
+ "Loan Security Unpledge",
+ ]
},
- {
- 'items': ['Loan Repayment', 'Loan Interest Accrual', 'Loan Write Off', 'Loan Security Unpledge']
- }
- ]
+ ],
}
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 5ebb2e1bdc..e2b0870c32 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -39,31 +39,69 @@ from erpnext.selling.doctype.customer.test_customer import get_customer_dict
class TestLoan(unittest.TestCase):
def setUp(self):
create_loan_accounts()
- create_loan_type("Personal Loan", 500000, 8.4,
+ create_loan_type(
+ "Personal Loan",
+ 500000,
+ 8.4,
is_term_loan=1,
- mode_of_payment='Cash',
- disbursement_account='Disbursement Account - _TC',
- payment_account='Payment Account - _TC',
- loan_account='Loan Account - _TC',
- interest_income_account='Interest Income Account - _TC',
- penalty_income_account='Penalty Income Account - _TC')
+ mode_of_payment="Cash",
+ disbursement_account="Disbursement Account - _TC",
+ payment_account="Payment Account - _TC",
+ loan_account="Loan Account - _TC",
+ interest_income_account="Interest Income Account - _TC",
+ penalty_income_account="Penalty Income Account - _TC",
+ )
- create_loan_type("Stock Loan", 2000000, 13.5, 25, 1, 5, 'Cash', 'Disbursement Account - _TC',
- 'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+ create_loan_type(
+ "Stock Loan",
+ 2000000,
+ 13.5,
+ 25,
+ 1,
+ 5,
+ "Cash",
+ "Disbursement Account - _TC",
+ "Payment Account - _TC",
+ "Loan Account - _TC",
+ "Interest Income Account - _TC",
+ "Penalty Income Account - _TC",
+ )
- create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Disbursement Account - _TC',
- 'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+ create_loan_type(
+ "Demand Loan",
+ 2000000,
+ 13.5,
+ 25,
+ 0,
+ 5,
+ "Cash",
+ "Disbursement Account - _TC",
+ "Payment Account - _TC",
+ "Loan Account - _TC",
+ "Interest Income Account - _TC",
+ "Penalty Income Account - _TC",
+ )
create_loan_security_type()
create_loan_security()
- create_loan_security_price("Test Security 1", 500, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
- create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
+ create_loan_security_price(
+ "Test Security 1", 500, "Nos", get_datetime(), get_datetime(add_to_date(nowdate(), hours=24))
+ )
+ create_loan_security_price(
+ "Test Security 2", 250, "Nos", get_datetime(), get_datetime(add_to_date(nowdate(), hours=24))
+ )
self.applicant1 = make_employee("robert_loan@loan.com")
- make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR', company="_Test Company")
+ make_salary_structure(
+ "Test Salary Structure Loan",
+ "Monthly",
+ employee=self.applicant1,
+ currency="INR",
+ company="_Test Company",
+ )
if not frappe.db.exists("Customer", "_Test Loan Customer"):
- frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
+ frappe.get_doc(get_customer_dict("_Test Loan Customer")).insert(ignore_permissions=True)
if not frappe.db.exists("Customer", "_Test Loan Customer 1"):
frappe.get_doc(get_customer_dict("_Test Loan Customer 1")).insert(ignore_permissions=True)
@@ -74,7 +112,7 @@ class TestLoan(unittest.TestCase):
create_loan(self.applicant1, "Personal Loan", 280000, "Repay Over Number of Periods", 20)
def test_loan(self):
- loan = frappe.get_doc("Loan", {"applicant":self.applicant1})
+ loan = frappe.get_doc("Loan", {"applicant": self.applicant1})
self.assertEqual(loan.monthly_repayment_amount, 15052)
self.assertEqual(flt(loan.total_interest_payable, 0), 21034)
self.assertEqual(flt(loan.total_payment, 0), 301034)
@@ -83,7 +121,11 @@ class TestLoan(unittest.TestCase):
self.assertEqual(len(schedule), 20)
- for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
+ for idx, principal_amount, interest_amount, balance_loan_amount in [
+ [3, 13369, 1683, 227080],
+ [19, 14941, 105, 0],
+ [17, 14740, 312, 29785],
+ ]:
self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount)
self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount)
self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount)
@@ -98,30 +140,35 @@ class TestLoan(unittest.TestCase):
def test_loan_with_security(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00,
- }]
+ pledge = [
+ {
+ "loan_security": "Test Security 1",
+ "qty": 4000.00,
+ }
+ ]
- loan_application = create_loan_application('_Test Company', self.applicant2,
- 'Stock Loan', pledge, "Repay Over Number of Periods", 12)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Stock Loan", pledge, "Repay Over Number of Periods", 12
+ )
create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods",
- 12, loan_application)
+ loan = create_loan_with_security(
+ self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application
+ )
self.assertEqual(loan.loan_amount, 1000000)
def test_loan_disbursement(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Stock Loan', pledge, "Repay Over Number of Periods", 12)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Stock Loan", pledge, "Repay Over Number of Periods", 12
+ )
create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
+ loan = create_loan_with_security(
+ self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application
+ )
self.assertEqual(loan.loan_amount, 1000000)
loan.submit()
@@ -130,14 +177,16 @@ class TestLoan(unittest.TestCase):
loan_disbursement_entry2 = make_loan_disbursement_entry(loan.name, 500000)
loan = frappe.get_doc("Loan", loan.name)
- gl_entries1 = frappe.db.get_all("GL Entry",
+ gl_entries1 = frappe.db.get_all(
+ "GL Entry",
fields=["name"],
- filters = {'voucher_type': 'Loan Disbursement', 'voucher_no': loan_disbursement_entry1.name}
+ filters={"voucher_type": "Loan Disbursement", "voucher_no": loan_disbursement_entry1.name},
)
- gl_entries2 = frappe.db.get_all("GL Entry",
+ gl_entries2 = frappe.db.get_all(
+ "GL Entry",
fields=["name"],
- filters = {'voucher_type': 'Loan Disbursement', 'voucher_no': loan_disbursement_entry2.name}
+ filters={"voucher_type": "Loan Disbursement", "voucher_no": loan_disbursement_entry2.name},
)
self.assertEqual(loan.status, "Disbursed")
@@ -151,73 +200,93 @@ class TestLoan(unittest.TestCase):
frappe.db.sql("DELETE FROM `tabLoan Application` where applicant = '_Test Loan Customer 1'")
frappe.db.sql("DELETE FROM `tabLoan Security Pledge` where applicant = '_Test Loan Customer 1'")
- if not frappe.db.get_value("Sanctioned Loan Amount", filters={"applicant_type": "Customer",
- "applicant": "_Test Loan Customer 1", "company": "_Test Company"}):
- frappe.get_doc({
- "doctype": "Sanctioned Loan Amount",
+ if not frappe.db.get_value(
+ "Sanctioned Loan Amount",
+ filters={
"applicant_type": "Customer",
"applicant": "_Test Loan Customer 1",
- "sanctioned_amount_limit": 1500000,
- "company": "_Test Company"
- }).insert(ignore_permissions=True)
+ "company": "_Test Company",
+ },
+ ):
+ frappe.get_doc(
+ {
+ "doctype": "Sanctioned Loan Amount",
+ "applicant_type": "Customer",
+ "applicant": "_Test Loan Customer 1",
+ "sanctioned_amount_limit": 1500000,
+ "company": "_Test Company",
+ }
+ ).insert(ignore_permissions=True)
# Make First Loan
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant3, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant3, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant3, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
# Make second loan greater than the sanctioned amount
- loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge,
- do_not_save=True)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant3, "Demand Loan", pledge, do_not_save=True
+ )
self.assertRaises(frappe.ValidationError, loan_application.save)
def test_regular_loan_repayment(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
- accrued_interest_amount = flt((loan.loan_amount * loan.rate_of_interest * no_of_days)
- / (days_in_year(get_datetime(first_date).year) * 100), 2)
+ accrued_interest_amount = flt(
+ (loan.loan_amount * loan.rate_of_interest * no_of_days)
+ / (days_in_year(get_datetime(first_date).year) * 100),
+ 2,
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), 111119)
+ repayment_entry = create_repayment_entry(
+ loan.name, self.applicant2, add_days(last_date, 10), 111119
+ )
repayment_entry.save()
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 5 * 25) / 100
- self.assertEqual(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount, 0), flt(penalty_amount, 0))
- amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount'])
+ amounts = frappe.db.get_all(
+ "Loan Interest Accrual", {"loan": loan.name}, ["paid_interest_amount"]
+ )
loan.load_from_db()
- total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount']
- self.assertEqual(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable)
- self.assertEqual(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid -
- penalty_amount - total_interest_paid, 0))
+ total_interest_paid = amounts[0]["paid_interest_amount"] + amounts[1]["paid_interest_amount"]
+ self.assertEqual(amounts[1]["paid_interest_amount"], repayment_entry.interest_payable)
+ self.assertEqual(
+ flt(loan.total_principal_paid, 0),
+ flt(repayment_entry.amount_paid - penalty_amount - total_interest_paid, 0),
+ )
# Check Repayment Entry cancel
repayment_entry.load_from_db()
@@ -228,21 +297,22 @@ class TestLoan(unittest.TestCase):
self.assertEqual(loan.total_principal_paid, 0)
def test_loan_closure(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
@@ -251,20 +321,27 @@ class TestLoan(unittest.TestCase):
# 5 days as well though in grace period
no_of_days += 5
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
- flt(loan.loan_amount + accrued_interest_amount))
+ repayment_entry = create_repayment_entry(
+ loan.name,
+ self.applicant2,
+ add_days(last_date, 5),
+ flt(loan.loan_amount + accrued_interest_amount),
+ )
repayment_entry.submit()
- amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
+ amount = frappe.db.get_value(
+ "Loan Interest Accrual", {"loan": loan.name}, ["sum(paid_interest_amount)"]
+ )
- self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(amount, 0), flt(accrued_interest_amount, 0))
self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
request_loan_closure(loan.name)
@@ -272,78 +349,101 @@ class TestLoan(unittest.TestCase):
self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_repayment_for_term_loan(self):
- pledges = [{
- "loan_security": "Test Security 2",
- "qty": 4000.00
- },
- {
- "loan_security": "Test Security 1",
- "qty": 2000.00
- }]
+ pledges = [
+ {"loan_security": "Test Security 2", "qty": 4000.00},
+ {"loan_security": "Test Security 1", "qty": 2000.00},
+ ]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Stock Loan', pledges,
- "Repay Over Number of Periods", 12)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Stock Loan", pledges, "Repay Over Number of Periods", 12
+ )
create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application,
- posting_date=add_months(nowdate(), -1))
+ loan = create_loan_with_security(
+ self.applicant2,
+ "Stock Loan",
+ "Repay Over Number of Periods",
+ 12,
+ loan_application,
+ posting_date=add_months(nowdate(), -1),
+ )
loan.submit()
- make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1))
+ make_loan_disbursement_entry(
+ loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1)
+ )
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(nowdate(), 5), 89768.75)
+ repayment_entry = create_repayment_entry(
+ loan.name, self.applicant2, add_days(nowdate(), 5), 89768.75
+ )
repayment_entry.submit()
- amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
- 'paid_principal_amount'])
+ amounts = frappe.db.get_value(
+ "Loan Interest Accrual", {"loan": loan.name}, ["paid_interest_amount", "paid_principal_amount"]
+ )
self.assertEqual(amounts[0], 11250.00)
self.assertEqual(amounts[1], 78303.00)
def test_repayment_schedule_update(self):
- loan = create_loan(self.applicant2, "Personal Loan", 200000, "Repay Over Number of Periods", 4,
- applicant_type='Customer', repayment_start_date='2021-04-30', posting_date='2021-04-01')
+ loan = create_loan(
+ self.applicant2,
+ "Personal Loan",
+ 200000,
+ "Repay Over Number of Periods",
+ 4,
+ applicant_type="Customer",
+ repayment_start_date="2021-04-30",
+ posting_date="2021-04-01",
+ )
loan.submit()
- make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date='2021-04-01')
+ make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date="2021-04-01")
- process_loan_interest_accrual_for_term_loans(posting_date='2021-05-01')
- process_loan_interest_accrual_for_term_loans(posting_date='2021-06-01')
+ process_loan_interest_accrual_for_term_loans(posting_date="2021-05-01")
+ process_loan_interest_accrual_for_term_loans(posting_date="2021-06-01")
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2021-06-05', 120000)
+ repayment_entry = create_repayment_entry(loan.name, self.applicant2, "2021-06-05", 120000)
repayment_entry.submit()
loan.load_from_db()
- self.assertEqual(flt(loan.get('repayment_schedule')[3].principal_amount, 2), 41369.83)
- self.assertEqual(flt(loan.get('repayment_schedule')[3].interest_amount, 2), 289.59)
- self.assertEqual(flt(loan.get('repayment_schedule')[3].total_payment, 2), 41659.41)
- self.assertEqual(flt(loan.get('repayment_schedule')[3].balance_loan_amount, 2), 0)
+ self.assertEqual(flt(loan.get("repayment_schedule")[3].principal_amount, 2), 41369.83)
+ self.assertEqual(flt(loan.get("repayment_schedule")[3].interest_amount, 2), 289.59)
+ self.assertEqual(flt(loan.get("repayment_schedule")[3].total_payment, 2), 41659.41)
+ self.assertEqual(flt(loan.get("repayment_schedule")[3].balance_loan_amount, 2), 0)
def test_security_shortfall(self):
- pledges = [{
- "loan_security": "Test Security 2",
- "qty": 8000.00,
- "haircut": 50,
- }]
+ pledges = [
+ {
+ "loan_security": "Test Security 2",
+ "qty": 8000.00,
+ "haircut": 50,
+ }
+ ]
- loan_application = create_loan_application('_Test Company', self.applicant2,
- 'Stock Loan', pledges, "Repay Over Number of Periods", 12)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Stock Loan", pledges, "Repay Over Number of Periods", 12
+ )
create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
+ loan = create_loan_with_security(
+ self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application
+ )
loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount)
- frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
- where loan_security='Test Security 2'""")
+ frappe.db.sql(
+ """UPDATE `tabLoan Security Price` SET loan_security_price = 100
+ where loan_security='Test Security 2'"""
+ )
create_process_loan_security_shortfall()
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
@@ -353,8 +453,10 @@ class TestLoan(unittest.TestCase):
self.assertEqual(loan_security_shortfall.security_value, 800000.00)
self.assertEqual(loan_security_shortfall.shortfall_amount, 600000.00)
- frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
- where loan_security='Test Security 2'""")
+ frappe.db.sql(
+ """ UPDATE `tabLoan Security Price` SET loan_security_price = 250
+ where loan_security='Test Security 2'"""
+ )
create_process_loan_security_shortfall()
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
@@ -362,33 +464,40 @@ class TestLoan(unittest.TestCase):
self.assertEqual(loan_security_shortfall.shortfall_amount, 0)
def test_loan_security_unpledge(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount + accrued_interest_amount))
+ repayment_entry = create_repayment_entry(
+ loan.name,
+ self.applicant2,
+ add_days(last_date, 5),
+ flt(loan.loan_amount + accrued_interest_amount),
+ )
repayment_entry.submit()
request_loan_closure(loan.name)
@@ -397,98 +506,108 @@ class TestLoan(unittest.TestCase):
unpledge_request = unpledge_security(loan=loan.name, save=1)
unpledge_request.submit()
- unpledge_request.status = 'Approved'
+ unpledge_request.status = "Approved"
unpledge_request.save()
loan.load_from_db()
pledged_qty = get_pledged_security_qty(loan.name)
- self.assertEqual(loan.status, 'Closed')
+ self.assertEqual(loan.status, "Closed")
self.assertEqual(sum(pledged_qty.values()), 0)
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEqual(amounts['pending_principal_amount'], 0)
- self.assertEqual(amounts['payable_principal_amount'], 0.0)
- self.assertEqual(amounts['interest_amount'], 0)
+ self.assertEqual(amounts["pending_principal_amount"], 0)
+ self.assertEqual(amounts["payable_principal_amount"], 0.0)
+ self.assertEqual(amounts["interest_amount"], 0)
def test_partial_loan_security_unpledge(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 2000.00
- },
- {
- "loan_security": "Test Security 2",
- "qty": 4000.00
- }]
+ pledge = [
+ {"loan_security": "Test Security 1", "qty": 2000.00},
+ {"loan_security": "Test Security 2", "qty": 4000.00},
+ ]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), 600000)
+ repayment_entry = create_repayment_entry(
+ loan.name, self.applicant2, add_days(last_date, 5), 600000
+ )
repayment_entry.submit()
- unpledge_map = {'Test Security 2': 2000}
+ unpledge_map = {"Test Security 2": 2000}
- unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
+ unpledge_request = unpledge_security(loan=loan.name, security_map=unpledge_map, save=1)
unpledge_request.submit()
- unpledge_request.status = 'Approved'
+ unpledge_request.status = "Approved"
unpledge_request.save()
unpledge_request.submit()
unpledge_request.load_from_db()
self.assertEqual(unpledge_request.docstatus, 1)
def test_sanctioned_loan_security_unpledge(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- unpledge_map = {'Test Security 1': 4000}
- unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
+ unpledge_map = {"Test Security 1": 4000}
+ unpledge_request = unpledge_security(loan=loan.name, security_map=unpledge_map, save=1)
unpledge_request.submit()
- unpledge_request.status = 'Approved'
+ unpledge_request.status = "Approved"
unpledge_request.save()
unpledge_request.submit()
def test_disbursal_check_with_shortfall(self):
- pledges = [{
- "loan_security": "Test Security 2",
- "qty": 8000.00,
- "haircut": 50,
- }]
+ pledges = [
+ {
+ "loan_security": "Test Security 2",
+ "qty": 8000.00,
+ "haircut": 50,
+ }
+ ]
- loan_application = create_loan_application('_Test Company', self.applicant2,
- 'Stock Loan', pledges, "Repay Over Number of Periods", 12)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Stock Loan", pledges, "Repay Over Number of Periods", 12
+ )
create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
+ loan = create_loan_with_security(
+ self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application
+ )
loan.submit()
- #Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
+ # Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
make_loan_disbursement_entry(loan.name, 700000)
- frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
- where loan_security='Test Security 2'""")
+ frappe.db.sql(
+ """UPDATE `tabLoan Security Price` SET loan_security_price = 100
+ where loan_security='Test Security 2'"""
+ )
create_process_loan_security_shortfall()
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
@@ -496,422 +615,505 @@ class TestLoan(unittest.TestCase):
self.assertEqual(get_disbursal_amount(loan.name), 0)
- frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
- where loan_security='Test Security 2'""")
+ frappe.db.sql(
+ """ UPDATE `tabLoan Security Price` SET loan_security_price = 250
+ where loan_security='Test Security 2'"""
+ )
def test_disbursal_check_without_shortfall(self):
- pledges = [{
- "loan_security": "Test Security 2",
- "qty": 8000.00,
- "haircut": 50,
- }]
+ pledges = [
+ {
+ "loan_security": "Test Security 2",
+ "qty": 8000.00,
+ "haircut": 50,
+ }
+ ]
- loan_application = create_loan_application('_Test Company', self.applicant2,
- 'Stock Loan', pledges, "Repay Over Number of Periods", 12)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Stock Loan", pledges, "Repay Over Number of Periods", 12
+ )
create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
+ loan = create_loan_with_security(
+ self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application
+ )
loan.submit()
- #Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
+ # Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
make_loan_disbursement_entry(loan.name, 700000)
self.assertEqual(get_disbursal_amount(loan.name), 300000)
def test_pending_loan_amount_after_closure_request(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount + accrued_interest_amount))
+ repayment_entry = create_repayment_entry(
+ loan.name,
+ self.applicant2,
+ add_days(last_date, 5),
+ flt(loan.loan_amount + accrued_interest_amount),
+ )
repayment_entry.submit()
- amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
- 'paid_principal_amount'])
+ amounts = frappe.db.get_value(
+ "Loan Interest Accrual", {"loan": loan.name}, ["paid_interest_amount", "paid_principal_amount"]
+ )
request_loan_closure(loan.name)
loan.load_from_db()
self.assertEqual(loan.status, "Loan Closure Requested")
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEqual(amounts['pending_principal_amount'], 0.0)
+ self.assertEqual(amounts["pending_principal_amount"], 0.0)
def test_partial_unaccrued_interest_payment(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5.5
# get partial unaccrued interest amount
- paid_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ paid_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
- paid_amount)
+ repayment_entry = create_repayment_entry(
+ loan.name, self.applicant2, add_days(last_date, 5), paid_amount
+ )
repayment_entry.submit()
repayment_entry.load_from_db()
- partial_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 5) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ partial_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 5) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
- interest_amount = flt(amounts['interest_amount'] + partial_accrued_interest_amount, 2)
+ interest_amount = flt(amounts["interest_amount"] + partial_accrued_interest_amount, 2)
self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0))
def test_penalty(self):
loan, amounts = create_loan_scenario_for_penalty(self)
# 30 days - grace period
penalty_days = 30 - 4
- penalty_applicable_amount = flt(amounts['interest_amount']/2)
+ penalty_applicable_amount = flt(amounts["interest_amount"] / 2)
penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days), 2)
- process = process_loan_interest_accrual_for_demand_loans(posting_date = '2019-11-30')
+ process = process_loan_interest_accrual_for_demand_loans(posting_date="2019-11-30")
- calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual',
- {'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount')
+ calculated_penalty_amount = frappe.db.get_value(
+ "Loan Interest Accrual",
+ {"process_loan_interest_accrual": process, "loan": loan.name},
+ "penalty_amount",
+ )
self.assertEqual(loan.loan_amount, 1000000)
self.assertEqual(calculated_penalty_amount, penalty_amount)
def test_penalty_repayment(self):
loan, dummy = create_loan_scenario_for_penalty(self)
- amounts = calculate_amounts(loan.name, '2019-11-30 00:00:00')
+ amounts = calculate_amounts(loan.name, "2019-11-30 00:00:00")
first_penalty = 10000
- second_penalty = amounts['penalty_amount'] - 10000
+ second_penalty = amounts["penalty_amount"] - 10000
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:00', 10000)
+ repayment_entry = create_repayment_entry(
+ loan.name, self.applicant2, "2019-11-30 00:00:00", 10000
+ )
repayment_entry.submit()
- amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01')
- self.assertEqual(amounts['penalty_amount'], second_penalty)
+ amounts = calculate_amounts(loan.name, "2019-11-30 00:00:01")
+ self.assertEqual(amounts["penalty_amount"], second_penalty)
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty)
+ repayment_entry = create_repayment_entry(
+ loan.name, self.applicant2, "2019-11-30 00:00:01", second_penalty
+ )
repayment_entry.submit()
- amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02')
- self.assertEqual(amounts['penalty_amount'], 0)
+ amounts = calculate_amounts(loan.name, "2019-11-30 00:00:02")
+ self.assertEqual(amounts["penalty_amount"], 0)
def test_loan_write_off_limit(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
# repay 50 less so that it can be automatically written off
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
- flt(loan.loan_amount + accrued_interest_amount - 50))
+ repayment_entry = create_repayment_entry(
+ loan.name,
+ self.applicant2,
+ add_days(last_date, 5),
+ flt(loan.loan_amount + accrued_interest_amount - 50),
+ )
repayment_entry.submit()
- amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
+ amount = frappe.db.get_value(
+ "Loan Interest Accrual", {"loan": loan.name}, ["sum(paid_interest_amount)"]
+ )
- self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(amount, 0), flt(accrued_interest_amount, 0))
self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEqual(flt(amounts['pending_principal_amount'], 0), 50)
+ self.assertEqual(flt(amounts["pending_principal_amount"], 0), 50)
request_loan_closure(loan.name)
loan.load_from_db()
self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_repayment_against_partially_disbursed_loan(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
- make_loan_disbursement_entry(loan.name, loan.loan_amount/2, disbursement_date=first_date)
+ make_loan_disbursement_entry(loan.name, loan.loan_amount / 2, disbursement_date=first_date)
loan.load_from_db()
self.assertEqual(loan.status, "Partially Disbursed")
- create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
- flt(loan.loan_amount/3))
+ create_repayment_entry(
+ loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount / 3)
+ )
def test_loan_amount_write_off(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant2, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
# repay 100 less so that it can be automatically written off
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
- flt(loan.loan_amount + accrued_interest_amount - 100))
+ repayment_entry = create_repayment_entry(
+ loan.name,
+ self.applicant2,
+ add_days(last_date, 5),
+ flt(loan.loan_amount + accrued_interest_amount - 100),
+ )
repayment_entry.submit()
- amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
+ amount = frappe.db.get_value(
+ "Loan Interest Accrual", {"loan": loan.name}, ["sum(paid_interest_amount)"]
+ )
- self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(amount, 0), flt(accrued_interest_amount, 0))
self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEqual(flt(amounts['pending_principal_amount'], 0), 100)
+ self.assertEqual(flt(amounts["pending_principal_amount"], 0), 100)
- we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount'])
+ we = make_loan_write_off(loan.name, amount=amounts["pending_principal_amount"])
we.submit()
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEqual(flt(amounts['pending_principal_amount'], 0), 0)
+ self.assertEqual(flt(amounts["pending_principal_amount"], 0), 0)
+
def create_loan_scenario_for_penalty(doc):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', doc.applicant2, 'Demand Loan', pledge)
+ loan_application = create_loan_application("_Test Company", doc.applicant2, "Demand Loan", pledge)
create_pledge(loan_application)
- loan = create_demand_loan(doc.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ doc.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
amounts = calculate_amounts(loan.name, add_days(last_date, 1))
- paid_amount = amounts['interest_amount']/2
+ paid_amount = amounts["interest_amount"] / 2
- repayment_entry = create_repayment_entry(loan.name, doc.applicant2, add_days(last_date, 5),
- paid_amount)
+ repayment_entry = create_repayment_entry(
+ loan.name, doc.applicant2, add_days(last_date, 5), paid_amount
+ )
repayment_entry.submit()
return loan, amounts
+
def create_loan_accounts():
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
- frappe.get_doc({
- "doctype": "Account",
- "account_name": "Loans and Advances (Assets)",
- "company": "_Test Company",
- "root_type": "Asset",
- "report_type": "Balance Sheet",
- "currency": "INR",
- "parent_account": "Current Assets - _TC",
- "account_type": "Bank",
- "is_group": 1
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": "Loans and Advances (Assets)",
+ "company": "_Test Company",
+ "root_type": "Asset",
+ "report_type": "Balance Sheet",
+ "currency": "INR",
+ "parent_account": "Current Assets - _TC",
+ "account_type": "Bank",
+ "is_group": 1,
+ }
+ ).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Loan Account - _TC"):
- frappe.get_doc({
- "doctype": "Account",
- "company": "_Test Company",
- "account_name": "Loan Account",
- "root_type": "Asset",
- "report_type": "Balance Sheet",
- "currency": "INR",
- "parent_account": "Loans and Advances (Assets) - _TC",
- "account_type": "Bank",
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "account_name": "Loan Account",
+ "root_type": "Asset",
+ "report_type": "Balance Sheet",
+ "currency": "INR",
+ "parent_account": "Loans and Advances (Assets) - _TC",
+ "account_type": "Bank",
+ }
+ ).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Payment Account - _TC"):
- frappe.get_doc({
- "doctype": "Account",
- "company": "_Test Company",
- "account_name": "Payment Account",
- "root_type": "Asset",
- "report_type": "Balance Sheet",
- "currency": "INR",
- "parent_account": "Bank Accounts - _TC",
- "account_type": "Bank",
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "account_name": "Payment Account",
+ "root_type": "Asset",
+ "report_type": "Balance Sheet",
+ "currency": "INR",
+ "parent_account": "Bank Accounts - _TC",
+ "account_type": "Bank",
+ }
+ ).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Disbursement Account - _TC"):
- frappe.get_doc({
- "doctype": "Account",
- "company": "_Test Company",
- "account_name": "Disbursement Account",
- "root_type": "Asset",
- "report_type": "Balance Sheet",
- "currency": "INR",
- "parent_account": "Bank Accounts - _TC",
- "account_type": "Bank",
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "account_name": "Disbursement Account",
+ "root_type": "Asset",
+ "report_type": "Balance Sheet",
+ "currency": "INR",
+ "parent_account": "Bank Accounts - _TC",
+ "account_type": "Bank",
+ }
+ ).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Interest Income Account - _TC"):
- frappe.get_doc({
- "doctype": "Account",
- "company": "_Test Company",
- "root_type": "Income",
- "account_name": "Interest Income Account",
- "report_type": "Profit and Loss",
- "currency": "INR",
- "parent_account": "Direct Income - _TC",
- "account_type": "Income Account",
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "root_type": "Income",
+ "account_name": "Interest Income Account",
+ "report_type": "Profit and Loss",
+ "currency": "INR",
+ "parent_account": "Direct Income - _TC",
+ "account_type": "Income Account",
+ }
+ ).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Penalty Income Account - _TC"):
- frappe.get_doc({
- "doctype": "Account",
- "company": "_Test Company",
- "account_name": "Penalty Income Account",
- "root_type": "Income",
- "report_type": "Profit and Loss",
- "currency": "INR",
- "parent_account": "Direct Income - _TC",
- "account_type": "Income Account",
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "account_name": "Penalty Income Account",
+ "root_type": "Income",
+ "report_type": "Profit and Loss",
+ "currency": "INR",
+ "parent_account": "Direct Income - _TC",
+ "account_type": "Income Account",
+ }
+ ).insert(ignore_permissions=True)
-def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest, penalty_interest_rate=None, is_term_loan=None, grace_period_in_days=None,
- mode_of_payment=None, disbursement_account=None, payment_account=None, loan_account=None, interest_income_account=None, penalty_income_account=None,
- repayment_method=None, repayment_periods=None):
+
+def create_loan_type(
+ loan_name,
+ maximum_loan_amount,
+ rate_of_interest,
+ penalty_interest_rate=None,
+ is_term_loan=None,
+ grace_period_in_days=None,
+ mode_of_payment=None,
+ disbursement_account=None,
+ payment_account=None,
+ loan_account=None,
+ interest_income_account=None,
+ penalty_income_account=None,
+ repayment_method=None,
+ repayment_periods=None,
+):
if not frappe.db.exists("Loan Type", loan_name):
- loan_type = frappe.get_doc({
- "doctype": "Loan Type",
- "company": "_Test Company",
- "loan_name": loan_name,
- "is_term_loan": is_term_loan,
- "maximum_loan_amount": maximum_loan_amount,
- "rate_of_interest": rate_of_interest,
- "penalty_interest_rate": penalty_interest_rate,
- "grace_period_in_days": grace_period_in_days,
- "mode_of_payment": mode_of_payment,
- "disbursement_account": disbursement_account,
- "payment_account": payment_account,
- "loan_account": loan_account,
- "interest_income_account": interest_income_account,
- "penalty_income_account": penalty_income_account,
- "repayment_method": repayment_method,
- "repayment_periods": repayment_periods,
- "write_off_amount": 100
- }).insert()
+ loan_type = frappe.get_doc(
+ {
+ "doctype": "Loan Type",
+ "company": "_Test Company",
+ "loan_name": loan_name,
+ "is_term_loan": is_term_loan,
+ "maximum_loan_amount": maximum_loan_amount,
+ "rate_of_interest": rate_of_interest,
+ "penalty_interest_rate": penalty_interest_rate,
+ "grace_period_in_days": grace_period_in_days,
+ "mode_of_payment": mode_of_payment,
+ "disbursement_account": disbursement_account,
+ "payment_account": payment_account,
+ "loan_account": loan_account,
+ "interest_income_account": interest_income_account,
+ "penalty_income_account": penalty_income_account,
+ "repayment_method": repayment_method,
+ "repayment_periods": repayment_periods,
+ "write_off_amount": 100,
+ }
+ ).insert()
loan_type.submit()
+
def create_loan_security_type():
if not frappe.db.exists("Loan Security Type", "Stock"):
- frappe.get_doc({
- "doctype": "Loan Security Type",
- "loan_security_type": "Stock",
- "unit_of_measure": "Nos",
- "haircut": 50.00,
- "loan_to_value_ratio": 50
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Loan Security Type",
+ "loan_security_type": "Stock",
+ "unit_of_measure": "Nos",
+ "haircut": 50.00,
+ "loan_to_value_ratio": 50,
+ }
+ ).insert(ignore_permissions=True)
+
def create_loan_security():
if not frappe.db.exists("Loan Security", "Test Security 1"):
- frappe.get_doc({
- "doctype": "Loan Security",
- "loan_security_type": "Stock",
- "loan_security_code": "532779",
- "loan_security_name": "Test Security 1",
- "unit_of_measure": "Nos",
- "haircut": 50.00,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Loan Security",
+ "loan_security_type": "Stock",
+ "loan_security_code": "532779",
+ "loan_security_name": "Test Security 1",
+ "unit_of_measure": "Nos",
+ "haircut": 50.00,
+ }
+ ).insert(ignore_permissions=True)
if not frappe.db.exists("Loan Security", "Test Security 2"):
- frappe.get_doc({
- "doctype": "Loan Security",
- "loan_security_type": "Stock",
- "loan_security_code": "531335",
- "loan_security_name": "Test Security 2",
- "unit_of_measure": "Nos",
- "haircut": 50.00,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Loan Security",
+ "loan_security_type": "Stock",
+ "loan_security_code": "531335",
+ "loan_security_name": "Test Security 2",
+ "unit_of_measure": "Nos",
+ "haircut": 50.00,
+ }
+ ).insert(ignore_permissions=True)
+
def create_loan_security_pledge(applicant, pledges, loan_application=None, loan=None):
lsp = frappe.new_doc("Loan Security Pledge")
- lsp.applicant_type = 'Customer'
+ lsp.applicant_type = "Customer"
lsp.applicant = applicant
lsp.company = "_Test Company"
lsp.loan_application = loan_application
@@ -920,64 +1122,82 @@ def create_loan_security_pledge(applicant, pledges, loan_application=None, loan=
lsp.loan = loan
for pledge in pledges:
- lsp.append('securities', {
- "loan_security": pledge['loan_security'],
- "qty": pledge['qty']
- })
+ lsp.append("securities", {"loan_security": pledge["loan_security"], "qty": pledge["qty"]})
lsp.save()
lsp.submit()
return lsp
+
def make_loan_disbursement_entry(loan, amount, disbursement_date=None):
- loan_disbursement_entry = frappe.get_doc({
- "doctype": "Loan Disbursement",
- "against_loan": loan,
- "disbursement_date": disbursement_date,
- "company": "_Test Company",
- "disbursed_amount": amount,
- "cost_center": 'Main - _TC'
- }).insert(ignore_permissions=True)
+ loan_disbursement_entry = frappe.get_doc(
+ {
+ "doctype": "Loan Disbursement",
+ "against_loan": loan,
+ "disbursement_date": disbursement_date,
+ "company": "_Test Company",
+ "disbursed_amount": amount,
+ "cost_center": "Main - _TC",
+ }
+ ).insert(ignore_permissions=True)
loan_disbursement_entry.save()
loan_disbursement_entry.submit()
return loan_disbursement_entry
+
def create_loan_security_price(loan_security, loan_security_price, uom, from_date, to_date):
- if not frappe.db.get_value("Loan Security Price",{"loan_security": loan_security,
- "valid_from": ("<=", from_date), "valid_upto": (">=", to_date)}, 'name'):
+ if not frappe.db.get_value(
+ "Loan Security Price",
+ {"loan_security": loan_security, "valid_from": ("<=", from_date), "valid_upto": (">=", to_date)},
+ "name",
+ ):
+
+ lsp = frappe.get_doc(
+ {
+ "doctype": "Loan Security Price",
+ "loan_security": loan_security,
+ "loan_security_price": loan_security_price,
+ "uom": uom,
+ "valid_from": from_date,
+ "valid_upto": to_date,
+ }
+ ).insert(ignore_permissions=True)
- lsp = frappe.get_doc({
- "doctype": "Loan Security Price",
- "loan_security": loan_security,
- "loan_security_price": loan_security_price,
- "uom": uom,
- "valid_from":from_date,
- "valid_upto": to_date
- }).insert(ignore_permissions=True)
def create_repayment_entry(loan, applicant, posting_date, paid_amount):
- lr = frappe.get_doc({
- "doctype": "Loan Repayment",
- "against_loan": loan,
- "company": "_Test Company",
- "posting_date": posting_date or nowdate(),
- "applicant": applicant,
- "amount_paid": paid_amount,
- "loan_type": "Stock Loan"
- }).insert(ignore_permissions=True)
+ lr = frappe.get_doc(
+ {
+ "doctype": "Loan Repayment",
+ "against_loan": loan,
+ "company": "_Test Company",
+ "posting_date": posting_date or nowdate(),
+ "applicant": applicant,
+ "amount_paid": paid_amount,
+ "loan_type": "Stock Loan",
+ }
+ ).insert(ignore_permissions=True)
return lr
-def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None,
- repayment_periods=None, posting_date=None, do_not_save=False):
- loan_application = frappe.new_doc('Loan Application')
- loan_application.applicant_type = 'Customer'
+
+def create_loan_application(
+ company,
+ applicant,
+ loan_type,
+ proposed_pledges,
+ repayment_method=None,
+ repayment_periods=None,
+ posting_date=None,
+ do_not_save=False,
+):
+ loan_application = frappe.new_doc("Loan Application")
+ loan_application.applicant_type = "Customer"
loan_application.company = company
loan_application.applicant = applicant
loan_application.loan_type = loan_type
@@ -989,7 +1209,7 @@ def create_loan_application(company, applicant, loan_type, proposed_pledges, rep
loan_application.repayment_periods = repayment_periods
for pledge in proposed_pledges:
- loan_application.append('proposed_pledges', pledge)
+ loan_application.append("proposed_pledges", pledge)
if do_not_save:
return loan_application
@@ -997,75 +1217,99 @@ def create_loan_application(company, applicant, loan_type, proposed_pledges, rep
loan_application.save()
loan_application.submit()
- loan_application.status = 'Approved'
+ loan_application.status = "Approved"
loan_application.save()
return loan_application.name
-def create_loan(applicant, loan_type, loan_amount, repayment_method, repayment_periods,
- applicant_type=None, repayment_start_date=None, posting_date=None):
+def create_loan(
+ applicant,
+ loan_type,
+ loan_amount,
+ repayment_method,
+ repayment_periods,
+ applicant_type=None,
+ repayment_start_date=None,
+ posting_date=None,
+):
- loan = frappe.get_doc({
- "doctype": "Loan",
- "applicant_type": applicant_type or "Employee",
- "company": "_Test Company",
- "applicant": applicant,
- "loan_type": loan_type,
- "loan_amount": loan_amount,
- "repayment_method": repayment_method,
- "repayment_periods": repayment_periods,
- "repayment_start_date": repayment_start_date or nowdate(),
- "is_term_loan": 1,
- "posting_date": posting_date or nowdate()
- })
+ loan = frappe.get_doc(
+ {
+ "doctype": "Loan",
+ "applicant_type": applicant_type or "Employee",
+ "company": "_Test Company",
+ "applicant": applicant,
+ "loan_type": loan_type,
+ "loan_amount": loan_amount,
+ "repayment_method": repayment_method,
+ "repayment_periods": repayment_periods,
+ "repayment_start_date": repayment_start_date or nowdate(),
+ "is_term_loan": 1,
+ "posting_date": posting_date or nowdate(),
+ }
+ )
loan.save()
return loan
-def create_loan_with_security(applicant, loan_type, repayment_method, repayment_periods, loan_application, posting_date=None, repayment_start_date=None):
- loan = frappe.get_doc({
- "doctype": "Loan",
- "company": "_Test Company",
- "applicant_type": "Customer",
- "posting_date": posting_date or nowdate(),
- "loan_application": loan_application,
- "applicant": applicant,
- "loan_type": loan_type,
- "is_term_loan": 1,
- "is_secured_loan": 1,
- "repayment_method": repayment_method,
- "repayment_periods": repayment_periods,
- "repayment_start_date": repayment_start_date or nowdate(),
- "mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
- "payment_account": 'Payment Account - _TC',
- "loan_account": 'Loan Account - _TC',
- "interest_income_account": 'Interest Income Account - _TC',
- "penalty_income_account": 'Penalty Income Account - _TC',
- })
+
+def create_loan_with_security(
+ applicant,
+ loan_type,
+ repayment_method,
+ repayment_periods,
+ loan_application,
+ posting_date=None,
+ repayment_start_date=None,
+):
+ loan = frappe.get_doc(
+ {
+ "doctype": "Loan",
+ "company": "_Test Company",
+ "applicant_type": "Customer",
+ "posting_date": posting_date or nowdate(),
+ "loan_application": loan_application,
+ "applicant": applicant,
+ "loan_type": loan_type,
+ "is_term_loan": 1,
+ "is_secured_loan": 1,
+ "repayment_method": repayment_method,
+ "repayment_periods": repayment_periods,
+ "repayment_start_date": repayment_start_date or nowdate(),
+ "mode_of_payment": frappe.db.get_value("Mode of Payment", {"type": "Cash"}, "name"),
+ "payment_account": "Payment Account - _TC",
+ "loan_account": "Loan Account - _TC",
+ "interest_income_account": "Interest Income Account - _TC",
+ "penalty_income_account": "Penalty Income Account - _TC",
+ }
+ )
loan.save()
return loan
+
def create_demand_loan(applicant, loan_type, loan_application, posting_date=None):
- loan = frappe.get_doc({
- "doctype": "Loan",
- "company": "_Test Company",
- "applicant_type": "Customer",
- "posting_date": posting_date or nowdate(),
- 'loan_application': loan_application,
- "applicant": applicant,
- "loan_type": loan_type,
- "is_term_loan": 0,
- "is_secured_loan": 1,
- "mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
- "payment_account": 'Payment Account - _TC',
- "loan_account": 'Loan Account - _TC',
- "interest_income_account": 'Interest Income Account - _TC',
- "penalty_income_account": 'Penalty Income Account - _TC',
- })
+ loan = frappe.get_doc(
+ {
+ "doctype": "Loan",
+ "company": "_Test Company",
+ "applicant_type": "Customer",
+ "posting_date": posting_date or nowdate(),
+ "loan_application": loan_application,
+ "applicant": applicant,
+ "loan_type": loan_type,
+ "is_term_loan": 0,
+ "is_secured_loan": 1,
+ "mode_of_payment": frappe.db.get_value("Mode of Payment", {"type": "Cash"}, "name"),
+ "payment_account": "Payment Account - _TC",
+ "loan_account": "Loan Account - _TC",
+ "interest_income_account": "Interest Income Account - _TC",
+ "penalty_income_account": "Penalty Income Account - _TC",
+ }
+ )
loan.save()
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index a8ffcb95ff..5f040e22c9 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -29,8 +29,13 @@ class LoanApplication(Document):
self.validate_loan_amount()
if self.is_term_loan:
- validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount,
- self.repayment_periods, self.is_term_loan)
+ validate_repayment_method(
+ self.repayment_method,
+ self.loan_amount,
+ self.repayment_amount,
+ self.repayment_periods,
+ self.is_term_loan,
+ )
self.validate_loan_type()
@@ -46,21 +51,35 @@ class LoanApplication(Document):
if not self.loan_amount:
frappe.throw(_("Loan Amount is mandatory"))
- maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount')
+ maximum_loan_limit = frappe.db.get_value("Loan Type", self.loan_type, "maximum_loan_amount")
if maximum_loan_limit and self.loan_amount > maximum_loan_limit:
- frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit))
+ frappe.throw(
+ _("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit)
+ )
if self.maximum_loan_amount and self.loan_amount > self.maximum_loan_amount:
- frappe.throw(_("Loan Amount exceeds maximum loan amount of {0} as per proposed securities").format(self.maximum_loan_amount))
+ frappe.throw(
+ _("Loan Amount exceeds maximum loan amount of {0} as per proposed securities").format(
+ self.maximum_loan_amount
+ )
+ )
def check_sanctioned_amount_limit(self):
- sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
+ sanctioned_amount_limit = get_sanctioned_amount_limit(
+ self.applicant_type, self.applicant, self.company
+ )
if sanctioned_amount_limit:
total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
- if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
- frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
+ if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(
+ sanctioned_amount_limit
+ ):
+ frappe.throw(
+ _("Sanctioned Amount limit crossed for {0} {1}").format(
+ self.applicant_type, frappe.bold(self.applicant)
+ )
+ )
def set_pledge_amount(self):
for proposed_pledge in self.proposed_pledges:
@@ -71,26 +90,31 @@ class LoanApplication(Document):
proposed_pledge.loan_security_price = get_loan_security_price(proposed_pledge.loan_security)
if not proposed_pledge.qty:
- proposed_pledge.qty = cint(proposed_pledge.amount/proposed_pledge.loan_security_price)
+ proposed_pledge.qty = cint(proposed_pledge.amount / proposed_pledge.loan_security_price)
proposed_pledge.amount = proposed_pledge.qty * proposed_pledge.loan_security_price
- proposed_pledge.post_haircut_amount = cint(proposed_pledge.amount - (proposed_pledge.amount * proposed_pledge.haircut/100))
+ proposed_pledge.post_haircut_amount = cint(
+ proposed_pledge.amount - (proposed_pledge.amount * proposed_pledge.haircut / 100)
+ )
def get_repayment_details(self):
if self.is_term_loan:
if self.repayment_method == "Repay Over Number of Periods":
- self.repayment_amount = get_monthly_repayment_amount(self.loan_amount, self.rate_of_interest, self.repayment_periods)
+ self.repayment_amount = get_monthly_repayment_amount(
+ self.loan_amount, self.rate_of_interest, self.repayment_periods
+ )
if self.repayment_method == "Repay Fixed Amount per Period":
- monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
+ monthly_interest_rate = flt(self.rate_of_interest) / (12 * 100)
if monthly_interest_rate:
- min_repayment_amount = self.loan_amount*monthly_interest_rate
+ min_repayment_amount = self.loan_amount * monthly_interest_rate
if self.repayment_amount - min_repayment_amount <= 0:
- frappe.throw(_("Repayment Amount must be greater than " \
- + str(flt(min_repayment_amount, 2))))
- self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
- math.log(self.repayment_amount - min_repayment_amount)) /(math.log(1 + monthly_interest_rate)))
+ frappe.throw(_("Repayment Amount must be greater than " + str(flt(min_repayment_amount, 2))))
+ self.repayment_periods = math.ceil(
+ (math.log(self.repayment_amount) - math.log(self.repayment_amount - min_repayment_amount))
+ / (math.log(1 + monthly_interest_rate))
+ )
else:
self.repayment_periods = self.loan_amount / self.repayment_amount
@@ -103,8 +127,8 @@ class LoanApplication(Document):
self.total_payable_amount = 0
self.total_payable_interest = 0
- while(balance_amount > 0):
- interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
+ while balance_amount > 0:
+ interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12 * 100))
balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
self.total_payable_interest += interest_amount
@@ -123,12 +147,21 @@ class LoanApplication(Document):
if not self.loan_amount and self.is_secured_loan and self.proposed_pledges:
self.loan_amount = self.maximum_loan_amount
+
@frappe.whitelist()
def create_loan(source_name, target_doc=None, submit=0):
def update_accounts(source_doc, target_doc, source_parent):
- account_details = frappe.get_all("Loan Type",
- fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"],
- filters = {'name': source_doc.loan_type})[0]
+ account_details = frappe.get_all(
+ "Loan Type",
+ fields=[
+ "mode_of_payment",
+ "payment_account",
+ "loan_account",
+ "interest_income_account",
+ "penalty_income_account",
+ ],
+ filters={"name": source_doc.loan_type},
+ )[0]
if source_doc.is_secured_loan:
target_doc.maximum_loan_amount = 0
@@ -140,22 +173,25 @@ def create_loan(source_name, target_doc=None, submit=0):
target_doc.penalty_income_account = account_details.penalty_income_account
target_doc.loan_application = source_name
-
- doclist = get_mapped_doc("Loan Application", source_name, {
- "Loan Application": {
- "doctype": "Loan",
- "validation": {
- "docstatus": ["=", 1]
- },
- "postprocess": update_accounts
- }
- }, target_doc)
+ doclist = get_mapped_doc(
+ "Loan Application",
+ source_name,
+ {
+ "Loan Application": {
+ "doctype": "Loan",
+ "validation": {"docstatus": ["=", 1]},
+ "postprocess": update_accounts,
+ }
+ },
+ target_doc,
+ )
if submit:
doclist.submit()
return doclist
+
@frappe.whitelist()
def create_pledge(loan_application, loan=None):
loan_application_doc = frappe.get_doc("Loan Application", loan_application)
@@ -171,12 +207,15 @@ def create_pledge(loan_application, loan=None):
for pledge in loan_application_doc.proposed_pledges:
- lsp.append('securities', {
- "loan_security": pledge.loan_security,
- "qty": pledge.qty,
- "loan_security_price": pledge.loan_security_price,
- "haircut": pledge.haircut
- })
+ lsp.append(
+ "securities",
+ {
+ "loan_security": pledge.loan_security,
+ "qty": pledge.qty,
+ "loan_security_price": pledge.loan_security_price,
+ "haircut": pledge.haircut,
+ },
+ )
lsp.save()
lsp.submit()
@@ -186,15 +225,14 @@ def create_pledge(loan_application, loan=None):
return lsp.name
-#This is a sandbox method to get the proposed pledges
+
+# This is a sandbox method to get the proposed pledges
@frappe.whitelist()
def get_proposed_pledge(securities):
if isinstance(securities, str):
securities = json.loads(securities)
- proposed_pledges = {
- 'securities': []
- }
+ proposed_pledges = {"securities": []}
maximum_loan_amount = 0
for security in securities:
@@ -205,15 +243,15 @@ def get_proposed_pledge(securities):
security.loan_security_price = get_loan_security_price(security.loan_security)
if not security.qty:
- security.qty = cint(security.amount/security.loan_security_price)
+ security.qty = cint(security.amount / security.loan_security_price)
security.amount = security.qty * security.loan_security_price
- security.post_haircut_amount = cint(security.amount - (security.amount * security.haircut/100))
+ security.post_haircut_amount = cint(security.amount - (security.amount * security.haircut / 100))
maximum_loan_amount += security.post_haircut_amount
- proposed_pledges['securities'].append(security)
+ proposed_pledges["securities"].append(security)
- proposed_pledges['maximum_loan_amount'] = maximum_loan_amount
+ proposed_pledges["maximum_loan_amount"] = maximum_loan_amount
return proposed_pledges
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py b/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py
index e8e2a2a0de..1d90e9bb11 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'loan_application',
- 'transactions': [
- {
- 'items': ['Loan', 'Loan Security Pledge']
- },
- ],
- }
+ return {
+ "fieldname": "loan_application",
+ "transactions": [
+ {"items": ["Loan", "Loan Security Pledge"]},
+ ],
+ }
diff --git a/erpnext/loan_management/doctype/loan_application/test_loan_application.py b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
index 640709c095..2a4bb882a8 100644
--- a/erpnext/loan_management/doctype/loan_application/test_loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
@@ -15,27 +15,45 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
class TestLoanApplication(unittest.TestCase):
def setUp(self):
create_loan_accounts()
- create_loan_type("Home Loan", 500000, 9.2, 0, 1, 0, 'Cash', 'Disbursement Account - _TC', 'Payment Account - _TC', 'Loan Account - _TC',
- 'Interest Income Account - _TC', 'Penalty Income Account - _TC', 'Repay Over Number of Periods', 18)
+ create_loan_type(
+ "Home Loan",
+ 500000,
+ 9.2,
+ 0,
+ 1,
+ 0,
+ "Cash",
+ "Disbursement Account - _TC",
+ "Payment Account - _TC",
+ "Loan Account - _TC",
+ "Interest Income Account - _TC",
+ "Penalty Income Account - _TC",
+ "Repay Over Number of Periods",
+ 18,
+ )
self.applicant = make_employee("kate_loan@loan.com", "_Test Company")
- make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant, currency='INR')
+ make_salary_structure(
+ "Test Salary Structure Loan", "Monthly", employee=self.applicant, currency="INR"
+ )
self.create_loan_application()
def create_loan_application(self):
loan_application = frappe.new_doc("Loan Application")
- loan_application.update({
- "applicant": self.applicant,
- "loan_type": "Home Loan",
- "rate_of_interest": 9.2,
- "loan_amount": 250000,
- "repayment_method": "Repay Over Number of Periods",
- "repayment_periods": 18,
- "company": "_Test Company"
- })
+ loan_application.update(
+ {
+ "applicant": self.applicant,
+ "loan_type": "Home Loan",
+ "rate_of_interest": 9.2,
+ "loan_amount": 250000,
+ "repayment_method": "Repay Over Number of Periods",
+ "repayment_periods": 18,
+ "company": "_Test Company",
+ }
+ )
loan_application.insert()
def test_loan_totals(self):
- loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
+ loan_application = frappe.get_doc("Loan Application", {"applicant": self.applicant})
self.assertEqual(loan_application.total_payable_interest, 18599)
self.assertEqual(loan_application.total_payable_amount, 268599)
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index 54a03b92b5..10174e531a 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -18,7 +18,6 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
class LoanDisbursement(AccountsController):
-
def validate(self):
self.set_missing_values()
self.validate_disbursal_amount()
@@ -30,7 +29,7 @@ class LoanDisbursement(AccountsController):
def on_cancel(self):
self.set_status_and_amounts(cancel=1)
self.make_gl_entries(cancel=1)
- self.ignore_linked_doctypes = ['GL Entry']
+ self.ignore_linked_doctypes = ["GL Entry"]
def set_missing_values(self):
if not self.disbursement_date:
@@ -49,21 +48,36 @@ class LoanDisbursement(AccountsController):
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount))
def set_status_and_amounts(self, cancel=0):
- loan_details = frappe.get_all("Loan",
- fields = ["loan_amount", "disbursed_amount", "total_payment", "total_principal_paid", "total_interest_payable",
- "status", "is_term_loan", "is_secured_loan"], filters= { "name": self.against_loan })[0]
+ loan_details = frappe.get_all(
+ "Loan",
+ fields=[
+ "loan_amount",
+ "disbursed_amount",
+ "total_payment",
+ "total_principal_paid",
+ "total_interest_payable",
+ "status",
+ "is_term_loan",
+ "is_secured_loan",
+ ],
+ filters={"name": self.against_loan},
+ )[0]
if cancel:
disbursed_amount, status, total_payment = self.get_values_on_cancel(loan_details)
else:
disbursed_amount, status, total_payment = self.get_values_on_submit(loan_details)
- frappe.db.set_value("Loan", self.against_loan, {
- "disbursement_date": self.disbursement_date,
- "disbursed_amount": disbursed_amount,
- "status": status,
- "total_payment": total_payment
- })
+ frappe.db.set_value(
+ "Loan",
+ self.against_loan,
+ {
+ "disbursement_date": self.disbursement_date,
+ "disbursed_amount": disbursed_amount,
+ "status": status,
+ "total_payment": total_payment,
+ },
+ )
def get_values_on_cancel(self, loan_details):
disbursed_amount = loan_details.disbursed_amount - self.disbursed_amount
@@ -91,8 +105,11 @@ class LoanDisbursement(AccountsController):
total_payment = loan_details.total_payment
if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan:
- process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
- loan=self.against_loan, accrual_type="Disbursement")
+ process_loan_interest_accrual_for_demand_loans(
+ posting_date=add_days(self.disbursement_date, -1),
+ loan=self.against_loan,
+ accrual_type="Disbursement",
+ )
if disbursed_amount > loan_details.loan_amount:
topup_amount = disbursed_amount - loan_details.loan_amount
@@ -116,72 +133,96 @@ class LoanDisbursement(AccountsController):
gle_map = []
gle_map.append(
- self.get_gl_dict({
- "account": self.loan_account,
- "against": self.disbursement_account,
- "debit": self.disbursed_amount,
- "debit_in_account_currency": self.disbursed_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": _("Disbursement against loan:") + self.against_loan,
- "cost_center": self.cost_center,
- "party_type": self.applicant_type,
- "party": self.applicant,
- "posting_date": self.disbursement_date
- })
+ self.get_gl_dict(
+ {
+ "account": self.loan_account,
+ "against": self.disbursement_account,
+ "debit": self.disbursed_amount,
+ "debit_in_account_currency": self.disbursed_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": _("Disbursement against loan:") + self.against_loan,
+ "cost_center": self.cost_center,
+ "party_type": self.applicant_type,
+ "party": self.applicant,
+ "posting_date": self.disbursement_date,
+ }
+ )
)
gle_map.append(
- self.get_gl_dict({
- "account": self.disbursement_account,
- "against": self.loan_account,
- "credit": self.disbursed_amount,
- "credit_in_account_currency": self.disbursed_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": _("Disbursement against loan:") + self.against_loan,
- "cost_center": self.cost_center,
- "posting_date": self.disbursement_date
- })
+ self.get_gl_dict(
+ {
+ "account": self.disbursement_account,
+ "against": self.loan_account,
+ "credit": self.disbursed_amount,
+ "credit_in_account_currency": self.disbursed_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": _("Disbursement against loan:") + self.against_loan,
+ "cost_center": self.cost_center,
+ "posting_date": self.disbursement_date,
+ }
+ )
)
if gle_map:
make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj)
+
def get_total_pledged_security_value(loan):
update_time = get_datetime()
- loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price",
- fields=["loan_security", "loan_security_price"],
- filters = {
- "valid_from": ("<=", update_time),
- "valid_upto": (">=", update_time)
- }, as_list=1))
+ loan_security_price_map = frappe._dict(
+ frappe.get_all(
+ "Loan Security Price",
+ fields=["loan_security", "loan_security_price"],
+ filters={"valid_from": ("<=", update_time), "valid_upto": (">=", update_time)},
+ as_list=1,
+ )
+ )
- hair_cut_map = frappe._dict(frappe.get_all('Loan Security',
- fields=["name", "haircut"], as_list=1))
+ hair_cut_map = frappe._dict(
+ frappe.get_all("Loan Security", fields=["name", "haircut"], as_list=1)
+ )
security_value = 0.0
pledged_securities = get_pledged_security_qty(loan)
for security, qty in pledged_securities.items():
after_haircut_percentage = 100 - hair_cut_map.get(security)
- security_value += (loan_security_price_map.get(security) * qty * after_haircut_percentage)/100
+ security_value += (loan_security_price_map.get(security) * qty * after_haircut_percentage) / 100
return security_value
+
@frappe.whitelist()
def get_disbursal_amount(loan, on_current_security_price=0):
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
get_pending_principal_amount,
)
- loan_details = frappe.get_value("Loan", loan, ["loan_amount", "disbursed_amount", "total_payment",
- "total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan",
- "maximum_loan_amount", "written_off_amount"], as_dict=1)
+ loan_details = frappe.get_value(
+ "Loan",
+ loan,
+ [
+ "loan_amount",
+ "disbursed_amount",
+ "total_payment",
+ "total_principal_paid",
+ "total_interest_payable",
+ "status",
+ "is_term_loan",
+ "is_secured_loan",
+ "maximum_loan_amount",
+ "written_off_amount",
+ ],
+ as_dict=1,
+ )
- if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
- 'status': 'Pending'}):
+ if loan_details.is_secured_loan and frappe.get_all(
+ "Loan Security Shortfall", filters={"loan": loan, "status": "Pending"}
+ ):
return 0
pending_principal_amount = get_pending_principal_amount(loan_details)
@@ -198,10 +239,14 @@ def get_disbursal_amount(loan, on_current_security_price=0):
disbursal_amount = flt(security_value) - flt(pending_principal_amount)
- if loan_details.is_term_loan and (disbursal_amount + loan_details.loan_amount) > loan_details.loan_amount:
+ if (
+ loan_details.is_term_loan
+ and (disbursal_amount + loan_details.loan_amount) > loan_details.loan_amount
+ ):
disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount
return disbursal_amount
+
def get_maximum_amount_as_per_pledged_security(loan):
- return flt(frappe.db.get_value('Loan Security Pledge', {'loan': loan}, 'sum(maximum_loan_value)'))
+ return flt(frappe.db.get_value("Loan Security Pledge", {"loan": loan}, "sum(maximum_loan_value)"))
diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
index 10be750b44..4daa2edb28 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
@@ -40,34 +40,50 @@ from erpnext.selling.doctype.customer.test_customer import get_customer_dict
class TestLoanDisbursement(unittest.TestCase):
-
def setUp(self):
create_loan_accounts()
- create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Disbursement Account - _TC',
- 'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+ create_loan_type(
+ "Demand Loan",
+ 2000000,
+ 13.5,
+ 25,
+ 0,
+ 5,
+ "Cash",
+ "Disbursement Account - _TC",
+ "Payment Account - _TC",
+ "Loan Account - _TC",
+ "Interest Income Account - _TC",
+ "Penalty Income Account - _TC",
+ )
create_loan_security_type()
create_loan_security()
- create_loan_security_price("Test Security 1", 500, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
- create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
+ create_loan_security_price(
+ "Test Security 1", 500, "Nos", get_datetime(), get_datetime(add_to_date(nowdate(), hours=24))
+ )
+ create_loan_security_price(
+ "Test Security 2", 250, "Nos", get_datetime(), get_datetime(add_to_date(nowdate(), hours=24))
+ )
if not frappe.db.exists("Customer", "_Test Loan Customer"):
- frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
+ frappe.get_doc(get_customer_dict("_Test Loan Customer")).insert(ignore_permissions=True)
- self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
+ self.applicant = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name")
def test_loan_topup(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
+ loan = create_demand_loan(
+ self.applicant, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())
+ )
loan.submit()
@@ -76,18 +92,22 @@ class TestLoanDisbursement(unittest.TestCase):
no_of_days = date_diff(last_date, first_date) + 1
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime().year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime().year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(last_date, 1))
# Should not be able to create loan disbursement entry before repayment
- self.assertRaises(frappe.ValidationError, make_loan_disbursement_entry, loan.name,
- 500000, first_date)
+ self.assertRaises(
+ frappe.ValidationError, make_loan_disbursement_entry, loan.name, 500000, first_date
+ )
- repayment_entry = create_repayment_entry(loan.name, self.applicant, add_days(get_last_day(nowdate()), 5), 611095.89)
+ repayment_entry = create_repayment_entry(
+ loan.name, self.applicant, add_days(get_last_day(nowdate()), 5), 611095.89
+ )
repayment_entry.submit()
loan.reload()
@@ -96,49 +116,48 @@ class TestLoanDisbursement(unittest.TestCase):
make_loan_disbursement_entry(loan.name, 500000, disbursement_date=add_days(last_date, 16))
# check for disbursement accrual
- loan_interest_accrual = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name,
- 'accrual_type': 'Disbursement'})
+ loan_interest_accrual = frappe.db.get_value(
+ "Loan Interest Accrual", {"loan": loan.name, "accrual_type": "Disbursement"}
+ )
self.assertTrue(loan_interest_accrual)
def test_loan_topup_with_additional_pledge(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan = create_demand_loan(
+ self.applicant, "Demand Loan", loan_application, posting_date="2019-10-01"
+ )
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
# Disbursed 10,00,000 amount
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
amounts = calculate_amounts(loan.name, add_days(last_date, 1))
- previous_interest = amounts['interest_amount']
+ previous_interest = amounts["interest_amount"]
- pledge1 = [{
- "loan_security": "Test Security 1",
- "qty": 2000.00
- }]
+ pledge1 = [{"loan_security": "Test Security 1", "qty": 2000.00}]
create_loan_security_pledge(self.applicant, pledge1, loan=loan.name)
# Topup 500000
make_loan_disbursement_entry(loan.name, 500000, disbursement_date=add_days(last_date, 1))
- process_loan_interest_accrual_for_demand_loans(posting_date = add_days(last_date, 15))
+ process_loan_interest_accrual_for_demand_loans(posting_date=add_days(last_date, 15))
amounts = calculate_amounts(loan.name, add_days(last_date, 15))
- per_day_interest = get_per_day_interest(1500000, 13.5, '2019-10-30')
+ per_day_interest = get_per_day_interest(1500000, 13.5, "2019-10-30")
interest = per_day_interest * 15
- self.assertEqual(amounts['pending_principal_amount'], 1500000)
- self.assertEqual(amounts['interest_amount'], flt(interest + previous_interest, 2))
+ self.assertEqual(amounts["pending_principal_amount"], 1500000)
+ self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2))
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index f6a3ededcb..3a4c6513e4 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -32,47 +32,53 @@ class LoanInterestAccrual(AccountsController):
self.update_is_accrued()
self.make_gl_entries(cancel=1)
- self.ignore_linked_doctypes = ['GL Entry']
+ self.ignore_linked_doctypes = ["GL Entry"]
def update_is_accrued(self):
- frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0)
+ frappe.db.set_value("Repayment Schedule", self.repayment_schedule_name, "is_accrued", 0)
def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = []
- cost_center = frappe.db.get_value('Loan', self.loan, 'cost_center')
+ cost_center = frappe.db.get_value("Loan", self.loan, "cost_center")
if self.interest_amount:
gle_map.append(
- self.get_gl_dict({
- "account": self.loan_account,
- "party_type": self.applicant_type,
- "party": self.applicant,
- "against": self.interest_income_account,
- "debit": self.interest_amount,
- "debit_in_account_currency": self.interest_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.loan,
- "remarks": _("Interest accrued from {0} to {1} against loan: {2}").format(
- self.last_accrual_date, self.posting_date, self.loan),
- "cost_center": cost_center,
- "posting_date": self.posting_date
- })
+ self.get_gl_dict(
+ {
+ "account": self.loan_account,
+ "party_type": self.applicant_type,
+ "party": self.applicant,
+ "against": self.interest_income_account,
+ "debit": self.interest_amount,
+ "debit_in_account_currency": self.interest_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.loan,
+ "remarks": _("Interest accrued from {0} to {1} against loan: {2}").format(
+ self.last_accrual_date, self.posting_date, self.loan
+ ),
+ "cost_center": cost_center,
+ "posting_date": self.posting_date,
+ }
+ )
)
gle_map.append(
- self.get_gl_dict({
- "account": self.interest_income_account,
- "against": self.loan_account,
- "credit": self.interest_amount,
- "credit_in_account_currency": self.interest_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.loan,
- "remarks": ("Interest accrued from {0} to {1} against loan: {2}").format(
- self.last_accrual_date, self.posting_date, self.loan),
- "cost_center": cost_center,
- "posting_date": self.posting_date
- })
+ self.get_gl_dict(
+ {
+ "account": self.interest_income_account,
+ "against": self.loan_account,
+ "credit": self.interest_amount,
+ "credit_in_account_currency": self.interest_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.loan,
+ "remarks": ("Interest accrued from {0} to {1} against loan: {2}").format(
+ self.last_accrual_date, self.posting_date, self.loan
+ ),
+ "cost_center": cost_center,
+ "posting_date": self.posting_date,
+ }
+ )
)
if gle_map:
@@ -82,7 +88,9 @@ class LoanInterestAccrual(AccountsController):
# For Eg: If Loan disbursement date is '01-09-2019' and disbursed amount is 1000000 and
# rate of interest is 13.5 then first loan interest accural will be on '01-10-2019'
# which means interest will be accrued for 30 days which should be equal to 11095.89
-def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest, accrual_type):
+def calculate_accrual_amount_for_demand_loans(
+ loan, posting_date, process_loan_interest, accrual_type
+):
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
calculate_amounts,
get_pending_principal_amount,
@@ -96,51 +104,76 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
pending_principal_amount = get_pending_principal_amount(loan)
- interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date)
+ interest_per_day = get_per_day_interest(
+ pending_principal_amount, loan.rate_of_interest, posting_date
+ )
payable_interest = interest_per_day * no_of_days
- pending_amounts = calculate_amounts(loan.name, posting_date, payment_type='Loan Closure')
+ pending_amounts = calculate_amounts(loan.name, posting_date, payment_type="Loan Closure")
- args = frappe._dict({
- 'loan': loan.name,
- 'applicant_type': loan.applicant_type,
- 'applicant': loan.applicant,
- 'interest_income_account': loan.interest_income_account,
- 'loan_account': loan.loan_account,
- 'pending_principal_amount': pending_principal_amount,
- 'interest_amount': payable_interest,
- 'total_pending_interest_amount': pending_amounts['interest_amount'],
- 'penalty_amount': pending_amounts['penalty_amount'],
- 'process_loan_interest': process_loan_interest,
- 'posting_date': posting_date,
- 'accrual_type': accrual_type
- })
+ args = frappe._dict(
+ {
+ "loan": loan.name,
+ "applicant_type": loan.applicant_type,
+ "applicant": loan.applicant,
+ "interest_income_account": loan.interest_income_account,
+ "loan_account": loan.loan_account,
+ "pending_principal_amount": pending_principal_amount,
+ "interest_amount": payable_interest,
+ "total_pending_interest_amount": pending_amounts["interest_amount"],
+ "penalty_amount": pending_amounts["penalty_amount"],
+ "process_loan_interest": process_loan_interest,
+ "posting_date": posting_date,
+ "accrual_type": accrual_type,
+ }
+ )
if flt(payable_interest, precision) > 0.0:
make_loan_interest_accrual_entry(args)
-def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"):
- query_filters = {
- "status": ('in', ['Disbursed', 'Partially Disbursed']),
- "docstatus": 1
- }
+
+def make_accrual_interest_entry_for_demand_loans(
+ posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"
+):
+ query_filters = {"status": ("in", ["Disbursed", "Partially Disbursed"]), "docstatus": 1}
if loan_type:
- query_filters.update({
- "loan_type": loan_type
- })
+ query_filters.update({"loan_type": loan_type})
if not open_loans:
- open_loans = frappe.get_all("Loan",
- fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account", "loan_amount",
- "is_term_loan", "status", "disbursement_date", "disbursed_amount", "applicant_type", "applicant",
- "rate_of_interest", "total_interest_payable", "written_off_amount", "total_principal_paid", "repayment_start_date"],
- filters=query_filters)
+ open_loans = frappe.get_all(
+ "Loan",
+ fields=[
+ "name",
+ "total_payment",
+ "total_amount_paid",
+ "loan_account",
+ "interest_income_account",
+ "loan_amount",
+ "is_term_loan",
+ "status",
+ "disbursement_date",
+ "disbursed_amount",
+ "applicant_type",
+ "applicant",
+ "rate_of_interest",
+ "total_interest_payable",
+ "written_off_amount",
+ "total_principal_paid",
+ "repayment_start_date",
+ ],
+ filters=query_filters,
+ )
for loan in open_loans:
- calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest, accrual_type)
+ calculate_accrual_amount_for_demand_loans(
+ loan, posting_date, process_loan_interest, accrual_type
+ )
-def make_accrual_interest_entry_for_term_loans(posting_date, process_loan_interest, term_loan=None, loan_type=None, accrual_type="Regular"):
+
+def make_accrual_interest_entry_for_term_loans(
+ posting_date, process_loan_interest, term_loan=None, loan_type=None, accrual_type="Regular"
+):
curr_date = posting_date or add_days(nowdate(), 1)
term_loans = get_term_loans(curr_date, term_loan, loan_type)
@@ -149,37 +182,44 @@ def make_accrual_interest_entry_for_term_loans(posting_date, process_loan_intere
for loan in term_loans:
accrued_entries.append(loan.payment_entry)
- args = frappe._dict({
- 'loan': loan.name,
- 'applicant_type': loan.applicant_type,
- 'applicant': loan.applicant,
- 'interest_income_account': loan.interest_income_account,
- 'loan_account': loan.loan_account,
- 'interest_amount': loan.interest_amount,
- 'payable_principal': loan.principal_amount,
- 'process_loan_interest': process_loan_interest,
- 'repayment_schedule_name': loan.payment_entry,
- 'posting_date': posting_date,
- 'accrual_type': accrual_type
- })
+ args = frappe._dict(
+ {
+ "loan": loan.name,
+ "applicant_type": loan.applicant_type,
+ "applicant": loan.applicant,
+ "interest_income_account": loan.interest_income_account,
+ "loan_account": loan.loan_account,
+ "interest_amount": loan.interest_amount,
+ "payable_principal": loan.principal_amount,
+ "process_loan_interest": process_loan_interest,
+ "repayment_schedule_name": loan.payment_entry,
+ "posting_date": posting_date,
+ "accrual_type": accrual_type,
+ }
+ )
make_loan_interest_accrual_entry(args)
if accrued_entries:
- frappe.db.sql("""UPDATE `tabRepayment Schedule`
- SET is_accrued = 1 where name in (%s)""" #nosec
- % ", ".join(['%s']*len(accrued_entries)), tuple(accrued_entries))
+ frappe.db.sql(
+ """UPDATE `tabRepayment Schedule`
+ SET is_accrued = 1 where name in (%s)""" # nosec
+ % ", ".join(["%s"] * len(accrued_entries)),
+ tuple(accrued_entries),
+ )
+
def get_term_loans(date, term_loan=None, loan_type=None):
- condition = ''
+ condition = ""
if term_loan:
- condition +=' AND l.name = %s' % frappe.db.escape(term_loan)
+ condition += " AND l.name = %s" % frappe.db.escape(term_loan)
if loan_type:
- condition += ' AND l.loan_type = %s' % frappe.db.escape(loan_type)
+ condition += " AND l.loan_type = %s" % frappe.db.escape(loan_type)
- term_loans = frappe.db.sql("""SELECT l.name, l.total_payment, l.total_amount_paid, l.loan_account,
+ term_loans = frappe.db.sql(
+ """SELECT l.name, l.total_payment, l.total_amount_paid, l.loan_account,
l.interest_income_account, l.is_term_loan, l.disbursement_date, l.applicant_type, l.applicant,
l.rate_of_interest, l.total_interest_payable, l.repayment_start_date, rs.name as payment_entry,
rs.payment_date, rs.principal_amount, rs.interest_amount, rs.is_accrued , rs.balance_loan_amount
@@ -190,10 +230,16 @@ def get_term_loans(date, term_loan=None, loan_type=None):
AND rs.payment_date <= %s
AND rs.is_accrued=0 {0}
AND l.status = 'Disbursed'
- ORDER BY rs.payment_date""".format(condition), (getdate(date)), as_dict=1)
+ ORDER BY rs.payment_date""".format(
+ condition
+ ),
+ (getdate(date)),
+ as_dict=1,
+ )
return term_loans
+
def make_loan_interest_accrual_entry(args):
precision = cint(frappe.db.get_default("currency_precision")) or 2
@@ -205,7 +251,9 @@ def make_loan_interest_accrual_entry(args):
loan_interest_accrual.loan_account = args.loan_account
loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
- loan_interest_accrual.total_pending_interest_amount = flt(args.total_pending_interest_amount, precision)
+ loan_interest_accrual.total_pending_interest_amount = flt(
+ args.total_pending_interest_amount, precision
+ )
loan_interest_accrual.penalty_amount = flt(args.penalty_amount, precision)
loan_interest_accrual.posting_date = args.posting_date or nowdate()
loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
@@ -224,15 +272,20 @@ def get_no_of_days_for_interest_accural(loan, posting_date):
return no_of_days
+
def get_last_accrual_date(loan):
- last_posting_date = frappe.db.sql(""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
- WHERE loan = %s and docstatus = 1""", (loan))
+ last_posting_date = frappe.db.sql(
+ """ SELECT MAX(posting_date) from `tabLoan Interest Accrual`
+ WHERE loan = %s and docstatus = 1""",
+ (loan),
+ )
if last_posting_date[0][0]:
# interest for last interest accrual date is already booked, so add 1 day
return add_days(last_posting_date[0][0], 1)
else:
- return frappe.db.get_value('Loan', loan, 'disbursement_date')
+ return frappe.db.get_value("Loan", loan, "disbursement_date")
+
def days_in_year(year):
days = 365
@@ -242,8 +295,11 @@ def days_in_year(year):
return days
+
def get_per_day_interest(principal_amount, rate_of_interest, posting_date=None):
if not posting_date:
posting_date = getdate()
- return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100))
+ return flt(
+ (principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)
+ )
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
index e8c77506fc..fd59393b82 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
@@ -30,78 +30,98 @@ class TestLoanInterestAccrual(unittest.TestCase):
def setUp(self):
create_loan_accounts()
- create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Disbursement Account - _TC',
- 'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+ create_loan_type(
+ "Demand Loan",
+ 2000000,
+ 13.5,
+ 25,
+ 0,
+ 5,
+ "Cash",
+ "Disbursement Account - _TC",
+ "Payment Account - _TC",
+ "Loan Account - _TC",
+ "Interest Income Account - _TC",
+ "Penalty Income Account - _TC",
+ )
create_loan_security_type()
create_loan_security()
- create_loan_security_price("Test Security 1", 500, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
+ create_loan_security_price(
+ "Test Security 1", 500, "Nos", get_datetime(), get_datetime(add_to_date(nowdate(), hours=24))
+ )
if not frappe.db.exists("Customer", "_Test Loan Customer"):
- frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
+ frappe.get_doc(get_customer_dict("_Test Loan Customer")).insert(ignore_permissions=True)
- self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
+ self.applicant = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name")
def test_loan_interest_accural(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
- posting_date=get_first_day(nowdate()))
+ loan = create_demand_loan(
+ self.applicant, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())
+ )
loan.submit()
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
- loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
+ loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {"loan": loan.name})
self.assertEqual(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
def test_accumulated_amounts(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
+ pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
- loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
+ loan_application = create_loan_application(
+ "_Test Company", self.applicant, "Demand Loan", pledge
+ )
create_pledge(loan_application)
- loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
- posting_date=get_first_day(nowdate()))
+ loan = create_demand_loan(
+ self.applicant, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())
+ )
loan.submit()
- first_date = '2019-10-01'
- last_date = '2019-10-30'
+ first_date = "2019-10-01"
+ last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
- accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
- loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
+ loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {"loan": loan.name})
self.assertEqual(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
- next_start_date = '2019-10-31'
- next_end_date = '2019-11-29'
+ next_start_date = "2019-10-31"
+ next_end_date = "2019-11-29"
no_of_days = date_diff(next_end_date, next_start_date) + 1
process = process_loan_interest_accrual_for_demand_loans(posting_date=next_end_date)
- new_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
- / (days_in_year(get_datetime(first_date).year) * 100)
+ new_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
+ days_in_year(get_datetime(first_date).year) * 100
+ )
total_pending_interest_amount = flt(accrued_interest_amount + new_accrued_interest_amount, 0)
- loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name,
- 'process_loan_interest_accrual': process})
- self.assertEqual(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
+ loan_interest_accrual = frappe.get_doc(
+ "Loan Interest Accrual", {"loan": loan.name, "process_loan_interest_accrual": process}
+ )
+ self.assertEqual(
+ flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount
+ )
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 67c2b1ee14..8cffe88f32 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -22,7 +22,6 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
class LoanRepayment(AccountsController):
-
def validate(self):
amounts = calculate_amounts(self.against_loan, self.posting_date)
self.set_missing_values(amounts)
@@ -42,7 +41,7 @@ class LoanRepayment(AccountsController):
self.check_future_accruals()
self.update_repayment_schedule(cancel=1)
self.mark_as_unpaid()
- self.ignore_linked_doctypes = ['GL Entry']
+ self.ignore_linked_doctypes = ["GL Entry"]
self.make_gl_entries(cancel=1)
def set_missing_values(self, amounts):
@@ -55,32 +54,38 @@ class LoanRepayment(AccountsController):
self.cost_center = erpnext.get_default_cost_center(self.company)
if not self.interest_payable:
- self.interest_payable = flt(amounts['interest_amount'], precision)
+ self.interest_payable = flt(amounts["interest_amount"], precision)
if not self.penalty_amount:
- self.penalty_amount = flt(amounts['penalty_amount'], precision)
+ self.penalty_amount = flt(amounts["penalty_amount"], precision)
if not self.pending_principal_amount:
- self.pending_principal_amount = flt(amounts['pending_principal_amount'], precision)
+ self.pending_principal_amount = flt(amounts["pending_principal_amount"], precision)
if not self.payable_principal_amount and self.is_term_loan:
- self.payable_principal_amount = flt(amounts['payable_principal_amount'], precision)
+ self.payable_principal_amount = flt(amounts["payable_principal_amount"], precision)
if not self.payable_amount:
- self.payable_amount = flt(amounts['payable_amount'], precision)
+ self.payable_amount = flt(amounts["payable_amount"], precision)
- shortfall_amount = flt(frappe.db.get_value('Loan Security Shortfall', {'loan': self.against_loan, 'status': 'Pending'},
- 'shortfall_amount'))
+ shortfall_amount = flt(
+ frappe.db.get_value(
+ "Loan Security Shortfall", {"loan": self.against_loan, "status": "Pending"}, "shortfall_amount"
+ )
+ )
if shortfall_amount:
self.shortfall_amount = shortfall_amount
- if amounts.get('due_date'):
- self.due_date = amounts.get('due_date')
+ if amounts.get("due_date"):
+ self.due_date = amounts.get("due_date")
def check_future_entries(self):
- future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date),
- "docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
+ future_repayment_date = frappe.db.get_value(
+ "Loan Repayment",
+ {"posting_date": (">", self.posting_date), "docstatus": 1, "against_loan": self.against_loan},
+ "posting_date",
+ )
if future_repayment_date:
frappe.throw("Repayment already made till date {0}".format(get_datetime(future_repayment_date)))
@@ -99,106 +104,167 @@ class LoanRepayment(AccountsController):
last_accrual_date = get_last_accrual_date(self.against_loan)
# get posting date upto which interest has to be accrued
- per_day_interest = get_per_day_interest(self.pending_principal_amount,
- self.rate_of_interest, self.posting_date)
+ per_day_interest = get_per_day_interest(
+ self.pending_principal_amount, self.rate_of_interest, self.posting_date
+ )
- no_of_days = flt(flt(self.total_interest_paid - self.interest_payable,
- precision)/per_day_interest, 0) - 1
+ no_of_days = (
+ flt(flt(self.total_interest_paid - self.interest_payable, precision) / per_day_interest, 0)
+ - 1
+ )
posting_date = add_days(last_accrual_date, no_of_days)
# book excess interest paid
- process = process_loan_interest_accrual_for_demand_loans(posting_date=posting_date,
- loan=self.against_loan, accrual_type="Repayment")
+ process = process_loan_interest_accrual_for_demand_loans(
+ posting_date=posting_date, loan=self.against_loan, accrual_type="Repayment"
+ )
# get loan interest accrual to update paid amount
- lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual':
- process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1)
+ lia = frappe.db.get_value(
+ "Loan Interest Accrual",
+ {"process_loan_interest_accrual": process},
+ ["name", "interest_amount", "payable_principal_amount"],
+ as_dict=1,
+ )
if lia:
- self.append('repayment_details', {
- 'loan_interest_accrual': lia.name,
- 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision),
- 'paid_principal_amount': 0.0,
- 'accrual_type': 'Repayment'
- })
+ self.append(
+ "repayment_details",
+ {
+ "loan_interest_accrual": lia.name,
+ "paid_interest_amount": flt(self.total_interest_paid - self.interest_payable, precision),
+ "paid_principal_amount": 0.0,
+ "accrual_type": "Repayment",
+ },
+ )
def update_paid_amount(self):
- loan = frappe.get_value("Loan", self.against_loan, ['total_amount_paid', 'total_principal_paid',
- 'status', 'is_secured_loan', 'total_payment', 'loan_amount', 'disbursed_amount', 'total_interest_payable',
- 'written_off_amount'], as_dict=1)
+ loan = frappe.get_value(
+ "Loan",
+ self.against_loan,
+ [
+ "total_amount_paid",
+ "total_principal_paid",
+ "status",
+ "is_secured_loan",
+ "total_payment",
+ "loan_amount",
+ "disbursed_amount",
+ "total_interest_payable",
+ "written_off_amount",
+ ],
+ as_dict=1,
+ )
- loan.update({
- 'total_amount_paid': loan.total_amount_paid + self.amount_paid,
- 'total_principal_paid': loan.total_principal_paid + self.principal_amount_paid
- })
+ loan.update(
+ {
+ "total_amount_paid": loan.total_amount_paid + self.amount_paid,
+ "total_principal_paid": loan.total_principal_paid + self.principal_amount_paid,
+ }
+ )
pending_principal_amount = get_pending_principal_amount(loan)
if not loan.is_secured_loan and pending_principal_amount <= 0:
- loan.update({'status': 'Loan Closure Requested'})
+ loan.update({"status": "Loan Closure Requested"})
for payment in self.repayment_details:
- frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
+ frappe.db.sql(
+ """ UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
- (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
+ (
+ flt(payment.paid_principal_amount),
+ flt(payment.paid_interest_amount),
+ payment.loan_interest_accrual,
+ ),
+ )
- frappe.db.sql(""" UPDATE `tabLoan`
+ frappe.db.sql(
+ """ UPDATE `tabLoan`
SET total_amount_paid = %s, total_principal_paid = %s, status = %s
- WHERE name = %s """, (loan.total_amount_paid, loan.total_principal_paid, loan.status,
- self.against_loan))
+ WHERE name = %s """,
+ (loan.total_amount_paid, loan.total_principal_paid, loan.status, self.against_loan),
+ )
update_shortfall_status(self.against_loan, self.principal_amount_paid)
def mark_as_unpaid(self):
- loan = frappe.get_value("Loan", self.against_loan, ['total_amount_paid', 'total_principal_paid',
- 'status', 'is_secured_loan', 'total_payment', 'loan_amount', 'disbursed_amount', 'total_interest_payable',
- 'written_off_amount'], as_dict=1)
+ loan = frappe.get_value(
+ "Loan",
+ self.against_loan,
+ [
+ "total_amount_paid",
+ "total_principal_paid",
+ "status",
+ "is_secured_loan",
+ "total_payment",
+ "loan_amount",
+ "disbursed_amount",
+ "total_interest_payable",
+ "written_off_amount",
+ ],
+ as_dict=1,
+ )
no_of_repayments = len(self.repayment_details)
- loan.update({
- 'total_amount_paid': loan.total_amount_paid - self.amount_paid,
- 'total_principal_paid': loan.total_principal_paid - self.principal_amount_paid
- })
+ loan.update(
+ {
+ "total_amount_paid": loan.total_amount_paid - self.amount_paid,
+ "total_principal_paid": loan.total_principal_paid - self.principal_amount_paid,
+ }
+ )
- if loan.status == 'Loan Closure Requested':
+ if loan.status == "Loan Closure Requested":
if loan.disbursed_amount >= loan.loan_amount:
- loan['status'] = 'Disbursed'
+ loan["status"] = "Disbursed"
else:
- loan['status'] = 'Partially Disbursed'
+ loan["status"] = "Partially Disbursed"
for payment in self.repayment_details:
- frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
+ frappe.db.sql(
+ """ UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` - %s,
paid_interest_amount = `paid_interest_amount` - %s
WHERE name = %s""",
- (payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual))
+ (payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual),
+ )
# Cancel repayment interest accrual
# checking idx as a preventive measure, repayment accrual will always be the last entry
- if payment.accrual_type == 'Repayment' and payment.idx == no_of_repayments:
- lia_doc = frappe.get_doc('Loan Interest Accrual', payment.loan_interest_accrual)
+ if payment.accrual_type == "Repayment" and payment.idx == no_of_repayments:
+ lia_doc = frappe.get_doc("Loan Interest Accrual", payment.loan_interest_accrual)
lia_doc.cancel()
- frappe.db.sql(""" UPDATE `tabLoan`
+ frappe.db.sql(
+ """ UPDATE `tabLoan`
SET total_amount_paid = %s, total_principal_paid = %s, status = %s
- WHERE name = %s """, (loan.total_amount_paid, loan.total_principal_paid, loan.status, self.against_loan))
+ WHERE name = %s """,
+ (loan.total_amount_paid, loan.total_principal_paid, loan.status, self.against_loan),
+ )
def check_future_accruals(self):
- future_accrual_date = frappe.db.get_value("Loan Interest Accrual", {"posting_date": (">", self.posting_date),
- "docstatus": 1, "loan": self.against_loan}, 'posting_date')
+ future_accrual_date = frappe.db.get_value(
+ "Loan Interest Accrual",
+ {"posting_date": (">", self.posting_date), "docstatus": 1, "loan": self.against_loan},
+ "posting_date",
+ )
if future_accrual_date:
- frappe.throw("Cannot cancel. Interest accruals already processed till {0}".format(get_datetime(future_accrual_date)))
+ frappe.throw(
+ "Cannot cancel. Interest accruals already processed till {0}".format(
+ get_datetime(future_accrual_date)
+ )
+ )
def update_repayment_schedule(self, cancel=0):
if self.is_term_loan and self.principal_amount_paid > self.payable_principal_amount:
regenerate_repayment_schedule(self.against_loan, cancel)
def allocate_amounts(self, repayment_details):
- self.set('repayment_details', [])
+ self.set("repayment_details", [])
self.principal_amount_paid = 0
self.total_penalty_paid = 0
interest_paid = self.amount_paid
@@ -231,15 +297,15 @@ class LoanRepayment(AccountsController):
idx = 1
if interest_paid > 0:
- for lia, amounts in repayment_details.get('pending_accrual_entries', []).items():
+ for lia, amounts in repayment_details.get("pending_accrual_entries", []).items():
interest_amount = 0
- if amounts['interest_amount'] <= interest_paid:
- interest_amount = amounts['interest_amount']
+ if amounts["interest_amount"] <= interest_paid:
+ interest_amount = amounts["interest_amount"]
self.total_interest_paid += interest_amount
interest_paid -= interest_amount
elif interest_paid:
- if interest_paid >= amounts['interest_amount']:
- interest_amount = amounts['interest_amount']
+ if interest_paid >= amounts["interest_amount"]:
+ interest_amount = amounts["interest_amount"]
self.total_interest_paid += interest_amount
interest_paid = 0
else:
@@ -248,27 +314,32 @@ class LoanRepayment(AccountsController):
interest_paid = 0
if interest_amount:
- self.append('repayment_details', {
- 'loan_interest_accrual': lia,
- 'paid_interest_amount': interest_amount,
- 'paid_principal_amount': 0
- })
+ self.append(
+ "repayment_details",
+ {
+ "loan_interest_accrual": lia,
+ "paid_interest_amount": interest_amount,
+ "paid_principal_amount": 0,
+ },
+ )
updated_entries[lia] = idx
idx += 1
return interest_paid, updated_entries
- def allocate_principal_amount_for_term_loans(self, interest_paid, repayment_details, updated_entries):
+ def allocate_principal_amount_for_term_loans(
+ self, interest_paid, repayment_details, updated_entries
+ ):
if interest_paid > 0:
- for lia, amounts in repayment_details.get('pending_accrual_entries', []).items():
+ for lia, amounts in repayment_details.get("pending_accrual_entries", []).items():
paid_principal = 0
- if amounts['payable_principal_amount'] <= interest_paid:
- paid_principal = amounts['payable_principal_amount']
+ if amounts["payable_principal_amount"] <= interest_paid:
+ paid_principal = amounts["payable_principal_amount"]
self.principal_amount_paid += paid_principal
interest_paid -= paid_principal
elif interest_paid:
- if interest_paid >= amounts['payable_principal_amount']:
- paid_principal = amounts['payable_principal_amount']
+ if interest_paid >= amounts["payable_principal_amount"]:
+ paid_principal = amounts["payable_principal_amount"]
self.principal_amount_paid += paid_principal
interest_paid = 0
else:
@@ -278,30 +349,34 @@ class LoanRepayment(AccountsController):
if updated_entries.get(lia):
idx = updated_entries.get(lia)
- self.get('repayment_details')[idx-1].paid_principal_amount += paid_principal
+ self.get("repayment_details")[idx - 1].paid_principal_amount += paid_principal
else:
- self.append('repayment_details', {
- 'loan_interest_accrual': lia,
- 'paid_interest_amount': 0,
- 'paid_principal_amount': paid_principal
- })
+ self.append(
+ "repayment_details",
+ {
+ "loan_interest_accrual": lia,
+ "paid_interest_amount": 0,
+ "paid_principal_amount": paid_principal,
+ },
+ )
if interest_paid > 0:
self.principal_amount_paid += interest_paid
def allocate_excess_payment_for_demand_loans(self, interest_paid, repayment_details):
- if repayment_details['unaccrued_interest'] and interest_paid > 0:
+ if repayment_details["unaccrued_interest"] and interest_paid > 0:
# no of days for which to accrue interest
# Interest can only be accrued for an entire day and not partial
- if interest_paid > repayment_details['unaccrued_interest']:
- interest_paid -= repayment_details['unaccrued_interest']
- self.total_interest_paid += repayment_details['unaccrued_interest']
+ if interest_paid > repayment_details["unaccrued_interest"]:
+ interest_paid -= repayment_details["unaccrued_interest"]
+ self.total_interest_paid += repayment_details["unaccrued_interest"]
else:
# get no of days for which interest can be paid
- per_day_interest = get_per_day_interest(self.pending_principal_amount,
- self.rate_of_interest, self.posting_date)
+ per_day_interest = get_per_day_interest(
+ self.pending_principal_amount, self.rate_of_interest, self.posting_date
+ )
- no_of_days = cint(interest_paid/per_day_interest)
+ no_of_days = cint(interest_paid / per_day_interest)
self.total_interest_paid += no_of_days * per_day_interest
interest_paid -= no_of_days * per_day_interest
@@ -312,8 +387,9 @@ class LoanRepayment(AccountsController):
gle_map = []
if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
- remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount,
- self.against_loan)
+ remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(
+ self.shortfall_amount, self.against_loan
+ )
elif self.shortfall_amount:
remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount)
else:
@@ -326,91 +402,113 @@ class LoanRepayment(AccountsController):
if self.total_penalty_paid:
gle_map.append(
- self.get_gl_dict({
- "account": self.loan_account,
- "against": payment_account,
- "debit": self.total_penalty_paid,
- "debit_in_account_currency": self.total_penalty_paid,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": _("Penalty against loan:") + self.against_loan,
- "cost_center": self.cost_center,
- "party_type": self.applicant_type,
- "party": self.applicant,
- "posting_date": getdate(self.posting_date)
- })
+ self.get_gl_dict(
+ {
+ "account": self.loan_account,
+ "against": payment_account,
+ "debit": self.total_penalty_paid,
+ "debit_in_account_currency": self.total_penalty_paid,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": _("Penalty against loan:") + self.against_loan,
+ "cost_center": self.cost_center,
+ "party_type": self.applicant_type,
+ "party": self.applicant,
+ "posting_date": getdate(self.posting_date),
+ }
+ )
)
gle_map.append(
- self.get_gl_dict({
- "account": self.penalty_income_account,
- "against": self.loan_account,
- "credit": self.total_penalty_paid,
- "credit_in_account_currency": self.total_penalty_paid,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": _("Penalty against loan:") + self.against_loan,
- "cost_center": self.cost_center,
- "posting_date": getdate(self.posting_date)
- })
+ self.get_gl_dict(
+ {
+ "account": self.penalty_income_account,
+ "against": self.loan_account,
+ "credit": self.total_penalty_paid,
+ "credit_in_account_currency": self.total_penalty_paid,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": _("Penalty against loan:") + self.against_loan,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(self.posting_date),
+ }
+ )
)
gle_map.append(
- self.get_gl_dict({
- "account": payment_account,
- "against": self.loan_account + ", " + self.penalty_income_account,
- "debit": self.amount_paid,
- "debit_in_account_currency": self.amount_paid,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": remarks,
- "cost_center": self.cost_center,
- "posting_date": getdate(self.posting_date),
- "party_type": self.applicant_type if self.repay_from_salary else '',
- "party": self.applicant if self.repay_from_salary else ''
- })
+ self.get_gl_dict(
+ {
+ "account": payment_account,
+ "against": self.loan_account + ", " + self.penalty_income_account,
+ "debit": self.amount_paid,
+ "debit_in_account_currency": self.amount_paid,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": remarks,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(self.posting_date),
+ "party_type": self.applicant_type if self.repay_from_salary else "",
+ "party": self.applicant if self.repay_from_salary else "",
+ }
+ )
)
gle_map.append(
- self.get_gl_dict({
- "account": self.loan_account,
- "party_type": self.applicant_type,
- "party": self.applicant,
- "against": payment_account,
- "credit": self.amount_paid,
- "credit_in_account_currency": self.amount_paid,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": remarks,
- "cost_center": self.cost_center,
- "posting_date": getdate(self.posting_date)
- })
+ self.get_gl_dict(
+ {
+ "account": self.loan_account,
+ "party_type": self.applicant_type,
+ "party": self.applicant,
+ "against": payment_account,
+ "credit": self.amount_paid,
+ "credit_in_account_currency": self.amount_paid,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": remarks,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(self.posting_date),
+ }
+ )
)
if gle_map:
make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
-def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
- payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None,
- payroll_payable_account=None):
- lr = frappe.get_doc({
- "doctype": "Loan Repayment",
- "against_loan": loan,
- "payment_type": payment_type,
- "company": company,
- "posting_date": posting_date,
- "applicant": applicant,
- "penalty_amount": penalty_amount,
- "interest_payable": interest_payable,
- "payable_principal_amount": payable_principal_amount,
- "amount_paid": amount_paid,
- "loan_type": loan_type,
- "payroll_payable_account": payroll_payable_account
- }).insert()
+def create_repayment_entry(
+ loan,
+ applicant,
+ company,
+ posting_date,
+ loan_type,
+ payment_type,
+ interest_payable,
+ payable_principal_amount,
+ amount_paid,
+ penalty_amount=None,
+ payroll_payable_account=None,
+):
+
+ lr = frappe.get_doc(
+ {
+ "doctype": "Loan Repayment",
+ "against_loan": loan,
+ "payment_type": payment_type,
+ "company": company,
+ "posting_date": posting_date,
+ "applicant": applicant,
+ "penalty_amount": penalty_amount,
+ "interest_payable": interest_payable,
+ "payable_principal_amount": payable_principal_amount,
+ "amount_paid": amount_paid,
+ "loan_type": loan_type,
+ "payroll_payable_account": payroll_payable_account,
+ }
+ ).insert()
return lr
+
def get_accrued_interest_entries(against_loan, posting_date=None):
if not posting_date:
posting_date = getdate()
@@ -430,35 +528,43 @@ def get_accrued_interest_entries(against_loan, posting_date=None):
AND
docstatus = 1
ORDER BY posting_date
- """, (against_loan, posting_date), as_dict=1)
+ """,
+ (against_loan, posting_date),
+ as_dict=1,
+ )
return unpaid_accrued_entries
+
def get_penalty_details(against_loan):
- penalty_details = frappe.db.sql("""
+ penalty_details = frappe.db.sql(
+ """
SELECT posting_date, (penalty_amount - total_penalty_paid) as pending_penalty_amount
FROM `tabLoan Repayment` where posting_date >= (SELECT MAX(posting_date) from `tabLoan Repayment`
where against_loan = %s) and docstatus = 1 and against_loan = %s
- """, (against_loan, against_loan))
+ """,
+ (against_loan, against_loan),
+ )
if penalty_details:
return penalty_details[0][0], flt(penalty_details[0][1])
else:
return None, 0
+
def regenerate_repayment_schedule(loan, cancel=0):
from erpnext.loan_management.doctype.loan.loan import (
add_single_month,
get_monthly_repayment_amount,
)
- loan_doc = frappe.get_doc('Loan', loan)
+ loan_doc = frappe.get_doc("Loan", loan)
next_accrual_date = None
accrued_entries = 0
last_repayment_amount = 0
last_balance_amount = 0
- for term in reversed(loan_doc.get('repayment_schedule')):
+ for term in reversed(loan_doc.get("repayment_schedule")):
if not term.is_accrued:
next_accrual_date = term.payment_date
loan_doc.remove(term)
@@ -473,20 +579,23 @@ def regenerate_repayment_schedule(loan, cancel=0):
balance_amount = get_pending_principal_amount(loan_doc)
- if loan_doc.repayment_method == 'Repay Fixed Amount per Period':
- monthly_repayment_amount = flt(balance_amount/len(loan_doc.get('repayment_schedule')) - accrued_entries)
+ if loan_doc.repayment_method == "Repay Fixed Amount per Period":
+ monthly_repayment_amount = flt(
+ balance_amount / len(loan_doc.get("repayment_schedule")) - accrued_entries
+ )
else:
if not cancel:
- monthly_repayment_amount = get_monthly_repayment_amount(balance_amount,
- loan_doc.rate_of_interest, loan_doc.repayment_periods - accrued_entries)
+ monthly_repayment_amount = get_monthly_repayment_amount(
+ balance_amount, loan_doc.rate_of_interest, loan_doc.repayment_periods - accrued_entries
+ )
else:
monthly_repayment_amount = last_repayment_amount
balance_amount = last_balance_amount
payment_date = next_accrual_date
- while(balance_amount > 0):
- interest_amount = flt(balance_amount * flt(loan_doc.rate_of_interest) / (12*100))
+ while balance_amount > 0:
+ interest_amount = flt(balance_amount * flt(loan_doc.rate_of_interest) / (12 * 100))
principal_amount = monthly_repayment_amount - interest_amount
balance_amount = flt(balance_amount + interest_amount - monthly_repayment_amount)
if balance_amount < 0:
@@ -494,31 +603,45 @@ def regenerate_repayment_schedule(loan, cancel=0):
balance_amount = 0.0
total_payment = principal_amount + interest_amount
- loan_doc.append("repayment_schedule", {
- "payment_date": payment_date,
- "principal_amount": principal_amount,
- "interest_amount": interest_amount,
- "total_payment": total_payment,
- "balance_loan_amount": balance_amount
- })
+ loan_doc.append(
+ "repayment_schedule",
+ {
+ "payment_date": payment_date,
+ "principal_amount": principal_amount,
+ "interest_amount": interest_amount,
+ "total_payment": total_payment,
+ "balance_loan_amount": balance_amount,
+ },
+ )
next_payment_date = add_single_month(payment_date)
payment_date = next_payment_date
loan_doc.save()
+
def get_pending_principal_amount(loan):
- if loan.status in ('Disbursed', 'Closed') or loan.disbursed_amount >= loan.loan_amount:
- pending_principal_amount = flt(loan.total_payment) - flt(loan.total_principal_paid) \
- - flt(loan.total_interest_payable) - flt(loan.written_off_amount)
+ if loan.status in ("Disbursed", "Closed") or loan.disbursed_amount >= loan.loan_amount:
+ pending_principal_amount = (
+ flt(loan.total_payment)
+ - flt(loan.total_principal_paid)
+ - flt(loan.total_interest_payable)
+ - flt(loan.written_off_amount)
+ )
else:
- pending_principal_amount = flt(loan.disbursed_amount) - flt(loan.total_principal_paid) \
- - flt(loan.total_interest_payable) - flt(loan.written_off_amount)
+ pending_principal_amount = (
+ flt(loan.disbursed_amount)
+ - flt(loan.total_principal_paid)
+ - flt(loan.total_interest_payable)
+ - flt(loan.written_off_amount)
+ )
return pending_principal_amount
+
# This function returns the amounts that are payable at the time of loan repayment based on posting date
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
+
def get_amounts(amounts, against_loan, posting_date):
precision = cint(frappe.db.get_default("currency_precision")) or 2
@@ -532,8 +655,8 @@ def get_amounts(amounts, against_loan, posting_date):
total_pending_interest = 0
penalty_amount = 0
payable_principal_amount = 0
- final_due_date = ''
- due_date = ''
+ final_due_date = ""
+ due_date = ""
for entry in accrued_interest_entries:
# Loan repayment due date is one day after the loan interest is accrued
@@ -549,16 +672,25 @@ def get_amounts(amounts, against_loan, posting_date):
no_of_late_days = date_diff(posting_date, due_date_after_grace_period) + 1
- if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary) and entry.accrual_type == 'Regular':
- penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)
+ if (
+ no_of_late_days > 0
+ and (not against_loan_doc.repay_from_salary)
+ and entry.accrual_type == "Regular"
+ ):
+ penalty_amount += (
+ entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days
+ )
total_pending_interest += entry.interest_amount
payable_principal_amount += entry.payable_principal_amount
- pending_accrual_entries.setdefault(entry.name, {
- 'interest_amount': flt(entry.interest_amount, precision),
- 'payable_principal_amount': flt(entry.payable_principal_amount, precision)
- })
+ pending_accrual_entries.setdefault(
+ entry.name,
+ {
+ "interest_amount": flt(entry.interest_amount, precision),
+ "payable_principal_amount": flt(entry.payable_principal_amount, precision),
+ },
+ )
if due_date and not final_due_date:
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
@@ -574,14 +706,18 @@ def get_amounts(amounts, against_loan, posting_date):
if pending_days > 0:
principal_amount = flt(pending_principal_amount, precision)
- per_day_interest = get_per_day_interest(principal_amount, loan_type_details.rate_of_interest, posting_date)
- unaccrued_interest += (pending_days * per_day_interest)
+ per_day_interest = get_per_day_interest(
+ principal_amount, loan_type_details.rate_of_interest, posting_date
+ )
+ unaccrued_interest += pending_days * per_day_interest
amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
amounts["interest_amount"] = flt(total_pending_interest, precision)
amounts["penalty_amount"] = flt(penalty_amount + pending_penalty_amount, precision)
- amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
+ amounts["payable_amount"] = flt(
+ payable_principal_amount + total_pending_interest + penalty_amount, precision
+ )
amounts["pending_accrual_entries"] = pending_accrual_entries
amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
@@ -590,24 +726,25 @@ def get_amounts(amounts, against_loan, posting_date):
return amounts
+
@frappe.whitelist()
-def calculate_amounts(against_loan, posting_date, payment_type=''):
+def calculate_amounts(against_loan, posting_date, payment_type=""):
amounts = {
- 'penalty_amount': 0.0,
- 'interest_amount': 0.0,
- 'pending_principal_amount': 0.0,
- 'payable_principal_amount': 0.0,
- 'payable_amount': 0.0,
- 'unaccrued_interest': 0.0,
- 'due_date': ''
+ "penalty_amount": 0.0,
+ "interest_amount": 0.0,
+ "pending_principal_amount": 0.0,
+ "payable_principal_amount": 0.0,
+ "payable_amount": 0.0,
+ "unaccrued_interest": 0.0,
+ "due_date": "",
}
amounts = get_amounts(amounts, against_loan, posting_date)
# update values for closure
- if payment_type == 'Loan Closure':
- amounts['payable_principal_amount'] = amounts['pending_principal_amount']
- amounts['interest_amount'] += amounts['unaccrued_interest']
- amounts['payable_amount'] = amounts['payable_principal_amount'] + amounts['interest_amount']
+ if payment_type == "Loan Closure":
+ amounts["payable_principal_amount"] = amounts["pending_principal_amount"]
+ amounts["interest_amount"] += amounts["unaccrued_interest"]
+ amounts["payable_amount"] = amounts["payable_principal_amount"] + amounts["interest_amount"]
return amounts
diff --git a/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py b/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py
index 964a1ae93a..1d96885e15 100644
--- a/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py
+++ b/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py
@@ -1,12 +1,8 @@
def get_data():
return {
- 'fieldname': 'loan_security',
- 'transactions': [
- {
- 'items': ['Loan Application', 'Loan Security Price']
- },
- {
- 'items': ['Loan Security Pledge', 'Loan Security Unpledge']
- }
- ]
+ "fieldname": "loan_security",
+ "transactions": [
+ {"items": ["Loan Application", "Loan Security Price"]},
+ {"items": ["Loan Security Pledge", "Loan Security Unpledge"]},
+ ],
}
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
index 7d02645609..f0d5954275 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
@@ -40,22 +40,28 @@ class LoanSecurityPledge(Document):
if security.loan_security not in security_list:
security_list.append(security.loan_security)
else:
- frappe.throw(_('Loan Security {0} added multiple times').format(frappe.bold(
- security.loan_security)))
+ frappe.throw(
+ _("Loan Security {0} added multiple times").format(frappe.bold(security.loan_security))
+ )
def validate_loan_security_type(self):
- existing_pledge = ''
+ existing_pledge = ""
if self.loan:
- existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan, 'docstatus': 1}, ['name'])
+ existing_pledge = frappe.db.get_value(
+ "Loan Security Pledge", {"loan": self.loan, "docstatus": 1}, ["name"]
+ )
if existing_pledge:
- loan_security_type = frappe.db.get_value('Pledge', {'parent': existing_pledge}, ['loan_security_type'])
+ loan_security_type = frappe.db.get_value(
+ "Pledge", {"parent": existing_pledge}, ["loan_security_type"]
+ )
else:
loan_security_type = self.securities[0].loan_security_type
- ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
- fields=["name", "loan_to_value_ratio"], as_list=1))
+ ltv_ratio_map = frappe._dict(
+ frappe.get_all("Loan Security Type", fields=["name", "loan_to_value_ratio"], as_list=1)
+ )
ltv_ratio = ltv_ratio_map.get(loan_security_type)
@@ -63,7 +69,6 @@ class LoanSecurityPledge(Document):
if ltv_ratio_map.get(security.loan_security_type) != ltv_ratio:
frappe.throw(_("Loan Securities with different LTV ratio cannot be pledged against one loan"))
-
def set_pledge_amount(self):
total_security_value = 0
maximum_loan_value = 0
@@ -77,10 +82,10 @@ class LoanSecurityPledge(Document):
pledge.loan_security_price = get_loan_security_price(pledge.loan_security)
if not pledge.qty:
- pledge.qty = cint(pledge.amount/pledge.loan_security_price)
+ pledge.qty = cint(pledge.amount / pledge.loan_security_price)
pledge.amount = pledge.qty * pledge.loan_security_price
- pledge.post_haircut_amount = cint(pledge.amount - (pledge.amount * pledge.haircut/100))
+ pledge.post_haircut_amount = cint(pledge.amount - (pledge.amount * pledge.haircut / 100))
total_security_value += pledge.amount
maximum_loan_value += pledge.post_haircut_amount
@@ -88,12 +93,19 @@ class LoanSecurityPledge(Document):
self.total_security_value = total_security_value
self.maximum_loan_value = maximum_loan_value
+
def update_loan(loan, maximum_value_against_pledge, cancel=0):
- maximum_loan_value = frappe.db.get_value('Loan', {'name': loan}, ['maximum_loan_amount'])
+ maximum_loan_value = frappe.db.get_value("Loan", {"name": loan}, ["maximum_loan_amount"])
if cancel:
- frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s
- WHERE name=%s""", (maximum_loan_value - maximum_value_against_pledge, loan))
+ frappe.db.sql(
+ """ UPDATE `tabLoan` SET maximum_loan_amount=%s
+ WHERE name=%s""",
+ (maximum_loan_value - maximum_value_against_pledge, loan),
+ )
else:
- frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1
- WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan))
+ frappe.db.sql(
+ """ UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1
+ WHERE name=%s""",
+ (maximum_loan_value + maximum_value_against_pledge, loan),
+ )
diff --git a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py
index fca9dd6bcb..45c4459ac3 100644
--- a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py
+++ b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py
@@ -17,23 +17,37 @@ class LoanSecurityPrice(Document):
if self.valid_from > self.valid_upto:
frappe.throw(_("Valid From Time must be lesser than Valid Upto Time."))
- existing_loan_security = frappe.db.sql(""" SELECT name from `tabLoan Security Price`
+ existing_loan_security = frappe.db.sql(
+ """ SELECT name from `tabLoan Security Price`
WHERE loan_security = %s AND name != %s AND (valid_from BETWEEN %s and %s OR valid_upto BETWEEN %s and %s) """,
- (self.loan_security, self.name, self.valid_from, self.valid_upto, self.valid_from, self.valid_upto))
+ (
+ self.loan_security,
+ self.name,
+ self.valid_from,
+ self.valid_upto,
+ self.valid_from,
+ self.valid_upto,
+ ),
+ )
if existing_loan_security:
frappe.throw(_("Loan Security Price overlapping with {0}").format(existing_loan_security[0][0]))
+
@frappe.whitelist()
def get_loan_security_price(loan_security, valid_time=None):
if not valid_time:
valid_time = get_datetime()
- loan_security_price = frappe.db.get_value("Loan Security Price", {
- 'loan_security': loan_security,
- 'valid_from': ("<=",valid_time),
- 'valid_upto': (">=", valid_time)
- }, 'loan_security_price')
+ loan_security_price = frappe.db.get_value(
+ "Loan Security Price",
+ {
+ "loan_security": loan_security,
+ "valid_from": ("<=", valid_time),
+ "valid_upto": (">=", valid_time),
+ },
+ "loan_security_price",
+ )
if not loan_security_price:
frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(loan_security)))
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 20e451b81e..b901e626b4 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -14,27 +14,42 @@ from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpled
class LoanSecurityShortfall(Document):
pass
+
def update_shortfall_status(loan, security_value, on_cancel=0):
- loan_security_shortfall = frappe.db.get_value("Loan Security Shortfall",
- {"loan": loan, "status": "Pending"}, ['name', 'shortfall_amount'], as_dict=1)
+ loan_security_shortfall = frappe.db.get_value(
+ "Loan Security Shortfall",
+ {"loan": loan, "status": "Pending"},
+ ["name", "shortfall_amount"],
+ as_dict=1,
+ )
if not loan_security_shortfall:
return
if security_value >= loan_security_shortfall.shortfall_amount:
- frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
- "status": "Completed",
- "shortfall_amount": loan_security_shortfall.shortfall_amount,
- "shortfall_percentage": 0
- })
+ frappe.db.set_value(
+ "Loan Security Shortfall",
+ loan_security_shortfall.name,
+ {
+ "status": "Completed",
+ "shortfall_amount": loan_security_shortfall.shortfall_amount,
+ "shortfall_percentage": 0,
+ },
+ )
else:
- frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
- "shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)
+ frappe.db.set_value(
+ "Loan Security Shortfall",
+ loan_security_shortfall.name,
+ "shortfall_amount",
+ loan_security_shortfall.shortfall_amount - security_value,
+ )
@frappe.whitelist()
def add_security(loan):
- loan_details = frappe.db.get_value("Loan", loan, ['applicant', 'company', 'applicant_type'], as_dict=1)
+ loan_details = frappe.db.get_value(
+ "Loan", loan, ["applicant", "company", "applicant_type"], as_dict=1
+ )
loan_security_pledge = frappe.new_doc("Loan Security Pledge")
loan_security_pledge.loan = loan
@@ -44,33 +59,51 @@ def add_security(loan):
return loan_security_pledge.as_dict()
+
def check_for_ltv_shortfall(process_loan_security_shortfall):
update_time = get_datetime()
- loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price",
- fields=["loan_security", "loan_security_price"],
- filters = {
- "valid_from": ("<=", update_time),
- "valid_upto": (">=", update_time)
- }, as_list=1))
+ loan_security_price_map = frappe._dict(
+ frappe.get_all(
+ "Loan Security Price",
+ fields=["loan_security", "loan_security_price"],
+ filters={"valid_from": ("<=", update_time), "valid_upto": (">=", update_time)},
+ as_list=1,
+ )
+ )
- loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid', 'total_payment',
- 'total_interest_payable', 'disbursed_amount', 'status'],
- filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
+ loans = frappe.get_all(
+ "Loan",
+ fields=[
+ "name",
+ "loan_amount",
+ "total_principal_paid",
+ "total_payment",
+ "total_interest_payable",
+ "disbursed_amount",
+ "status",
+ ],
+ filters={"status": ("in", ["Disbursed", "Partially Disbursed"]), "is_secured_loan": 1},
+ )
- loan_shortfall_map = frappe._dict(frappe.get_all("Loan Security Shortfall",
- fields=["loan", "name"], filters={"status": "Pending"}, as_list=1))
+ loan_shortfall_map = frappe._dict(
+ frappe.get_all(
+ "Loan Security Shortfall", fields=["loan", "name"], filters={"status": "Pending"}, as_list=1
+ )
+ )
loan_security_map = {}
for loan in loans:
- if loan.status == 'Disbursed':
- outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
- - flt(loan.total_principal_paid)
+ if loan.status == "Disbursed":
+ outstanding_amount = (
+ flt(loan.total_payment) - flt(loan.total_interest_payable) - flt(loan.total_principal_paid)
+ )
else:
- outstanding_amount = flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
- - flt(loan.total_principal_paid)
+ outstanding_amount = (
+ flt(loan.disbursed_amount) - flt(loan.total_interest_payable) - flt(loan.total_principal_paid)
+ )
pledged_securities = get_pledged_security_qty(loan.name)
ltv_ratio = 0.0
@@ -81,21 +114,36 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
ltv_ratio = get_ltv_ratio(security)
security_value += flt(loan_security_price_map.get(security)) * flt(qty)
- current_ratio = (outstanding_amount/security_value) * 100 if security_value else 0
+ current_ratio = (outstanding_amount / security_value) * 100 if security_value else 0
if current_ratio > ltv_ratio:
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
- create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount,
- current_ratio, process_loan_security_shortfall)
+ create_loan_security_shortfall(
+ loan.name,
+ outstanding_amount,
+ security_value,
+ shortfall_amount,
+ current_ratio,
+ process_loan_security_shortfall,
+ )
elif loan_shortfall_map.get(loan.name):
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
if shortfall_amount <= 0:
shortfall = loan_shortfall_map.get(loan.name)
update_pending_shortfall(shortfall)
-def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, shortfall_ratio,
- process_loan_security_shortfall):
- existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
+
+def create_loan_security_shortfall(
+ loan,
+ loan_amount,
+ security_value,
+ shortfall_amount,
+ shortfall_ratio,
+ process_loan_security_shortfall,
+):
+ existing_shortfall = frappe.db.get_value(
+ "Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name"
+ )
if existing_shortfall:
ltv_shortfall = frappe.get_doc("Loan Security Shortfall", existing_shortfall)
@@ -111,16 +159,17 @@ def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_
ltv_shortfall.process_loan_security_shortfall = process_loan_security_shortfall
ltv_shortfall.save()
+
def get_ltv_ratio(loan_security):
- loan_security_type = frappe.db.get_value('Loan Security', loan_security, 'loan_security_type')
- ltv_ratio = frappe.db.get_value('Loan Security Type', loan_security_type, 'loan_to_value_ratio')
+ loan_security_type = frappe.db.get_value("Loan Security", loan_security, "loan_security_type")
+ ltv_ratio = frappe.db.get_value("Loan Security Type", loan_security_type, "loan_to_value_ratio")
return ltv_ratio
+
def update_pending_shortfall(shortfall):
# Get all pending loan security shortfall
- frappe.db.set_value("Loan Security Shortfall", shortfall,
- {
- "status": "Completed",
- "shortfall_amount": 0,
- "shortfall_percentage": 0
- })
+ frappe.db.set_value(
+ "Loan Security Shortfall",
+ shortfall,
+ {"status": "Completed", "shortfall_amount": 0, "shortfall_percentage": 0},
+ )
diff --git a/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py b/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py
index 2a9ceed2d8..8fc4520ccd 100644
--- a/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py
+++ b/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py
@@ -1,12 +1,8 @@
def get_data():
return {
- 'fieldname': 'loan_security_type',
- 'transactions': [
- {
- 'items': ['Loan Security', 'Loan Security Price']
- },
- {
- 'items': ['Loan Security Pledge', 'Loan Security Unpledge']
- }
- ]
+ "fieldname": "loan_security_type",
+ "transactions": [
+ {"items": ["Loan Security", "Loan Security Price"]},
+ {"items": ["Loan Security Pledge", "Loan Security Unpledge"]},
+ ],
}
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index 4567374062..0ab7beb0fc 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -15,7 +15,7 @@ class LoanSecurityUnpledge(Document):
def on_cancel(self):
self.update_loan_status(cancel=1)
- self.db_set('status', 'Requested')
+ self.db_set("status", "Requested")
def validate_duplicate_securities(self):
security_list = []
@@ -23,8 +23,11 @@ class LoanSecurityUnpledge(Document):
if d.loan_security not in security_list:
security_list.append(d.loan_security)
else:
- frappe.throw(_("Row {0}: Loan Security {1} added multiple times").format(
- d.idx, frappe.bold(d.loan_security)))
+ frappe.throw(
+ _("Row {0}: Loan Security {1} added multiple times").format(
+ d.idx, frappe.bold(d.loan_security)
+ )
+ )
def validate_unpledge_qty(self):
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
@@ -36,18 +39,33 @@ class LoanSecurityUnpledge(Document):
pledge_qty_map = get_pledged_security_qty(self.loan)
- ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
- fields=["name", "loan_to_value_ratio"], as_list=1))
+ ltv_ratio_map = frappe._dict(
+ frappe.get_all("Loan Security Type", fields=["name", "loan_to_value_ratio"], as_list=1)
+ )
- loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price",
- fields=["loan_security", "loan_security_price"],
- filters = {
- "valid_from": ("<=", get_datetime()),
- "valid_upto": (">=", get_datetime())
- }, as_list=1))
+ loan_security_price_map = frappe._dict(
+ frappe.get_all(
+ "Loan Security Price",
+ fields=["loan_security", "loan_security_price"],
+ filters={"valid_from": ("<=", get_datetime()), "valid_upto": (">=", get_datetime())},
+ as_list=1,
+ )
+ )
- loan_details = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid', 'loan_amount',
- 'total_interest_payable', 'written_off_amount', 'disbursed_amount', 'status'], as_dict=1)
+ loan_details = frappe.get_value(
+ "Loan",
+ self.loan,
+ [
+ "total_payment",
+ "total_principal_paid",
+ "loan_amount",
+ "total_interest_payable",
+ "written_off_amount",
+ "disbursed_amount",
+ "status",
+ ],
+ as_dict=1,
+ )
pending_principal_amount = get_pending_principal_amount(loan_details)
@@ -58,8 +76,13 @@ class LoanSecurityUnpledge(Document):
for security in self.securities:
pledged_qty = pledge_qty_map.get(security.loan_security, 0)
if security.qty > pledged_qty:
- msg = _("Row {0}: {1} {2} of {3} is pledged against Loan {4}.").format(security.idx, pledged_qty, security.uom,
- frappe.bold(security.loan_security), frappe.bold(self.loan))
+ msg = _("Row {0}: {1} {2} of {3} is pledged against Loan {4}.").format(
+ security.idx,
+ pledged_qty,
+ security.uom,
+ frappe.bold(security.loan_security),
+ frappe.bold(self.loan),
+ )
msg += "
"
msg += _("You are trying to unpledge more.")
frappe.throw(msg, title=_("Loan Security Unpledge Error"))
@@ -78,14 +101,14 @@ class LoanSecurityUnpledge(Document):
if not security_value and flt(pending_principal_amount, 2) > 0:
self._throw(security_value, pending_principal_amount, ltv_ratio)
- if security_value and flt(pending_principal_amount/security_value) * 100 > ltv_ratio:
+ if security_value and flt(pending_principal_amount / security_value) * 100 > ltv_ratio:
self._throw(security_value, pending_principal_amount, ltv_ratio)
def _throw(self, security_value, pending_principal_amount, ltv_ratio):
msg = _("Loan Security Value after unpledge is {0}").format(frappe.bold(security_value))
- msg += '
'
+ msg += "
"
msg += _("Pending principal amount is {0}").format(frappe.bold(flt(pending_principal_amount, 2)))
- msg += '
'
+ msg += "
"
msg += _("Loan To Security Value ratio must always be {0}").format(frappe.bold(ltv_ratio))
frappe.throw(msg, title=_("Loan To Value ratio breach"))
@@ -95,13 +118,13 @@ class LoanSecurityUnpledge(Document):
def approve(self):
if self.status == "Approved" and not self.unpledge_time:
self.update_loan_status()
- self.db_set('unpledge_time', get_datetime())
+ self.db_set("unpledge_time", get_datetime())
def update_loan_status(self, cancel=0):
if cancel:
- loan_status = frappe.get_value('Loan', self.loan, 'status')
- if loan_status == 'Closed':
- frappe.db.set_value('Loan', self.loan, 'status', 'Loan Closure Requested')
+ loan_status = frappe.get_value("Loan", self.loan, "status")
+ if loan_status == "Closed":
+ frappe.db.set_value("Loan", self.loan, "status", "Loan Closure Requested")
else:
pledged_qty = 0
current_pledges = get_pledged_security_qty(self.loan)
@@ -110,34 +133,41 @@ class LoanSecurityUnpledge(Document):
pledged_qty += qty
if not pledged_qty:
- frappe.db.set_value('Loan', self.loan,
- {
- 'status': 'Closed',
- 'closure_date': getdate()
- })
+ frappe.db.set_value("Loan", self.loan, {"status": "Closed", "closure_date": getdate()})
+
@frappe.whitelist()
def get_pledged_security_qty(loan):
current_pledges = {}
- unpledges = frappe._dict(frappe.db.sql("""
+ unpledges = frappe._dict(
+ frappe.db.sql(
+ """
SELECT u.loan_security, sum(u.qty) as qty
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
WHERE up.loan = %s
AND u.parent = up.name
AND up.status = 'Approved'
GROUP BY u.loan_security
- """, (loan)))
+ """,
+ (loan),
+ )
+ )
- pledges = frappe._dict(frappe.db.sql("""
+ pledges = frappe._dict(
+ frappe.db.sql(
+ """
SELECT p.loan_security, sum(p.qty) as qty
FROM `tabLoan Security Pledge` lp, `tabPledge`p
WHERE lp.loan = %s
AND p.parent = lp.name
AND lp.status = 'Pledged'
GROUP BY p.loan_security
- """, (loan)))
+ """,
+ (loan),
+ )
+ )
for security, qty in pledges.items():
current_pledges.setdefault(security, qty)
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.py b/erpnext/loan_management/doctype/loan_type/loan_type.py
index 592229cf99..51ee05b277 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.py
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.py
@@ -12,12 +12,20 @@ class LoanType(Document):
self.validate_accounts()
def validate_accounts(self):
- for fieldname in ['payment_account', 'loan_account', 'interest_income_account', 'penalty_income_account']:
- company = frappe.get_value("Account", self.get(fieldname), 'company')
+ for fieldname in [
+ "payment_account",
+ "loan_account",
+ "interest_income_account",
+ "penalty_income_account",
+ ]:
+ company = frappe.get_value("Account", self.get(fieldname), "company")
if company and company != self.company:
- frappe.throw(_("Account {0} does not belong to company {1}").format(frappe.bold(self.get(fieldname)),
- frappe.bold(self.company)))
+ frappe.throw(
+ _("Account {0} does not belong to company {1}").format(
+ frappe.bold(self.get(fieldname)), frappe.bold(self.company)
+ )
+ )
- if self.get('loan_account') == self.get('payment_account'):
- frappe.throw(_('Loan Account and Payment Account cannot be same'))
+ if self.get("loan_account") == self.get("payment_account"):
+ frappe.throw(_("Loan Account and Payment Account cannot be same"))
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py b/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py
index 245e102120..e2467c6455 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py
+++ b/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py
@@ -1,12 +1,5 @@
def get_data():
return {
- 'fieldname': 'loan_type',
- 'transactions': [
- {
- 'items': ['Loan Repayment', 'Loan']
- },
- {
- 'items': ['Loan Application']
- }
- ]
+ "fieldname": "loan_type",
+ "transactions": [{"items": ["Loan Repayment", "Loan"]}, {"items": ["Loan Application"]}],
}
diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py
index 35be587f87..e19fd15fc8 100644
--- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py
+++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py
@@ -22,11 +22,16 @@ class LoanWriteOff(AccountsController):
def validate_write_off_amount(self):
precision = cint(frappe.db.get_default("currency_precision")) or 2
- total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan,
- ['total_payment', 'total_principal_paid','total_interest_payable', 'written_off_amount'])
+ total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value(
+ "Loan",
+ self.loan,
+ ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"],
+ )
- pending_principal_amount = flt(flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount),
- precision)
+ pending_principal_amount = flt(
+ flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount),
+ precision,
+ )
if self.write_off_amount > pending_principal_amount:
frappe.throw(_("Write off amount cannot be greater than pending principal amount"))
@@ -37,52 +42,55 @@ class LoanWriteOff(AccountsController):
def on_cancel(self):
self.update_outstanding_amount(cancel=1)
- self.ignore_linked_doctypes = ['GL Entry']
+ self.ignore_linked_doctypes = ["GL Entry"]
self.make_gl_entries(cancel=1)
def update_outstanding_amount(self, cancel=0):
- written_off_amount = frappe.db.get_value('Loan', self.loan, 'written_off_amount')
+ written_off_amount = frappe.db.get_value("Loan", self.loan, "written_off_amount")
if cancel:
written_off_amount -= self.write_off_amount
else:
written_off_amount += self.write_off_amount
- frappe.db.set_value('Loan', self.loan, 'written_off_amount', written_off_amount)
-
+ frappe.db.set_value("Loan", self.loan, "written_off_amount", written_off_amount)
def make_gl_entries(self, cancel=0):
gl_entries = []
loan_details = frappe.get_doc("Loan", self.loan)
gl_entries.append(
- self.get_gl_dict({
- "account": self.write_off_account,
- "against": loan_details.loan_account,
- "debit": self.write_off_amount,
- "debit_in_account_currency": self.write_off_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.loan,
- "remarks": _("Against Loan:") + self.loan,
- "cost_center": self.cost_center,
- "posting_date": getdate(self.posting_date)
- })
+ self.get_gl_dict(
+ {
+ "account": self.write_off_account,
+ "against": loan_details.loan_account,
+ "debit": self.write_off_amount,
+ "debit_in_account_currency": self.write_off_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.loan,
+ "remarks": _("Against Loan:") + self.loan,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(self.posting_date),
+ }
+ )
)
gl_entries.append(
- self.get_gl_dict({
- "account": loan_details.loan_account,
- "party_type": loan_details.applicant_type,
- "party": loan_details.applicant,
- "against": self.write_off_account,
- "credit": self.write_off_amount,
- "credit_in_account_currency": self.write_off_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.loan,
- "remarks": _("Against Loan:") + self.loan,
- "cost_center": self.cost_center,
- "posting_date": getdate(self.posting_date)
- })
+ self.get_gl_dict(
+ {
+ "account": loan_details.loan_account,
+ "party_type": loan_details.applicant_type,
+ "party": loan_details.applicant,
+ "against": self.write_off_account,
+ "credit": self.write_off_amount,
+ "credit_in_account_currency": self.write_off_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.loan,
+ "remarks": _("Against Loan:") + self.loan,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(self.posting_date),
+ }
+ )
)
make_gl_entries(gl_entries, cancel=cancel, merge_entries=False)
diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
index 4c34ccd983..81464a36c3 100644
--- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
@@ -17,24 +17,36 @@ class ProcessLoanInterestAccrual(Document):
open_loans = []
if self.loan:
- loan_doc = frappe.get_doc('Loan', self.loan)
+ loan_doc = frappe.get_doc("Loan", self.loan)
if loan_doc:
open_loans.append(loan_doc)
- if (not self.loan or not loan_doc.is_term_loan) and self.process_type != 'Term Loans':
- make_accrual_interest_entry_for_demand_loans(self.posting_date, self.name,
- open_loans = open_loans, loan_type = self.loan_type, accrual_type=self.accrual_type)
+ if (not self.loan or not loan_doc.is_term_loan) and self.process_type != "Term Loans":
+ make_accrual_interest_entry_for_demand_loans(
+ self.posting_date,
+ self.name,
+ open_loans=open_loans,
+ loan_type=self.loan_type,
+ accrual_type=self.accrual_type,
+ )
- if (not self.loan or loan_doc.is_term_loan) and self.process_type != 'Demand Loans':
- make_accrual_interest_entry_for_term_loans(self.posting_date, self.name, term_loan=self.loan,
- loan_type=self.loan_type, accrual_type=self.accrual_type)
+ if (not self.loan or loan_doc.is_term_loan) and self.process_type != "Demand Loans":
+ make_accrual_interest_entry_for_term_loans(
+ self.posting_date,
+ self.name,
+ term_loan=self.loan,
+ loan_type=self.loan_type,
+ accrual_type=self.accrual_type,
+ )
-def process_loan_interest_accrual_for_demand_loans(posting_date=None, loan_type=None, loan=None, accrual_type="Regular"):
- loan_process = frappe.new_doc('Process Loan Interest Accrual')
+def process_loan_interest_accrual_for_demand_loans(
+ posting_date=None, loan_type=None, loan=None, accrual_type="Regular"
+):
+ loan_process = frappe.new_doc("Process Loan Interest Accrual")
loan_process.posting_date = posting_date or nowdate()
loan_process.loan_type = loan_type
- loan_process.process_type = 'Demand Loans'
+ loan_process.process_type = "Demand Loans"
loan_process.loan = loan
loan_process.accrual_type = accrual_type
@@ -42,25 +54,26 @@ def process_loan_interest_accrual_for_demand_loans(posting_date=None, loan_type=
return loan_process.name
+
def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None):
if not term_loan_accrual_pending(posting_date or nowdate()):
return
- loan_process = frappe.new_doc('Process Loan Interest Accrual')
+ loan_process = frappe.new_doc("Process Loan Interest Accrual")
loan_process.posting_date = posting_date or nowdate()
loan_process.loan_type = loan_type
- loan_process.process_type = 'Term Loans'
+ loan_process.process_type = "Term Loans"
loan_process.loan = loan
loan_process.submit()
return loan_process.name
+
def term_loan_accrual_pending(date):
- pending_accrual = frappe.db.get_value('Repayment Schedule', {
- 'payment_date': ('<=', date),
- 'is_accrued': 0
- })
+ pending_accrual = frappe.db.get_value(
+ "Repayment Schedule", {"payment_date": ("<=", date), "is_accrued": 0}
+ )
return pending_accrual
diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py
index 932087cb5a..ac85df761c 100644
--- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py
+++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
return {
- 'fieldname': 'process_loan_interest_accrual',
- 'transactions': [
- {
- 'items': ['Loan Interest Accrual']
- }
- ]
+ "fieldname": "process_loan_interest_accrual",
+ "transactions": [{"items": ["Loan Interest Accrual"]}],
}
diff --git a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py
index ba9fb0c449..fffc5d4876 100644
--- a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py
@@ -13,16 +13,18 @@ from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_short
class ProcessLoanSecurityShortfall(Document):
def onload(self):
- self.set_onload('update_time', get_datetime())
+ self.set_onload("update_time", get_datetime())
def on_submit(self):
check_for_ltv_shortfall(self.name)
+
def create_process_loan_security_shortfall():
if check_for_secured_loans():
process = frappe.new_doc("Process Loan Security Shortfall")
process.update_time = get_datetime()
process.submit()
+
def check_for_secured_loans():
- return frappe.db.count('Loan', {'docstatus': 1, 'is_secured_loan': 1})
+ return frappe.db.count("Loan", {"docstatus": 1, "is_secured_loan": 1})
diff --git a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py
index 1f5d8438ff..4d7b1630ba 100644
--- a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py
+++ b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
return {
- 'fieldname': 'process_loan_security_shortfall',
- 'transactions': [
- {
- 'items': ['Loan Security Shortfall']
- }
- ]
+ "fieldname": "process_loan_security_shortfall",
+ "transactions": [{"items": ["Loan Security Shortfall"]}],
}
diff --git a/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py b/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py
index 6063b7bad8..e7487cb34d 100644
--- a/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py
+++ b/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py
@@ -9,9 +9,13 @@ from frappe.model.document import Document
class SanctionedLoanAmount(Document):
def validate(self):
- sanctioned_doc = frappe.db.exists('Sanctioned Loan Amount', {'applicant': self.applicant, 'company': self.company})
+ sanctioned_doc = frappe.db.exists(
+ "Sanctioned Loan Amount", {"applicant": self.applicant, "company": self.company}
+ )
if sanctioned_doc and sanctioned_doc != self.name:
- frappe.throw(_("Sanctioned Loan Amount already exists for {0} against company {1}").format(
- frappe.bold(self.applicant), frappe.bold(self.company)
- ))
+ frappe.throw(
+ _("Sanctioned Loan Amount already exists for {0} against company {1}").format(
+ frappe.bold(self.applicant), frappe.bold(self.company)
+ )
+ )
diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
index 512b47f799..02da8109ca 100644
--- a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
+++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
@@ -17,84 +17,166 @@ def execute(filters=None):
def get_columns(filters):
columns = [
- {"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
- {"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
- {"label": _("Loan Security"), "fieldname": "loan_security", "fieldtype": "Link", "options": "Loan Security", "width": 160},
- {"label": _("Loan Security Code"), "fieldname": "loan_security_code", "fieldtype": "Data", "width": 100},
- {"label": _("Loan Security Name"), "fieldname": "loan_security_name", "fieldtype": "Data", "width": 150},
+ {
+ "label": _("Applicant Type"),
+ "fieldname": "applicant_type",
+ "options": "DocType",
+ "width": 100,
+ },
+ {
+ "label": _("Applicant Name"),
+ "fieldname": "applicant_name",
+ "fieldtype": "Dynamic Link",
+ "options": "applicant_type",
+ "width": 150,
+ },
+ {
+ "label": _("Loan Security"),
+ "fieldname": "loan_security",
+ "fieldtype": "Link",
+ "options": "Loan Security",
+ "width": 160,
+ },
+ {
+ "label": _("Loan Security Code"),
+ "fieldname": "loan_security_code",
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ {
+ "label": _("Loan Security Name"),
+ "fieldname": "loan_security_name",
+ "fieldtype": "Data",
+ "width": 150,
+ },
{"label": _("Haircut"), "fieldname": "haircut", "fieldtype": "Percent", "width": 100},
- {"label": _("Loan Security Type"), "fieldname": "loan_security_type", "fieldtype": "Link", "options": "Loan Security Type", "width": 120},
+ {
+ "label": _("Loan Security Type"),
+ "fieldname": "loan_security_type",
+ "fieldtype": "Link",
+ "options": "Loan Security Type",
+ "width": 120,
+ },
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
- {"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
- {"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100},
- {"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
- {"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
- {"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
+ {
+ "label": _("Latest Price"),
+ "fieldname": "latest_price",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("Price Valid Upto"),
+ "fieldname": "price_valid_upto",
+ "fieldtype": "Datetime",
+ "width": 100,
+ },
+ {
+ "label": _("Current Value"),
+ "fieldname": "current_value",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("% Of Applicant Portfolio"),
+ "fieldname": "portfolio_percent",
+ "fieldtype": "Percentage",
+ "width": 100,
+ },
+ {
+ "label": _("Currency"),
+ "fieldname": "currency",
+ "fieldtype": "Currency",
+ "options": "Currency",
+ "hidden": 1,
+ "width": 100,
+ },
]
return columns
+
def get_data(filters):
data = []
loan_security_details = get_loan_security_details()
- pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
- loan_security_details)
+ pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(
+ filters, loan_security_details
+ )
- currency = erpnext.get_company_currency(filters.get('company'))
+ currency = erpnext.get_company_currency(filters.get("company"))
for key, qty in pledge_values.items():
if qty:
row = {}
- current_value = flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
- valid_upto = loan_security_details.get(key[1], {}).get('valid_upto')
+ current_value = flt(qty * loan_security_details.get(key[1], {}).get("latest_price", 0))
+ valid_upto = loan_security_details.get(key[1], {}).get("valid_upto")
row.update(loan_security_details.get(key[1]))
- row.update({
- 'applicant_type': applicant_type_map.get(key[0]),
- 'applicant_name': key[0],
- 'total_qty': qty,
- 'current_value': current_value,
- 'price_valid_upto': valid_upto,
- 'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2) if total_value_map.get(key[0]) \
+ row.update(
+ {
+ "applicant_type": applicant_type_map.get(key[0]),
+ "applicant_name": key[0],
+ "total_qty": qty,
+ "current_value": current_value,
+ "price_valid_upto": valid_upto,
+ "portfolio_percent": flt(current_value * 100 / total_value_map.get(key[0]), 2)
+ if total_value_map.get(key[0])
else 0.0,
- 'currency': currency
- })
+ "currency": currency,
+ }
+ )
data.append(row)
return data
+
def get_loan_security_details():
security_detail_map = {}
loan_security_price_map = {}
lsp_validity_map = {}
- loan_security_prices = frappe.db.sql("""
+ loan_security_prices = frappe.db.sql(
+ """
SELECT loan_security, loan_security_price, valid_upto
FROM `tabLoan Security Price` t1
WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
WHERE t1.loan_security = t2.loan_security)
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
for security in loan_security_prices:
loan_security_price_map.setdefault(security.loan_security, security.loan_security_price)
lsp_validity_map.setdefault(security.loan_security, security.valid_upto)
- loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security',
- 'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type',
- 'disabled'])
+ loan_security_details = frappe.get_all(
+ "Loan Security",
+ fields=[
+ "name as loan_security",
+ "loan_security_code",
+ "loan_security_name",
+ "haircut",
+ "loan_security_type",
+ "disabled",
+ ],
+ )
for security in loan_security_details:
- security.update({
- 'latest_price': flt(loan_security_price_map.get(security.loan_security)),
- 'valid_upto': lsp_validity_map.get(security.loan_security)
- })
+ security.update(
+ {
+ "latest_price": flt(loan_security_price_map.get(security.loan_security)),
+ "valid_upto": lsp_validity_map.get(security.loan_security),
+ }
+ )
security_detail_map.setdefault(security.loan_security, security)
return security_detail_map
+
def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
current_pledges = {}
total_value_map = {}
@@ -102,39 +184,53 @@ def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
applicant_wise_unpledges = {}
conditions = ""
- if filters.get('company'):
+ if filters.get("company"):
conditions = "AND company = %(company)s"
- unpledges = frappe.db.sql("""
+ unpledges = frappe.db.sql(
+ """
SELECT up.applicant, u.loan_security, sum(u.qty) as qty
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
WHERE u.parent = up.name
AND up.status = 'Approved'
{conditions}
GROUP BY up.applicant, u.loan_security
- """.format(conditions=conditions), filters, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
for unpledge in unpledges:
applicant_wise_unpledges.setdefault((unpledge.applicant, unpledge.loan_security), unpledge.qty)
- pledges = frappe.db.sql("""
+ pledges = frappe.db.sql(
+ """
SELECT lp.applicant_type, lp.applicant, p.loan_security, sum(p.qty) as qty
FROM `tabLoan Security Pledge` lp, `tabPledge`p
WHERE p.parent = lp.name
AND lp.status = 'Pledged'
{conditions}
GROUP BY lp.applicant, p.loan_security
- """.format(conditions=conditions), filters, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
for security in pledges:
current_pledges.setdefault((security.applicant, security.loan_security), security.qty)
total_value_map.setdefault(security.applicant, 0.0)
applicant_type_map.setdefault(security.applicant, security.applicant_type)
- current_pledges[(security.applicant, security.loan_security)] -= \
- applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0)
+ current_pledges[(security.applicant, security.loan_security)] -= applicant_wise_unpledges.get(
+ (security.applicant, security.loan_security), 0.0
+ )
- total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \
- * loan_security_details.get(security.loan_security, {}).get('latest_price', 0)
+ total_value_map[security.applicant] += current_pledges.get(
+ (security.applicant, security.loan_security)
+ ) * loan_security_details.get(security.loan_security, {}).get("latest_price", 0)
return current_pledges, total_value_map, applicant_type_map
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
index 7c51267956..9186ce6174 100644
--- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -17,41 +17,148 @@ def execute(filters=None):
data = get_active_loan_details(filters)
return columns, data
+
def get_columns(filters):
columns = [
{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
- {"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
- {"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
- {"label": _("Loan Type"), "fieldname": "loan_type", "fieldtype": "Link", "options": "Loan Type", "width": 100},
- {"label": _("Sanctioned Amount"), "fieldname": "sanctioned_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Disbursed Amount"), "fieldname": "disbursed_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Penalty Amount"), "fieldname": "penalty", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Accrued Interest"), "fieldname": "accrued_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Total Repayment"), "fieldname": "total_repayment", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Principal Outstanding"), "fieldname": "principal_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Interest Outstanding"), "fieldname": "interest_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Total Outstanding"), "fieldname": "total_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
- {"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100},
- {"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100},
- {"label": _("Loan To Value Ratio"), "fieldname": "loan_to_value", "fieldtype": "Percent", "width": 100},
- {"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
+ {
+ "label": _("Applicant Type"),
+ "fieldname": "applicant_type",
+ "options": "DocType",
+ "width": 100,
+ },
+ {
+ "label": _("Applicant Name"),
+ "fieldname": "applicant_name",
+ "fieldtype": "Dynamic Link",
+ "options": "applicant_type",
+ "width": 150,
+ },
+ {
+ "label": _("Loan Type"),
+ "fieldname": "loan_type",
+ "fieldtype": "Link",
+ "options": "Loan Type",
+ "width": 100,
+ },
+ {
+ "label": _("Sanctioned Amount"),
+ "fieldname": "sanctioned_amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Disbursed Amount"),
+ "fieldname": "disbursed_amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Penalty Amount"),
+ "fieldname": "penalty",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Accrued Interest"),
+ "fieldname": "accrued_interest",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Total Repayment"),
+ "fieldname": "total_repayment",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Principal Outstanding"),
+ "fieldname": "principal_outstanding",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Interest Outstanding"),
+ "fieldname": "interest_outstanding",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Total Outstanding"),
+ "fieldname": "total_outstanding",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Undue Booked Interest"),
+ "fieldname": "undue_interest",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Interest %"),
+ "fieldname": "rate_of_interest",
+ "fieldtype": "Percent",
+ "width": 100,
+ },
+ {
+ "label": _("Penalty Interest %"),
+ "fieldname": "penalty_interest",
+ "fieldtype": "Percent",
+ "width": 100,
+ },
+ {
+ "label": _("Loan To Value Ratio"),
+ "fieldname": "loan_to_value",
+ "fieldtype": "Percent",
+ "width": 100,
+ },
+ {
+ "label": _("Currency"),
+ "fieldname": "currency",
+ "fieldtype": "Currency",
+ "options": "Currency",
+ "hidden": 1,
+ "width": 100,
+ },
]
return columns
+
def get_active_loan_details(filters):
filter_obj = {"status": ("!=", "Closed")}
- if filters.get('company'):
- filter_obj.update({'company': filters.get('company')})
+ if filters.get("company"):
+ filter_obj.update({"company": filters.get("company")})
- loan_details = frappe.get_all("Loan",
- fields=["name as loan", "applicant_type", "applicant as applicant_name", "loan_type",
- "disbursed_amount", "rate_of_interest", "total_payment", "total_principal_paid",
- "total_interest_payable", "written_off_amount", "status"],
- filters=filter_obj)
+ loan_details = frappe.get_all(
+ "Loan",
+ fields=[
+ "name as loan",
+ "applicant_type",
+ "applicant as applicant_name",
+ "loan_type",
+ "disbursed_amount",
+ "rate_of_interest",
+ "total_payment",
+ "total_principal_paid",
+ "total_interest_payable",
+ "written_off_amount",
+ "status",
+ ],
+ filters=filter_obj,
+ )
loan_list = [d.loan for d in loan_details]
@@ -62,70 +169,105 @@ def get_active_loan_details(filters):
penal_interest_rate_map = get_penal_interest_rate_map()
payments = get_payments(loan_list)
accrual_map = get_interest_accruals(loan_list)
- currency = erpnext.get_company_currency(filters.get('company'))
+ currency = erpnext.get_company_currency(filters.get("company"))
for loan in loan_details:
- total_payment = loan.total_payment if loan.status == 'Disbursed' else loan.disbursed_amount
+ total_payment = loan.total_payment if loan.status == "Disbursed" else loan.disbursed_amount
- loan.update({
- "sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
- "principal_outstanding": flt(total_payment) - flt(loan.total_principal_paid) \
- - flt(loan.total_interest_payable) - flt(loan.written_off_amount),
- "total_repayment": flt(payments.get(loan.loan)),
- "accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
- "interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
- "penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
- "penalty_interest": penal_interest_rate_map.get(loan.loan_type),
- "undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
- "loan_to_value": 0.0,
- "currency": currency
- })
+ loan.update(
+ {
+ "sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
+ "principal_outstanding": flt(total_payment)
+ - flt(loan.total_principal_paid)
+ - flt(loan.total_interest_payable)
+ - flt(loan.written_off_amount),
+ "total_repayment": flt(payments.get(loan.loan)),
+ "accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
+ "interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
+ "penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
+ "penalty_interest": penal_interest_rate_map.get(loan.loan_type),
+ "undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
+ "loan_to_value": 0.0,
+ "currency": currency,
+ }
+ )
- loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
- + loan['penalty']
+ loan["total_outstanding"] = (
+ loan["principal_outstanding"] + loan["interest_outstanding"] + loan["penalty"]
+ )
if loan_wise_security_value.get(loan.loan):
- loan['loan_to_value'] = flt((loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan))
+ loan["loan_to_value"] = flt(
+ (loan["principal_outstanding"] * 100) / loan_wise_security_value.get(loan.loan)
+ )
return loan_details
+
def get_sanctioned_amount_map():
- return frappe._dict(frappe.get_all("Sanctioned Loan Amount", fields=["applicant", "sanctioned_amount_limit"],
- as_list=1))
+ return frappe._dict(
+ frappe.get_all(
+ "Sanctioned Loan Amount", fields=["applicant", "sanctioned_amount_limit"], as_list=1
+ )
+ )
+
def get_payments(loans):
- return frappe._dict(frappe.get_all("Loan Repayment", fields=["against_loan", "sum(amount_paid)"],
- filters={"against_loan": ("in", loans)}, group_by="against_loan", as_list=1))
+ return frappe._dict(
+ frappe.get_all(
+ "Loan Repayment",
+ fields=["against_loan", "sum(amount_paid)"],
+ filters={"against_loan": ("in", loans)},
+ group_by="against_loan",
+ as_list=1,
+ )
+ )
+
def get_interest_accruals(loans):
accrual_map = {}
- interest_accruals = frappe.get_all("Loan Interest Accrual",
- fields=["loan", "interest_amount", "posting_date", "penalty_amount",
- "paid_interest_amount", "accrual_type"], filters={"loan": ("in", loans)}, order_by="posting_date desc")
+ interest_accruals = frappe.get_all(
+ "Loan Interest Accrual",
+ fields=[
+ "loan",
+ "interest_amount",
+ "posting_date",
+ "penalty_amount",
+ "paid_interest_amount",
+ "accrual_type",
+ ],
+ filters={"loan": ("in", loans)},
+ order_by="posting_date desc",
+ )
for entry in interest_accruals:
- accrual_map.setdefault(entry.loan, {
- "accrued_interest": 0.0,
- "undue_interest": 0.0,
- "interest_outstanding": 0.0,
- "last_accrual_date": '',
- "due_date": ''
- })
+ accrual_map.setdefault(
+ entry.loan,
+ {
+ "accrued_interest": 0.0,
+ "undue_interest": 0.0,
+ "interest_outstanding": 0.0,
+ "last_accrual_date": "",
+ "due_date": "",
+ },
+ )
- if entry.accrual_type == 'Regular':
- if not accrual_map[entry.loan]['due_date']:
- accrual_map[entry.loan]['due_date'] = add_days(entry.posting_date, 1)
- if not accrual_map[entry.loan]['last_accrual_date']:
- accrual_map[entry.loan]['last_accrual_date'] = entry.posting_date
+ if entry.accrual_type == "Regular":
+ if not accrual_map[entry.loan]["due_date"]:
+ accrual_map[entry.loan]["due_date"] = add_days(entry.posting_date, 1)
+ if not accrual_map[entry.loan]["last_accrual_date"]:
+ accrual_map[entry.loan]["last_accrual_date"] = entry.posting_date
- due_date = accrual_map[entry.loan]['due_date']
- last_accrual_date = accrual_map[entry.loan]['last_accrual_date']
+ due_date = accrual_map[entry.loan]["due_date"]
+ last_accrual_date = accrual_map[entry.loan]["last_accrual_date"]
if due_date and getdate(entry.posting_date) < getdate(due_date):
- accrual_map[entry.loan]["interest_outstanding"] += entry.interest_amount - entry.paid_interest_amount
+ accrual_map[entry.loan]["interest_outstanding"] += (
+ entry.interest_amount - entry.paid_interest_amount
+ )
else:
- accrual_map[entry.loan]['undue_interest'] += entry.interest_amount - entry.paid_interest_amount
+ accrual_map[entry.loan]["undue_interest"] += entry.interest_amount - entry.paid_interest_amount
accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
@@ -134,8 +276,12 @@ def get_interest_accruals(loans):
return accrual_map
+
def get_penal_interest_rate_map():
- return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
+ return frappe._dict(
+ frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1)
+ )
+
def get_loan_wise_pledges(filters):
loan_wise_unpledges = {}
@@ -143,37 +289,51 @@ def get_loan_wise_pledges(filters):
conditions = ""
- if filters.get('company'):
+ if filters.get("company"):
conditions = "AND company = %(company)s"
- unpledges = frappe.db.sql("""
+ unpledges = frappe.db.sql(
+ """
SELECT up.loan, u.loan_security, sum(u.qty) as qty
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
WHERE u.parent = up.name
AND up.status = 'Approved'
{conditions}
GROUP BY up.loan, u.loan_security
- """.format(conditions=conditions), filters, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
for unpledge in unpledges:
loan_wise_unpledges.setdefault((unpledge.loan, unpledge.loan_security), unpledge.qty)
- pledges = frappe.db.sql("""
+ pledges = frappe.db.sql(
+ """
SELECT lp.loan, p.loan_security, sum(p.qty) as qty
FROM `tabLoan Security Pledge` lp, `tabPledge`p
WHERE p.parent = lp.name
AND lp.status = 'Pledged'
{conditions}
GROUP BY lp.loan, p.loan_security
- """.format(conditions=conditions), filters, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
for security in pledges:
current_pledges.setdefault((security.loan, security.loan_security), security.qty)
- current_pledges[(security.loan, security.loan_security)] -= \
- loan_wise_unpledges.get((security.loan, security.loan_security), 0.0)
+ current_pledges[(security.loan, security.loan_security)] -= loan_wise_unpledges.get(
+ (security.loan, security.loan_security), 0.0
+ )
return current_pledges
+
def get_loan_wise_security_value(filters, current_pledges):
loan_security_details = get_loan_security_details()
loan_wise_security_value = {}
@@ -181,7 +341,8 @@ def get_loan_wise_security_value(filters, current_pledges):
for key in current_pledges:
qty = current_pledges.get(key)
loan_wise_security_value.setdefault(key[0], 0.0)
- loan_wise_security_value[key[0]] += \
- flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
+ loan_wise_security_value[key[0]] += flt(
+ qty * loan_security_details.get(key[1], {}).get("latest_price", 0)
+ )
return loan_wise_security_value
diff --git a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
index 68fd3d8e8b..253b994ae0 100644
--- a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
+++ b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
@@ -11,101 +11,96 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_columns():
return [
- {
- "label": _("Posting Date"),
- "fieldtype": "Date",
- "fieldname": "posting_date",
- "width": 100
- },
- {
- "label": _("Loan Repayment"),
- "fieldtype": "Link",
- "fieldname": "loan_repayment",
- "options": "Loan Repayment",
- "width": 100
- },
- {
- "label": _("Against Loan"),
- "fieldtype": "Link",
- "fieldname": "against_loan",
- "options": "Loan",
- "width": 200
- },
- {
- "label": _("Applicant"),
- "fieldtype": "Data",
- "fieldname": "applicant",
- "width": 150
- },
- {
- "label": _("Payment Type"),
- "fieldtype": "Data",
- "fieldname": "payment_type",
- "width": 150
- },
- {
- "label": _("Principal Amount"),
- "fieldtype": "Currency",
- "fieldname": "principal_amount",
- "options": "currency",
- "width": 100
- },
- {
- "label": _("Interest Amount"),
- "fieldtype": "Currency",
- "fieldname": "interest",
- "options": "currency",
- "width": 100
- },
- {
- "label": _("Penalty Amount"),
- "fieldtype": "Currency",
- "fieldname": "penalty",
- "options": "currency",
- "width": 100
- },
- {
- "label": _("Payable Amount"),
- "fieldtype": "Currency",
- "fieldname": "payable_amount",
- "options": "currency",
- "width": 100
- },
- {
- "label": _("Paid Amount"),
- "fieldtype": "Currency",
- "fieldname": "paid_amount",
- "options": "currency",
- "width": 100
- },
- {
- "label": _("Currency"),
- "fieldtype": "Link",
- "fieldname": "currency",
- "options": "Currency",
- "width": 100
- }
- ]
+ {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 100},
+ {
+ "label": _("Loan Repayment"),
+ "fieldtype": "Link",
+ "fieldname": "loan_repayment",
+ "options": "Loan Repayment",
+ "width": 100,
+ },
+ {
+ "label": _("Against Loan"),
+ "fieldtype": "Link",
+ "fieldname": "against_loan",
+ "options": "Loan",
+ "width": 200,
+ },
+ {"label": _("Applicant"), "fieldtype": "Data", "fieldname": "applicant", "width": 150},
+ {"label": _("Payment Type"), "fieldtype": "Data", "fieldname": "payment_type", "width": 150},
+ {
+ "label": _("Principal Amount"),
+ "fieldtype": "Currency",
+ "fieldname": "principal_amount",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("Interest Amount"),
+ "fieldtype": "Currency",
+ "fieldname": "interest",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("Penalty Amount"),
+ "fieldtype": "Currency",
+ "fieldname": "penalty",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("Payable Amount"),
+ "fieldtype": "Currency",
+ "fieldname": "payable_amount",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("Paid Amount"),
+ "fieldtype": "Currency",
+ "fieldname": "paid_amount",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "fieldname": "currency",
+ "options": "Currency",
+ "width": 100,
+ },
+ ]
+
def get_data(filters):
data = []
query_filters = {
"docstatus": 1,
- "company": filters.get('company'),
+ "company": filters.get("company"),
}
- if filters.get('applicant'):
- query_filters.update({
- "applicant": filters.get('applicant')
- })
+ if filters.get("applicant"):
+ query_filters.update({"applicant": filters.get("applicant")})
- loan_repayments = frappe.get_all("Loan Repayment",
- filters = query_filters,
- fields=["posting_date", "applicant", "name", "against_loan", "payable_amount",
- "pending_principal_amount", "interest_payable", "penalty_amount", "amount_paid"]
+ loan_repayments = frappe.get_all(
+ "Loan Repayment",
+ filters=query_filters,
+ fields=[
+ "posting_date",
+ "applicant",
+ "name",
+ "against_loan",
+ "payable_amount",
+ "pending_principal_amount",
+ "interest_payable",
+ "penalty_amount",
+ "amount_paid",
+ ],
)
default_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
@@ -122,7 +117,7 @@ def get_data(filters):
"penalty": repayment.penalty_amount,
"payable_amount": repayment.payable_amount,
"paid_amount": repayment.amount_paid,
- "currency": default_currency
+ "currency": default_currency,
}
data.append(row)
diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
index 7dbb966233..a92f960ea8 100644
--- a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
+++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
@@ -17,46 +17,110 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_columns(filters):
columns = [
- {"label": _("Loan Security"), "fieldname": "loan_security", "fieldtype": "Link", "options": "Loan Security", "width": 160},
- {"label": _("Loan Security Code"), "fieldname": "loan_security_code", "fieldtype": "Data", "width": 100},
- {"label": _("Loan Security Name"), "fieldname": "loan_security_name", "fieldtype": "Data", "width": 150},
+ {
+ "label": _("Loan Security"),
+ "fieldname": "loan_security",
+ "fieldtype": "Link",
+ "options": "Loan Security",
+ "width": 160,
+ },
+ {
+ "label": _("Loan Security Code"),
+ "fieldname": "loan_security_code",
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ {
+ "label": _("Loan Security Name"),
+ "fieldname": "loan_security_name",
+ "fieldtype": "Data",
+ "width": 150,
+ },
{"label": _("Haircut"), "fieldname": "haircut", "fieldtype": "Percent", "width": 100},
- {"label": _("Loan Security Type"), "fieldname": "loan_security_type", "fieldtype": "Link", "options": "Loan Security Type", "width": 120},
+ {
+ "label": _("Loan Security Type"),
+ "fieldname": "loan_security_type",
+ "fieldtype": "Link",
+ "options": "Loan Security Type",
+ "width": 120,
+ },
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
- {"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
- {"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100},
- {"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
- {"label": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
- {"label": _("Pledged Applicant Count"), "fieldname": "pledged_applicant_count", "fieldtype": "Percentage", "width": 100},
- {"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
+ {
+ "label": _("Latest Price"),
+ "fieldname": "latest_price",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("Price Valid Upto"),
+ "fieldname": "price_valid_upto",
+ "fieldtype": "Datetime",
+ "width": 100,
+ },
+ {
+ "label": _("Current Value"),
+ "fieldname": "current_value",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100,
+ },
+ {
+ "label": _("% Of Total Portfolio"),
+ "fieldname": "portfolio_percent",
+ "fieldtype": "Percentage",
+ "width": 100,
+ },
+ {
+ "label": _("Pledged Applicant Count"),
+ "fieldname": "pledged_applicant_count",
+ "fieldtype": "Percentage",
+ "width": 100,
+ },
+ {
+ "label": _("Currency"),
+ "fieldname": "currency",
+ "fieldtype": "Currency",
+ "options": "Currency",
+ "hidden": 1,
+ "width": 100,
+ },
]
return columns
+
def get_data(filters):
data = []
loan_security_details = get_loan_security_details()
- current_pledges, total_portfolio_value = get_company_wise_loan_security_details(filters, loan_security_details)
- currency = erpnext.get_company_currency(filters.get('company'))
+ current_pledges, total_portfolio_value = get_company_wise_loan_security_details(
+ filters, loan_security_details
+ )
+ currency = erpnext.get_company_currency(filters.get("company"))
for security, value in current_pledges.items():
- if value.get('qty'):
+ if value.get("qty"):
row = {}
- current_value = flt(value.get('qty', 0) * loan_security_details.get(security, {}).get('latest_price', 0))
- valid_upto = loan_security_details.get(security, {}).get('valid_upto')
+ current_value = flt(
+ value.get("qty", 0) * loan_security_details.get(security, {}).get("latest_price", 0)
+ )
+ valid_upto = loan_security_details.get(security, {}).get("valid_upto")
row.update(loan_security_details.get(security))
- row.update({
- 'total_qty': value.get('qty'),
- 'current_value': current_value,
- 'price_valid_upto': valid_upto,
- 'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
- 'pledged_applicant_count': value.get('applicant_count'),
- 'currency': currency
- })
+ row.update(
+ {
+ "total_qty": value.get("qty"),
+ "current_value": current_value,
+ "price_valid_upto": valid_upto,
+ "portfolio_percent": flt(current_value * 100 / total_portfolio_value, 2),
+ "pledged_applicant_count": value.get("applicant_count"),
+ "currency": currency,
+ }
+ )
data.append(row)
@@ -64,21 +128,19 @@ def get_data(filters):
def get_company_wise_loan_security_details(filters, loan_security_details):
- pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
- loan_security_details)
+ pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(
+ filters, loan_security_details
+ )
total_portfolio_value = 0
security_wise_map = {}
for key, qty in pledge_values.items():
- security_wise_map.setdefault(key[1], {
- 'qty': 0.0,
- 'applicant_count': 0.0
- })
+ security_wise_map.setdefault(key[1], {"qty": 0.0, "applicant_count": 0.0})
- security_wise_map[key[1]]['qty'] += qty
+ security_wise_map[key[1]]["qty"] += qty
if qty:
- security_wise_map[key[1]]['applicant_count'] += 1
+ security_wise_map[key[1]]["applicant_count"] += 1
- total_portfolio_value += flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
+ total_portfolio_value += flt(qty * loan_security_details.get(key[1], {}).get("latest_price", 0))
return security_wise_map, total_portfolio_value
diff --git a/erpnext/loan_management/report/loan_security_status/loan_security_status.py b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
index b7e716880e..9a5a18001e 100644
--- a/erpnext/loan_management/report/loan_security_status/loan_security_status.py
+++ b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
@@ -11,66 +11,41 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_columns(filters):
- columns= [
+ columns = [
{
"label": _("Loan Security Pledge"),
"fieldtype": "Link",
"fieldname": "loan_security_pledge",
"options": "Loan Security Pledge",
- "width": 200
- },
- {
- "label": _("Loan"),
- "fieldtype": "Link",
- "fieldname": "loan",
- "options": "Loan",
- "width": 200
- },
- {
- "label": _("Applicant"),
- "fieldtype": "Data",
- "fieldname": "applicant",
- "width": 200
- },
- {
- "label": _("Status"),
- "fieldtype": "Data",
- "fieldname": "status",
- "width": 100
- },
- {
- "label": _("Pledge Time"),
- "fieldtype": "Data",
- "fieldname": "pledge_time",
- "width": 150
+ "width": 200,
},
+ {"label": _("Loan"), "fieldtype": "Link", "fieldname": "loan", "options": "Loan", "width": 200},
+ {"label": _("Applicant"), "fieldtype": "Data", "fieldname": "applicant", "width": 200},
+ {"label": _("Status"), "fieldtype": "Data", "fieldname": "status", "width": 100},
+ {"label": _("Pledge Time"), "fieldtype": "Data", "fieldname": "pledge_time", "width": 150},
{
"label": _("Loan Security"),
"fieldtype": "Link",
"fieldname": "loan_security",
"options": "Loan Security",
- "width": 150
- },
- {
- "label": _("Quantity"),
- "fieldtype": "Float",
- "fieldname": "qty",
- "width": 100
+ "width": 150,
},
+ {"label": _("Quantity"), "fieldtype": "Float", "fieldname": "qty", "width": 100},
{
"label": _("Loan Security Price"),
"fieldtype": "Currency",
"fieldname": "loan_security_price",
"options": "currency",
- "width": 200
+ "width": 200,
},
{
"label": _("Loan Security Value"),
"fieldtype": "Currency",
"fieldname": "loan_security_value",
"options": "currency",
- "width": 200
+ "width": 200,
},
{
"label": _("Currency"),
@@ -78,18 +53,20 @@ def get_columns(filters):
"fieldname": "currency",
"options": "Currency",
"width": 50,
- "hidden": 1
- }
+ "hidden": 1,
+ },
]
return columns
+
def get_data(filters):
data = []
conditions = get_conditions(filters)
- loan_security_pledges = frappe.db.sql("""
+ loan_security_pledges = frappe.db.sql(
+ """
SELECT
p.name, p.applicant, p.loan, p.status, p.pledge_time,
c.loan_security, c.qty, c.loan_security_price, c.amount
@@ -100,7 +77,12 @@ def get_data(filters):
AND c.parent = p.name
AND p.company = %(company)s
{conditions}
- """.format(conditions = conditions), (filters), as_dict=1) #nosec
+ """.format(
+ conditions=conditions
+ ),
+ (filters),
+ as_dict=1,
+ ) # nosec
default_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
@@ -121,6 +103,7 @@ def get_data(filters):
return data
+
def get_conditions(filters):
conditions = []
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 07d928c221..256f66071f 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -16,17 +16,17 @@ class MaintenanceSchedule(TransactionBase):
def generate_schedule(self):
if self.docstatus != 0:
return
- self.set('schedules', [])
+ self.set("schedules", [])
count = 1
- for d in self.get('items'):
+ for d in self.get("items"):
self.validate_maintenance_detail()
s_list = []
s_list = self.create_schedule_list(d.start_date, d.end_date, d.no_of_visits, d.sales_person)
for i in range(d.no_of_visits):
- child = self.append('schedules')
+ child = self.append("schedules")
child.item_code = d.item_code
child.item_name = d.item_name
- child.scheduled_date = s_list[i].strftime('%Y-%m-%d')
+ child.scheduled_date = s_list[i].strftime("%Y-%m-%d")
if d.serial_no:
child.serial_no = d.serial_no
child.idx = count
@@ -37,18 +37,14 @@ class MaintenanceSchedule(TransactionBase):
@frappe.whitelist()
def validate_end_date_visits(self):
- days_in_period = {
- "Weekly": 7,
- "Monthly": 30,
- "Quarterly": 91,
- "Half Yearly": 182,
- "Yearly": 365
- }
+ days_in_period = {"Weekly": 7, "Monthly": 30, "Quarterly": 91, "Half Yearly": 182, "Yearly": 365}
for item in self.items:
if item.periodicity and item.periodicity != "Random" and item.start_date:
if not item.end_date:
if item.no_of_visits:
- item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
+ item.end_date = add_days(
+ item.start_date, item.no_of_visits * days_in_period[item.periodicity]
+ )
else:
item.end_date = add_days(item.start_date, days_in_period[item.periodicity])
@@ -61,20 +57,23 @@ class MaintenanceSchedule(TransactionBase):
item.no_of_visits = cint(diff / days_in_period[item.periodicity])
elif item.no_of_visits > no_of_visits:
- item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
+ item.end_date = add_days(
+ item.start_date, item.no_of_visits * days_in_period[item.periodicity]
+ )
elif item.no_of_visits < no_of_visits:
- item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
-
+ item.end_date = add_days(
+ item.start_date, item.no_of_visits * days_in_period[item.periodicity]
+ )
def on_submit(self):
- if not self.get('schedules'):
+ if not self.get("schedules"):
throw(_("Please click on 'Generate Schedule' to get schedule"))
self.check_serial_no_added()
self.validate_schedule()
email_map = {}
- for d in self.get('items'):
+ for d in self.get("items"):
if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no)
self.validate_serial_no(d.item_code, serial_nos, d.start_date)
@@ -90,29 +89,37 @@ class MaintenanceSchedule(TransactionBase):
if no_email_sp:
frappe.msgprint(
- _("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format(
- self.owner, "
" + "
".join(no_email_sp)
- )
+ _(
+ "Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}"
+ ).format(self.owner, "
" + "
".join(no_email_sp))
)
- scheduled_date = frappe.db.sql("""select scheduled_date from
+ scheduled_date = frappe.db.sql(
+ """select scheduled_date from
`tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and
- parent=%s""", (d.sales_person, d.item_code, self.name), as_dict=1)
+ parent=%s""",
+ (d.sales_person, d.item_code, self.name),
+ as_dict=1,
+ )
for key in scheduled_date:
- description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
- event = frappe.get_doc({
- "doctype": "Event",
- "owner": email_map.get(d.sales_person, self.owner),
- "subject": description,
- "description": description,
- "starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
- "event_type": "Private",
- })
+ description = frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(
+ self.name, d.item_code, self.customer
+ )
+ event = frappe.get_doc(
+ {
+ "doctype": "Event",
+ "owner": email_map.get(d.sales_person, self.owner),
+ "subject": description,
+ "description": description,
+ "starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
+ "event_type": "Private",
+ }
+ )
event.add_participant(self.doctype, self.name)
event.insert(ignore_permissions=1)
- frappe.db.set(self, 'status', 'Submitted')
+ frappe.db.set(self, "status", "Submitted")
def create_schedule_list(self, start_date, end_date, no_of_visit, sales_person):
schedule_list = []
@@ -121,11 +128,12 @@ class MaintenanceSchedule(TransactionBase):
add_by = date_diff / no_of_visit
for visit in range(cint(no_of_visit)):
- if (getdate(start_date_copy) < getdate(end_date)):
+ if getdate(start_date_copy) < getdate(end_date):
start_date_copy = add_days(start_date_copy, add_by)
if len(schedule_list) < no_of_visit:
- schedule_date = self.validate_schedule_date_for_holiday_list(getdate(start_date_copy),
- sales_person)
+ schedule_date = self.validate_schedule_date_for_holiday_list(
+ getdate(start_date_copy), sales_person
+ )
if schedule_date > getdate(end_date):
schedule_date = getdate(end_date)
schedule_list.append(schedule_date)
@@ -139,9 +147,11 @@ class MaintenanceSchedule(TransactionBase):
if employee:
holiday_list = get_holiday_list_for_employee(employee)
else:
- holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list")
+ holiday_list = frappe.get_cached_value("Company", self.company, "default_holiday_list")
- holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list)
+ holidays = frappe.db.sql_list(
+ """select holiday_date from `tabHoliday` where parent=%s""", holiday_list
+ )
if not validated and holidays:
@@ -157,25 +167,28 @@ class MaintenanceSchedule(TransactionBase):
def validate_dates_with_periodicity(self):
for d in self.get("items"):
- if d.start_date and d.end_date and d.periodicity and d.periodicity!="Random":
+ if d.start_date and d.end_date and d.periodicity and d.periodicity != "Random":
date_diff = (getdate(d.end_date) - getdate(d.start_date)).days + 1
days_in_period = {
"Weekly": 7,
"Monthly": 30,
"Quarterly": 90,
"Half Yearly": 180,
- "Yearly": 365
+ "Yearly": 365,
}
if date_diff < days_in_period[d.periodicity]:
- throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}")
- .format(d.idx, d.periodicity, days_in_period[d.periodicity]))
+ throw(
+ _(
+ "Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}"
+ ).format(d.idx, d.periodicity, days_in_period[d.periodicity])
+ )
def validate_maintenance_detail(self):
- if not self.get('items'):
+ if not self.get("items"):
throw(_("Please enter Maintaince Details first"))
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.item_code:
throw(_("Please select item code"))
elif not d.start_date or not d.end_date:
@@ -189,11 +202,14 @@ class MaintenanceSchedule(TransactionBase):
throw(_("Start date should be less than end date for Item {0}").format(d.item_code))
def validate_sales_order(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.sales_order:
- chk = frappe.db.sql("""select ms.name from `tabMaintenance Schedule` ms,
+ chk = frappe.db.sql(
+ """select ms.name from `tabMaintenance Schedule` ms,
`tabMaintenance Schedule Item` msi where msi.parent=ms.name and
- msi.sales_order=%s and ms.docstatus=1""", d.sales_order)
+ msi.sales_order=%s and ms.docstatus=1""",
+ d.sales_order,
+ )
if chk:
throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order))
@@ -209,7 +225,7 @@ class MaintenanceSchedule(TransactionBase):
self.generate_schedule()
def on_update(self):
- frappe.db.set(self, 'status', 'Draft')
+ frappe.db.set(self, "status", "Draft")
def update_amc_date(self, serial_nos, amc_expiry_date=None):
for serial_no in serial_nos:
@@ -219,65 +235,96 @@ class MaintenanceSchedule(TransactionBase):
def validate_serial_no(self, item_code, serial_nos, amc_start_date):
for serial_no in serial_nos:
- sr_details = frappe.db.get_value("Serial No", serial_no,
- ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"], as_dict=1)
+ sr_details = frappe.db.get_value(
+ "Serial No",
+ serial_no,
+ ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"],
+ as_dict=1,
+ )
if not sr_details:
frappe.throw(_("Serial No {0} not found").format(serial_no))
if sr_details.get("item_code") != item_code:
- frappe.throw(_("Serial No {0} does not belong to Item {1}")
- .format(frappe.bold(serial_no), frappe.bold(item_code)), title="Invalid")
+ frappe.throw(
+ _("Serial No {0} does not belong to Item {1}").format(
+ frappe.bold(serial_no), frappe.bold(item_code)
+ ),
+ title="Invalid",
+ )
- if sr_details.warranty_expiry_date \
- and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date):
- throw(_("Serial No {0} is under warranty upto {1}")
- .format(serial_no, sr_details.warranty_expiry_date))
+ if sr_details.warranty_expiry_date and getdate(sr_details.warranty_expiry_date) >= getdate(
+ amc_start_date
+ ):
+ throw(
+ _("Serial No {0} is under warranty upto {1}").format(
+ serial_no, sr_details.warranty_expiry_date
+ )
+ )
- if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(amc_start_date):
- throw(_("Serial No {0} is under maintenance contract upto {1}")
- .format(serial_no, sr_details.amc_expiry_date))
+ if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(
+ amc_start_date
+ ):
+ throw(
+ _("Serial No {0} is under maintenance contract upto {1}").format(
+ serial_no, sr_details.amc_expiry_date
+ )
+ )
- if not sr_details.warehouse and sr_details.delivery_date and \
- getdate(sr_details.delivery_date) >= getdate(amc_start_date):
- throw(_("Maintenance start date can not be before delivery date for Serial No {0}")
- .format(serial_no))
+ if (
+ not sr_details.warehouse
+ and sr_details.delivery_date
+ and getdate(sr_details.delivery_date) >= getdate(amc_start_date)
+ ):
+ throw(
+ _("Maintenance start date can not be before delivery date for Serial No {0}").format(
+ serial_no
+ )
+ )
def validate_schedule(self):
- item_lst1 =[]
- item_lst2 =[]
- for d in self.get('items'):
+ item_lst1 = []
+ item_lst2 = []
+ for d in self.get("items"):
if d.item_code not in item_lst1:
item_lst1.append(d.item_code)
- for m in self.get('schedules'):
+ for m in self.get("schedules"):
if m.item_code not in item_lst2:
item_lst2.append(m.item_code)
if len(item_lst1) != len(item_lst2):
- throw(_("Maintenance Schedule is not generated for all the items. Please click on 'Generate Schedule'"))
+ throw(
+ _(
+ "Maintenance Schedule is not generated for all the items. Please click on 'Generate Schedule'"
+ )
+ )
else:
for x in item_lst1:
if x not in item_lst2:
throw(_("Please click on 'Generate Schedule'"))
def check_serial_no_added(self):
- serial_present =[]
- for d in self.get('items'):
+ serial_present = []
+ for d in self.get("items"):
if d.serial_no:
serial_present.append(d.item_code)
- for m in self.get('schedules'):
+ for m in self.get("schedules"):
if serial_present:
if m.item_code in serial_present and not m.serial_no:
- throw(_("Please click on 'Generate Schedule' to fetch Serial No added for Item {0}").format(m.item_code))
+ throw(
+ _("Please click on 'Generate Schedule' to fetch Serial No added for Item {0}").format(
+ m.item_code
+ )
+ )
def on_cancel(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no)
self.update_amc_date(serial_nos)
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
delete_events(self.doctype, self.name)
def on_trash(self):
@@ -301,23 +348,26 @@ class MaintenanceSchedule(TransactionBase):
return items
elif data_type == "id":
for schedule in self.schedules:
- if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date, "dd-mm-yyyy"):
+ if schedule.item_name == item_name and s_date == formatdate(
+ schedule.scheduled_date, "dd-mm-yyyy"
+ ):
return schedule.name
+
@frappe.whitelist()
def get_serial_nos_from_schedule(item_code, schedule=None):
serial_nos = []
if schedule:
- serial_nos = frappe.db.get_value('Maintenance Schedule Item', {
- 'parent': schedule,
- 'item_code': item_code
- }, 'serial_no')
+ serial_nos = frappe.db.get_value(
+ "Maintenance Schedule Item", {"parent": schedule, "item_code": item_code}, "serial_no"
+ )
if serial_nos:
serial_nos = get_serial_nos(serial_nos)
return serial_nos
+
@frappe.whitelist()
def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
from frappe.model.mapper import get_mapped_doc
@@ -331,27 +381,26 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
if len(serial_nos) == 1:
target.serial_no = serial_nos[0]
else:
- target.serial_no = ''
+ target.serial_no = ""
- doclist = get_mapped_doc("Maintenance Schedule", source_name, {
- "Maintenance Schedule": {
- "doctype": "Maintenance Visit",
- "field_map": {
- "name": "maintenance_schedule"
+ doclist = get_mapped_doc(
+ "Maintenance Schedule",
+ source_name,
+ {
+ "Maintenance Schedule": {
+ "doctype": "Maintenance Visit",
+ "field_map": {"name": "maintenance_schedule"},
+ "validation": {"docstatus": ["=", 1]},
+ "postprocess": update_status_and_detail,
},
- "validation": {
- "docstatus": ["=", 1]
+ "Maintenance Schedule Item": {
+ "doctype": "Maintenance Visit Purpose",
+ "condition": lambda doc: doc.item_name == item_name,
+ "field_map": {"sales_person": "service_person"},
+ "postprocess": update_serial,
},
- "postprocess": update_status_and_detail
},
- "Maintenance Schedule Item": {
- "doctype": "Maintenance Visit Purpose",
- "condition": lambda doc: doc.item_name == item_name,
- "field_map": {
- "sales_person": "service_person"
- },
- "postprocess": update_serial
- }
- }, target_doc)
+ target_doc,
+ )
return doclist
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
index 6e727e53ef..a98cd10e32 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
@@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_i
# test_records = frappe.get_test_records('Maintenance Schedule')
+
class TestMaintenanceSchedule(unittest.TestCase):
def test_events_should_be_created_and_deleted(self):
ms = make_maintenance_schedule()
@@ -42,15 +43,15 @@ class TestMaintenanceSchedule(unittest.TestCase):
expected_end_date = add_days(i.start_date, i.no_of_visits * 7)
self.assertEqual(i.end_date, expected_end_date)
- items = ms.get_pending_data(data_type = "items")
- items = items.split('\n')
+ items = ms.get_pending_data(data_type="items")
+ items = items.split("\n")
items.pop(0)
- expected_items = ['_Test Item']
+ expected_items = ["_Test Item"]
self.assertTrue(items, expected_items)
# "dates" contains all generated schedule dates
- dates = ms.get_pending_data(data_type = "date", item_name = i.item_name)
- dates = dates.split('\n')
+ dates = ms.get_pending_data(data_type="date", item_name=i.item_name)
+ dates = dates.split("\n")
dates.pop(0)
expected_dates.append(formatdate(add_days(i.start_date, 7), "dd-MM-yyyy"))
expected_dates.append(formatdate(add_days(i.start_date, 14), "dd-MM-yyyy"))
@@ -59,33 +60,38 @@ class TestMaintenanceSchedule(unittest.TestCase):
self.assertEqual(dates, expected_dates)
ms.submit()
- s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1])
+ s_id = ms.get_pending_data(data_type="id", item_name=i.item_name, s_date=expected_dates[1])
# Check if item is mapped in visit.
- test_map_visit = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id)
+ test_map_visit = make_maintenance_visit(source_name=ms.name, item_name="_Test Item", s_id=s_id)
self.assertEqual(len(test_map_visit.purposes), 1)
self.assertEqual(test_map_visit.purposes[0].item_name, "_Test Item")
- visit = frappe.new_doc('Maintenance Visit')
+ visit = frappe.new_doc("Maintenance Visit")
visit = test_map_visit
visit.maintenance_schedule = ms.name
visit.maintenance_schedule_detail = s_id
visit.completion_status = "Partially Completed"
- visit.set('purposes', [{
- 'item_code': i.item_code,
- 'description': "test",
- 'work_done': "test",
- 'service_person': "Sales Team",
- }])
+ visit.set(
+ "purposes",
+ [
+ {
+ "item_code": i.item_code,
+ "description": "test",
+ "work_done": "test",
+ "service_person": "Sales Team",
+ }
+ ],
+ )
visit.save()
visit.submit()
- ms = frappe.get_doc('Maintenance Schedule', ms.name)
+ ms = frappe.get_doc("Maintenance Schedule", ms.name)
- #checks if visit status is back updated in schedule
+ # checks if visit status is back updated in schedule
self.assertTrue(ms.schedules[1].completion_status, "Partially Completed")
self.assertEqual(format_date(visit.mntc_date), format_date(ms.schedules[1].actual_date))
- #checks if visit status is updated on cancel
+ # checks if visit status is updated on cancel
visit.cancel()
ms.reload()
self.assertTrue(ms.schedules[1].completion_status, "Pending")
@@ -117,22 +123,24 @@ class TestMaintenanceSchedule(unittest.TestCase):
frappe.db.rollback()
+
def make_serial_item_with_serial(item_code):
serial_item_doc = create_item(item_code, is_stock_item=1)
if not serial_item_doc.has_serial_no or not serial_item_doc.serial_no_series:
serial_item_doc.has_serial_no = 1
serial_item_doc.serial_no_series = "TEST.###"
serial_item_doc.save(ignore_permissions=True)
- active_serials = frappe.db.get_all('Serial No', {"status": "Active", "item_code": item_code})
+ active_serials = frappe.db.get_all("Serial No", {"status": "Active", "item_code": item_code})
if len(active_serials) < 2:
make_serialized_item(item_code=item_code)
+
def get_events(ms):
- return frappe.get_all("Event Participants", filters={
- "reference_doctype": ms.doctype,
- "reference_docname": ms.name,
- "parenttype": "Event"
- })
+ return frappe.get_all(
+ "Event Participants",
+ filters={"reference_doctype": ms.doctype, "reference_docname": ms.name, "parenttype": "Event"},
+ )
+
def make_maintenance_schedule(**args):
ms = frappe.new_doc("Maintenance Schedule")
@@ -140,14 +148,17 @@ def make_maintenance_schedule(**args):
ms.customer = "_Test Customer"
ms.transaction_date = today()
- ms.append("items", {
- "item_code": args.get("item_code") or "_Test Item",
- "start_date": today(),
- "periodicity": "Weekly",
- "no_of_visits": 4,
- "serial_no": args.get("serial_no"),
- "sales_person": "Sales Team",
- })
+ ms.append(
+ "items",
+ {
+ "item_code": args.get("item_code") or "_Test Item",
+ "start_date": today(),
+ "periodicity": "Weekly",
+ "no_of_visits": 4,
+ "serial_no": args.get("serial_no"),
+ "sales_person": "Sales Team",
+ },
+ )
ms.insert(ignore_permissions=True)
return ms
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 6fe2466be2..29a17849fd 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -14,7 +14,7 @@ class MaintenanceVisit(TransactionBase):
return _("To {0}").format(self.customer_name)
def validate_serial_no(self):
- for d in self.get('purposes'):
+ for d in self.get("purposes"):
if d.serial_no and not frappe.db.exists("Serial No", d.serial_no):
frappe.throw(_("Serial No {0} does not exist").format(d.serial_no))
@@ -24,13 +24,19 @@ class MaintenanceVisit(TransactionBase):
def validate_maintenance_date(self):
if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
- item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference')
+ item_ref = frappe.db.get_value(
+ "Maintenance Schedule Detail", self.maintenance_schedule_detail, "item_reference"
+ )
if item_ref:
- start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date'])
- if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date):
- frappe.throw(_("Date must be between {0} and {1}")
- .format(format_date(start_date), format_date(end_date)))
-
+ start_date, end_date = frappe.db.get_value(
+ "Maintenance Schedule Item", item_ref, ["start_date", "end_date"]
+ )
+ if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(
+ self.mntc_date
+ ) > get_datetime(end_date):
+ frappe.throw(
+ _("Date must be between {0} and {1}").format(format_date(start_date), format_date(end_date))
+ )
def validate(self):
self.validate_serial_no()
@@ -44,73 +50,87 @@ class MaintenanceVisit(TransactionBase):
status = self.completion_status
actual_date = self.mntc_date
if self.maintenance_schedule_detail:
- frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', status)
- frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', actual_date)
+ frappe.db.set_value(
+ "Maintenance Schedule Detail", self.maintenance_schedule_detail, "completion_status", status
+ )
+ frappe.db.set_value(
+ "Maintenance Schedule Detail", self.maintenance_schedule_detail, "actual_date", actual_date
+ )
def update_customer_issue(self, flag):
if not self.maintenance_schedule:
- for d in self.get('purposes'):
- if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' :
- if flag==1:
+ for d in self.get("purposes"):
+ if d.prevdoc_docname and d.prevdoc_doctype == "Warranty Claim":
+ if flag == 1:
mntc_date = self.mntc_date
service_person = d.service_person
work_done = d.work_done
status = "Open"
- if self.completion_status == 'Fully Completed':
- status = 'Closed'
- elif self.completion_status == 'Partially Completed':
- status = 'Work In Progress'
+ if self.completion_status == "Fully Completed":
+ status = "Closed"
+ elif self.completion_status == "Partially Completed":
+ status = "Work In Progress"
else:
- nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name))
+ nm = frappe.db.sql(
+ "select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1",
+ (d.prevdoc_docname, self.name),
+ )
if nm:
- status = 'Work In Progress'
- mntc_date = nm and nm[0][1] or ''
- service_person = nm and nm[0][2] or ''
- work_done = nm and nm[0][3] or ''
+ status = "Work In Progress"
+ mntc_date = nm and nm[0][1] or ""
+ service_person = nm and nm[0][2] or ""
+ work_done = nm and nm[0][3] or ""
else:
- status = 'Open'
+ status = "Open"
mntc_date = None
service_person = None
work_done = None
- wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
- wc_doc.update({
- 'resolution_date': mntc_date,
- 'resolved_by': service_person,
- 'resolution_details': work_done,
- 'status': status
- })
+ wc_doc = frappe.get_doc("Warranty Claim", d.prevdoc_docname)
+ wc_doc.update(
+ {
+ "resolution_date": mntc_date,
+ "resolved_by": service_person,
+ "resolution_details": work_done,
+ "status": status,
+ }
+ )
wc_doc.db_update()
def check_if_last_visit(self):
"""check if last maintenance visit against same sales order/ Warranty Claim"""
check_for_docname = None
- for d in self.get('purposes'):
+ for d in self.get("purposes"):
if d.prevdoc_docname:
check_for_docname = d.prevdoc_docname
- #check_for_doctype = d.prevdoc_doctype
+ # check_for_doctype = d.prevdoc_doctype
if check_for_docname:
- check = frappe.db.sql("select t1.name from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.name!=%s and t2.prevdoc_docname=%s and t1.docstatus = 1 and (t1.mntc_date > %s or (t1.mntc_date = %s and t1.mntc_time > %s))", (self.name, check_for_docname, self.mntc_date, self.mntc_date, self.mntc_time))
+ check = frappe.db.sql(
+ "select t1.name from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.name!=%s and t2.prevdoc_docname=%s and t1.docstatus = 1 and (t1.mntc_date > %s or (t1.mntc_date = %s and t1.mntc_time > %s))",
+ (self.name, check_for_docname, self.mntc_date, self.mntc_date, self.mntc_time),
+ )
if check:
check_lst = [x[0] for x in check]
- check_lst =','.join(check_lst)
- frappe.throw(_("Cancel Material Visits {0} before cancelling this Maintenance Visit").format(check_lst))
+ check_lst = ",".join(check_lst)
+ frappe.throw(
+ _("Cancel Material Visits {0} before cancelling this Maintenance Visit").format(check_lst)
+ )
raise Exception
else:
self.update_customer_issue(0)
def on_submit(self):
self.update_customer_issue(1)
- frappe.db.set(self, 'status', 'Submitted')
+ frappe.db.set(self, "status", "Submitted")
self.update_status_and_actual_date()
def on_cancel(self):
self.check_if_last_visit()
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
self.update_status_and_actual_date(cancel=True)
def on_update(self):
diff --git a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py
index 6a9e70a1a9..9c868c471d 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py
@@ -8,9 +8,11 @@ from frappe.utils.data import today
# test_records = frappe.get_test_records('Maintenance Visit')
+
class TestMaintenanceVisit(unittest.TestCase):
pass
+
def make_maintenance_visit():
mv = frappe.new_doc("Maintenance Visit")
mv.company = "_Test Company"
@@ -20,22 +22,23 @@ def make_maintenance_visit():
sales_person = make_sales_person("Dwight Schrute")
- mv.append("purposes", {
- "item_code": "_Test Item",
- "sales_person": "Sales Team",
- "description": "Test Item",
- "work_done": "Test Work Done",
- "service_person": sales_person.name
- })
+ mv.append(
+ "purposes",
+ {
+ "item_code": "_Test Item",
+ "sales_person": "Sales Team",
+ "description": "Test Item",
+ "work_done": "Test Work Done",
+ "service_person": sales_person.name,
+ },
+ )
mv.insert(ignore_permissions=True)
return mv
+
def make_sales_person(name):
- sales_person = frappe.get_doc({
- 'doctype': "Sales Person",
- 'sales_person_name': name
- })
- sales_person.insert(ignore_if_duplicate = True)
+ sales_person = frappe.get_doc({"doctype": "Sales Person", "sales_person_name": name})
+ sales_person.insert(ignore_if_duplicate=True)
return sales_person
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
index 1bc12ff35e..9e64f4dc19 100644
--- a/erpnext/manufacturing/dashboard_fixtures.py
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -11,33 +11,39 @@ import erpnext
def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
- })
+ return frappe._dict(
+ {
+ "dashboards": get_dashboards(),
+ "charts": get_charts(),
+ "number_cards": get_number_cards(),
+ }
+ )
+
def get_dashboards():
- return [{
- "name": "Manufacturing",
- "dashboard_name": "Manufacturing",
- "charts": [
- { "chart": "Produced Quantity", "width": "Half" },
- { "chart": "Completed Operation", "width": "Half" },
- { "chart": "Work Order Analysis", "width": "Half" },
- { "chart": "Quality Inspection Analysis", "width": "Half" },
- { "chart": "Pending Work Order", "width": "Half" },
- { "chart": "Last Month Downtime Analysis", "width": "Half" },
- { "chart": "Work Order Qty Analysis", "width": "Full" },
- { "chart": "Job Card Analysis", "width": "Full" }
- ],
- "cards": [
- { "card": "Monthly Total Work Order" },
- { "card": "Monthly Completed Work Order" },
- { "card": "Ongoing Job Card" },
- { "card": "Monthly Quality Inspection"}
- ]
- }]
+ return [
+ {
+ "name": "Manufacturing",
+ "dashboard_name": "Manufacturing",
+ "charts": [
+ {"chart": "Produced Quantity", "width": "Half"},
+ {"chart": "Completed Operation", "width": "Half"},
+ {"chart": "Work Order Analysis", "width": "Half"},
+ {"chart": "Quality Inspection Analysis", "width": "Half"},
+ {"chart": "Pending Work Order", "width": "Half"},
+ {"chart": "Last Month Downtime Analysis", "width": "Half"},
+ {"chart": "Work Order Qty Analysis", "width": "Full"},
+ {"chart": "Job Card Analysis", "width": "Full"},
+ ],
+ "cards": [
+ {"card": "Monthly Total Work Order"},
+ {"card": "Monthly Completed Work Order"},
+ {"card": "Ongoing Job Card"},
+ {"card": "Monthly Quality Inspection"},
+ ],
+ }
+ ]
+
def get_charts():
company = erpnext.get_default_company()
@@ -45,200 +51,198 @@ def get_charts():
if not company:
company = frappe.db.get_value("Company", {"is_group": 0}, "name")
- return [{
- "doctype": "Dashboard Chart",
- "based_on": "modified",
- "chart_type": "Sum",
- "chart_name": _("Produced Quantity"),
- "name": "Produced Quantity",
- "document_type": "Work Order",
- "filters_json": json.dumps([['Work Order', 'docstatus', '=', 1, False]]),
- "group_by_type": "Count",
- "time_interval": "Monthly",
- "timespan": "Last Year",
- "owner": "Administrator",
- "type": "Line",
- "value_based_on": "produced_qty",
- "is_public": 1,
- "timeseries": 1
- }, {
- "doctype": "Dashboard Chart",
- "based_on": "creation",
- "chart_type": "Sum",
- "chart_name": _("Completed Operation"),
- "name": "Completed Operation",
- "document_type": "Work Order Operation",
- "filters_json": json.dumps([['Work Order Operation', 'docstatus', '=', 1, False]]),
- "group_by_type": "Count",
- "time_interval": "Quarterly",
- "timespan": "Last Year",
- "owner": "Administrator",
- "type": "Line",
- "value_based_on": "completed_qty",
- "is_public": 1,
- "timeseries": 1
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Work Order Analysis"),
- "name": "Work Order Analysis",
- "timespan": "Last Year",
- "report_name": "Work Order Summary",
- "owner": "Administrator",
- "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}),
- "type": "Donut",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "axisOptions": {
- "shortenYAxisNumbers": 1
- },
- "height": 300
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Quality Inspection Analysis"),
- "name": "Quality Inspection Analysis",
- "timespan": "Last Year",
- "report_name": "Quality Inspection Summary",
- "owner": "Administrator",
- "filters_json": json.dumps({}),
- "type": "Donut",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "axisOptions": {
- "shortenYAxisNumbers": 1
- },
- "height": 300
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Pending Work Order"),
- "name": "Pending Work Order",
- "timespan": "Last Year",
- "report_name": "Work Order Summary",
- "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}),
- "owner": "Administrator",
- "type": "Donut",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "axisOptions": {
- "shortenYAxisNumbers": 1
- },
- "height": 300
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Last Month Downtime Analysis"),
- "name": "Last Month Downtime Analysis",
- "timespan": "Last Year",
- "filters_json": json.dumps({}),
- "report_name": "Downtime Analysis",
- "owner": "Administrator",
- "is_public": 1,
- "is_custom": 1,
- "type": "Bar"
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Work Order Qty Analysis"),
- "name": "Work Order Qty Analysis",
- "timespan": "Last Year",
- "report_name": "Work Order Summary",
- "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}),
- "owner": "Administrator",
- "type": "Bar",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "barOptions": { "stacked": 1 }
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Job Card Analysis"),
- "name": "Job Card Analysis",
- "timespan": "Last Year",
- "report_name": "Job Card Summary",
- "owner": "Administrator",
- "is_public": 1,
- "is_custom": 1,
- "filters_json": json.dumps({"company": company, "docstatus": 1, "range":"Monthly"}),
- "custom_options": json.dumps({
- "barOptions": { "stacked": 1 }
- }),
- "type": "Bar"
- }]
+ return [
+ {
+ "doctype": "Dashboard Chart",
+ "based_on": "modified",
+ "chart_type": "Sum",
+ "chart_name": _("Produced Quantity"),
+ "name": "Produced Quantity",
+ "document_type": "Work Order",
+ "filters_json": json.dumps([["Work Order", "docstatus", "=", 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Monthly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "produced_qty",
+ "is_public": 1,
+ "timeseries": 1,
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "based_on": "creation",
+ "chart_type": "Sum",
+ "chart_name": _("Completed Operation"),
+ "name": "Completed Operation",
+ "document_type": "Work Order Operation",
+ "filters_json": json.dumps([["Work Order Operation", "docstatus", "=", 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Quarterly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "completed_qty",
+ "is_public": 1,
+ "timeseries": 1,
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Analysis"),
+ "name": "Work Order Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"axisOptions": {"shortenYAxisNumbers": 1}, "height": 300}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Quality Inspection Analysis"),
+ "name": "Quality Inspection Analysis",
+ "timespan": "Last Year",
+ "report_name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"axisOptions": {"shortenYAxisNumbers": 1}, "height": 300}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Pending Work Order"),
+ "name": "Pending Work Order",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}),
+ "owner": "Administrator",
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"axisOptions": {"shortenYAxisNumbers": 1}, "height": 300}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Last Month Downtime Analysis"),
+ "name": "Last Month Downtime Analysis",
+ "timespan": "Last Year",
+ "filters_json": json.dumps({}),
+ "report_name": "Downtime Analysis",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "type": "Bar",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Qty Analysis"),
+ "name": "Work Order Qty Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}),
+ "owner": "Administrator",
+ "type": "Bar",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"barOptions": {"stacked": 1}}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Job Card Analysis"),
+ "name": "Job Card Analysis",
+ "timespan": "Last Year",
+ "report_name": "Job Card Summary",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "filters_json": json.dumps({"company": company, "docstatus": 1, "range": "Monthly"}),
+ "custom_options": json.dumps({"barOptions": {"stacked": 1}}),
+ "type": "Bar",
+ },
+ ]
+
def get_number_cards():
start_date = add_months(nowdate(), -1)
end_date = nowdate()
- return [{
- "doctype": "Number Card",
- "document_type": "Work Order",
- "name": "Monthly Total Work Order",
- "filters_json": json.dumps([
- ['Work Order', 'docstatus', '=', 1],
- ['Work Order', 'creation', 'between', [start_date, end_date]]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Monthly Total Work Orders"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Work Order",
- "name": "Monthly Completed Work Order",
- "filters_json": json.dumps([
- ['Work Order', 'status', '=', 'Completed'],
- ['Work Order', 'docstatus', '=', 1],
- ['Work Order', 'creation', 'between', [start_date, end_date]]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Monthly Completed Work Orders"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Job Card",
- "name": "Ongoing Job Card",
- "filters_json": json.dumps([
- ['Job Card', 'status','!=','Completed'],
- ['Job Card', 'docstatus', '=', 1]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Ongoing Job Cards"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Quality Inspection",
- "name": "Monthly Quality Inspection",
- "filters_json": json.dumps([
- ['Quality Inspection', 'docstatus', '=', 1],
- ['Quality Inspection', 'creation', 'between', [start_date, end_date]]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Monthly Quality Inspections"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- }]
+ return [
+ {
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Monthly Total Work Order",
+ "filters_json": json.dumps(
+ [
+ ["Work Order", "docstatus", "=", 1],
+ ["Work Order", "creation", "between", [start_date, end_date]],
+ ]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Total Work Orders"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Monthly Completed Work Order",
+ "filters_json": json.dumps(
+ [
+ ["Work Order", "status", "=", "Completed"],
+ ["Work Order", "docstatus", "=", 1],
+ ["Work Order", "creation", "between", [start_date, end_date]],
+ ]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Completed Work Orders"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Job Card",
+ "name": "Ongoing Job Card",
+ "filters_json": json.dumps(
+ [["Job Card", "status", "!=", "Completed"], ["Job Card", "docstatus", "=", 1]]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Ongoing Job Cards"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Quality Inspection",
+ "name": "Monthly Quality Inspection",
+ "filters_json": json.dumps(
+ [
+ ["Quality Inspection", "docstatus", "=", 1],
+ ["Quality Inspection", "creation", "between", [start_date, end_date]],
+ ]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Quality Inspections"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ ]
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index 5340c51131..ff2140199d 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -29,7 +29,9 @@ class BlanketOrder(Document):
def update_ordered_qty(self):
ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
- item_ordered_qty = frappe._dict(frappe.db.sql("""
+ item_ordered_qty = frappe._dict(
+ frappe.db.sql(
+ """
select trans_item.item_code, sum(trans_item.stock_qty) as qty
from `tab{0} Item` trans_item, `tab{0}` trans
where trans.name = trans_item.parent
@@ -37,18 +39,24 @@ class BlanketOrder(Document):
and trans.docstatus=1
and trans.status not in ('Closed', 'Stopped')
group by trans_item.item_code
- """.format(ref_doctype), self.name))
+ """.format(
+ ref_doctype
+ ),
+ self.name,
+ )
+ )
for d in self.items:
d.db_set("ordered_qty", item_ordered_qty.get(d.item_code, 0))
+
@frappe.whitelist()
def make_order(source_name):
doctype = frappe.flags.args.doctype
def update_doc(source_doc, target_doc, source_parent):
- if doctype == 'Quotation':
- target_doc.quotation_to = 'Customer'
+ if doctype == "Quotation":
+ target_doc.quotation_to = "Customer"
target_doc.party_name = source_doc.customer
def update_item(source, target, source_parent):
@@ -62,18 +70,16 @@ def make_order(source_name):
target.against_blanket_order = 1
target.blanket_order = source_name
- target_doc = get_mapped_doc("Blanket Order", source_name, {
- "Blanket Order": {
- "doctype": doctype,
- "postprocess": update_doc
- },
- "Blanket Order Item": {
- "doctype": doctype + " Item",
- "field_map": {
- "rate": "blanket_order_rate",
- "parent": "blanket_order"
+ target_doc = get_mapped_doc(
+ "Blanket Order",
+ source_name,
+ {
+ "Blanket Order": {"doctype": doctype, "postprocess": update_doc},
+ "Blanket Order Item": {
+ "doctype": doctype + " Item",
+ "field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"},
+ "postprocess": update_item,
},
- "postprocess": update_item
- }
- })
+ },
+ )
return target_doc
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
index c6745c877c..31062342d0 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
return {
- 'fieldname': 'blanket_order',
- 'transactions': [
- {
- 'items': ['Purchase Order', 'Sales Order', 'Quotation']
- }
- ]
+ "fieldname": "blanket_order",
+ "transactions": [{"items": ["Purchase Order", "Sales Order", "Quotation"]}],
}
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index d4d337d841..2f1f3ae0f5 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -16,7 +16,7 @@ class TestBlanketOrder(FrappeTestCase):
def test_sales_order_creation(self):
bo = make_blanket_order(blanket_order_type="Selling")
- frappe.flags.args.doctype = 'Sales Order'
+ frappe.flags.args.doctype = "Sales Order"
so = make_order(bo.name)
so.currency = get_company_currency(so.company)
so.delivery_date = today()
@@ -33,16 +33,15 @@ class TestBlanketOrder(FrappeTestCase):
self.assertEqual(so.items[0].qty, bo.items[0].ordered_qty)
# test the quantity
- frappe.flags.args.doctype = 'Sales Order'
+ frappe.flags.args.doctype = "Sales Order"
so1 = make_order(bo.name)
so1.currency = get_company_currency(so1.company)
- self.assertEqual(so1.items[0].qty, (bo.items[0].qty-bo.items[0].ordered_qty))
-
+ self.assertEqual(so1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
def test_purchase_order_creation(self):
bo = make_blanket_order(blanket_order_type="Purchasing")
- frappe.flags.args.doctype = 'Purchase Order'
+ frappe.flags.args.doctype = "Purchase Order"
po = make_order(bo.name)
po.currency = get_company_currency(po.company)
po.schedule_date = today()
@@ -59,11 +58,10 @@ class TestBlanketOrder(FrappeTestCase):
self.assertEqual(po.items[0].qty, bo.items[0].ordered_qty)
# test the quantity
- frappe.flags.args.doctype = 'Purchase Order'
+ frappe.flags.args.doctype = "Purchase Order"
po1 = make_order(bo.name)
po1.currency = get_company_currency(po1.company)
- self.assertEqual(po1.items[0].qty, (bo.items[0].qty-bo.items[0].ordered_qty))
-
+ self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
def make_blanket_order(**args):
@@ -80,11 +78,14 @@ def make_blanket_order(**args):
bo.from_date = today()
bo.to_date = add_months(bo.from_date, months=12)
- bo.append("items", {
- "item_code": args.item_code or "_Test Item",
- "qty": args.quantity or 1000,
- "rate": args.rate or 100
- })
+ bo.append(
+ "items",
+ {
+ "item_code": args.item_code or "_Test Item",
+ "qty": args.quantity or 1000,
+ "rate": args.rate or 100,
+ },
+ )
bo.insert()
bo.submit()
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 4d7e459778..bf29474c00 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -19,9 +19,7 @@ from erpnext.setup.utils import get_exchange_rate
from erpnext.stock.doctype.item.item import get_item_details
from erpnext.stock.get_item_details import get_conversion_factor, get_price_list_rate
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
class BOMTree:
@@ -31,10 +29,12 @@ class BOMTree:
# ref: https://docs.python.org/3/reference/datamodel.html#slots
__slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"]
- def __init__(self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1) -> None:
+ def __init__(
+ self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1
+ ) -> None:
self.name = name # name of node, BOM number if is_bom else item_code
self.child_items: List["BOMTree"] = [] # list of child items
- self.is_bom = is_bom # true if the node is a BOM and not a leaf item
+ self.is_bom = is_bom # true if the node is a BOM and not a leaf item
self.item_code: str = None # item_code associated with node
self.qty = qty # required unit quantity to make one unit of parent item.
self.exploded_qty = exploded_qty # total exploded qty required for making root of tree.
@@ -62,12 +62,12 @@ class BOMTree:
"""Get level order traversal of tree.
E.g. for following tree the traversal will return list of nodes in order from top to bottom.
BOM:
- - SubAssy1
- - item1
- - item2
- - SubAssy2
- - item3
- - item4
+ - SubAssy1
+ - item1
+ - item2
+ - SubAssy2
+ - item3
+ - item4
returns = [SubAssy1, item1, item2, SubAssy2, item3, item4]
"""
@@ -96,19 +96,18 @@ class BOMTree:
rep += child.__repr__(level=level + 1)
return rep
+
class BOM(WebsiteGenerator):
website = frappe._dict(
# page_title_field = "item_name",
- condition_field = "show_in_website",
- template = "templates/generators/bom.html"
+ condition_field="show_in_website",
+ template="templates/generators/bom.html",
)
def autoname(self):
# ignore amended documents while calculating current index
existing_boms = frappe.get_all(
- "BOM",
- filters={"item": self.item, "amended_from": ["is", "not set"]},
- pluck="name"
+ "BOM", filters={"item": self.item, "amended_from": ["is", "not set"]}, pluck="name"
)
if existing_boms:
@@ -135,11 +134,15 @@ class BOM(WebsiteGenerator):
conflicting_bom = frappe.get_doc("BOM", name)
if conflicting_bom.item != self.item:
- msg = (_("A BOM with name {0} already exists for item {1}.")
- .format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
+ msg = _("A BOM with name {0} already exists for item {1}.").format(
+ frappe.bold(name), frappe.bold(conflicting_bom.item)
+ )
- frappe.throw(_("{0}{1} Did you rename the item? Please contact Administrator / Tech support")
- .format(msg, "
"))
+ frappe.throw(
+ _("{0}{1} Did you rename the item? Please contact Administrator / Tech support").format(
+ msg, "
"
+ )
+ )
self.name = name
@@ -164,7 +167,7 @@ class BOM(WebsiteGenerator):
return index
def validate(self):
- self.route = frappe.scrub(self.name).replace('_', '-')
+ self.route = frappe.scrub(self.name).replace("_", "-")
if not self.company:
frappe.throw(_("Please select a Company first."), title=_("Mandatory"))
@@ -184,14 +187,14 @@ class BOM(WebsiteGenerator):
self.validate_operations()
self.calculate_cost()
self.update_stock_qty()
- self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
+ self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
self.validate_scrap_items()
def get_context(self, context):
- context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
+ context.parents = [{"name": "boms", "title": _("All BOMs")}]
def on_update(self):
- frappe.cache().hdel('bom_children', self.name)
+ frappe.cache().hdel("bom_children", self.name)
self.check_recursion()
def on_submit(self):
@@ -221,37 +224,53 @@ class BOM(WebsiteGenerator):
def get_routing(self):
if self.routing:
self.set("operations", [])
- fields = ["sequence_id", "operation", "workstation", "description",
- "time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate",
- "set_cost_based_on_bom_qty", "fixed_time"]
+ fields = [
+ "sequence_id",
+ "operation",
+ "workstation",
+ "description",
+ "time_in_mins",
+ "batch_size",
+ "operating_cost",
+ "idx",
+ "hour_rate",
+ "set_cost_based_on_bom_qty",
+ "fixed_time",
+ ]
- for row in frappe.get_all("BOM Operation", fields = fields,
- filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
- child = self.append('operations', row)
+ for row in frappe.get_all(
+ "BOM Operation",
+ fields=fields,
+ filters={"parenttype": "Routing", "parent": self.routing},
+ order_by="sequence_id, idx",
+ ):
+ child = self.append("operations", row)
child.hour_rate = flt(row.hour_rate / self.conversion_rate, child.precision("hour_rate"))
def set_bom_material_details(self):
for item in self.get("items"):
self.validate_bom_currency(item)
- item.bom_no = ''
+ item.bom_no = ""
if not item.do_not_explode:
item.bom_no = item.bom_no
- ret = self.get_bom_material_detail({
- "company": self.company,
- "item_code": item.item_code,
- "item_name": item.item_name,
- "bom_no": item.bom_no,
- "stock_qty": item.stock_qty,
- "include_item_in_manufacturing": item.include_item_in_manufacturing,
- "qty": item.qty,
- "uom": item.uom,
- "stock_uom": item.stock_uom,
- "conversion_factor": item.conversion_factor,
- "sourced_by_supplier": item.sourced_by_supplier,
- "do_not_explode": item.do_not_explode
- })
+ ret = self.get_bom_material_detail(
+ {
+ "company": self.company,
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "bom_no": item.bom_no,
+ "stock_qty": item.stock_qty,
+ "include_item_in_manufacturing": item.include_item_in_manufacturing,
+ "qty": item.qty,
+ "uom": item.uom,
+ "stock_uom": item.stock_uom,
+ "conversion_factor": item.conversion_factor,
+ "sourced_by_supplier": item.sourced_by_supplier,
+ "do_not_explode": item.do_not_explode,
+ }
+ )
for r in ret:
if not item.get(r):
@@ -263,7 +282,7 @@ class BOM(WebsiteGenerator):
"item_code": item.item_code,
"company": self.company,
"scrap_items": True,
- "bom_no": '',
+ "bom_no": "",
}
ret = self.get_bom_material_detail(args)
for key, value in ret.items():
@@ -272,75 +291,93 @@ class BOM(WebsiteGenerator):
@frappe.whitelist()
def get_bom_material_detail(self, args=None):
- """ Get raw material details like uom, desc and rate"""
+ """Get raw material details like uom, desc and rate"""
if not args:
- args = frappe.form_dict.get('args')
+ args = frappe.form_dict.get("args")
if isinstance(args, str):
import json
+
args = json.loads(args)
- item = self.get_item_det(args['item_code'])
+ item = self.get_item_det(args["item_code"])
- args['bom_no'] = args['bom_no'] or item and cstr(item['default_bom']) or ''
- args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or
- item and item.include_item_in_manufacturing or 0)
+ args["bom_no"] = args["bom_no"] or item and cstr(item["default_bom"]) or ""
+ args["transfer_for_manufacture"] = (
+ cstr(args.get("include_item_in_manufacturing", ""))
+ or item
+ and item.include_item_in_manufacturing
+ or 0
+ )
args.update(item)
rate = self.get_rm_rate(args)
ret_item = {
- 'item_name' : item and args['item_name'] or '',
- 'description' : item and args['description'] or '',
- 'image' : item and args['image'] or '',
- 'stock_uom' : item and args['stock_uom'] or '',
- 'uom' : item and args['stock_uom'] or '',
- 'conversion_factor': 1,
- 'bom_no' : args['bom_no'],
- 'rate' : rate,
- 'qty' : args.get("qty") or args.get("stock_qty") or 1,
- 'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
- 'base_rate' : flt(rate) * (flt(self.conversion_rate) or 1),
- 'include_item_in_manufacturing': cint(args.get('transfer_for_manufacture')),
- 'sourced_by_supplier' : args.get('sourced_by_supplier', 0)
+ "item_name": item and args["item_name"] or "",
+ "description": item and args["description"] or "",
+ "image": item and args["image"] or "",
+ "stock_uom": item and args["stock_uom"] or "",
+ "uom": item and args["stock_uom"] or "",
+ "conversion_factor": 1,
+ "bom_no": args["bom_no"],
+ "rate": rate,
+ "qty": args.get("qty") or args.get("stock_qty") or 1,
+ "stock_qty": args.get("qty") or args.get("stock_qty") or 1,
+ "base_rate": flt(rate) * (flt(self.conversion_rate) or 1),
+ "include_item_in_manufacturing": cint(args.get("transfer_for_manufacture")),
+ "sourced_by_supplier": args.get("sourced_by_supplier", 0),
}
- if args.get('do_not_explode'):
- ret_item['bom_no'] = ''
+ if args.get("do_not_explode"):
+ ret_item["bom_no"] = ""
return ret_item
def validate_bom_currency(self, item):
- if item.get('bom_no') and frappe.db.get_value('BOM', item.get('bom_no'), 'currency') != self.currency:
- frappe.throw(_("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}")
- .format(item.idx, item.bom_no, self.currency))
+ if (
+ item.get("bom_no")
+ and frappe.db.get_value("BOM", item.get("bom_no"), "currency") != self.currency
+ ):
+ frappe.throw(
+ _("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}").format(
+ item.idx, item.bom_no, self.currency
+ )
+ )
def get_rm_rate(self, arg):
- """ Get raw material rate as per selected method, if bom exists takes bom cost """
+ """Get raw material rate as per selected method, if bom exists takes bom cost"""
rate = 0
if not self.rm_cost_as_per:
self.rm_cost_as_per = "Valuation Rate"
- if arg.get('scrap_items'):
+ if arg.get("scrap_items"):
rate = get_valuation_rate(arg)
elif arg:
- #Customer Provided parts and Supplier sourced parts will have zero rate
- if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item') and not arg.get('sourced_by_supplier'):
- if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
- rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1)
+ # Customer Provided parts and Supplier sourced parts will have zero rate
+ if not frappe.db.get_value(
+ "Item", arg["item_code"], "is_customer_provided_item"
+ ) and not arg.get("sourced_by_supplier"):
+ if arg.get("bom_no") and self.set_rate_of_sub_assembly_item_based_on_bom:
+ rate = flt(self.get_bom_unitcost(arg["bom_no"])) * (arg.get("conversion_factor") or 1)
else:
rate = get_bom_item_rate(arg, self)
if not rate:
if self.rm_cost_as_per == "Price List":
- frappe.msgprint(_("Price not found for item {0} in price list {1}")
- .format(arg["item_code"], self.buying_price_list), alert=True)
+ frappe.msgprint(
+ _("Price not found for item {0} in price list {1}").format(
+ arg["item_code"], self.buying_price_list
+ ),
+ alert=True,
+ )
else:
- frappe.msgprint(_("{0} not found for item {1}")
- .format(self.rm_cost_as_per, arg["item_code"]), alert=True)
+ frappe.msgprint(
+ _("{0} not found for item {1}").format(self.rm_cost_as_per, arg["item_code"]), alert=True
+ )
return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
@frappe.whitelist()
- def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate = True, save=True):
+ def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate=True, save=True):
if self.docstatus == 2:
return
@@ -350,16 +387,18 @@ class BOM(WebsiteGenerator):
if not d.item_code:
continue
- rate = self.get_rm_rate({
- "company": self.company,
- "item_code": d.item_code,
- "bom_no": d.bom_no,
- "qty": d.qty,
- "uom": d.uom,
- "stock_uom": d.stock_uom,
- "conversion_factor": d.conversion_factor,
- "sourced_by_supplier": d.sourced_by_supplier
- })
+ rate = self.get_rm_rate(
+ {
+ "company": self.company,
+ "item_code": d.item_code,
+ "bom_no": d.bom_no,
+ "qty": d.qty,
+ "uom": d.uom,
+ "stock_uom": d.stock_uom,
+ "conversion_factor": d.conversion_factor,
+ "sourced_by_supplier": d.sourced_by_supplier,
+ }
+ )
if rate:
d.rate = rate
@@ -380,8 +419,11 @@ class BOM(WebsiteGenerator):
# update parent BOMs
if self.total_cost != existing_bom_cost and update_parent:
- parent_boms = frappe.db.sql_list("""select distinct parent from `tabBOM Item`
- where bom_no = %s and docstatus=1 and parenttype='BOM'""", self.name)
+ parent_boms = frappe.db.sql_list(
+ """select distinct parent from `tabBOM Item`
+ where bom_no = %s and docstatus=1 and parenttype='BOM'""",
+ self.name,
+ )
for bom in parent_boms:
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
@@ -393,45 +435,54 @@ class BOM(WebsiteGenerator):
if self.total_cost:
cost = self.total_cost / self.quantity
- frappe.db.sql("""update `tabBOM Item` set rate=%s, amount=stock_qty*%s
+ frappe.db.sql(
+ """update `tabBOM Item` set rate=%s, amount=stock_qty*%s
where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
- (cost, cost, self.name))
+ (cost, cost, self.name),
+ )
def get_bom_unitcost(self, bom_no):
- bom = frappe.db.sql("""select name, base_total_cost/quantity as unit_cost from `tabBOM`
- where is_active = 1 and name = %s""", bom_no, as_dict=1)
- return bom and bom[0]['unit_cost'] or 0
+ bom = frappe.db.sql(
+ """select name, base_total_cost/quantity as unit_cost from `tabBOM`
+ where is_active = 1 and name = %s""",
+ bom_no,
+ as_dict=1,
+ )
+ return bom and bom[0]["unit_cost"] or 0
def manage_default_bom(self):
- """ Uncheck others if current one is selected as default or
- check the current one as default if it the only bom for the selected item,
- update default bom in item master
+ """Uncheck others if current one is selected as default or
+ check the current one as default if it the only bom for the selected item,
+ update default bom in item master
"""
if self.is_default and self.is_active:
from frappe.model.utils import set_default
+
set_default(self, "item")
item = frappe.get_doc("Item", self.item)
if item.default_bom != self.name:
- frappe.db.set_value('Item', self.item, 'default_bom', self.name)
- elif not frappe.db.exists(dict(doctype='BOM', docstatus=1, item=self.item, is_default=1)) \
- and self.is_active:
+ frappe.db.set_value("Item", self.item, "default_bom", self.name)
+ elif (
+ not frappe.db.exists(dict(doctype="BOM", docstatus=1, item=self.item, is_default=1))
+ and self.is_active
+ ):
frappe.db.set(self, "is_default", 1)
else:
frappe.db.set(self, "is_default", 0)
item = frappe.get_doc("Item", self.item)
if item.default_bom == self.name:
- frappe.db.set_value('Item', self.item, 'default_bom', None)
+ frappe.db.set_value("Item", self.item, "default_bom", None)
def clear_operations(self):
if not self.with_operations:
- self.set('operations', [])
+ self.set("operations", [])
def clear_inspection(self):
if not self.inspection_required:
self.quality_inspection_template = None
def validate_main_item(self):
- """ Validate main FG item"""
+ """Validate main FG item"""
item = self.get_item_det(self.item)
if not item:
frappe.throw(_("Item {0} does not exist in the system or has expired").format(self.item))
@@ -439,30 +490,34 @@ class BOM(WebsiteGenerator):
ret = frappe.db.get_value("Item", self.item, ["description", "stock_uom", "item_name"])
self.description = ret[0]
self.uom = ret[1]
- self.item_name= ret[2]
+ self.item_name = ret[2]
if not self.quantity:
frappe.throw(_("Quantity should be greater than 0"))
def validate_currency(self):
- if self.rm_cost_as_per == 'Price List':
- price_list_currency = frappe.db.get_value('Price List', self.buying_price_list, 'currency')
+ if self.rm_cost_as_per == "Price List":
+ price_list_currency = frappe.db.get_value("Price List", self.buying_price_list, "currency")
if price_list_currency not in (self.currency, self.company_currency()):
- frappe.throw(_("Currency of the price list {0} must be {1} or {2}")
- .format(self.buying_price_list, self.currency, self.company_currency()))
+ frappe.throw(
+ _("Currency of the price list {0} must be {1} or {2}").format(
+ self.buying_price_list, self.currency, self.company_currency()
+ )
+ )
def update_stock_qty(self):
- for m in self.get('items'):
+ for m in self.get("items"):
if not m.conversion_factor:
- m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)['conversion_factor'])
+ m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)["conversion_factor"])
if m.uom and m.qty:
- m.stock_qty = flt(m.conversion_factor)*flt(m.qty)
+ m.stock_qty = flt(m.conversion_factor) * flt(m.qty)
if not m.uom and m.stock_uom:
m.uom = m.stock_uom
m.qty = m.stock_qty
def validate_uom_is_interger(self):
from erpnext.utilities.transaction_base import validate_uom_is_integer
+
validate_uom_is_integer(self, "uom", "qty", "BOM Item")
validate_uom_is_integer(self, "stock_uom", "stock_qty", "BOM Item")
@@ -470,23 +525,26 @@ class BOM(WebsiteGenerator):
if self.currency == self.company_currency():
self.conversion_rate = 1
elif self.conversion_rate == 1 or flt(self.conversion_rate) <= 0:
- self.conversion_rate = get_exchange_rate(self.currency, self.company_currency(), args="for_buying")
+ self.conversion_rate = get_exchange_rate(
+ self.currency, self.company_currency(), args="for_buying"
+ )
def set_plc_conversion_rate(self):
if self.rm_cost_as_per in ["Valuation Rate", "Last Purchase Rate"]:
self.plc_conversion_rate = 1
elif not self.plc_conversion_rate and self.price_list_currency:
- self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
- self.company_currency(), args="for_buying")
+ self.plc_conversion_rate = get_exchange_rate(
+ self.price_list_currency, self.company_currency(), args="for_buying"
+ )
def validate_materials(self):
- """ Validate raw material entries """
+ """Validate raw material entries"""
- if not self.get('items'):
+ if not self.get("items"):
frappe.throw(_("Raw Materials cannot be blank."))
check_list = []
- for m in self.get('items'):
+ for m in self.get("items"):
if m.bom_no:
validate_bom_no(m.item_code, m.bom_no)
if flt(m.qty) <= 0:
@@ -494,13 +552,20 @@ class BOM(WebsiteGenerator):
check_list.append(m)
def check_recursion(self, bom_list=None):
- """ Check whether recursion occurs in any bom"""
+ """Check whether recursion occurs in any bom"""
+
def _throw_error(bom_name):
frappe.throw(_("BOM recursion: {0} cannot be parent or child of {0}").format(bom_name))
bom_list = self.traverse_tree()
- child_items = frappe.get_all('BOM Item', fields=["bom_no", "item_code"],
- filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) or []
+ child_items = (
+ frappe.get_all(
+ "BOM Item",
+ fields=["bom_no", "item_code"],
+ filters={"parent": ("in", bom_list), "parenttype": "BOM"},
+ )
+ or []
+ )
child_bom = {d.bom_no for d in child_items}
child_items_codes = {d.item_code for d in child_items}
@@ -511,19 +576,26 @@ class BOM(WebsiteGenerator):
if self.item in child_items_codes:
_throw_error(self.item)
- bom_nos = frappe.get_all('BOM Item', fields=["parent"],
- filters={'bom_no': self.name, 'parenttype': 'BOM'}) or []
+ bom_nos = (
+ frappe.get_all(
+ "BOM Item", fields=["parent"], filters={"bom_no": self.name, "parenttype": "BOM"}
+ )
+ or []
+ )
if self.name in {d.parent for d in bom_nos}:
_throw_error(self.name)
def traverse_tree(self, bom_list=None):
def _get_children(bom_no):
- children = frappe.cache().hget('bom_children', bom_no)
+ children = frappe.cache().hget("bom_children", bom_no)
if children is None:
- children = frappe.db.sql_list("""SELECT `bom_no` FROM `tabBOM Item`
- WHERE `parent`=%s AND `bom_no`!='' AND `parenttype`='BOM'""", bom_no)
- frappe.cache().hset('bom_children', bom_no, children)
+ children = frappe.db.sql_list(
+ """SELECT `bom_no` FROM `tabBOM Item`
+ WHERE `parent`=%s AND `bom_no`!='' AND `parenttype`='BOM'""",
+ bom_no,
+ )
+ frappe.cache().hset("bom_children", bom_no, children)
return children
count = 0
@@ -533,7 +605,7 @@ class BOM(WebsiteGenerator):
if self.name not in bom_list:
bom_list.append(self.name)
- while(count < len(bom_list)):
+ while count < len(bom_list):
for child_bom in _get_children(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
@@ -541,19 +613,21 @@ class BOM(WebsiteGenerator):
bom_list.reverse()
return bom_list
- def calculate_cost(self, update_hour_rate = False):
+ def calculate_cost(self, update_hour_rate=False):
"""Calculate bom totals"""
self.calculate_op_cost(update_hour_rate)
self.calculate_rm_cost()
self.calculate_sm_cost()
self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
- self.base_total_cost = self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
+ self.base_total_cost = (
+ self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
+ )
- def calculate_op_cost(self, update_hour_rate = False):
+ def calculate_op_cost(self, update_hour_rate=False):
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
self.base_operating_cost = 0
- for d in self.get('operations'):
+ for d in self.get("operations"):
if d.workstation:
self.update_rate_and_time(d, update_hour_rate)
@@ -566,13 +640,14 @@ class BOM(WebsiteGenerator):
self.operating_cost += flt(operating_cost)
self.base_operating_cost += flt(base_operating_cost)
- def update_rate_and_time(self, row, update_hour_rate = False):
+ def update_rate_and_time(self, row, update_hour_rate=False):
if not row.hour_rate or update_hour_rate:
hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate"))
if hour_rate:
- row.hour_rate = (hour_rate / flt(self.conversion_rate)
- if self.conversion_rate and hour_rate else hour_rate)
+ row.hour_rate = (
+ hour_rate / flt(self.conversion_rate) if self.conversion_rate and hour_rate else hour_rate
+ )
if row.hour_rate and row.time_in_mins:
row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
@@ -589,12 +664,13 @@ class BOM(WebsiteGenerator):
total_rm_cost = 0
base_total_rm_cost = 0
- for d in self.get('items'):
+ for d in self.get("items"):
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
d.amount = flt(d.rate, d.precision("rate")) * flt(d.qty, d.precision("qty"))
d.base_amount = d.amount * flt(self.conversion_rate)
- d.qty_consumed_per_unit = flt(d.stock_qty, d.precision("stock_qty")) \
- / flt(self.quantity, self.precision("quantity"))
+ d.qty_consumed_per_unit = flt(d.stock_qty, d.precision("stock_qty")) / flt(
+ self.quantity, self.precision("quantity")
+ )
total_rm_cost += d.amount
base_total_rm_cost += d.base_amount
@@ -607,10 +683,14 @@ class BOM(WebsiteGenerator):
total_sm_cost = 0
base_total_sm_cost = 0
- for d in self.get('scrap_items'):
- d.base_rate = flt(d.rate, d.precision("rate")) * flt(self.conversion_rate, self.precision("conversion_rate"))
+ for d in self.get("scrap_items"):
+ d.base_rate = flt(d.rate, d.precision("rate")) * flt(
+ self.conversion_rate, self.precision("conversion_rate")
+ )
d.amount = flt(d.rate, d.precision("rate")) * flt(d.stock_qty, d.precision("stock_qty"))
- d.base_amount = flt(d.amount, d.precision("amount")) * flt(self.conversion_rate, self.precision("conversion_rate"))
+ d.base_amount = flt(d.amount, d.precision("amount")) * flt(
+ self.conversion_rate, self.precision("conversion_rate")
+ )
total_sm_cost += d.amount
base_total_sm_cost += d.base_amount
@@ -619,37 +699,42 @@ class BOM(WebsiteGenerator):
def update_new_bom(self, old_bom, new_bom, rate):
for d in self.get("items"):
- if d.bom_no != old_bom: continue
+ if d.bom_no != old_bom:
+ continue
d.bom_no = new_bom
d.rate = rate
d.amount = (d.stock_qty or d.qty) * rate
def update_exploded_items(self, save=True):
- """ Update Flat BOM, following will be correct data"""
+ """Update Flat BOM, following will be correct data"""
self.get_exploded_items()
self.add_exploded_items(save=save)
def get_exploded_items(self):
- """ Get all raw materials including items from child bom"""
+ """Get all raw materials including items from child bom"""
self.cur_exploded_items = {}
- for d in self.get('items'):
+ for d in self.get("items"):
if d.bom_no:
self.get_child_exploded_items(d.bom_no, d.stock_qty)
elif d.item_code:
- self.add_to_cur_exploded_items(frappe._dict({
- 'item_code' : d.item_code,
- 'item_name' : d.item_name,
- 'operation' : d.operation,
- 'source_warehouse': d.source_warehouse,
- 'description' : d.description,
- 'image' : d.image,
- 'stock_uom' : d.stock_uom,
- 'stock_qty' : flt(d.stock_qty),
- 'rate' : flt(d.base_rate) / (flt(d.conversion_factor) or 1.0),
- 'include_item_in_manufacturing': d.include_item_in_manufacturing,
- 'sourced_by_supplier': d.sourced_by_supplier
- }))
+ self.add_to_cur_exploded_items(
+ frappe._dict(
+ {
+ "item_code": d.item_code,
+ "item_name": d.item_name,
+ "operation": d.operation,
+ "source_warehouse": d.source_warehouse,
+ "description": d.description,
+ "image": d.image,
+ "stock_uom": d.stock_uom,
+ "stock_qty": flt(d.stock_qty),
+ "rate": flt(d.base_rate) / (flt(d.conversion_factor) or 1.0),
+ "include_item_in_manufacturing": d.include_item_in_manufacturing,
+ "sourced_by_supplier": d.sourced_by_supplier,
+ }
+ )
+ )
def company_currency(self):
return erpnext.get_company_currency(self.company)
@@ -661,9 +746,10 @@ class BOM(WebsiteGenerator):
self.cur_exploded_items[args.item_code] = args
def get_child_exploded_items(self, bom_no, stock_qty):
- """ Add all items from Flat BOM of child BOM"""
+ """Add all items from Flat BOM of child BOM"""
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
- child_fb_items = frappe.db.sql("""
+ child_fb_items = frappe.db.sql(
+ """
SELECT
bom_item.item_code,
bom_item.item_name,
@@ -681,31 +767,38 @@ class BOM(WebsiteGenerator):
bom_item.parent = bom.name
AND bom.name = %s
AND bom.docstatus = 1
- """, bom_no, as_dict = 1)
+ """,
+ bom_no,
+ as_dict=1,
+ )
for d in child_fb_items:
- self.add_to_cur_exploded_items(frappe._dict({
- 'item_code' : d['item_code'],
- 'item_name' : d['item_name'],
- 'source_warehouse' : d['source_warehouse'],
- 'operation' : d['operation'],
- 'description' : d['description'],
- 'stock_uom' : d['stock_uom'],
- 'stock_qty' : d['qty_consumed_per_unit'] * stock_qty,
- 'rate' : flt(d['rate']),
- 'include_item_in_manufacturing': d.get('include_item_in_manufacturing', 0),
- 'sourced_by_supplier': d.get('sourced_by_supplier', 0)
- }))
+ self.add_to_cur_exploded_items(
+ frappe._dict(
+ {
+ "item_code": d["item_code"],
+ "item_name": d["item_name"],
+ "source_warehouse": d["source_warehouse"],
+ "operation": d["operation"],
+ "description": d["description"],
+ "stock_uom": d["stock_uom"],
+ "stock_qty": d["qty_consumed_per_unit"] * stock_qty,
+ "rate": flt(d["rate"]),
+ "include_item_in_manufacturing": d.get("include_item_in_manufacturing", 0),
+ "sourced_by_supplier": d.get("sourced_by_supplier", 0),
+ }
+ )
+ )
def add_exploded_items(self, save=True):
"Add items to Flat BOM table"
- self.set('exploded_items', [])
+ self.set("exploded_items", [])
if save:
frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name)
for d in sorted(self.cur_exploded_items, key=itemgetter(0)):
- ch = self.append('exploded_items', {})
+ ch = self.append("exploded_items", {})
for i in self.cur_exploded_items[d].keys():
ch.set(i, self.cur_exploded_items[d][i])
ch.amount = flt(ch.stock_qty) * flt(ch.rate)
@@ -717,10 +810,13 @@ class BOM(WebsiteGenerator):
def validate_bom_links(self):
if not self.is_active:
- act_pbom = frappe.db.sql("""select distinct bom_item.parent from `tabBOM Item` bom_item
+ act_pbom = frappe.db.sql(
+ """select distinct bom_item.parent from `tabBOM Item` bom_item
where bom_item.bom_no = %s and bom_item.docstatus = 1 and bom_item.parenttype='BOM'
and exists (select * from `tabBOM` where name = bom_item.parent
- and docstatus = 1 and is_active = 1)""", self.name)
+ and docstatus = 1 and is_active = 1)""",
+ self.name,
+ )
if act_pbom and act_pbom[0][0]:
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
@@ -729,20 +825,23 @@ class BOM(WebsiteGenerator):
if not self.with_operations:
self.transfer_material_against = "Work Order"
if not self.transfer_material_against and not self.is_new():
- frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+ frappe.throw(
+ _("Setting {} is required").format(self.meta.get_label("transfer_material_against")),
+ title=_("Missing value"),
+ )
def set_routing_operations(self):
if self.routing and self.with_operations and not self.operations:
self.get_routing()
def validate_operations(self):
- if self.with_operations and not self.get('operations') and self.docstatus == 1:
+ if self.with_operations and not self.get("operations") and self.docstatus == 1:
frappe.throw(_("Operations cannot be left blank"))
if self.with_operations:
for d in self.operations:
if not d.description:
- d.description = frappe.db.get_value('Operation', d.operation, 'description')
+ d.description = frappe.db.get_value("Operation", d.operation, "description")
if not d.batch_size or d.batch_size <= 0:
d.batch_size = 1
@@ -754,64 +853,75 @@ class BOM(WebsiteGenerator):
for item in self.scrap_items:
msg = ""
if item.item_code == self.item and not item.is_process_loss:
- msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked.') \
- .format(frappe.bold(item.item_code))
+ msg = _(
+ "Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked."
+ ).format(frappe.bold(item.item_code))
elif item.item_code != self.item and item.is_process_loss:
- msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked') \
- .format(frappe.bold(item.item_code))
+ msg = _(
+ "Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked"
+ ).format(frappe.bold(item.item_code))
must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number")
if item.is_process_loss and must_be_whole_number:
- msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM.") \
- .format(frappe.bold(item.item_code), frappe.bold(item.stock_uom))
+ msg = _(
+ "Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM."
+ ).format(frappe.bold(item.item_code), frappe.bold(item.stock_uom))
if item.is_process_loss and (item.stock_qty >= self.quantity):
- msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.") \
- .format(frappe.bold(item.item_code))
+ msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.").format(
+ frappe.bold(item.item_code)
+ )
if item.is_process_loss and (item.rate > 0):
- msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked.") \
- .format(frappe.bold(item.item_code))
+ msg = _(
+ "Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked."
+ ).format(frappe.bold(item.item_code))
if msg:
frappe.throw(msg, title=_("Note"))
+
def get_bom_item_rate(args, bom_doc):
- if bom_doc.rm_cost_as_per == 'Valuation Rate':
+ if bom_doc.rm_cost_as_per == "Valuation Rate":
rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1)
- elif bom_doc.rm_cost_as_per == 'Last Purchase Rate':
- rate = (flt(args.get('last_purchase_rate'))
- or flt(frappe.db.get_value("Item", args['item_code'], "last_purchase_rate"))) \
- * (args.get("conversion_factor") or 1)
+ elif bom_doc.rm_cost_as_per == "Last Purchase Rate":
+ rate = (
+ flt(args.get("last_purchase_rate"))
+ or flt(frappe.db.get_value("Item", args["item_code"], "last_purchase_rate"))
+ ) * (args.get("conversion_factor") or 1)
elif bom_doc.rm_cost_as_per == "Price List":
if not bom_doc.buying_price_list:
frappe.throw(_("Please select Price List"))
- bom_args = frappe._dict({
- "doctype": "BOM",
- "price_list": bom_doc.buying_price_list,
- "qty": args.get("qty") or 1,
- "uom": args.get("uom") or args.get("stock_uom"),
- "stock_uom": args.get("stock_uom"),
- "transaction_type": "buying",
- "company": bom_doc.company,
- "currency": bom_doc.currency,
- "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
- "conversion_factor": args.get("conversion_factor") or 1,
- "plc_conversion_rate": 1,
- "ignore_party": True,
- "ignore_conversion_rate": True
- })
+ bom_args = frappe._dict(
+ {
+ "doctype": "BOM",
+ "price_list": bom_doc.buying_price_list,
+ "qty": args.get("qty") or 1,
+ "uom": args.get("uom") or args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "transaction_type": "buying",
+ "company": bom_doc.company,
+ "currency": bom_doc.currency,
+ "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
+ "conversion_factor": args.get("conversion_factor") or 1,
+ "plc_conversion_rate": 1,
+ "ignore_party": True,
+ "ignore_conversion_rate": True,
+ }
+ )
item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
price_list_data = get_price_list_rate(bom_args, item_doc)
rate = price_list_data.price_list_rate
return flt(rate)
+
def get_valuation_rate(args):
- """ Get weighted average of valuation rate from all warehouses """
+ """Get weighted average of valuation rate from all warehouses"""
total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
- item_bins = frappe.db.sql("""
+ item_bins = frappe.db.sql(
+ """
select
bin.actual_qty, bin.stock_value
from
@@ -820,33 +930,48 @@ def get_valuation_rate(args):
bin.item_code=%(item)s
and bin.warehouse = warehouse.name
and warehouse.company=%(company)s""",
- {"item": args['item_code'], "company": args['company']}, as_dict=1)
+ {"item": args["item_code"], "company": args["company"]},
+ as_dict=1,
+ )
for d in item_bins:
total_qty += flt(d.actual_qty)
total_value += flt(d.stock_value)
if total_qty:
- valuation_rate = total_value / total_qty
+ valuation_rate = total_value / total_qty
if valuation_rate <= 0:
- last_valuation_rate = frappe.db.sql("""select valuation_rate
+ last_valuation_rate = frappe.db.sql(
+ """select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and valuation_rate > 0 and is_cancelled = 0
- order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
+ order by posting_date desc, posting_time desc, creation desc limit 1""",
+ args["item_code"],
+ )
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
if not valuation_rate:
- valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
+ valuation_rate = frappe.db.get_value("Item", args["item_code"], "valuation_rate")
return flt(valuation_rate)
+
def get_list_context(context):
context.title = _("Bill of Materials")
# context.introduction = _('Boms')
-def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_items=0, include_non_stock_items=False, fetch_qty_in_stock_uom=True):
+
+def get_bom_items_as_dict(
+ bom,
+ company,
+ qty=1,
+ fetch_exploded=1,
+ fetch_scrap_items=0,
+ include_non_stock_items=False,
+ fetch_qty_in_stock_uom=True,
+):
item_dict = {}
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
@@ -883,30 +1008,40 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
is_stock_item = 0 if include_non_stock_items else 1
if cint(fetch_exploded):
- query = query.format(table="BOM Explosion Item",
+ query = query.format(
+ table="BOM Explosion Item",
where_conditions="",
is_stock_item=is_stock_item,
qty_field="stock_qty",
- select_columns = """, bom_item.source_warehouse, bom_item.operation,
+ select_columns=""", bom_item.source_warehouse, bom_item.operation,
bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate, bom_item.sourced_by_supplier,
- (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
-
- items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
- elif fetch_scrap_items:
- query = query.format(
- table="BOM Scrap Item", where_conditions="",
- select_columns=", bom_item.idx, item.description, is_process_loss",
- is_stock_item=is_stock_item, qty_field="stock_qty"
+ (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""",
)
- items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
+ items = frappe.db.sql(
+ query, {"parent": bom, "qty": qty, "bom": bom, "company": company}, as_dict=True
+ )
+ elif fetch_scrap_items:
+ query = query.format(
+ table="BOM Scrap Item",
+ where_conditions="",
+ select_columns=", bom_item.idx, item.description, is_process_loss",
+ is_stock_item=is_stock_item,
+ qty_field="stock_qty",
+ )
+
+ items = frappe.db.sql(query, {"qty": qty, "bom": bom, "company": company}, as_dict=True)
else:
- query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
+ query = query.format(
+ table="BOM Item",
+ where_conditions="",
+ is_stock_item=is_stock_item,
qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
- select_columns = """, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse,
+ select_columns=""", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse,
bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing, bom_item.sourced_by_supplier,
- bom_item.description, bom_item.base_rate as rate """)
- items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
+ bom_item.description, bom_item.base_rate as rate """,
+ )
+ items = frappe.db.sql(query, {"qty": qty, "bom": bom, "company": company}, as_dict=True)
for item in items:
if item.item_code in item_dict:
@@ -915,21 +1050,28 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
item_dict[item.item_code] = item
for item, item_details in item_dict.items():
- for d in [["Account", "expense_account", "stock_adjustment_account"],
- ["Cost Center", "cost_center", "cost_center"], ["Warehouse", "default_warehouse", ""]]:
- company_in_record = frappe.db.get_value(d[0], item_details.get(d[1]), "company")
- if not item_details.get(d[1]) or (company_in_record and company != company_in_record):
- item_dict[item][d[1]] = frappe.get_cached_value('Company', company, d[2]) if d[2] else None
+ for d in [
+ ["Account", "expense_account", "stock_adjustment_account"],
+ ["Cost Center", "cost_center", "cost_center"],
+ ["Warehouse", "default_warehouse", ""],
+ ]:
+ company_in_record = frappe.db.get_value(d[0], item_details.get(d[1]), "company")
+ if not item_details.get(d[1]) or (company_in_record and company != company_in_record):
+ item_dict[item][d[1]] = frappe.get_cached_value("Company", company, d[2]) if d[2] else None
return item_dict
+
@frappe.whitelist()
def get_bom_items(bom, company, qty=1, fetch_exploded=1):
- items = get_bom_items_as_dict(bom, company, qty, fetch_exploded, include_non_stock_items=True).values()
+ items = get_bom_items_as_dict(
+ bom, company, qty, fetch_exploded, include_non_stock_items=True
+ ).values()
items = list(items)
- items.sort(key = functools.cmp_to_key(lambda a, b: a.item_code > b.item_code and 1 or -1))
+ items.sort(key=functools.cmp_to_key(lambda a, b: a.item_code > b.item_code and 1 or -1))
return items
+
def validate_bom_no(item, bom_no):
"""Validate BOM No of sub-contracted items"""
bom = frappe.get_doc("BOM", bom_no)
@@ -941,21 +1083,24 @@ def validate_bom_no(item, bom_no):
if item:
rm_item_exists = False
for d in bom.items:
- if (d.item_code.lower() == item.lower()):
+ if d.item_code.lower() == item.lower():
rm_item_exists = True
for d in bom.scrap_items:
- if (d.item_code.lower() == item.lower()):
- rm_item_exists = True
- if bom.item.lower() == item.lower() or \
- bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower():
+ if d.item_code.lower() == item.lower():
rm_item_exists = True
+ if (
+ bom.item.lower() == item.lower()
+ or bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower()
+ ):
+ rm_item_exists = True
if not rm_item_exists:
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
+
@frappe.whitelist()
def get_children(parent=None, is_root=False, **filters):
- if not parent or parent=="BOM":
- frappe.msgprint(_('Please select a BOM'))
+ if not parent or parent == "BOM":
+ frappe.msgprint(_("Please select a BOM"))
return
if parent:
@@ -965,38 +1110,45 @@ def get_children(parent=None, is_root=False, **filters):
bom_doc = frappe.get_cached_doc("BOM", frappe.form_dict.parent)
frappe.has_permission("BOM", doc=bom_doc, throw=True)
- bom_items = frappe.get_all('BOM Item',
- fields=['item_code', 'bom_no as value', 'stock_qty'],
- filters=[['parent', '=', frappe.form_dict.parent]],
- order_by='idx')
+ bom_items = frappe.get_all(
+ "BOM Item",
+ fields=["item_code", "bom_no as value", "stock_qty"],
+ filters=[["parent", "=", frappe.form_dict.parent]],
+ order_by="idx",
+ )
- item_names = tuple(d.get('item_code') for d in bom_items)
+ item_names = tuple(d.get("item_code") for d in bom_items)
- items = frappe.get_list('Item',
- fields=['image', 'description', 'name', 'stock_uom', 'item_name', 'is_sub_contracted_item'],
- filters=[['name', 'in', item_names]]) # to get only required item dicts
+ items = frappe.get_list(
+ "Item",
+ fields=["image", "description", "name", "stock_uom", "item_name", "is_sub_contracted_item"],
+ filters=[["name", "in", item_names]],
+ ) # to get only required item dicts
for bom_item in bom_items:
# extend bom_item dict with respective item dict
bom_item.update(
# returns an item dict from items list which matches with item_code
- next(item for item in items if item.get('name')
- == bom_item.get('item_code'))
+ next(item for item in items if item.get("name") == bom_item.get("item_code"))
)
bom_item.parent_bom_qty = bom_doc.quantity
- bom_item.expandable = 0 if bom_item.value in ('', None) else 1
+ bom_item.expandable = 0 if bom_item.value in ("", None) else 1
bom_item.image = frappe.db.escape(bom_item.image)
return bom_items
+
def get_boms_in_bottom_up_order(bom_no=None):
def _get_parent(bom_no):
- return frappe.db.sql_list("""
+ return frappe.db.sql_list(
+ """
select distinct bom_item.parent from `tabBOM Item` bom_item
where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
- """, bom_no)
+ """,
+ bom_no,
+ )
count = 0
bom_list = []
@@ -1004,12 +1156,14 @@ def get_boms_in_bottom_up_order(bom_no=None):
bom_list.append(bom_no)
else:
# get all leaf BOMs
- bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
+ bom_list = frappe.db.sql_list(
+ """select name from `tabBOM` bom
where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item`
- where parent=bom.name and ifnull(bom_no, '')!='')""")
+ where parent=bom.name and ifnull(bom_no, '')!='')"""
+ )
- while(count < len(bom_list)):
+ while count < len(bom_list):
for child_bom in _get_parent(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
@@ -1017,69 +1171,92 @@ def get_boms_in_bottom_up_order(bom_no=None):
return bom_list
+
def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
stock_entry.additional_costs = []
- expenses_included_in_valuation = frappe.get_cached_value("Company", work_order.company,
- "expenses_included_in_valuation")
+ expenses_included_in_valuation = frappe.get_cached_value(
+ "Company", work_order.company, "expenses_included_in_valuation"
+ )
add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation)
add_operations_cost(stock_entry, work_order, expenses_included_in_valuation)
+
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
- bom = frappe.get_doc('BOM', work_order.bom_no)
- table = 'exploded_items' if work_order.get('use_multi_level_bom') else 'items'
+ bom = frappe.get_doc("BOM", work_order.bom_no)
+ table = "exploded_items" if work_order.get("use_multi_level_bom") else "items"
items = {}
for d in bom.get(table):
items.setdefault(d.item_code, d.amount)
- non_stock_items = frappe.get_all('Item',
- fields="name", filters={'name': ('in', list(items.keys())), 'ifnull(is_stock_item, 0)': 0}, as_list=1)
+ non_stock_items = frappe.get_all(
+ "Item",
+ fields="name",
+ filters={"name": ("in", list(items.keys())), "ifnull(is_stock_item, 0)": 0},
+ as_list=1,
+ )
non_stock_items_cost = 0.0
for name in non_stock_items:
- non_stock_items_cost += flt(items.get(name[0])) * flt(stock_entry.fg_completed_qty) / flt(bom.quantity)
+ non_stock_items_cost += (
+ flt(items.get(name[0])) * flt(stock_entry.fg_completed_qty) / flt(bom.quantity)
+ )
if non_stock_items_cost:
- stock_entry.append('additional_costs', {
- 'expense_account': expense_account,
- 'description': _("Non stock items"),
- 'amount': non_stock_items_cost
- })
+ stock_entry.append(
+ "additional_costs",
+ {
+ "expense_account": expense_account,
+ "description": _("Non stock items"),
+ "amount": non_stock_items_cost,
+ },
+ )
+
def add_operations_cost(stock_entry, work_order=None, expense_account=None):
from erpnext.stock.doctype.stock_entry.stock_entry import get_operating_cost_per_unit
+
operating_cost_per_unit = get_operating_cost_per_unit(work_order, stock_entry.bom_no)
if operating_cost_per_unit:
- stock_entry.append('additional_costs', {
- "expense_account": expense_account,
- "description": _("Operating Cost as per Work Order / BOM"),
- "amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty)
- })
+ stock_entry.append(
+ "additional_costs",
+ {
+ "expense_account": expense_account,
+ "description": _("Operating Cost as per Work Order / BOM"),
+ "amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
+ },
+ )
if work_order and work_order.additional_operating_cost and work_order.qty:
- additional_operating_cost_per_unit = \
- flt(work_order.additional_operating_cost) / flt(work_order.qty)
+ additional_operating_cost_per_unit = flt(work_order.additional_operating_cost) / flt(
+ work_order.qty
+ )
if additional_operating_cost_per_unit:
- stock_entry.append('additional_costs', {
- "expense_account": expense_account,
- "description": "Additional Operating Cost",
- "amount": additional_operating_cost_per_unit * flt(stock_entry.fg_completed_qty)
- })
+ stock_entry.append(
+ "additional_costs",
+ {
+ "expense_account": expense_account,
+ "description": "Additional Operating Cost",
+ "amount": additional_operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
+ },
+ )
+
@frappe.whitelist()
def get_bom_diff(bom1, bom2):
from frappe.model import table_fields
if bom1 == bom2:
- frappe.throw(_("BOM 1 {0} and BOM 2 {1} should not be same")
- .format(frappe.bold(bom1), frappe.bold(bom2)))
+ frappe.throw(
+ _("BOM 1 {0} and BOM 2 {1} should not be same").format(frappe.bold(bom1), frappe.bold(bom2))
+ )
- doc1 = frappe.get_doc('BOM', bom1)
- doc2 = frappe.get_doc('BOM', bom2)
+ doc1 = frappe.get_doc("BOM", bom1)
+ doc2 = frappe.get_doc("BOM", bom2)
out = get_diff(doc1, doc2)
out.row_changed = []
@@ -1089,10 +1266,10 @@ def get_bom_diff(bom1, bom2):
meta = doc1.meta
identifiers = {
- 'operations': 'operation',
- 'items': 'item_code',
- 'scrap_items': 'item_code',
- 'exploded_items': 'item_code'
+ "operations": "operation",
+ "items": "item_code",
+ "scrap_items": "item_code",
+ "exploded_items": "item_code",
}
for df in meta.fields:
@@ -1123,6 +1300,7 @@ def get_bom_diff(bom1, bom2):
return out
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters):
@@ -1132,25 +1310,28 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
order_by = "idx desc, name, item_name"
fields = ["name", "item_group", "item_name", "description"]
- fields.extend([field for field in searchfields
- if not field in ["name", "item_group", "description"]])
+ fields.extend(
+ [field for field in searchfields if not field in ["name", "item_group", "description"]]
+ )
- searchfields = searchfields + [field for field in [searchfield or "name", "item_code", "item_group", "item_name"]
- if not field in searchfields]
+ searchfields = searchfields + [
+ field
+ for field in [searchfield or "name", "item_code", "item_group", "item_name"]
+ if not field in searchfields
+ ]
- query_filters = {
- "disabled": 0,
- "ifnull(end_of_life, '5050-50-50')": (">", today())
- }
+ query_filters = {"disabled": 0, "ifnull(end_of_life, '5050-50-50')": (">", today())}
or_cond_filters = {}
if txt:
for s_field in searchfields:
or_cond_filters[s_field] = ("like", "%{0}%".format(txt))
- barcodes = frappe.get_all("Item Barcode",
+ barcodes = frappe.get_all(
+ "Item Barcode",
fields=["distinct parent as item_code"],
- filters = {"barcode": ("like", "%{0}%".format(txt))})
+ filters={"barcode": ("like", "%{0}%".format(txt))},
+ )
barcodes = [d.item_code for d in barcodes]
if barcodes:
@@ -1164,10 +1345,17 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
if filters and filters.get("is_stock_item"):
query_filters["is_stock_item"] = 1
- return frappe.get_list("Item",
- fields = fields, filters=query_filters,
- or_filters = or_cond_filters, order_by=order_by,
- limit_start=start, limit_page_length=page_len, as_list=1)
+ return frappe.get_list(
+ "Item",
+ fields=fields,
+ filters=query_filters,
+ or_filters=or_cond_filters,
+ order_by=order_by,
+ limit_start=start,
+ limit_page_length=page_len,
+ as_list=1,
+ )
+
@frappe.whitelist()
def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
@@ -1178,28 +1366,31 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
doc.quantity = 1
item_data = get_item_details(item)
- doc.update({
- "item_name": item_data.item_name,
- "description": item_data.description,
- "uom": item_data.stock_uom,
- "allow_alternative_item": item_data.allow_alternative_item
- })
+ doc.update(
+ {
+ "item_name": item_data.item_name,
+ "description": item_data.description,
+ "uom": item_data.stock_uom,
+ "allow_alternative_item": item_data.allow_alternative_item,
+ }
+ )
add_variant_item(variant_items, doc, source_name)
- doc = get_mapped_doc('BOM', source_name, {
- 'BOM': {
- 'doctype': 'BOM',
- 'validation': {
- 'docstatus': ['=', 1]
- }
+ doc = get_mapped_doc(
+ "BOM",
+ source_name,
+ {
+ "BOM": {"doctype": "BOM", "validation": {"docstatus": ["=", 1]}},
+ "BOM Item": {
+ "doctype": "BOM Item",
+ # stop get_mapped_doc copying parent bom_no to children
+ "field_no_map": ["bom_no"],
+ "condition": lambda doc: doc.has_variants == 0,
+ },
},
- 'BOM Item': {
- 'doctype': 'BOM Item',
- # stop get_mapped_doc copying parent bom_no to children
- 'field_no_map': ['bom_no'],
- 'condition': lambda doc: doc.has_variants == 0
- },
- }, target_doc, postprocess)
+ target_doc,
+ postprocess,
+ )
return doc
diff --git a/erpnext/manufacturing/doctype/bom/bom_dashboard.py b/erpnext/manufacturing/doctype/bom/bom_dashboard.py
index 0699f74aad..d8a810bd0a 100644
--- a/erpnext/manufacturing/doctype/bom/bom_dashboard.py
+++ b/erpnext/manufacturing/doctype/bom/bom_dashboard.py
@@ -3,27 +3,28 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'bom_no',
- 'non_standard_fieldnames': {
- 'Item': 'default_bom',
- 'Purchase Order': 'bom',
- 'Purchase Receipt': 'bom',
- 'Purchase Invoice': 'bom'
+ "fieldname": "bom_no",
+ "non_standard_fieldnames": {
+ "Item": "default_bom",
+ "Purchase Order": "bom",
+ "Purchase Receipt": "bom",
+ "Purchase Invoice": "bom",
},
- 'transactions': [
+ "transactions": [
+ {"label": _("Stock"), "items": ["Item", "Stock Entry", "Quality Inspection"]},
+ {"label": _("Manufacture"), "items": ["BOM", "Work Order", "Job Card"]},
{
- 'label': _('Stock'),
- 'items': ['Item', 'Stock Entry', 'Quality Inspection']
+ "label": _("Subcontract"),
+ "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"],
},
- {
- 'label': _('Manufacture'),
- 'items': ['BOM', 'Work Order', 'Job Card']
- },
- {
- 'label': _('Subcontract'),
- 'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
- }
],
- 'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
- "Purchase Invoice", "Job Card", "Stock Entry", "BOM"]
+ "disable_create_buttons": [
+ "Item",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Job Card",
+ "Stock Entry",
+ "BOM",
+ ],
}
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 21e0006282..524f45bfc2 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -18,22 +18,27 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
)
from erpnext.tests.test_subcontracting import set_backflush_based_on
-test_records = frappe.get_test_records('BOM')
+test_records = frappe.get_test_records("BOM")
test_dependencies = ["Item", "Quality Inspection Template"]
+
class TestBOM(FrappeTestCase):
def test_get_items(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
- items_dict = get_bom_items_as_dict(bom=get_default_bom(),
- company="_Test Company", qty=1, fetch_exploded=0)
+
+ items_dict = get_bom_items_as_dict(
+ bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=0
+ )
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict)
self.assertEqual(len(items_dict.values()), 2)
def test_get_items_exploded(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
- items_dict = get_bom_items_as_dict(bom=get_default_bom(),
- company="_Test Company", qty=1, fetch_exploded=1)
+
+ items_dict = get_bom_items_as_dict(
+ bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=1
+ )
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
self.assertFalse(test_records[2]["items"][1]["item_code"] in items_dict)
self.assertTrue(test_records[0]["items"][0]["item_code"] in items_dict)
@@ -42,13 +47,14 @@ class TestBOM(FrappeTestCase):
def test_get_items_list(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items
+
self.assertEqual(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3)
def test_default_bom(self):
def _get_default_bom_in_item():
return cstr(frappe.db.get_value("Item", "_Test FG Item 2", "default_bom"))
- bom = frappe.get_doc("BOM", {"item":"_Test FG Item 2", "is_default": 1})
+ bom = frappe.get_doc("BOM", {"item": "_Test FG Item 2", "is_default": 1})
self.assertEqual(_get_default_bom_in_item(), bom.name)
bom.is_active = 0
@@ -56,28 +62,33 @@ class TestBOM(FrappeTestCase):
self.assertEqual(_get_default_bom_in_item(), "")
bom.is_active = 1
- bom.is_default=1
+ bom.is_default = 1
bom.save()
self.assertTrue(_get_default_bom_in_item(), bom.name)
def test_update_bom_cost_in_all_boms(self):
# get current rate for '_Test Item 2'
- rm_rate = frappe.db.sql("""select rate from `tabBOM Item`
+ rm_rate = frappe.db.sql(
+ """select rate from `tabBOM Item`
where parent='BOM-_Test Item Home Desktop Manufactured-001'
- and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""")
+ and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'"""
+ )
rm_rate = rm_rate[0][0] if rm_rate else 0
# Reset item valuation rate
- reset_item_valuation_rate(item_code='_Test Item 2', qty=200, rate=rm_rate + 10)
+ reset_item_valuation_rate(item_code="_Test Item 2", qty=200, rate=rm_rate + 10)
# update cost of all BOMs based on latest valuation rate
update_cost()
# check if new valuation rate updated in all BOMs
- for d in frappe.db.sql("""select rate from `tabBOM Item`
- where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""", as_dict=1):
- self.assertEqual(d.rate, rm_rate + 10)
+ for d in frappe.db.sql(
+ """select rate from `tabBOM Item`
+ where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""",
+ as_dict=1,
+ ):
+ self.assertEqual(d.rate, rm_rate + 10)
def test_bom_cost(self):
bom = frappe.copy_doc(test_records[2])
@@ -92,7 +103,9 @@ class TestBOM(FrappeTestCase):
for row in bom.items:
raw_material_cost += row.amount
- base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
+ base_raw_material_cost = raw_material_cost * flt(
+ bom.conversion_rate, bom.precision("conversion_rate")
+ )
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
# test amounts in selected currency, almostEqual checks for 7 digits by default
@@ -120,14 +133,15 @@ class TestBOM(FrappeTestCase):
for op_row in bom.operations:
self.assertAlmostEqual(op_row.cost_per_unit, op_row.operating_cost / 2)
- self.assertAlmostEqual(bom.operating_cost, op_cost/2)
+ self.assertAlmostEqual(bom.operating_cost, op_cost / 2)
bom.delete()
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
- frappe.db.sql("delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s",
- item_code)
+ frappe.db.sql(
+ "delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s", item_code
+ )
item_price = frappe.new_doc("Item Price")
item_price.price_list = "_Test Price List"
item_price.item_code = item_code
@@ -142,7 +156,7 @@ class TestBOM(FrappeTestCase):
bom.items[0].conversion_factor = 5
bom.insert()
- bom.update_cost(update_hour_rate = False)
+ bom.update_cost(update_hour_rate=False)
# test amounts in selected currency
self.assertEqual(bom.items[0].rate, 300)
@@ -167,11 +181,12 @@ class TestBOM(FrappeTestCase):
bom.insert()
reset_item_valuation_rate(
- item_code='_Test Item',
- warehouse_list=frappe.get_all("Warehouse",
- {"is_group":0, "company": bom.company}, pluck="name"),
+ item_code="_Test Item",
+ warehouse_list=frappe.get_all(
+ "Warehouse", {"is_group": 0, "company": bom.company}, pluck="name"
+ ),
qty=200,
- rate=200
+ rate=200,
)
bom.update_cost()
@@ -180,68 +195,64 @@ class TestBOM(FrappeTestCase):
def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
- set_backflush_based_on('Material Transferred for Subcontract')
+ set_backflush_based_on("Material Transferred for Subcontract")
- if not frappe.db.exists('Item', item_code):
- make_item(item_code, {
- 'is_stock_item': 1,
- 'is_sub_contracted_item': 1,
- 'stock_uom': 'Nos'
- })
+ if not frappe.db.exists("Item", item_code):
+ make_item(item_code, {"is_stock_item": 1, "is_sub_contracted_item": 1, "stock_uom": "Nos"})
- if not frappe.db.exists('Item', "Test Extra Item 1"):
- make_item("Test Extra Item 1", {
- 'is_stock_item': 1,
- 'stock_uom': 'Nos'
- })
+ if not frappe.db.exists("Item", "Test Extra Item 1"):
+ make_item("Test Extra Item 1", {"is_stock_item": 1, "stock_uom": "Nos"})
- if not frappe.db.exists('Item', "Test Extra Item 2"):
- make_item("Test Extra Item 2", {
- 'is_stock_item': 1,
- 'stock_uom': 'Nos'
- })
+ if not frappe.db.exists("Item", "Test Extra Item 2"):
+ make_item("Test Extra Item 2", {"is_stock_item": 1, "stock_uom": "Nos"})
- if not frappe.db.exists('Item', "Test Extra Item 3"):
- make_item("Test Extra Item 3", {
- 'is_stock_item': 1,
- 'stock_uom': 'Nos'
- })
- bom = frappe.get_doc({
- 'doctype': 'BOM',
- 'is_default': 1,
- 'item': item_code,
- 'currency': 'USD',
- 'quantity': 1,
- 'company': '_Test Company'
- })
+ if not frappe.db.exists("Item", "Test Extra Item 3"):
+ make_item("Test Extra Item 3", {"is_stock_item": 1, "stock_uom": "Nos"})
+ bom = frappe.get_doc(
+ {
+ "doctype": "BOM",
+ "is_default": 1,
+ "item": item_code,
+ "currency": "USD",
+ "quantity": 1,
+ "company": "_Test Company",
+ }
+ )
for item in ["Test Extra Item 1", "Test Extra Item 2"]:
- item_doc = frappe.get_doc('Item', item)
+ item_doc = frappe.get_doc("Item", item)
- bom.append('items', {
- 'item_code': item,
- 'qty': 1,
- 'uom': item_doc.stock_uom,
- 'stock_uom': item_doc.stock_uom,
- 'rate': item_doc.valuation_rate
- })
+ bom.append(
+ "items",
+ {
+ "item_code": item,
+ "qty": 1,
+ "uom": item_doc.stock_uom,
+ "stock_uom": item_doc.stock_uom,
+ "rate": item_doc.valuation_rate,
+ },
+ )
- bom.append('items', {
- 'item_code': "Test Extra Item 3",
- 'qty': 1,
- 'uom': item_doc.stock_uom,
- 'stock_uom': item_doc.stock_uom,
- 'rate': 0,
- 'sourced_by_supplier': 1
- })
+ bom.append(
+ "items",
+ {
+ "item_code": "Test Extra Item 3",
+ "qty": 1,
+ "uom": item_doc.stock_uom,
+ "stock_uom": item_doc.stock_uom,
+ "rate": 0,
+ "sourced_by_supplier": 1,
+ },
+ )
bom.insert(ignore_permissions=True)
bom.update_cost()
bom.submit()
# test that sourced_by_supplier rate is zero even after updating cost
self.assertEqual(bom.items[2].rate, 0)
# test in Purchase Order sourced_by_supplier is not added to Supplied Item
- po = create_purchase_order(item_code=item_code, qty=1,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
self.assertEqual(bom_items, supplied_items)
@@ -249,7 +260,10 @@ class TestBOM(FrappeTestCase):
def test_bom_tree_representation(self):
bom_tree = {
"Assembly": {
- "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+ "SubAssembly1": {
+ "ChildPart1": {},
+ "ChildPart2": {},
+ },
"SubAssembly2": {"ChildPart3": {}},
"SubAssembly3": {"SubSubAssy1": {"ChildPart4": {}}},
"ChildPart5": {},
@@ -260,7 +274,7 @@ class TestBOM(FrappeTestCase):
parent_bom = create_nested_bom(bom_tree, prefix="")
created_tree = parent_bom.get_tree_representation()
- reqd_order = level_order_traversal(bom_tree)[1:] # skip first item
+ reqd_order = level_order_traversal(bom_tree)[1:] # skip first item
created_order = created_tree.level_order_traversal()
self.assertEqual(len(reqd_order), len(created_order))
@@ -272,14 +286,23 @@ class TestBOM(FrappeTestCase):
from erpnext.controllers.item_variant import create_variant
template_item = make_item(
- "_TestTemplateItem", {"has_variants": 1, "attributes": [{"attribute": "Test Size"},]}
+ "_TestTemplateItem",
+ {
+ "has_variants": 1,
+ "attributes": [
+ {"attribute": "Test Size"},
+ ],
+ },
)
variant = create_variant(template_item.item_code, {"Test Size": "Large"})
variant.insert(ignore_if_duplicate=True)
bom_tree = {
template_item.item_code: {
- "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+ "SubAssembly1": {
+ "ChildPart1": {},
+ "ChildPart2": {},
+ },
"ChildPart5": {},
}
}
@@ -302,7 +325,7 @@ class TestBOM(FrappeTestCase):
def test_bom_recursion_1st_level(self):
"""BOM should not allow BOM item again in child"""
item_code = "_Test BOM Recursion"
- make_item(item_code, {'is_stock_item': 1})
+ make_item(item_code, {"is_stock_item": 1})
bom = frappe.new_doc("BOM")
bom.item = item_code
@@ -316,8 +339,8 @@ class TestBOM(FrappeTestCase):
def test_bom_recursion_transitive(self):
item1 = "_Test BOM Recursion"
item2 = "_Test BOM Recursion 2"
- make_item(item1, {'is_stock_item': 1})
- make_item(item2, {'is_stock_item': 1})
+ make_item(item1, {"is_stock_item": 1})
+ make_item(item2, {"is_stock_item": 1})
bom1 = frappe.new_doc("BOM")
bom1.item = item1
@@ -373,19 +396,29 @@ class TestBOM(FrappeTestCase):
self.assertRaises(frappe.ValidationError, bom_doc.submit)
def test_bom_item_query(self):
- query = partial(item_query, doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters={"is_stock_item": 1})
+ query = partial(
+ item_query,
+ doctype="Item",
+ txt="",
+ searchfield="name",
+ start=0,
+ page_len=20,
+ filters={"is_stock_item": 1},
+ )
test_items = query(txt="_Test")
filtered = query(txt="_Test Item 2")
- self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
+ self.assertNotEqual(
+ len(test_items), len(filtered), msg="Item filtering showing excessive results"
+ )
self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
def test_exclude_exploded_items_from_bom(self):
bom_no = get_default_bom()
- new_bom = frappe.copy_doc(frappe.get_doc('BOM', bom_no))
+ new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no))
for row in new_bom.items:
- if row.item_code == '_Test Item Home Desktop Manufactured':
+ if row.item_code == "_Test Item Home Desktop Manufactured":
self.assertTrue(row.bom_no)
row.do_not_explode = True
@@ -394,13 +427,15 @@ class TestBOM(FrappeTestCase):
new_bom.load_from_db()
for row in new_bom.items:
- if row.item_code == '_Test Item Home Desktop Manufactured' and row.do_not_explode:
+ if row.item_code == "_Test Item Home Desktop Manufactured" and row.do_not_explode:
self.assertFalse(row.bom_no)
new_bom.delete()
def test_valid_transfer_defaults(self):
- bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1})
+ bom_with_op = frappe.db.get_value(
+ "BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1}
+ )
bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False)
# test defaults
@@ -429,12 +464,8 @@ class TestBOM(FrappeTestCase):
bom.delete()
def test_bom_name_length(self):
- """ test >140 char names"""
- bom_tree = {
- "x" * 140 : {
- " ".join(["abc"] * 35): {}
- }
- }
+ """test >140 char names"""
+ bom_tree = {"x" * 140: {" ".join(["abc"] * 35): {}}}
create_nested_bom(bom_tree, prefix="")
def test_version_index(self):
@@ -452,15 +483,14 @@ class TestBOM(FrappeTestCase):
for expected_index, existing_boms in version_index_test_cases:
with self.subTest():
- self.assertEqual(expected_index, bom.get_next_version_index(existing_boms),
- msg=f"Incorrect index for {existing_boms}")
+ self.assertEqual(
+ expected_index,
+ bom.get_next_version_index(existing_boms),
+ msg=f"Incorrect index for {existing_boms}",
+ )
def test_bom_versioning(self):
- bom_tree = {
- frappe.generate_hash(length=10) : {
- frappe.generate_hash(length=10): {}
- }
- }
+ bom_tree = {frappe.generate_hash(length=10): {frappe.generate_hash(length=10): {}}}
bom = create_nested_bom(bom_tree, prefix="")
self.assertEqual(int(bom.name.split("-")[-1]), 1)
original_bom_name = bom.name
@@ -501,7 +531,7 @@ class TestBOM(FrappeTestCase):
bom.save()
bom.reload()
- self.assertEqual(bom.quality_inspection_template, '_Test Quality Inspection Template')
+ self.assertEqual(bom.quality_inspection_template, "_Test Quality Inspection Template")
bom.inspection_required = 0
bom.save()
@@ -514,8 +544,7 @@ class TestBOM(FrappeTestCase):
parent = frappe.generate_hash(length=10)
child = frappe.generate_hash(length=10)
- bom_tree = {parent: {child: {}}
- }
+ bom_tree = {parent: {child: {}}}
bom = create_nested_bom(bom_tree, prefix="")
# add last purchase price
@@ -534,6 +563,7 @@ class TestBOM(FrappeTestCase):
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
+
def level_order_traversal(node):
traversal = []
q = deque()
@@ -548,9 +578,9 @@ def level_order_traversal(node):
return traversal
+
def create_nested_bom(tree, prefix="_Test bom "):
- """ Helper function to create a simple nested bom from tree describing item names. (along with required items)
- """
+ """Helper function to create a simple nested bom from tree describing item names. (along with required items)"""
def create_items(bom_tree):
for item_code, subtree in bom_tree.items():
@@ -558,6 +588,7 @@ def create_nested_bom(tree, prefix="_Test bom "):
if not frappe.db.exists("Item", bom_item_code):
frappe.get_doc(doctype="Item", item_code=bom_item_code, item_group="_Test Item Group").insert()
create_items(subtree)
+
create_items(tree)
def dfs(tree, node):
@@ -592,10 +623,13 @@ def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=Non
warehouse_list = [warehouse_list]
if not warehouse_list:
- warehouse_list = frappe.db.sql_list("""
+ warehouse_list = frappe.db.sql_list(
+ """
select warehouse from `tabBin`
where item_code=%s and actual_qty > 0
- """, item_code)
+ """,
+ item_code,
+ )
if not warehouse_list:
warehouse_list.append("_Test Warehouse - _TC")
@@ -603,44 +637,51 @@ def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=Non
for warehouse in warehouse_list:
create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate)
+
def create_bom_with_process_loss_item(
- fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1):
+ fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1
+):
bom_doc = frappe.new_doc("BOM")
bom_doc.item = fg_item.item_code
bom_doc.quantity = fg_qty
- bom_doc.append("items", {
- "item_code": bom_item.item_code,
- "qty": 1,
- "uom": bom_item.stock_uom,
- "stock_uom": bom_item.stock_uom,
- "rate": 100.0
- })
- bom_doc.append("scrap_items", {
- "item_code": fg_item.item_code,
- "qty": scrap_qty,
- "stock_qty": scrap_qty,
- "uom": fg_item.stock_uom,
- "stock_uom": fg_item.stock_uom,
- "rate": scrap_rate,
- "is_process_loss": is_process_loss
- })
+ bom_doc.append(
+ "items",
+ {
+ "item_code": bom_item.item_code,
+ "qty": 1,
+ "uom": bom_item.stock_uom,
+ "stock_uom": bom_item.stock_uom,
+ "rate": 100.0,
+ },
+ )
+ bom_doc.append(
+ "scrap_items",
+ {
+ "item_code": fg_item.item_code,
+ "qty": scrap_qty,
+ "stock_qty": scrap_qty,
+ "uom": fg_item.stock_uom,
+ "stock_uom": fg_item.stock_uom,
+ "rate": scrap_rate,
+ "is_process_loss": is_process_loss,
+ },
+ )
bom_doc.currency = "INR"
return bom_doc
+
def create_process_loss_bom_items():
item_list = [
("_Test Item - Non Whole UOM", "Kg"),
("_Test Item - Whole UOM", "Unit"),
- ("_Test PL BOM Item", "Unit")
+ ("_Test PL BOM Item", "Unit"),
]
return [create_process_loss_bom_item(it) for it in item_list]
+
def create_process_loss_bom_item(item_tuple):
item_code, stock_uom = item_tuple
if frappe.db.exists("Item", item_code) is None:
- return make_item(
- item_code,
- {'stock_uom':stock_uom, 'valuation_rate':100}
- )
+ return make_item(item_code, {"stock_uom": stock_uom, "valuation_rate": 100})
else:
return frappe.get_doc("Item", item_code)
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 0e3955f5a7..00711caf62 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -20,14 +20,14 @@ class BOMUpdateTool(Document):
unit_cost = get_new_bom_unit_cost(self.new_bom)
self.update_new_bom(unit_cost)
- frappe.cache().delete_key('bom_children')
+ frappe.cache().delete_key("bom_children")
bom_list = self.get_parent_boms(self.new_bom)
with click.progressbar(bom_list) as bom_list:
pass
for bom in bom_list:
try:
- bom_obj = frappe.get_cached_doc('BOM', bom)
+ bom_obj = frappe.get_cached_doc("BOM", bom)
# this is only used for versioning and we do not want
# to make separate db calls by using load_doc_before_save
# which proves to be expensive while doing bulk replace
@@ -37,7 +37,7 @@ class BOMUpdateTool(Document):
bom_obj.calculate_cost()
bom_obj.update_parent_cost()
bom_obj.db_update()
- if bom_obj.meta.get('track_changes') and not bom_obj.flags.ignore_version:
+ if bom_obj.meta.get("track_changes") and not bom_obj.flags.ignore_version:
bom_obj.save_version()
except Exception:
frappe.log_error(frappe.get_traceback())
@@ -46,20 +46,26 @@ class BOMUpdateTool(Document):
if cstr(self.current_bom) == cstr(self.new_bom):
frappe.throw(_("Current BOM and New BOM can not be same"))
- if frappe.db.get_value("BOM", self.current_bom, "item") \
- != frappe.db.get_value("BOM", self.new_bom, "item"):
- frappe.throw(_("The selected BOMs are not for the same item"))
+ if frappe.db.get_value("BOM", self.current_bom, "item") != frappe.db.get_value(
+ "BOM", self.new_bom, "item"
+ ):
+ frappe.throw(_("The selected BOMs are not for the same item"))
def update_new_bom(self, unit_cost):
- frappe.db.sql("""update `tabBOM Item` set bom_no=%s,
+ frappe.db.sql(
+ """update `tabBOM Item` set bom_no=%s,
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
- (self.new_bom, unit_cost, unit_cost, self.current_bom))
+ (self.new_bom, unit_cost, unit_cost, self.current_bom),
+ )
def get_parent_boms(self, bom, bom_list=None):
if bom_list is None:
bom_list = []
- data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item`
- WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom)
+ data = frappe.db.sql(
+ """SELECT DISTINCT parent FROM `tabBOM Item`
+ WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""",
+ bom,
+ )
for d in data:
if self.new_bom == d[0]:
@@ -70,29 +76,45 @@ class BOMUpdateTool(Document):
return list(set(bom_list))
+
def get_new_bom_unit_cost(bom):
- new_bom_unitcost = frappe.db.sql("""SELECT `total_cost`/`quantity`
- FROM `tabBOM` WHERE name = %s""", bom)
+ new_bom_unitcost = frappe.db.sql(
+ """SELECT `total_cost`/`quantity`
+ FROM `tabBOM` WHERE name = %s""",
+ bom,
+ )
return flt(new_bom_unitcost[0][0]) if new_bom_unitcost else 0
+
@frappe.whitelist()
def enqueue_replace_bom(args):
if isinstance(args, str):
args = json.loads(args)
- frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom", args=args, timeout=40000)
+ frappe.enqueue(
+ "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom",
+ args=args,
+ timeout=40000,
+ )
frappe.msgprint(_("Queued for replacing the BOM. It may take a few minutes."))
+
@frappe.whitelist()
def enqueue_update_cost():
- frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost", timeout=40000)
- frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes."))
+ frappe.enqueue(
+ "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost", timeout=40000
+ )
+ frappe.msgprint(
+ _("Queued for updating latest price in all Bill of Materials. It may take a few minutes.")
+ )
+
def update_latest_price_in_all_boms():
if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"):
update_cost()
+
def replace_bom(args):
frappe.db.auto_commit_on_many_writes = 1
args = frappe._dict(args)
@@ -104,6 +126,7 @@ def replace_bom(args):
frappe.db.auto_commit_on_many_writes = 0
+
def update_cost():
frappe.db.auto_commit_on_many_writes = 1
bom_list = get_boms_in_bottom_up_order()
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index b4c625d610..57785e58dd 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -8,7 +8,8 @@ from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import create_item
-test_records = frappe.get_test_records('BOM')
+test_records = frappe.get_test_records("BOM")
+
class TestBOMUpdateTool(FrappeTestCase):
def test_replace_bom(self):
@@ -37,10 +38,13 @@ class TestBOMUpdateTool(FrappeTestCase):
if item_doc.valuation_rate != 100.00:
frappe.db.set_value("Item", item_doc.name, "valuation_rate", 100)
- bom_no = frappe.db.get_value('BOM', {'item': 'BOM Cost Test Item 1'}, "name")
+ bom_no = frappe.db.get_value("BOM", {"item": "BOM Cost Test Item 1"}, "name")
if not bom_no:
- doc = make_bom(item = 'BOM Cost Test Item 1',
- raw_materials =['BOM Cost Test Item 2', 'BOM Cost Test Item 3'], currency="INR")
+ doc = make_bom(
+ item="BOM Cost Test Item 1",
+ raw_materials=["BOM Cost Test Item 2", "BOM Cost Test Item 3"],
+ currency="INR",
+ )
else:
doc = frappe.get_doc("BOM", bom_no)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 960d0e5152..bf4f82f57e 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -26,15 +26,27 @@ from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings
)
-class OverlapError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
+
+class OperationMismatchError(frappe.ValidationError):
+ pass
+
+
+class OperationSequenceError(frappe.ValidationError):
+ pass
+
+
+class JobCardCancelError(frappe.ValidationError):
+ pass
-class OperationMismatchError(frappe.ValidationError): pass
-class OperationSequenceError(frappe.ValidationError): pass
-class JobCardCancelError(frappe.ValidationError): pass
class JobCard(Document):
def onload(self):
- excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
+ excess_transfer = frappe.db.get_single_value(
+ "Manufacturing Settings", "job_card_excess_transfer"
+ )
self.set_onload("job_card_excess_transfer", excess_transfer)
self.set_onload("work_order_closed", self.is_work_order_closed())
@@ -50,25 +62,33 @@ class JobCard(Document):
def set_sub_operations(self):
if not self.sub_operations and self.operation:
self.sub_operations = []
- for row in frappe.get_all('Sub Operation',
- filters = {'parent': self.operation}, fields=['operation', 'idx'], order_by='idx'):
- row.status = 'Pending'
+ for row in frappe.get_all(
+ "Sub Operation",
+ filters={"parent": self.operation},
+ fields=["operation", "idx"],
+ order_by="idx",
+ ):
+ row.status = "Pending"
row.sub_operation = row.operation
- self.append('sub_operations', row)
+ self.append("sub_operations", row)
def validate_time_logs(self):
self.total_time_in_mins = 0.0
self.total_completed_qty = 0.0
- if self.get('time_logs'):
- for d in self.get('time_logs'):
+ if self.get("time_logs"):
+ for d in self.get("time_logs"):
if d.to_time and get_datetime(d.from_time) > get_datetime(d.to_time):
frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx))
data = self.get_overlap_for(d)
if data:
- frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
- .format(d.idx, self.name, data.name), OverlapError)
+ frappe.throw(
+ _("Row {0}: From Time and To Time of {1} is overlapping with {2}").format(
+ d.idx, self.name, data.name
+ ),
+ OverlapError,
+ )
if d.from_time and d.to_time:
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
@@ -86,8 +106,9 @@ class JobCard(Document):
production_capacity = 1
if self.workstation:
- production_capacity = frappe.get_cached_value("Workstation",
- self.workstation, 'production_capacity') or 1
+ production_capacity = (
+ frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
+ )
validate_overlap_for = " and jc.workstation = %(workstation)s "
if args.get("employee"):
@@ -95,11 +116,12 @@ class JobCard(Document):
production_capacity = 1
validate_overlap_for = " and jctl.employee = %(employee)s "
- extra_cond = ''
+ extra_cond = ""
if check_next_available_slot:
extra_cond = " or (%(from_time)s <= jctl.from_time and %(to_time)s <= jctl.to_time)"
- existing = frappe.db.sql("""select jc.name as name, jctl.to_time from
+ existing = frappe.db.sql(
+ """select jc.name as name, jctl.to_time from
`tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and
(
(%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or
@@ -107,15 +129,19 @@ class JobCard(Document):
(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
)
and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
- order by jctl.to_time desc limit 1""".format(extra_cond, validate_overlap_for),
+ order by jctl.to_time desc limit 1""".format(
+ extra_cond, validate_overlap_for
+ ),
{
"from_time": args.from_time,
"to_time": args.to_time,
"name": args.name or "No Name",
"parent": args.parent or "No Name",
"employee": args.get("employee"),
- "workstation": self.workstation
- }, as_dict=True)
+ "workstation": self.workstation,
+ },
+ as_dict=True,
+ )
if existing and production_capacity > len(existing):
return
@@ -125,10 +151,7 @@ class JobCard(Document):
def schedule_time_logs(self, row):
row.remaining_time_in_mins = row.time_in_mins
while row.remaining_time_in_mins > 0:
- args = frappe._dict({
- "from_time": row.planned_start_time,
- "to_time": row.planned_end_time
- })
+ args = frappe._dict({"from_time": row.planned_start_time, "to_time": row.planned_end_time})
self.validate_overlap_for_workstation(args, row)
self.check_workstation_time(row)
@@ -141,13 +164,16 @@ class JobCard(Document):
def check_workstation_time(self, row):
workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
- if (not workstation_doc.working_hours or
- cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))):
+ if not workstation_doc.working_hours or cint(
+ frappe.db.get_single_value("Manufacturing Settings", "allow_overtime")
+ ):
if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time):
row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins)
row.remaining_time_in_mins = 0.0
else:
- row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time, row.planned_start_time)
+ row.remaining_time_in_mins -= time_diff_in_minutes(
+ row.planned_end_time, row.planned_start_time
+ )
self.update_time_logs(row)
return
@@ -167,14 +193,15 @@ class JobCard(Document):
workstation_start_time = datetime.datetime.combine(start_date, get_time(time_slot.start_time))
workstation_end_time = datetime.datetime.combine(start_date, get_time(time_slot.end_time))
- if (get_datetime(row.planned_start_time) >= workstation_start_time and
- get_datetime(row.planned_start_time) <= workstation_end_time):
+ if (
+ get_datetime(row.planned_start_time) >= workstation_start_time
+ and get_datetime(row.planned_start_time) <= workstation_end_time
+ ):
time_in_mins = time_diff_in_minutes(workstation_end_time, row.planned_start_time)
# If remaining time fit in workstation time logs else split hours as per workstation time
if time_in_mins > row.remaining_time_in_mins:
- row.planned_end_time = add_to_date(row.planned_start_time,
- minutes=row.remaining_time_in_mins)
+ row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.remaining_time_in_mins)
row.remaining_time_in_mins = 0
else:
row.planned_end_time = add_to_date(row.planned_start_time, minutes=time_in_mins)
@@ -182,14 +209,16 @@ class JobCard(Document):
self.update_time_logs(row)
- if total_idx != (i+1) and row.remaining_time_in_mins > 0:
- row.planned_start_time = datetime.datetime.combine(start_date,
- get_time(workstation_doc.working_hours[i+1].start_time))
+ if total_idx != (i + 1) and row.remaining_time_in_mins > 0:
+ row.planned_start_time = datetime.datetime.combine(
+ start_date, get_time(workstation_doc.working_hours[i + 1].start_time)
+ )
if row.remaining_time_in_mins > 0:
start_date = add_days(start_date, 1)
- row.planned_start_time = datetime.datetime.combine(start_date,
- get_time(workstation_doc.working_hours[0].start_time))
+ row.planned_start_time = datetime.datetime.combine(
+ start_date, get_time(workstation_doc.working_hours[0].start_time)
+ )
def add_time_log(self, args):
last_row = []
@@ -204,21 +233,25 @@ class JobCard(Document):
if last_row and args.get("complete_time"):
for row in self.time_logs:
if not row.to_time:
- row.update({
- "to_time": get_datetime(args.get("complete_time")),
- "operation": args.get("sub_operation"),
- "completed_qty": args.get("completed_qty") or 0.0
- })
+ row.update(
+ {
+ "to_time": get_datetime(args.get("complete_time")),
+ "operation": args.get("sub_operation"),
+ "completed_qty": args.get("completed_qty") or 0.0,
+ }
+ )
elif args.get("start_time"):
- new_args = frappe._dict({
- "from_time": get_datetime(args.get("start_time")),
- "operation": args.get("sub_operation"),
- "completed_qty": 0.0
- })
+ new_args = frappe._dict(
+ {
+ "from_time": get_datetime(args.get("start_time")),
+ "operation": args.get("sub_operation"),
+ "completed_qty": 0.0,
+ }
+ )
if employees:
for name in employees:
- new_args.employee = name.get('employee')
+ new_args.employee = name.get("employee")
self.add_start_time_log(new_args)
else:
self.add_start_time_log(new_args)
@@ -236,10 +269,7 @@ class JobCard(Document):
def set_employees(self, employees):
for name in employees:
- self.append('employee', {
- 'employee': name.get('employee'),
- 'completed_qty': 0.0
- })
+ self.append("employee", {"employee": name.get("employee"), "completed_qty": 0.0})
def reset_timer_value(self, args):
self.started_time = None
@@ -263,13 +293,17 @@ class JobCard(Document):
operation_wise_completed_time = {}
for time_log in self.time_logs:
if time_log.operation not in operation_wise_completed_time:
- operation_wise_completed_time.setdefault(time_log.operation,
- frappe._dict({"status": "Pending", "completed_qty":0.0, "completed_time": 0.0, "employee": []}))
+ operation_wise_completed_time.setdefault(
+ time_log.operation,
+ frappe._dict(
+ {"status": "Pending", "completed_qty": 0.0, "completed_time": 0.0, "employee": []}
+ ),
+ )
op_row = operation_wise_completed_time[time_log.operation]
op_row.status = "Work In Progress" if not time_log.time_in_mins else "Complete"
- if self.status == 'On Hold':
- op_row.status = 'Pause'
+ if self.status == "On Hold":
+ op_row.status = "Pause"
op_row.employee.append(time_log.employee)
if time_log.time_in_mins:
@@ -279,7 +313,7 @@ class JobCard(Document):
for row in self.sub_operations:
operation_deatils = operation_wise_completed_time.get(row.sub_operation)
if operation_deatils:
- if row.status != 'Complete':
+ if row.status != "Complete":
row.status = operation_deatils.status
row.completed_time = operation_deatils.completed_time
@@ -289,43 +323,52 @@ class JobCard(Document):
if operation_deatils.completed_qty:
row.completed_qty = operation_deatils.completed_qty / len(set(operation_deatils.employee))
else:
- row.status = 'Pending'
+ row.status = "Pending"
row.completed_time = 0.0
row.completed_qty = 0.0
def update_time_logs(self, row):
- self.append("time_logs", {
- "from_time": row.planned_start_time,
- "to_time": row.planned_end_time,
- "completed_qty": 0,
- "time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
- })
+ self.append(
+ "time_logs",
+ {
+ "from_time": row.planned_start_time,
+ "to_time": row.planned_end_time,
+ "completed_qty": 0,
+ "time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
+ },
+ )
@frappe.whitelist()
def get_required_items(self):
- if not self.get('work_order'):
+ if not self.get("work_order"):
return
- doc = frappe.get_doc('Work Order', self.get('work_order'))
- if doc.transfer_material_against == 'Work Order' or doc.skip_transfer:
+ doc = frappe.get_doc("Work Order", self.get("work_order"))
+ if doc.transfer_material_against == "Work Order" or doc.skip_transfer:
return
for d in doc.required_items:
if not d.operation:
- frappe.throw(_("Row {0} : Operation is required against the raw material item {1}")
- .format(d.idx, d.item_code))
+ frappe.throw(
+ _("Row {0} : Operation is required against the raw material item {1}").format(
+ d.idx, d.item_code
+ )
+ )
- if self.get('operation') == d.operation:
- self.append('items', {
- "item_code": d.item_code,
- "source_warehouse": d.source_warehouse,
- "uom": frappe.db.get_value("Item", d.item_code, 'stock_uom'),
- "item_name": d.item_name,
- "description": d.description,
- "required_qty": (d.required_qty * flt(self.for_quantity)) / doc.qty,
- "rate": d.rate,
- "amount": d.amount
- })
+ if self.get("operation") == d.operation:
+ self.append(
+ "items",
+ {
+ "item_code": d.item_code,
+ "source_warehouse": d.source_warehouse,
+ "uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
+ "item_name": d.item_name,
+ "description": d.description,
+ "required_qty": (d.required_qty * flt(self.for_quantity)) / doc.qty,
+ "rate": d.rate,
+ "amount": d.amount,
+ },
+ )
def on_submit(self):
self.validate_transfer_qty()
@@ -339,31 +382,52 @@ class JobCard(Document):
def validate_transfer_qty(self):
if self.items and self.transferred_qty < self.for_quantity:
- frappe.throw(_('Materials needs to be transferred to the work in progress warehouse for the job card {0}')
- .format(self.name))
+ frappe.throw(
+ _(
+ "Materials needs to be transferred to the work in progress warehouse for the job card {0}"
+ ).format(self.name)
+ )
def validate_job_card(self):
- if self.work_order and frappe.get_cached_value('Work Order', self.work_order, 'status') == 'Stopped':
- frappe.throw(_("Transaction not allowed against stopped Work Order {0}")
- .format(get_link_to_form('Work Order', self.work_order)))
+ if (
+ self.work_order
+ and frappe.get_cached_value("Work Order", self.work_order, "status") == "Stopped"
+ ):
+ frappe.throw(
+ _("Transaction not allowed against stopped Work Order {0}").format(
+ get_link_to_form("Work Order", self.work_order)
+ )
+ )
if not self.time_logs:
- frappe.throw(_("Time logs are required for {0} {1}")
- .format(bold("Job Card"), get_link_to_form("Job Card", self.name)))
+ frappe.throw(
+ _("Time logs are required for {0} {1}").format(
+ bold("Job Card"), get_link_to_form("Job Card", self.name)
+ )
+ )
if self.for_quantity and self.total_completed_qty != self.for_quantity:
total_completed_qty = bold(_("Total Completed Qty"))
qty_to_manufacture = bold(_("Qty to Manufacture"))
- frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})")
- .format(total_completed_qty, bold(self.total_completed_qty), qty_to_manufacture,bold(self.for_quantity)))
+ frappe.throw(
+ _("The {0} ({1}) must be equal to {2} ({3})").format(
+ total_completed_qty,
+ bold(self.total_completed_qty),
+ qty_to_manufacture,
+ bold(self.for_quantity),
+ )
+ )
def update_work_order(self):
if not self.work_order:
return
- if self.is_corrective_job_card and not cint(frappe.db.get_single_value('Manufacturing Settings',
- 'add_corrective_operation_cost_in_finished_good_valuation')):
+ if self.is_corrective_job_card and not cint(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "add_corrective_operation_cost_in_finished_good_valuation"
+ )
+ ):
return
for_quantity, time_in_mins = 0, 0
@@ -375,7 +439,7 @@ class JobCard(Document):
for_quantity = flt(data[0].completed_qty)
time_in_mins = flt(data[0].time_in_mins)
- wo = frappe.get_doc('Work Order', self.work_order)
+ wo = frappe.get_doc("Work Order", self.work_order)
if self.is_corrective_job_card:
self.update_corrective_in_work_order(wo)
@@ -386,8 +450,11 @@ class JobCard(Document):
def update_corrective_in_work_order(self, wo):
wo.corrective_operation_cost = 0.0
- for row in frappe.get_all('Job Card', fields = ['total_time_in_mins', 'hour_rate'],
- filters = {'is_corrective_job_card': 1, 'docstatus': 1, 'work_order': self.work_order}):
+ for row in frappe.get_all(
+ "Job Card",
+ fields=["total_time_in_mins", "hour_rate"],
+ filters={"is_corrective_job_card": 1, "docstatus": 1, "work_order": self.work_order},
+ ):
wo.corrective_operation_cost += flt(row.total_time_in_mins) * flt(row.hour_rate)
wo.calculate_operating_cost()
@@ -395,27 +462,37 @@ class JobCard(Document):
wo.save()
def validate_produced_quantity(self, for_quantity, wo):
- if self.docstatus < 2: return
+ if self.docstatus < 2:
+ return
if wo.produced_qty > for_quantity:
- first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
- .format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
+ first_part_msg = _(
+ "The {0} {1} is used to calculate the valuation cost for the finished good {2}."
+ ).format(
+ frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)
+ )
- second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
- .format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
+ second_part_msg = _(
+ "Kindly cancel the Manufacturing Entries first against the work order {0}."
+ ).format(frappe.bold(get_link_to_form("Work Order", self.work_order)))
- frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
- JobCardCancelError, title = _("Error"))
+ frappe.throw(
+ _("{0} {1}").format(first_part_msg, second_part_msg), JobCardCancelError, title=_("Error")
+ )
def update_work_order_data(self, for_quantity, time_in_mins, wo):
- time_data = frappe.db.sql("""
+ time_data = frappe.db.sql(
+ """
SELECT
min(from_time) as start_time, max(to_time) as end_time
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s and jc.operation_id = %s
and jc.docstatus = 1 and IFNULL(jc.is_corrective_job_card, 0) = 0
- """, (self.work_order, self.operation_id), as_dict=1)
+ """,
+ (self.work_order, self.operation_id),
+ as_dict=1,
+ )
for data in wo.operations:
if data.get("name") == self.operation_id:
@@ -434,91 +511,118 @@ class JobCard(Document):
wo.save()
def get_current_operation_data(self):
- return frappe.get_all('Job Card',
- fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
- filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id,
- "is_corrective_job_card": 0})
+ return frappe.get_all(
+ "Job Card",
+ fields=["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
+ filters={
+ "docstatus": 1,
+ "work_order": self.work_order,
+ "operation_id": self.operation_id,
+ "is_corrective_job_card": 0,
+ },
+ )
def set_transferred_qty_in_job_card(self, ste_doc):
for row in ste_doc.items:
- if not row.job_card_item: continue
+ if not row.job_card_item:
+ continue
- qty = frappe.db.sql(""" SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
+ qty = frappe.db.sql(
+ """ SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and
se.purpose = 'Material Transfer for Manufacture'
- """, (row.job_card_item))[0][0]
+ """,
+ (row.job_card_item),
+ )[0][0]
- frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty))
+ frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(qty))
def set_transferred_qty(self, update_status=False):
"Set total FG Qty for which RM was transferred."
if not self.items:
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
- doc = frappe.get_doc('Work Order', self.get('work_order'))
- if doc.transfer_material_against == 'Work Order' or doc.skip_transfer:
+ doc = frappe.get_doc("Work Order", self.get("work_order"))
+ if doc.transfer_material_against == "Work Order" or doc.skip_transfer:
return
if self.items:
# sum of 'For Quantity' of Stock Entries against JC
- self.transferred_qty = frappe.db.get_value('Stock Entry', {
- 'job_card': self.name,
- 'work_order': self.work_order,
- 'docstatus': 1,
- 'purpose': 'Material Transfer for Manufacture'
- }, 'sum(fg_completed_qty)') or 0
+ self.transferred_qty = (
+ frappe.db.get_value(
+ "Stock Entry",
+ {
+ "job_card": self.name,
+ "work_order": self.work_order,
+ "docstatus": 1,
+ "purpose": "Material Transfer for Manufacture",
+ },
+ "sum(fg_completed_qty)",
+ )
+ or 0
+ )
self.db_set("transferred_qty", self.transferred_qty)
qty = 0
if self.work_order:
- doc = frappe.get_doc('Work Order', self.work_order)
- if doc.transfer_material_against == 'Job Card' and not doc.skip_transfer:
+ doc = frappe.get_doc("Work Order", self.work_order)
+ if doc.transfer_material_against == "Job Card" and not doc.skip_transfer:
completed = True
for d in doc.operations:
- if d.status != 'Completed':
+ if d.status != "Completed":
completed = False
break
if completed:
- job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order,
- 'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id')
+ job_cards = frappe.get_all(
+ "Job Card",
+ filters={"work_order": self.work_order, "docstatus": ("!=", 2)},
+ fields="sum(transferred_qty) as qty",
+ group_by="operation_id",
+ )
if job_cards:
qty = min(d.qty for d in job_cards)
- doc.db_set('material_transferred_for_manufacturing', qty)
+ doc.db_set("material_transferred_for_manufacturing", qty)
self.set_status(update_status)
def set_status(self, update_status=False):
- if self.status == "On Hold": return
+ if self.status == "On Hold":
+ return
- self.status = {
- 0: "Open",
- 1: "Submitted",
- 2: "Cancelled"
- }[self.docstatus or 0]
+ self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if self.for_quantity <= self.transferred_qty:
- self.status = 'Material Transferred'
+ self.status = "Material Transferred"
if self.time_logs:
- self.status = 'Work In Progress'
+ self.status = "Work In Progress"
- if (self.docstatus == 1 and
- (self.for_quantity <= self.total_completed_qty or not self.items)):
- self.status = 'Completed'
+ if self.docstatus == 1 and (self.for_quantity <= self.total_completed_qty or not self.items):
+ self.status = "Completed"
if update_status:
- self.db_set('status', self.status)
+ self.db_set("status", self.status)
def validate_operation_id(self):
- if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
- frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name") != self.operation_id):
+ if (
+ self.get("operation_id")
+ and self.get("operation_row_number")
+ and self.operation
+ and self.work_order
+ and frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name")
+ != self.operation_id
+ ):
work_order = bold(get_link_to_form("Work Order", self.work_order))
- frappe.throw(_("Operation {0} does not belong to the work order {1}")
- .format(bold(self.operation), work_order), OperationMismatchError)
+ frappe.throw(
+ _("Operation {0} does not belong to the work order {1}").format(
+ bold(self.operation), work_order
+ ),
+ OperationMismatchError,
+ )
def validate_sequence_id(self):
if self.is_corrective_job_card:
@@ -534,18 +638,25 @@ class JobCard(Document):
current_operation_qty += flt(self.total_completed_qty)
- data = frappe.get_all("Work Order Operation",
- fields = ["operation", "status", "completed_qty"],
- filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id)},
- order_by = "sequence_id, idx")
+ data = frappe.get_all(
+ "Work Order Operation",
+ fields=["operation", "status", "completed_qty"],
+ filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ("<", self.sequence_id)},
+ order_by="sequence_id, idx",
+ )
- message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(bold(self.name),
- bold(get_link_to_form("Work Order", self.work_order)))
+ message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(
+ bold(self.name), bold(get_link_to_form("Work Order", self.work_order))
+ )
for row in data:
if row.status != "Completed" and row.completed_qty < current_operation_qty:
- frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
- .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
+ frappe.throw(
+ _("{0}, complete the operation {1} before the operation {2}.").format(
+ message, bold(row.operation), bold(self.operation)
+ ),
+ OperationSequenceError,
+ )
def validate_work_order(self):
if self.is_work_order_closed():
@@ -553,13 +664,14 @@ class JobCard(Document):
def is_work_order_closed(self):
if self.work_order:
- status = frappe.get_value('Work Order', self.work_order)
+ status = frappe.get_value("Work Order", self.work_order)
if status == "Closed":
return True
return False
+
@frappe.whitelist()
def make_time_log(args):
if isinstance(args, str):
@@ -570,16 +682,17 @@ def make_time_log(args):
doc.validate_sequence_id()
doc.add_time_log(args)
+
@frappe.whitelist()
def get_operation_details(work_order, operation):
if work_order and operation:
- return frappe.get_all("Work Order Operation", fields = ["name", "idx"],
- filters = {
- "parent": work_order,
- "operation": operation
- }
+ return frappe.get_all(
+ "Work Order Operation",
+ fields=["name", "idx"],
+ filters={"parent": work_order, "operation": operation},
)
+
@frappe.whitelist()
def get_operations(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("work_order"):
@@ -589,12 +702,16 @@ def get_operations(doctype, txt, searchfield, start, page_len, filters):
if txt:
args["operation"] = ("like", "%{0}%".format(txt))
- return frappe.get_all("Work Order Operation",
- filters = args,
- fields = ["distinct operation as operation"],
- limit_start = start,
- limit_page_length = page_len,
- order_by="idx asc", as_list=1)
+ return frappe.get_all(
+ "Work Order Operation",
+ filters=args,
+ fields=["distinct operation as operation"],
+ limit_start=start,
+ limit_page_length=page_len,
+ order_by="idx asc",
+ as_list=1,
+ )
+
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
@@ -604,26 +721,29 @@ def make_material_request(source_name, target_doc=None):
def set_missing_values(source, target):
target.material_request_type = "Material Transfer"
- doclist = get_mapped_doc("Job Card", source_name, {
- "Job Card": {
- "doctype": "Material Request",
- "field_map": {
- "name": "job_card",
+ doclist = get_mapped_doc(
+ "Job Card",
+ source_name,
+ {
+ "Job Card": {
+ "doctype": "Material Request",
+ "field_map": {
+ "name": "job_card",
+ },
+ },
+ "Job Card Item": {
+ "doctype": "Material Request Item",
+ "field_map": {"required_qty": "qty", "uom": "stock_uom", "name": "job_card_item"},
+ "postprocess": update_item,
},
},
- "Job Card Item": {
- "doctype": "Material Request Item",
- "field_map": {
- "required_qty": "qty",
- "uom": "stock_uom",
- "name": "job_card_item"
- },
- "postprocess": update_item,
- }
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
@frappe.whitelist()
def make_stock_entry(source_name, target_doc=None):
def update_item(source, target, source_parent):
@@ -641,7 +761,7 @@ def make_stock_entry(source_name, target_doc=None):
target.from_bom = 1
# avoid negative 'For Quantity'
- pending_fg_qty = flt(source.get('for_quantity', 0)) - flt(source.get('transferred_qty', 0))
+ pending_fg_qty = flt(source.get("for_quantity", 0)) - flt(source.get("transferred_qty", 0))
target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0
target.set_transfer_qty()
@@ -649,36 +769,45 @@ def make_stock_entry(source_name, target_doc=None):
target.set_missing_values()
target.set_stock_entry_type()
- wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item")
+ wo_allows_alternate_item = frappe.db.get_value(
+ "Work Order", target.work_order, "allow_alternative_item"
+ )
for item in target.items:
- item.allow_alternative_item = int(wo_allows_alternate_item and
- frappe.get_cached_value("Item", item.item_code, "allow_alternative_item"))
+ item.allow_alternative_item = int(
+ wo_allows_alternate_item
+ and frappe.get_cached_value("Item", item.item_code, "allow_alternative_item")
+ )
- doclist = get_mapped_doc("Job Card", source_name, {
- "Job Card": {
- "doctype": "Stock Entry",
- "field_map": {
- "name": "job_card",
- "for_quantity": "fg_completed_qty"
+ doclist = get_mapped_doc(
+ "Job Card",
+ source_name,
+ {
+ "Job Card": {
+ "doctype": "Stock Entry",
+ "field_map": {"name": "job_card", "for_quantity": "fg_completed_qty"},
+ },
+ "Job Card Item": {
+ "doctype": "Stock Entry Detail",
+ "field_map": {
+ "source_warehouse": "s_warehouse",
+ "required_qty": "qty",
+ "name": "job_card_item",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: doc.required_qty > 0,
},
},
- "Job Card Item": {
- "doctype": "Stock Entry Detail",
- "field_map": {
- "source_warehouse": "s_warehouse",
- "required_qty": "qty",
- "name": "job_card_item"
- },
- "postprocess": update_item,
- "condition": lambda doc: doc.required_qty > 0
- }
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
def time_diff_in_minutes(string_ed_date, string_st_date):
return time_diff(string_ed_date, string_st_date).total_seconds() / 60
+
@frappe.whitelist()
def get_job_details(start, end, filters=None):
events = []
@@ -686,41 +815,49 @@ def get_job_details(start, end, filters=None):
event_color = {
"Completed": "#cdf5a6",
"Material Transferred": "#ffdd9e",
- "Work In Progress": "#D3D3D3"
+ "Work In Progress": "#D3D3D3",
}
from frappe.desk.reportview import get_filters_cond
+
conditions = get_filters_cond("Job Card", filters, [])
- job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order,
+ job_cards = frappe.db.sql(
+ """ SELECT `tabJob Card`.name, `tabJob Card`.work_order,
`tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
min(`tabJob Card Time Log`.from_time) as from_time,
max(`tabJob Card Time Log`.to_time) as to_time
FROM `tabJob Card` , `tabJob Card Time Log`
WHERE
`tabJob Card`.name = `tabJob Card Time Log`.parent {0}
- group by `tabJob Card`.name""".format(conditions), as_dict=1)
+ group by `tabJob Card`.name""".format(
+ conditions
+ ),
+ as_dict=1,
+ )
for d in job_cards:
- subject_data = []
- for field in ["name", "work_order", "remarks"]:
- if not d.get(field): continue
+ subject_data = []
+ for field in ["name", "work_order", "remarks"]:
+ if not d.get(field):
+ continue
- subject_data.append(d.get(field))
+ subject_data.append(d.get(field))
- color = event_color.get(d.status)
- job_card_data = {
- 'from_time': d.from_time,
- 'to_time': d.to_time,
- 'name': d.name,
- 'subject': '\n'.join(subject_data),
- 'color': color if color else "#89bcde"
- }
+ color = event_color.get(d.status)
+ job_card_data = {
+ "from_time": d.from_time,
+ "to_time": d.to_time,
+ "name": d.name,
+ "subject": "\n".join(subject_data),
+ "color": color if color else "#89bcde",
+ }
- events.append(job_card_data)
+ events.append(job_card_data)
return events
+
@frappe.whitelist()
def make_corrective_job_card(source_name, operation=None, for_operation=None, target_doc=None):
def set_missing_values(source, target):
@@ -728,20 +865,26 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
target.operation = operation
target.for_operation = for_operation
- target.set('time_logs', [])
- target.set('employee', [])
- target.set('items', [])
+ target.set("time_logs", [])
+ target.set("employee", [])
+ target.set("items", [])
target.set_sub_operations()
target.get_required_items()
target.validate_time_logs()
- doclist = get_mapped_doc("Job Card", source_name, {
- "Job Card": {
- "doctype": "Job Card",
- "field_map": {
- "name": "for_job_card",
- },
- }
- }, target_doc, set_missing_values)
+ doclist = get_mapped_doc(
+ "Job Card",
+ source_name,
+ {
+ "Job Card": {
+ "doctype": "Job Card",
+ "field_map": {
+ "name": "for_job_card",
+ },
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
index 2c488721b0..14c1f36d0d 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
@@ -3,18 +3,10 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'job_card',
- 'non_standard_fieldnames': {
- 'Quality Inspection': 'reference_name'
- },
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Material Request', 'Stock Entry']
- },
- {
- 'label': _('Reference'),
- 'items': ['Quality Inspection']
- }
- ]
+ "fieldname": "job_card",
+ "non_standard_fieldnames": {"Quality Inspection": "reference_name"},
+ "transactions": [
+ {"label": _("Transactions"), "items": ["Material Request", "Stock Entry"]},
+ {"label": _("Reference"), "items": ["Quality Inspection"]},
+ ],
}
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index c5841c16f2..4647ddf05f 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -20,13 +20,11 @@ class TestJobCard(FrappeTestCase):
transfer_material_against, source_warehouse = None, None
- tests_that_skip_setup = (
- "test_job_card_material_transfer_correctness",
- )
+ tests_that_skip_setup = ("test_job_card_material_transfer_correctness",)
tests_that_transfer_against_jc = (
"test_job_card_multiple_materials_transfer",
"test_job_card_excess_material_transfer",
- "test_job_card_partial_material_transfer"
+ "test_job_card_partial_material_transfer",
)
if self._testMethodName in tests_that_skip_setup:
@@ -40,7 +38,7 @@ class TestJobCard(FrappeTestCase):
item="_Test FG Item 2",
qty=2,
transfer_material_against=transfer_material_against,
- source_warehouse=source_warehouse
+ source_warehouse=source_warehouse,
)
def tearDown(self):
@@ -48,8 +46,9 @@ class TestJobCard(FrappeTestCase):
def test_job_card(self):
- job_cards = frappe.get_all('Job Card',
- filters = {'work_order': self.work_order.name}, fields = ["operation_id", "name"])
+ job_cards = frappe.get_all(
+ "Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"]
+ )
if job_cards:
job_card = job_cards[0]
@@ -63,30 +62,38 @@ class TestJobCard(FrappeTestCase):
frappe.delete_doc("Job Card", d.name)
def test_job_card_with_different_work_station(self):
- job_cards = frappe.get_all('Job Card',
- filters = {'work_order': self.work_order.name},
- fields = ["operation_id", "workstation", "name", "for_quantity"])
+ job_cards = frappe.get_all(
+ "Job Card",
+ filters={"work_order": self.work_order.name},
+ fields=["operation_id", "workstation", "name", "for_quantity"],
+ )
job_card = job_cards[0]
if job_card:
- workstation = frappe.db.get_value("Workstation",
- {"name": ("not in", [job_card.workstation])}, "name")
+ workstation = frappe.db.get_value(
+ "Workstation", {"name": ("not in", [job_card.workstation])}, "name"
+ )
if not workstation or job_card.workstation == workstation:
workstation = make_workstation(workstation_name=random_string(5)).name
doc = frappe.get_doc("Job Card", job_card.name)
doc.workstation = workstation
- doc.append("time_logs", {
- "from_time": "2009-01-01 12:06:25",
- "to_time": "2009-01-01 12:37:25",
- "time_in_mins": "31.00002",
- "completed_qty": job_card.for_quantity
- })
+ doc.append(
+ "time_logs",
+ {
+ "from_time": "2009-01-01 12:06:25",
+ "to_time": "2009-01-01 12:37:25",
+ "time_in_mins": "31.00002",
+ "completed_qty": job_card.for_quantity,
+ },
+ )
doc.submit()
- completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty")
+ completed_qty = frappe.db.get_value(
+ "Work Order Operation", job_card.operation_id, "completed_qty"
+ )
self.assertEqual(completed_qty, job_card.for_quantity)
doc.cancel()
@@ -97,51 +104,49 @@ class TestJobCard(FrappeTestCase):
def test_job_card_overlap(self):
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
- jc1_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
- jc2_name = frappe.db.get_value("Job Card", {'work_order': wo2.name})
+ jc1_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
+ jc2_name = frappe.db.get_value("Job Card", {"work_order": wo2.name})
jc1 = frappe.get_doc("Job Card", jc1_name)
jc2 = frappe.get_doc("Job Card", jc2_name)
- employee = "_T-Employee-00001" # from test records
+ employee = "_T-Employee-00001" # from test records
- jc1.append("time_logs", {
- "from_time": "2021-01-01 00:00:00",
- "to_time": "2021-01-01 08:00:00",
- "completed_qty": 1,
- "employee": employee,
- })
+ jc1.append(
+ "time_logs",
+ {
+ "from_time": "2021-01-01 00:00:00",
+ "to_time": "2021-01-01 08:00:00",
+ "completed_qty": 1,
+ "employee": employee,
+ },
+ )
jc1.save()
# add a new entry in same time slice
- jc2.append("time_logs", {
- "from_time": "2021-01-01 00:01:00",
- "to_time": "2021-01-01 06:00:00",
- "completed_qty": 1,
- "employee": employee,
- })
+ jc2.append(
+ "time_logs",
+ {
+ "from_time": "2021-01-01 00:01:00",
+ "to_time": "2021-01-01 06:00:00",
+ "completed_qty": 1,
+ "employee": employee,
+ },
+ )
self.assertRaises(OverlapError, jc2.save)
def test_job_card_multiple_materials_transfer(self):
"Test transferring RMs separately against Job Card with multiple RMs."
+ make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100)
make_stock_entry(
- item_code="_Test Item",
- target="Stores - _TC",
- qty=10,
- basic_rate=100
- )
- make_stock_entry(
- item_code="_Test Item Home Desktop Manufactured",
- target="Stores - _TC",
- qty=6,
- basic_rate=100
+ item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=6, basic_rate=100
)
- job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
+ job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
- del transfer_entry_1.items[1] # transfer only 1 of 2 RMs
+ del transfer_entry_1.items[1] # transfer only 1 of 2 RMs
transfer_entry_1.insert()
transfer_entry_1.submit()
@@ -162,12 +167,12 @@ class TestJobCard(FrappeTestCase):
def test_job_card_excess_material_transfer(self):
"Test transferring more than required RM against Job Card."
- make_stock_entry(item_code="_Test Item", target="Stores - _TC",
- qty=25, basic_rate=100)
- make_stock_entry(item_code="_Test Item Home Desktop Manufactured",
- target="Stores - _TC", qty=15, basic_rate=100)
+ make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
+ )
- job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
+ job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
self.assertEqual(job_card.status, "Open")
@@ -193,11 +198,10 @@ class TestJobCard(FrappeTestCase):
transfer_entry_3 = make_stock_entry_from_jc(job_card_name)
self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
- job_card.append("time_logs", {
- "from_time": "2021-01-01 00:01:00",
- "to_time": "2021-01-01 06:00:00",
- "completed_qty": 2
- })
+ job_card.append(
+ "time_logs",
+ {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 2},
+ )
job_card.save()
job_card.submit()
@@ -207,12 +211,12 @@ class TestJobCard(FrappeTestCase):
def test_job_card_partial_material_transfer(self):
"Test partial material transfer against Job Card"
- make_stock_entry(item_code="_Test Item", target="Stores - _TC",
- qty=25, basic_rate=100)
- make_stock_entry(item_code="_Test Item Home Desktop Manufactured",
- target="Stores - _TC", qty=15, basic_rate=100)
+ make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
+ )
- job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
+ job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
# partially transfer
@@ -242,15 +246,14 @@ class TestJobCard(FrappeTestCase):
def test_job_card_material_transfer_correctness(self):
"""
- 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card
- 2. Test impact of changing 'For Qty' in such a Stock Entry
+ 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card
+ 2. Test impact of changing 'For Qty' in such a Stock Entry
"""
create_bom_with_multiple_operations()
work_order = make_wo_with_transfer_against_jc()
job_card_name = frappe.db.get_value(
- "Job Card",
- {"work_order": work_order.name,"operation": "Test Operation A"}
+ "Job Card", {"work_order": work_order.name, "operation": "Test Operation A"}
)
job_card = frappe.get_doc("Job Card", job_card_name)
@@ -276,6 +279,7 @@ class TestJobCard(FrappeTestCase):
# rollback via tearDown method
+
def create_bom_with_multiple_operations():
"Create a BOM with multiple operations and Material Transfer against Job Card"
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
@@ -287,19 +291,22 @@ def create_bom_with_multiple_operations():
"operation": "Test Operation A",
"workstation": "_Test Workstation A",
"hour_rate_rent": 300,
- "time_in_mins": 60
+ "time_in_mins": 60,
}
make_workstation(row)
make_operation(row)
- bom_doc.append("operations", {
- "operation": "Test Operation A",
- "description": "Test Operation A",
- "workstation": "_Test Workstation A",
- "hour_rate": 300,
- "time_in_mins": 60,
- "operating_cost": 100
- })
+ bom_doc.append(
+ "operations",
+ {
+ "operation": "Test Operation A",
+ "description": "Test Operation A",
+ "workstation": "_Test Workstation A",
+ "hour_rate": 300,
+ "time_in_mins": 60,
+ "operating_cost": 100,
+ },
+ )
bom_doc.transfer_material_against = "Job Card"
bom_doc.save()
@@ -307,6 +314,7 @@ def create_bom_with_multiple_operations():
return bom_doc
+
def make_wo_with_transfer_against_jc():
"Create a WO with multiple operations and Material Transfer against Job Card"
@@ -315,7 +323,7 @@ def make_wo_with_transfer_against_jc():
qty=4,
transfer_material_against="Job Card",
source_warehouse="Stores - _TC",
- do_not_submit=True
+ do_not_submit=True,
)
work_order.required_items[0].operation = "Test Operation A"
work_order.required_items[1].operation = "_Test Operation 1"
@@ -323,8 +331,9 @@ def make_wo_with_transfer_against_jc():
return work_order
+
def make_bom_for_jc_tests():
- test_records = frappe.get_test_records('BOM')
+ test_records = frappe.get_test_records("BOM")
bom = frappe.copy_doc(test_records[2])
bom.set_rate_of_sub_assembly_item_based_on_bom = 0
bom.rm_cost_as_per = "Valuation Rate"
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
index c919e8bef1..730a857524 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
@@ -11,14 +11,19 @@ from frappe.utils import cint
class ManufacturingSettings(Document):
pass
+
def get_mins_between_operations():
- return relativedelta(minutes=cint(frappe.db.get_single_value("Manufacturing Settings",
- "mins_between_operations")) or 10)
+ return relativedelta(
+ minutes=cint(frappe.db.get_single_value("Manufacturing Settings", "mins_between_operations"))
+ or 10
+ )
+
@frappe.whitelist()
def is_material_consumption_enabled():
- if not hasattr(frappe.local, 'material_consumption'):
- frappe.local.material_consumption = cint(frappe.db.get_single_value('Manufacturing Settings',
- 'material_consumption'))
+ if not hasattr(frappe.local, "material_consumption"):
+ frappe.local.material_consumption = cint(
+ frappe.db.get_single_value("Manufacturing Settings", "material_consumption")
+ )
return frappe.local.material_consumption
diff --git a/erpnext/manufacturing/doctype/operation/operation.py b/erpnext/manufacturing/doctype/operation/operation.py
index 41726f30cf..9c8f9ac8d0 100644
--- a/erpnext/manufacturing/doctype/operation/operation.py
+++ b/erpnext/manufacturing/doctype/operation/operation.py
@@ -19,12 +19,14 @@ class Operation(Document):
operation_list = []
for row in self.sub_operations:
if row.operation in operation_list:
- frappe.throw(_("The operation {0} can not add multiple times")
- .format(frappe.bold(row.operation)))
+ frappe.throw(
+ _("The operation {0} can not add multiple times").format(frappe.bold(row.operation))
+ )
if self.name == row.operation:
- frappe.throw(_("The operation {0} can not be the sub operation")
- .format(frappe.bold(row.operation)))
+ frappe.throw(
+ _("The operation {0} can not be the sub operation").format(frappe.bold(row.operation))
+ )
operation_list.append(row.operation)
diff --git a/erpnext/manufacturing/doctype/operation/operation_dashboard.py b/erpnext/manufacturing/doctype/operation/operation_dashboard.py
index 9f7efa2b38..8dc901a296 100644
--- a/erpnext/manufacturing/doctype/operation/operation_dashboard.py
+++ b/erpnext/manufacturing/doctype/operation/operation_dashboard.py
@@ -3,11 +3,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'operation',
- 'transactions': [
- {
- 'label': _('Manufacture'),
- 'items': ['BOM', 'Work Order', 'Job Card']
- }
- ]
+ "fieldname": "operation",
+ "transactions": [{"label": _("Manufacture"), "items": ["BOM", "Work Order", "Job Card"]}],
}
diff --git a/erpnext/manufacturing/doctype/operation/test_operation.py b/erpnext/manufacturing/doctype/operation/test_operation.py
index e511084e7d..ce9f8e0649 100644
--- a/erpnext/manufacturing/doctype/operation/test_operation.py
+++ b/erpnext/manufacturing/doctype/operation/test_operation.py
@@ -5,11 +5,13 @@ import unittest
import frappe
-test_records = frappe.get_test_records('Operation')
+test_records = frappe.get_test_records("Operation")
+
class TestOperation(unittest.TestCase):
pass
+
def make_operation(*args, **kwargs):
args = args if args else kwargs
if isinstance(args, tuple):
@@ -18,11 +20,9 @@ def make_operation(*args, **kwargs):
args = frappe._dict(args)
if not frappe.db.exists("Operation", args.operation):
- doc = frappe.get_doc({
- "doctype": "Operation",
- "name": args.operation,
- "workstation": args.workstation
- })
+ doc = frappe.get_doc(
+ {"doctype": "Operation", "name": args.operation, "workstation": args.workstation}
+ )
doc.insert()
return doc
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 349f40e9c7..89f9ca6d83 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -36,7 +36,7 @@ class ProductionPlan(Document):
def set_pending_qty_in_row_without_reference(self):
"Set Pending Qty in independent rows (not from SO or MR)."
- if self.docstatus > 0: # set only to initialise value before submit
+ if self.docstatus > 0: # set only to initialise value before submit
return
for item in self.po_items:
@@ -49,7 +49,7 @@ class ProductionPlan(Document):
self.total_planned_qty += flt(d.planned_qty)
def validate_data(self):
- for d in self.get('po_items'):
+ for d in self.get("po_items"):
if not d.bom_no:
frappe.throw(_("Please select BOM for Item in Row {0}").format(d.idx))
else:
@@ -59,9 +59,9 @@ class ProductionPlan(Document):
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
def _rename_temporary_references(self):
- """ po_items and sub_assembly_items items are both constructed client side without saving.
+ """po_items and sub_assembly_items items are both constructed client side without saving.
- Attempt to fix linkages by using temporary names to map final row names.
+ Attempt to fix linkages by using temporary names to map final row names.
"""
new_name_map = {d.temporary_name: d.name for d in self.po_items if d.temporary_name}
actual_names = {d.name for d in self.po_items}
@@ -72,7 +72,7 @@ class ProductionPlan(Document):
@frappe.whitelist()
def get_open_sales_orders(self):
- """ Pull sales orders which are pending to deliver based on criteria selected"""
+ """Pull sales orders which are pending to deliver based on criteria selected"""
open_so = get_sales_orders(self)
if open_so:
@@ -81,20 +81,23 @@ class ProductionPlan(Document):
frappe.msgprint(_("Sales orders are not available for production"))
def add_so_in_table(self, open_so):
- """ Add sales orders in the table"""
- self.set('sales_orders', [])
+ """Add sales orders in the table"""
+ self.set("sales_orders", [])
for data in open_so:
- self.append('sales_orders', {
- 'sales_order': data.name,
- 'sales_order_date': data.transaction_date,
- 'customer': data.customer,
- 'grand_total': data.base_grand_total
- })
+ self.append(
+ "sales_orders",
+ {
+ "sales_order": data.name,
+ "sales_order_date": data.transaction_date,
+ "customer": data.customer,
+ "grand_total": data.base_grand_total,
+ },
+ )
@frappe.whitelist()
def get_pending_material_requests(self):
- """ Pull Material Requests that are pending based on criteria selected"""
+ """Pull Material Requests that are pending based on criteria selected"""
mr_filter = item_filter = ""
if self.from_date:
mr_filter += " and mr.transaction_date >= %(from_date)s"
@@ -106,7 +109,8 @@ class ProductionPlan(Document):
if self.item_code:
item_filter += " and mr_item.item_code = %(item)s"
- pending_mr = frappe.db.sql("""
+ pending_mr = frappe.db.sql(
+ """
select distinct mr.name, mr.transaction_date
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr_item.parent = mr.name
@@ -115,29 +119,34 @@ class ProductionPlan(Document):
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1))
- """.format(mr_filter, item_filter), {
+ """.format(
+ mr_filter, item_filter
+ ),
+ {
"from_date": self.from_date,
"to_date": self.to_date,
"warehouse": self.warehouse,
"item": self.item_code,
- "company": self.company
- }, as_dict=1)
+ "company": self.company,
+ },
+ as_dict=1,
+ )
self.add_mr_in_table(pending_mr)
def add_mr_in_table(self, pending_mr):
- """ Add Material Requests in the table"""
- self.set('material_requests', [])
+ """Add Material Requests in the table"""
+ self.set("material_requests", [])
for data in pending_mr:
- self.append('material_requests', {
- 'material_request': data.name,
- 'material_request_date': data.transaction_date
- })
+ self.append(
+ "material_requests",
+ {"material_request": data.name, "material_request_date": data.transaction_date},
+ )
@frappe.whitelist()
def get_items(self):
- self.set('po_items', [])
+ self.set("po_items", [])
if self.get_items_from == "Sales Order":
self.get_so_items()
@@ -152,10 +161,12 @@ class ProductionPlan(Document):
def get_bom_item(self):
"""Check if Item or if its Template has a BOM."""
bom_item = None
- has_bom = frappe.db.exists({'doctype': 'BOM', 'item': self.item_code, 'docstatus': 1})
+ has_bom = frappe.db.exists({"doctype": "BOM", "item": self.item_code, "docstatus": 1})
if not has_bom:
- template_item = frappe.db.get_value('Item', self.item_code, ['variant_of'])
- bom_item = "bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item
+ template_item = frappe.db.get_value("Item", self.item_code, ["variant_of"])
+ bom_item = (
+ "bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item
+ )
return bom_item
def get_so_items(self):
@@ -167,11 +178,12 @@ class ProductionPlan(Document):
item_condition = ""
bom_item = "bom.item = so_item.item_code"
- if self.item_code and frappe.db.exists('Item', self.item_code):
+ if self.item_code and frappe.db.exists("Item", self.item_code):
bom_item = self.get_bom_item() or bom_item
- item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
+ item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code))
- items = frappe.db.sql("""
+ items = frappe.db.sql(
+ """
select
distinct parent, item_code, warehouse,
(qty - work_order_qty) * conversion_factor as pending_qty,
@@ -181,16 +193,17 @@ class ProductionPlan(Document):
where
parent in (%s) and docstatus = 1 and qty > work_order_qty
and exists (select name from `tabBOM` bom where %s
- and bom.is_active = 1) %s""" %
- (", ".join(["%s"] * len(so_list)),
- bom_item,
- item_condition),
- tuple(so_list), as_dict=1)
+ and bom.is_active = 1) %s"""
+ % (", ".join(["%s"] * len(so_list)), bom_item, item_condition),
+ tuple(so_list),
+ as_dict=1,
+ )
if self.item_code:
- item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
+ item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code))
- packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
+ packed_items = frappe.db.sql(
+ """select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
(((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty)
as pending_qty, pi.parent_item, pi.description, so_item.name
from `tabSales Order Item` so_item, `tabPacked Item` pi
@@ -198,16 +211,23 @@ class ProductionPlan(Document):
and pi.parent_item = so_item.item_code
and so_item.parent in (%s) and so_item.qty > so_item.work_order_qty
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
- and bom.is_active = 1) %s""" % \
- (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
+ and bom.is_active = 1) %s"""
+ % (", ".join(["%s"] * len(so_list)), item_condition),
+ tuple(so_list),
+ as_dict=1,
+ )
self.add_items(items + packed_items)
self.calculate_total_planned_qty()
def get_mr_items(self):
# Check for empty table or empty rows
- if not self.get("material_requests") or not self.get_so_mr_list("material_request", "material_requests"):
- frappe.throw(_("Please fill the Material Requests table"), title=_("Material Requests Required"))
+ if not self.get("material_requests") or not self.get_so_mr_list(
+ "material_request", "material_requests"
+ ):
+ frappe.throw(
+ _("Please fill the Material Requests table"), title=_("Material Requests Required")
+ )
mr_list = self.get_so_mr_list("material_request", "material_requests")
@@ -215,13 +235,17 @@ class ProductionPlan(Document):
if self.item_code:
item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
- items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description,
+ items = frappe.db.sql(
+ """select distinct parent, name, item_code, warehouse, description,
(qty - ordered_qty) * conversion_factor as pending_qty
from `tabMaterial Request Item` mr_item
where parent in (%s) and docstatus = 1 and qty > ordered_qty
and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
- and bom.is_active = 1) %s""" % \
- (", ".join(["%s"] * len(mr_list)), item_condition), tuple(mr_list), as_dict=1)
+ and bom.is_active = 1) %s"""
+ % (", ".join(["%s"] * len(mr_list)), item_condition),
+ tuple(mr_list),
+ as_dict=1,
+ )
self.add_items(items)
self.calculate_total_planned_qty()
@@ -232,37 +256,36 @@ class ProductionPlan(Document):
item_details = get_item_details(data.item_code)
if self.combine_items:
if item_details.bom_no in refs:
- refs[item_details.bom_no]['so_details'].append({
- 'sales_order': data.parent,
- 'sales_order_item': data.name,
- 'qty': data.pending_qty
- })
- refs[item_details.bom_no]['qty'] += data.pending_qty
+ refs[item_details.bom_no]["so_details"].append(
+ {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty}
+ )
+ refs[item_details.bom_no]["qty"] += data.pending_qty
continue
else:
refs[item_details.bom_no] = {
- 'qty': data.pending_qty,
- 'po_item_ref': data.name,
- 'so_details': []
+ "qty": data.pending_qty,
+ "po_item_ref": data.name,
+ "so_details": [],
}
- refs[item_details.bom_no]['so_details'].append({
- 'sales_order': data.parent,
- 'sales_order_item': data.name,
- 'qty': data.pending_qty
- })
+ refs[item_details.bom_no]["so_details"].append(
+ {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty}
+ )
- pi = self.append('po_items', {
- 'warehouse': data.warehouse,
- 'item_code': data.item_code,
- 'description': data.description or item_details.description,
- 'stock_uom': item_details and item_details.stock_uom or '',
- 'bom_no': item_details and item_details.bom_no or '',
- 'planned_qty': data.pending_qty,
- 'pending_qty': data.pending_qty,
- 'planned_start_date': now_datetime(),
- 'product_bundle_item': data.parent_item
- })
+ pi = self.append(
+ "po_items",
+ {
+ "warehouse": data.warehouse,
+ "item_code": data.item_code,
+ "description": data.description or item_details.description,
+ "stock_uom": item_details and item_details.stock_uom or "",
+ "bom_no": item_details and item_details.bom_no or "",
+ "planned_qty": data.pending_qty,
+ "pending_qty": data.pending_qty,
+ "planned_start_date": now_datetime(),
+ "product_bundle_item": data.parent_item,
+ },
+ )
pi._set_defaults()
if self.get_items_from == "Sales Order":
@@ -277,20 +300,23 @@ class ProductionPlan(Document):
if refs:
for po_item in self.po_items:
- po_item.planned_qty = refs[po_item.bom_no]['qty']
- po_item.pending_qty = refs[po_item.bom_no]['qty']
- po_item.sales_order = ''
+ po_item.planned_qty = refs[po_item.bom_no]["qty"]
+ po_item.pending_qty = refs[po_item.bom_no]["qty"]
+ po_item.sales_order = ""
self.add_pp_ref(refs)
def add_pp_ref(self, refs):
for bom_no in refs:
- for so_detail in refs[bom_no]['so_details']:
- self.append('prod_plan_references', {
- 'item_reference': refs[bom_no]['po_item_ref'],
- 'sales_order': so_detail['sales_order'],
- 'sales_order_item': so_detail['sales_order_item'],
- 'qty': so_detail['qty']
- })
+ for so_detail in refs[bom_no]["so_details"]:
+ self.append(
+ "prod_plan_references",
+ {
+ "item_reference": refs[bom_no]["po_item_ref"],
+ "sales_order": so_detail["sales_order"],
+ "sales_order_item": so_detail["sales_order_item"],
+ "qty": so_detail["qty"],
+ },
+ )
def calculate_total_produced_qty(self):
self.total_produced_qty = 0
@@ -308,27 +334,24 @@ class ProductionPlan(Document):
self.calculate_total_produced_qty()
self.set_status()
- self.db_set('status', self.status)
+ self.db_set("status", self.status)
def on_cancel(self):
- self.db_set('status', 'Cancelled')
+ self.db_set("status", "Cancelled")
self.delete_draft_work_order()
def delete_draft_work_order(self):
- for d in frappe.get_all('Work Order', fields = ["name"],
- filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
- frappe.delete_doc('Work Order', d.name)
+ for d in frappe.get_all(
+ "Work Order", fields=["name"], filters={"docstatus": 0, "production_plan": ("=", self.name)}
+ ):
+ frappe.delete_doc("Work Order", d.name)
@frappe.whitelist()
def set_status(self, close=None):
- self.status = {
- 0: 'Draft',
- 1: 'Submitted',
- 2: 'Cancelled'
- }.get(self.docstatus)
+ self.status = {0: "Draft", 1: "Submitted", 2: "Cancelled"}.get(self.docstatus)
if close:
- self.db_set('status', 'Closed')
+ self.db_set("status", "Closed")
return
if self.total_produced_qty > 0:
@@ -336,12 +359,12 @@ class ProductionPlan(Document):
if self.all_items_completed():
self.status = "Completed"
- if self.status != 'Completed':
+ if self.status != "Completed":
self.update_ordered_status()
self.update_requested_status()
if close is not None:
- self.db_set('status', self.status)
+ self.db_set("status", self.status)
def update_ordered_status(self):
update_status = False
@@ -349,8 +372,8 @@ class ProductionPlan(Document):
if d.planned_qty == d.ordered_qty:
update_status = True
- if update_status and self.status != 'Completed':
- self.status = 'In Process'
+ if update_status and self.status != "Completed":
+ self.status = "In Process"
def update_requested_status(self):
if not self.mr_items:
@@ -362,44 +385,44 @@ class ProductionPlan(Document):
update_status = False
if update_status:
- self.status = 'Material Requested'
+ self.status = "Material Requested"
def get_production_items(self):
item_dict = {}
for d in self.po_items:
item_details = {
- "production_item" : d.item_code,
- "use_multi_level_bom" : d.include_exploded_items,
- "sales_order" : d.sales_order,
- "sales_order_item" : d.sales_order_item,
- "material_request" : d.material_request,
- "material_request_item" : d.material_request_item,
- "bom_no" : d.bom_no,
- "description" : d.description,
- "stock_uom" : d.stock_uom,
- "company" : self.company,
- "fg_warehouse" : d.warehouse,
- "production_plan" : self.name,
- "production_plan_item" : d.name,
- "product_bundle_item" : d.product_bundle_item,
- "planned_start_date" : d.planned_start_date,
- "project" : self.project
+ "production_item": d.item_code,
+ "use_multi_level_bom": d.include_exploded_items,
+ "sales_order": d.sales_order,
+ "sales_order_item": d.sales_order_item,
+ "material_request": d.material_request,
+ "material_request_item": d.material_request_item,
+ "bom_no": d.bom_no,
+ "description": d.description,
+ "stock_uom": d.stock_uom,
+ "company": self.company,
+ "fg_warehouse": d.warehouse,
+ "production_plan": self.name,
+ "production_plan_item": d.name,
+ "product_bundle_item": d.product_bundle_item,
+ "planned_start_date": d.planned_start_date,
+ "project": self.project,
}
- if not item_details['project'] and d.sales_order:
- item_details['project'] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
+ if not item_details["project"] and d.sales_order:
+ item_details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
if self.get_items_from == "Material Request":
- item_details.update({
- "qty": d.planned_qty
- })
+ item_details.update({"qty": d.planned_qty})
item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details
else:
- item_details.update({
- "qty": flt(item_dict.get((d.item_code, d.sales_order, d.warehouse),{})
- .get("qty")) + (flt(d.planned_qty) - flt(d.ordered_qty))
- })
+ item_details.update(
+ {
+ "qty": flt(item_dict.get((d.item_code, d.sales_order, d.warehouse), {}).get("qty"))
+ + (flt(d.planned_qty) - flt(d.ordered_qty))
+ }
+ )
item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details
return item_dict
@@ -415,15 +438,15 @@ class ProductionPlan(Document):
self.make_work_order_for_finished_goods(wo_list, default_warehouses)
self.make_work_order_for_subassembly_items(wo_list, subcontracted_po, default_warehouses)
self.make_subcontracted_purchase_order(subcontracted_po, po_list)
- self.show_list_created_message('Work Order', wo_list)
- self.show_list_created_message('Purchase Order', po_list)
+ self.show_list_created_message("Work Order", wo_list)
+ self.show_list_created_message("Purchase Order", po_list)
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
items_data = self.get_production_items()
for key, item in items_data.items():
if self.sub_assembly_items:
- item['use_multi_level_bom'] = 0
+ item["use_multi_level_bom"] = 0
set_default_warehouses(item, default_warehouses)
work_order = self.create_work_order(item)
@@ -432,13 +455,13 @@ class ProductionPlan(Document):
def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses):
for row in self.sub_assembly_items:
- if row.type_of_manufacturing == 'Subcontract':
+ if row.type_of_manufacturing == "Subcontract":
subcontracted_po.setdefault(row.supplier, []).append(row)
continue
work_order_data = {
- 'wip_warehouse': default_warehouses.get('wip_warehouse'),
- 'fg_warehouse': default_warehouses.get('fg_warehouse')
+ "wip_warehouse": default_warehouses.get("wip_warehouse"),
+ "fg_warehouse": default_warehouses.get("fg_warehouse"),
}
self.prepare_data_for_sub_assembly_items(row, work_order_data)
@@ -447,41 +470,59 @@ class ProductionPlan(Document):
wo_list.append(work_order)
def prepare_data_for_sub_assembly_items(self, row, wo_data):
- for field in ["production_item", "item_name", "qty", "fg_warehouse",
- "description", "bom_no", "stock_uom", "bom_level",
- "production_plan_item", "schedule_date"]:
+ for field in [
+ "production_item",
+ "item_name",
+ "qty",
+ "fg_warehouse",
+ "description",
+ "bom_no",
+ "stock_uom",
+ "bom_level",
+ "production_plan_item",
+ "schedule_date",
+ ]:
if row.get(field):
wo_data[field] = row.get(field)
- wo_data.update({
- "use_multi_level_bom": 0,
- "production_plan": self.name,
- "production_plan_sub_assembly_item": row.name
- })
+ wo_data.update(
+ {
+ "use_multi_level_bom": 0,
+ "production_plan": self.name,
+ "production_plan_sub_assembly_item": row.name,
+ }
+ )
def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders):
if not subcontracted_po:
return
for supplier, po_list in subcontracted_po.items():
- po = frappe.new_doc('Purchase Order')
+ po = frappe.new_doc("Purchase Order")
po.supplier = supplier
po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
- po.is_subcontracted = 'Yes'
+ po.is_subcontracted = "Yes"
for row in po_list:
po_data = {
- 'item_code': row.production_item,
- 'warehouse': row.fg_warehouse,
- 'production_plan_sub_assembly_item': row.name,
- 'bom': row.bom_no,
- 'production_plan': self.name
+ "item_code": row.production_item,
+ "warehouse": row.fg_warehouse,
+ "production_plan_sub_assembly_item": row.name,
+ "bom": row.bom_no,
+ "production_plan": self.name,
}
- for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name',
- 'description', 'production_plan_item']:
+ for field in [
+ "schedule_date",
+ "qty",
+ "uom",
+ "stock_uom",
+ "item_name",
+ "description",
+ "production_plan_item",
+ ]:
po_data[field] = row.get(field)
- po.append('items', po_data)
+ po.append("items", po_data)
po.set_missing_values()
po.flags.ignore_mandatory = True
@@ -503,7 +544,7 @@ class ProductionPlan(Document):
wo = frappe.new_doc("Work Order")
wo.update(item)
- wo.planned_start_date = item.get('planned_start_date') or item.get('schedule_date')
+ wo.planned_start_date = item.get("planned_start_date") or item.get("schedule_date")
if item.get("warehouse"):
wo.fg_warehouse = item.get("warehouse")
@@ -521,54 +562,60 @@ class ProductionPlan(Document):
@frappe.whitelist()
def make_material_request(self):
- '''Create Material Requests grouped by Sales Order and Material Request Type'''
+ """Create Material Requests grouped by Sales Order and Material Request Type"""
material_request_list = []
material_request_map = {}
for item in self.mr_items:
- item_doc = frappe.get_cached_doc('Item', item.item_code)
+ item_doc = frappe.get_cached_doc("Item", item.item_code)
material_request_type = item.material_request_type or item_doc.default_material_request_type
# key for Sales Order:Material Request Type:Customer
- key = '{}:{}:{}'.format(item.sales_order, material_request_type, item_doc.customer or '')
+ key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "")
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
if not key in material_request_map:
# make a new MR for the combination
material_request_map[key] = frappe.new_doc("Material Request")
material_request = material_request_map[key]
- material_request.update({
- "transaction_date": nowdate(),
- "status": "Draft",
- "company": self.company,
- 'material_request_type': material_request_type,
- 'customer': item_doc.customer or ''
- })
+ material_request.update(
+ {
+ "transaction_date": nowdate(),
+ "status": "Draft",
+ "company": self.company,
+ "material_request_type": material_request_type,
+ "customer": item_doc.customer or "",
+ }
+ )
material_request_list.append(material_request)
else:
material_request = material_request_map[key]
# add item
- material_request.append("items", {
- "item_code": item.item_code,
- "from_warehouse": item.from_warehouse,
- "qty": item.quantity,
- "schedule_date": schedule_date,
- "warehouse": item.warehouse,
- "sales_order": item.sales_order,
- 'production_plan': self.name,
- 'material_request_plan_item': item.name,
- "project": frappe.db.get_value("Sales Order", item.sales_order, "project") \
- if item.sales_order else None
- })
+ material_request.append(
+ "items",
+ {
+ "item_code": item.item_code,
+ "from_warehouse": item.from_warehouse,
+ "qty": item.quantity,
+ "schedule_date": schedule_date,
+ "warehouse": item.warehouse,
+ "sales_order": item.sales_order,
+ "production_plan": self.name,
+ "material_request_plan_item": item.name,
+ "project": frappe.db.get_value("Sales Order", item.sales_order, "project")
+ if item.sales_order
+ else None,
+ },
+ )
for material_request in material_request_list:
# submit
material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values")
- if self.get('submit_material_request'):
+ if self.get("submit_material_request"):
material_request.submit()
else:
material_request.save()
@@ -576,17 +623,19 @@ class ProductionPlan(Document):
frappe.flags.mute_messages = False
if material_request_list:
- material_request_list = ["""{1}""".format(m.name, m.name) \
- for m in material_request_list]
+ material_request_list = [
+ """{1}""".format(m.name, m.name)
+ for m in material_request_list
+ ]
msgprint(_("{0} created").format(comma_and(material_request_list)))
- else :
+ else:
msgprint(_("No material request created"))
@frappe.whitelist()
def get_sub_assembly_items(self, manufacturing_type=None):
"Fetch sub assembly items and optionally combine them."
self.sub_assembly_items = []
- sub_assembly_items_store = [] # temporary store to process all subassembly items
+ sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
bom_data = []
@@ -598,7 +647,7 @@ class ProductionPlan(Document):
# Combine subassembly items
sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
- sub_assembly_items_store.sort(key= lambda d: d.bom_level, reverse=True) # sort by bom level
+ sub_assembly_items_store.sort(key=lambda d: d.bom_level, reverse=True) # sort by bom level
for idx, row in enumerate(sub_assembly_items_store):
row.idx = idx + 1
@@ -611,16 +660,19 @@ class ProductionPlan(Document):
data.production_plan_item = row.name
data.fg_warehouse = row.warehouse
data.schedule_date = row.planned_start_date
- data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
- else "In House")
+ data.type_of_manufacturing = manufacturing_type or (
+ "Subcontract" if data.is_sub_contracted_item else "In House"
+ )
def combine_subassembly_items(self, sub_assembly_items_store):
"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
key_wise_data = {}
for row in sub_assembly_items_store:
key = (
- row.get("production_item"), row.get("fg_warehouse"),
- row.get("bom_no"), row.get("type_of_manufacturing")
+ row.get("production_item"),
+ row.get("fg_warehouse"),
+ row.get("bom_no"),
+ row.get("type_of_manufacturing"),
)
if key not in key_wise_data:
# intialise (item, wh, bom no, man.g type) wise dict
@@ -638,12 +690,15 @@ class ProductionPlan(Document):
# add row with key
key_wise_data[key] = row
- sub_assembly_items_store = [key_wise_data[key] for key in key_wise_data] # unpack into single level list
+ sub_assembly_items_store = [
+ key_wise_data[key] for key in key_wise_data
+ ] # unpack into single level list
return sub_assembly_items_store
def all_items_completed(self):
- all_items_produced = all(flt(d.planned_qty) - flt(d.produced_qty) < 0.000001
- for d in self.po_items)
+ all_items_produced = all(
+ flt(d.planned_qty) - flt(d.produced_qty) < 0.000001 for d in self.po_items
+ )
if not all_items_produced:
return False
@@ -660,40 +715,81 @@ class ProductionPlan(Document):
all_work_orders_completed = all(s == "Completed" for s in wo_status)
return all_work_orders_completed
+
@frappe.whitelist()
def download_raw_materials(doc, warehouses=None):
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
- item_list = [['Item Code', 'Item Name', 'Description',
- 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
- 'Projected Qty', 'Available Qty In Hand', 'Ordered Qty', 'Planned Qty',
- 'Reserved Qty for Production', 'Safety Stock', 'Required Qty']]
+ item_list = [
+ [
+ "Item Code",
+ "Item Name",
+ "Description",
+ "Stock UOM",
+ "Warehouse",
+ "Required Qty as per BOM",
+ "Projected Qty",
+ "Available Qty In Hand",
+ "Ordered Qty",
+ "Planned Qty",
+ "Reserved Qty for Production",
+ "Safety Stock",
+ "Required Qty",
+ ]
+ ]
doc.warehouse = None
frappe.flags.show_qty_in_stock_uom = 1
- items = get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True)
+ items = get_items_for_material_requests(
+ doc, warehouses=warehouses, get_parent_warehouse_data=True
+ )
for d in items:
- item_list.append([d.get('item_code'), d.get('item_name'),
- d.get('description'), d.get('stock_uom'), d.get('warehouse'),
- d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'),
- d.get('planned_qty'), d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')])
+ item_list.append(
+ [
+ d.get("item_code"),
+ d.get("item_name"),
+ d.get("description"),
+ d.get("stock_uom"),
+ d.get("warehouse"),
+ d.get("required_bom_qty"),
+ d.get("projected_qty"),
+ d.get("actual_qty"),
+ d.get("ordered_qty"),
+ d.get("planned_qty"),
+ d.get("reserved_qty_for_production"),
+ d.get("safety_stock"),
+ d.get("quantity"),
+ ]
+ )
- if not doc.get('for_warehouse'):
- row = {'item_code': d.get('item_code')}
+ if not doc.get("for_warehouse"):
+ row = {"item_code": d.get("item_code")}
for bin_dict in get_bin_details(row, doc.company, all_warehouse=True):
- if d.get("warehouse") == bin_dict.get('warehouse'):
+ if d.get("warehouse") == bin_dict.get("warehouse"):
continue
- item_list.append(['', '', '', bin_dict.get('warehouse'), '',
- bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0),
- bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)])
+ item_list.append(
+ [
+ "",
+ "",
+ "",
+ bin_dict.get("warehouse"),
+ "",
+ bin_dict.get("projected_qty", 0),
+ bin_dict.get("actual_qty", 0),
+ bin_dict.get("ordered_qty", 0),
+ bin_dict.get("reserved_qty_for_production", 0),
+ ]
+ )
build_csv_response(item_list, doc.name)
+
def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
- for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom,
+ for d in frappe.db.sql(
+ """select bei.item_code, item.default_bom as bom,
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name,
bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse,
item.default_material_request_type, item.min_order_qty, item_default.default_warehouse,
@@ -709,21 +805,38 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p
where
bei.docstatus < 2
and bom.name=%s and item.is_stock_item in (1, {0})
- group by bei.item_code, bei.stock_uom""".format(0 if include_non_stock_items else 1),
- (planned_qty, company, bom_no), as_dict=1):
+ group by bei.item_code, bei.stock_uom""".format(
+ 0 if include_non_stock_items else 1
+ ),
+ (planned_qty, company, bom_no),
+ as_dict=1,
+ ):
if not d.conversion_factor and d.purchase_uom:
d.conversion_factor = get_uom_conversion_factor(d.item_code, d.purchase_uom)
- item_details.setdefault(d.get('item_code'), d)
+ item_details.setdefault(d.get("item_code"), d)
return item_details
-def get_uom_conversion_factor(item_code, uom):
- return frappe.db.get_value('UOM Conversion Detail',
- {'parent': item_code, 'uom': uom}, 'conversion_factor')
-def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_items,
- include_subcontracted_items, parent_qty, planned_qty=1):
- items = frappe.db.sql("""
+def get_uom_conversion_factor(item_code, uom):
+ return frappe.db.get_value(
+ "UOM Conversion Detail", {"parent": item_code, "uom": uom}, "conversion_factor"
+ )
+
+
+def get_subitems(
+ doc,
+ data,
+ item_details,
+ bom_no,
+ company,
+ include_non_stock_items,
+ include_subcontracted_items,
+ parent_qty,
+ planned_qty=1,
+):
+ items = frappe.db.sql(
+ """
SELECT
bom_item.item_code, default_material_request_type, item.item_name,
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty,
@@ -743,15 +856,15 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite
bom.name = %(bom)s
and bom_item.docstatus < 2
and item.is_stock_item in (1, {0})
- group by bom_item.item_code""".format(0 if include_non_stock_items else 1),{
- 'bom': bom_no,
- 'parent_qty': parent_qty,
- 'planned_qty': planned_qty,
- 'company': company
- }, as_dict=1)
+ group by bom_item.item_code""".format(
+ 0 if include_non_stock_items else 1
+ ),
+ {"bom": bom_no, "parent_qty": parent_qty, "planned_qty": planned_qty, "company": company},
+ as_dict=1,
+ )
for d in items:
- if not data.get('include_exploded_items') or not d.default_bom:
+ if not data.get("include_exploded_items") or not d.default_bom:
if d.item_code in item_details:
item_details[d.item_code].qty = item_details[d.item_code].qty + d.qty
else:
@@ -760,89 +873,107 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite
item_details[d.item_code] = d
- if data.get('include_exploded_items') and d.default_bom:
- if ((d.default_material_request_type in ["Manufacture", "Purchase"] and
- not d.is_sub_contracted) or (d.is_sub_contracted and include_subcontracted_items)):
+ if data.get("include_exploded_items") and d.default_bom:
+ if (
+ d.default_material_request_type in ["Manufacture", "Purchase"] and not d.is_sub_contracted
+ ) or (d.is_sub_contracted and include_subcontracted_items):
if d.qty > 0:
- get_subitems(doc, data, item_details, d.default_bom, company,
- include_non_stock_items, include_subcontracted_items, d.qty)
+ get_subitems(
+ doc,
+ data,
+ item_details,
+ d.default_bom,
+ company,
+ include_non_stock_items,
+ include_subcontracted_items,
+ d.qty,
+ )
return item_details
-def get_material_request_items(row, sales_order, company,
- ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict):
- total_qty = row['qty']
+
+def get_material_request_items(
+ row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict
+):
+ total_qty = row["qty"]
required_qty = 0
if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0:
required_qty = total_qty
elif total_qty > bin_dict.get("projected_qty", 0):
required_qty = total_qty - bin_dict.get("projected_qty", 0)
- if required_qty > 0 and required_qty < row['min_order_qty']:
- required_qty = row['min_order_qty']
+ if required_qty > 0 and required_qty < row["min_order_qty"]:
+ required_qty = row["min_order_qty"]
item_group_defaults = get_item_group_defaults(row.item_code, company)
- if not row['purchase_uom']:
- row['purchase_uom'] = row['stock_uom']
+ if not row["purchase_uom"]:
+ row["purchase_uom"] = row["stock_uom"]
- if row['purchase_uom'] != row['stock_uom']:
- if not (row['conversion_factor'] or frappe.flags.show_qty_in_stock_uom):
- frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}")
- .format(row['purchase_uom'], row['stock_uom'], row.item_code))
+ if row["purchase_uom"] != row["stock_uom"]:
+ if not (row["conversion_factor"] or frappe.flags.show_qty_in_stock_uom):
+ frappe.throw(
+ _("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(
+ row["purchase_uom"], row["stock_uom"], row.item_code
+ )
+ )
- required_qty = required_qty / row['conversion_factor']
+ required_qty = required_qty / row["conversion_factor"]
- if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"):
+ if frappe.db.get_value("UOM", row["purchase_uom"], "must_be_whole_number"):
required_qty = ceil(required_qty)
if include_safety_stock:
- required_qty += flt(row['safety_stock'])
+ required_qty += flt(row["safety_stock"])
if required_qty > 0:
return {
- 'item_code': row.item_code,
- 'item_name': row.item_name,
- 'quantity': required_qty,
- 'required_bom_qty': total_qty,
- 'stock_uom': row.get("stock_uom"),
- 'warehouse': warehouse or row.get('source_warehouse') \
- or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
- 'safety_stock': row.safety_stock,
- 'actual_qty': bin_dict.get("actual_qty", 0),
- 'projected_qty': bin_dict.get("projected_qty", 0),
- 'ordered_qty': bin_dict.get("ordered_qty", 0),
- 'reserved_qty_for_production': bin_dict.get("reserved_qty_for_production", 0),
- 'min_order_qty': row['min_order_qty'],
- 'material_request_type': row.get("default_material_request_type"),
- 'sales_order': sales_order,
- 'description': row.get("description"),
- 'uom': row.get("purchase_uom") or row.get("stock_uom")
+ "item_code": row.item_code,
+ "item_name": row.item_name,
+ "quantity": required_qty,
+ "required_bom_qty": total_qty,
+ "stock_uom": row.get("stock_uom"),
+ "warehouse": warehouse
+ or row.get("source_warehouse")
+ or row.get("default_warehouse")
+ or item_group_defaults.get("default_warehouse"),
+ "safety_stock": row.safety_stock,
+ "actual_qty": bin_dict.get("actual_qty", 0),
+ "projected_qty": bin_dict.get("projected_qty", 0),
+ "ordered_qty": bin_dict.get("ordered_qty", 0),
+ "reserved_qty_for_production": bin_dict.get("reserved_qty_for_production", 0),
+ "min_order_qty": row["min_order_qty"],
+ "material_request_type": row.get("default_material_request_type"),
+ "sales_order": sales_order,
+ "description": row.get("description"),
+ "uom": row.get("purchase_uom") or row.get("stock_uom"),
}
+
def get_sales_orders(self):
so_filter = item_filter = ""
bom_item = "bom.item = so_item.item_code"
date_field_mapper = {
- 'from_date': ('>=', 'so.transaction_date'),
- 'to_date': ('<=', 'so.transaction_date'),
- 'from_delivery_date': ('>=', 'so_item.delivery_date'),
- 'to_delivery_date': ('<=', 'so_item.delivery_date')
+ "from_date": (">=", "so.transaction_date"),
+ "to_date": ("<=", "so.transaction_date"),
+ "from_delivery_date": (">=", "so_item.delivery_date"),
+ "to_delivery_date": ("<=", "so_item.delivery_date"),
}
for field, value in date_field_mapper.items():
if self.get(field):
so_filter += f" and {value[1]} {value[0]} %({field})s"
- for field in ['customer', 'project', 'sales_order_status']:
+ for field in ["customer", "project", "sales_order_status"]:
if self.get(field):
- so_field = 'status' if field == 'sales_order_status' else field
+ so_field = "status" if field == "sales_order_status" else field
so_filter += f" and so.{so_field} = %({field})s"
- if self.item_code and frappe.db.exists('Item', self.item_code):
+ if self.item_code and frappe.db.exists("Item", self.item_code):
bom_item = self.get_bom_item() or bom_item
item_filter += " and so_item.item_code = %(item_code)s"
- open_so = frappe.db.sql(f"""
+ open_so = frappe.db.sql(
+ f"""
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent = so.name
@@ -855,10 +986,14 @@ def get_sales_orders(self):
where pi.parent = so.name and pi.parent_item = so_item.item_code
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1)))
- """, self.as_dict(), as_dict=1)
+ """,
+ self.as_dict(),
+ as_dict=1,
+ )
return open_so
+
@frappe.whitelist()
def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
if isinstance(row, str):
@@ -867,30 +1002,42 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
company = frappe.db.escape(company)
conditions, warehouse = "", ""
- conditions = " and warehouse in (select name from `tabWarehouse` where company = {0})".format(company)
+ conditions = " and warehouse in (select name from `tabWarehouse` where company = {0})".format(
+ company
+ )
if not all_warehouse:
- warehouse = for_warehouse or row.get('source_warehouse') or row.get('default_warehouse')
+ warehouse = for_warehouse or row.get("source_warehouse") or row.get("default_warehouse")
if warehouse:
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
conditions = """ and warehouse in (select name from `tabWarehouse`
where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse and company = {2})
- """.format(lft, rgt, company)
+ """.format(
+ lft, rgt, company
+ )
- return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
+ return frappe.db.sql(
+ """ select ifnull(sum(projected_qty),0) as projected_qty,
ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty,
ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse,
ifnull(sum(planned_qty),0) as planned_qty
from `tabBin` where item_code = %(item_code)s {conditions}
group by item_code, warehouse
- """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ {"item_code": row["item_code"]},
+ as_dict=1,
+ )
+
@frappe.whitelist()
def get_so_details(sales_order):
- return frappe.db.get_value("Sales Order", sales_order,
- ['transaction_date', 'customer', 'grand_total'], as_dict=1
+ return frappe.db.get_value(
+ "Sales Order", sales_order, ["transaction_date", "customer", "grand_total"], as_dict=1
)
+
def get_warehouse_list(warehouses):
warehouse_list = []
@@ -898,7 +1045,7 @@ def get_warehouse_list(warehouses):
warehouses = json.loads(warehouses)
for row in warehouses:
- child_warehouses = frappe.db.get_descendants('Warehouse', row.get("warehouse"))
+ child_warehouses = frappe.db.get_descendants("Warehouse", row.get("warehouse"))
if child_warehouses:
warehouse_list.extend(child_warehouses)
else:
@@ -906,6 +1053,7 @@ def get_warehouse_list(warehouses):
return warehouse_list
+
@frappe.whitelist()
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
if isinstance(doc, str):
@@ -914,73 +1062,92 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
if warehouses:
warehouses = list(set(get_warehouse_list(warehouses)))
- if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses:
+ if (
+ doc.get("for_warehouse")
+ and not get_parent_warehouse_data
+ and doc.get("for_warehouse") in warehouses
+ ):
warehouses.remove(doc.get("for_warehouse"))
- doc['mr_items'] = []
+ doc["mr_items"] = []
- po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
+ po_items = doc.get("po_items") if doc.get("po_items") else doc.get("items")
# Check for empty table or empty rows
- if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]:
- frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."),
- title=_("Items Required"))
+ if not po_items or not [row.get("item_code") for row in po_items if row.get("item_code")]:
+ frappe.throw(
+ _("Items to Manufacture are required to pull the Raw Materials associated with it."),
+ title=_("Items Required"),
+ )
- company = doc.get('company')
- ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')
- include_safety_stock = doc.get('include_safety_stock')
+ company = doc.get("company")
+ ignore_existing_ordered_qty = doc.get("ignore_existing_ordered_qty")
+ include_safety_stock = doc.get("include_safety_stock")
so_item_details = frappe._dict()
for data in po_items:
if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
data["include_exploded_items"] = 1
- planned_qty = data.get('required_qty') or data.get('planned_qty')
- ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty
- warehouse = doc.get('for_warehouse')
+ planned_qty = data.get("required_qty") or data.get("planned_qty")
+ ignore_existing_ordered_qty = (
+ data.get("ignore_existing_ordered_qty") or ignore_existing_ordered_qty
+ )
+ warehouse = doc.get("for_warehouse")
item_details = {}
if data.get("bom") or data.get("bom_no"):
- if data.get('required_qty'):
- bom_no = data.get('bom')
+ if data.get("required_qty"):
+ bom_no = data.get("bom")
include_non_stock_items = 1
- include_subcontracted_items = 1 if data.get('include_exploded_items') else 0
+ include_subcontracted_items = 1 if data.get("include_exploded_items") else 0
else:
- bom_no = data.get('bom_no')
- include_subcontracted_items = doc.get('include_subcontracted_items')
- include_non_stock_items = doc.get('include_non_stock_items')
+ bom_no = data.get("bom_no")
+ include_subcontracted_items = doc.get("include_subcontracted_items")
+ include_non_stock_items = doc.get("include_non_stock_items")
if not planned_qty:
- frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx')))
+ frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx")))
if bom_no:
- if data.get('include_exploded_items') and include_subcontracted_items:
+ if data.get("include_exploded_items") and include_subcontracted_items:
# fetch exploded items from BOM
- item_details = get_exploded_items(item_details,
- company, bom_no, include_non_stock_items, planned_qty=planned_qty)
+ item_details = get_exploded_items(
+ item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty
+ )
else:
- item_details = get_subitems(doc, data, item_details, bom_no, company,
- include_non_stock_items, include_subcontracted_items, 1, planned_qty=planned_qty)
- elif data.get('item_code'):
- item_master = frappe.get_doc('Item', data['item_code']).as_dict()
+ item_details = get_subitems(
+ doc,
+ data,
+ item_details,
+ bom_no,
+ company,
+ include_non_stock_items,
+ include_subcontracted_items,
+ 1,
+ planned_qty=planned_qty,
+ )
+ elif data.get("item_code"):
+ item_master = frappe.get_doc("Item", data["item_code"]).as_dict()
purchase_uom = item_master.purchase_uom or item_master.stock_uom
- conversion_factor = (get_uom_conversion_factor(item_master.name, purchase_uom)
- if item_master.purchase_uom else 1.0)
+ conversion_factor = (
+ get_uom_conversion_factor(item_master.name, purchase_uom) if item_master.purchase_uom else 1.0
+ )
item_details[item_master.name] = frappe._dict(
{
- 'item_name' : item_master.item_name,
- 'default_bom' : doc.bom,
- 'purchase_uom' : purchase_uom,
- 'default_warehouse': item_master.default_warehouse,
- 'min_order_qty' : item_master.min_order_qty,
- 'default_material_request_type' : item_master.default_material_request_type,
- 'qty': planned_qty or 1,
- 'is_sub_contracted' : item_master.is_subcontracted_item,
- 'item_code' : item_master.name,
- 'description' : item_master.description,
- 'stock_uom' : item_master.stock_uom,
- 'conversion_factor' : conversion_factor,
- 'safety_stock': item_master.safety_stock
+ "item_name": item_master.item_name,
+ "default_bom": doc.bom,
+ "purchase_uom": purchase_uom,
+ "default_warehouse": item_master.default_warehouse,
+ "min_order_qty": item_master.min_order_qty,
+ "default_material_request_type": item_master.default_material_request_type,
+ "qty": planned_qty or 1,
+ "is_sub_contracted": item_master.is_subcontracted_item,
+ "item_code": item_master.name,
+ "description": item_master.description,
+ "stock_uom": item_master.stock_uom,
+ "conversion_factor": conversion_factor,
+ "safety_stock": item_master.safety_stock,
}
)
@@ -989,7 +1156,9 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
for item_code, details in item_details.items():
so_item_details.setdefault(sales_order, frappe._dict())
if item_code in so_item_details.get(sales_order, {}):
- so_item_details[sales_order][item_code]['qty'] = so_item_details[sales_order][item_code].get("qty", 0) + flt(details.qty)
+ so_item_details[sales_order][item_code]["qty"] = so_item_details[sales_order][item_code].get(
+ "qty", 0
+ ) + flt(details.qty)
else:
so_item_details[sales_order][item_code] = details
@@ -1001,8 +1170,15 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
bin_dict = bin_dict[0] if bin_dict else {}
if details.qty > 0:
- items = get_material_request_items(details, sales_order, company,
- ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict)
+ items = get_material_request_items(
+ details,
+ sales_order,
+ company,
+ ignore_existing_ordered_qty,
+ include_safety_stock,
+ warehouse,
+ bin_dict,
+ )
if items:
mr_items.append(items)
@@ -1015,18 +1191,26 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
if not mr_items:
to_enable = frappe.bold(_("Ignore Existing Projected Quantity"))
- warehouse = frappe.bold(doc.get('for_warehouse'))
- message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "
"
+ warehouse = frappe.bold(doc.get("for_warehouse"))
+ message = (
+ _(
+ "As there are sufficient raw materials, Material Request is not required for Warehouse {0}."
+ ).format(warehouse)
+ + "
"
+ )
message += _("If you still want to proceed, please enable {0}.").format(to_enable)
frappe.msgprint(message, title=_("Note"))
return mr_items
+
def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations
- locations = get_available_item_locations(item.get("item_code"),
- warehouses, item.get("quantity"), company, ignore_validation=True)
+
+ locations = get_available_item_locations(
+ item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True
+ )
required_qty = item.get("quantity")
# get available material by transferring to production warehouse
@@ -1037,12 +1221,14 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
new_dict = copy.deepcopy(item)
quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
- new_dict.update({
- "quantity": quantity,
- "material_request_type": "Material Transfer",
- "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
- "from_warehouse": d.get("warehouse")
- })
+ new_dict.update(
+ {
+ "quantity": quantity,
+ "material_request_type": "Material Transfer",
+ "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
+ "from_warehouse": d.get("warehouse"),
+ }
+ )
required_qty -= quantity
new_mr_items.append(new_dict)
@@ -1050,16 +1236,17 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
# raise purchase request for remaining qty
if required_qty:
stock_uom, purchase_uom = frappe.db.get_value(
- 'Item',
- item['item_code'],
- ['stock_uom', 'purchase_uom']
+ "Item", item["item_code"], ["stock_uom", "purchase_uom"]
)
- if purchase_uom != stock_uom and purchase_uom == item['uom']:
- conversion_factor = get_uom_conversion_factor(item['item_code'], item['uom'])
+ if purchase_uom != stock_uom and purchase_uom == item["uom"]:
+ conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
if not (conversion_factor or frappe.flags.show_qty_in_stock_uom):
- frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}")
- .format(purchase_uom, stock_uom, item['item_code']))
+ frappe.throw(
+ _("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(
+ purchase_uom, stock_uom, item["item_code"]
+ )
+ )
required_qty = required_qty / conversion_factor
@@ -1070,6 +1257,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
new_mr_items.append(item)
+
@frappe.whitelist()
def get_item_data(item_code):
item_details = get_item_details(item_code)
@@ -1077,33 +1265,39 @@ def get_item_data(item_code):
return {
"bom_no": item_details.get("bom_no"),
"stock_uom": item_details.get("stock_uom")
-# "description": item_details.get("description")
+ # "description": item_details.get("description")
}
+
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
data = get_bom_children(parent=bom_no)
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
- bom_data.append(frappe._dict({
- 'parent_item_code': parent_item_code,
- 'description': d.description,
- 'production_item': d.item_code,
- 'item_name': d.item_name,
- 'stock_uom': d.stock_uom,
- 'uom': d.stock_uom,
- 'bom_no': d.value,
- 'is_sub_contracted_item': d.is_sub_contracted_item,
- 'bom_level': indent,
- 'indent': indent,
- 'stock_qty': stock_qty
- }))
+ bom_data.append(
+ frappe._dict(
+ {
+ "parent_item_code": parent_item_code,
+ "description": d.description,
+ "production_item": d.item_code,
+ "item_name": d.item_name,
+ "stock_uom": d.stock_uom,
+ "uom": d.stock_uom,
+ "bom_no": d.value,
+ "is_sub_contracted_item": d.is_sub_contracted_item,
+ "bom_level": indent,
+ "indent": indent,
+ "stock_qty": stock_qty,
+ }
+ )
+ )
if d.value:
- get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1)
+ get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent + 1)
+
def set_default_warehouses(row, default_warehouses):
- for field in ['wip_warehouse', 'fg_warehouse']:
+ for field in ["wip_warehouse", "fg_warehouse"]:
if not row.get(field):
row[field] = default_warehouses.get(field)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
index e13f042e43..6fc28a3097 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
@@ -3,15 +3,9 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'production_plan',
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Work Order', 'Material Request']
- },
- {
- 'label': _('Subcontract'),
- 'items': ['Purchase Order']
- },
- ]
+ "fieldname": "production_plan",
+ "transactions": [
+ {"label": _("Transactions"), "items": ["Work Order", "Material Request"]},
+ {"label": _("Subcontract"), "items": ["Purchase Order"]},
+ ],
}
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index ec497035fe..891a497878 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -21,80 +21,88 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
class TestProductionPlan(FrappeTestCase):
def setUp(self):
- for item in ['Test Production Item 1', 'Subassembly Item 1',
- 'Raw Material Item 1', 'Raw Material Item 2']:
+ for item in [
+ "Test Production Item 1",
+ "Subassembly Item 1",
+ "Raw Material Item 1",
+ "Raw Material Item 2",
+ ]:
create_item(item, valuation_rate=100)
- sr = frappe.db.get_value('Stock Reconciliation Item',
- {'item_code': item, 'docstatus': 1}, 'parent')
+ sr = frappe.db.get_value(
+ "Stock Reconciliation Item", {"item_code": item, "docstatus": 1}, "parent"
+ )
if sr:
- sr_doc = frappe.get_doc('Stock Reconciliation', sr)
+ sr_doc = frappe.get_doc("Stock Reconciliation", sr)
sr_doc.cancel()
- create_item('Test Non Stock Raw Material', is_stock_item=0)
- for item, raw_materials in {'Subassembly Item 1': ['Raw Material Item 1', 'Raw Material Item 2'],
- 'Test Production Item 1': ['Raw Material Item 1', 'Subassembly Item 1',
- 'Test Non Stock Raw Material']}.items():
- if not frappe.db.get_value('BOM', {'item': item}):
- make_bom(item = item, raw_materials = raw_materials)
+ create_item("Test Non Stock Raw Material", is_stock_item=0)
+ for item, raw_materials in {
+ "Subassembly Item 1": ["Raw Material Item 1", "Raw Material Item 2"],
+ "Test Production Item 1": [
+ "Raw Material Item 1",
+ "Subassembly Item 1",
+ "Test Non Stock Raw Material",
+ ],
+ }.items():
+ if not frappe.db.get_value("BOM", {"item": item}):
+ make_bom(item=item, raw_materials=raw_materials)
def tearDown(self) -> None:
frappe.db.rollback()
def test_production_plan_mr_creation(self):
"Test if MRs are created for unavailable raw materials."
- pln = create_production_plan(item_code='Test Production Item 1')
+ pln = create_production_plan(item_code="Test Production Item 1")
self.assertTrue(len(pln.mr_items), 2)
pln.make_material_request()
pln.reload()
- self.assertTrue(pln.status, 'Material Requested')
+ self.assertTrue(pln.status, "Material Requested")
material_requests = frappe.get_all(
- 'Material Request Item',
- fields = ['distinct parent'],
- filters = {'production_plan': pln.name},
- as_list=1
+ "Material Request Item",
+ fields=["distinct parent"],
+ filters={"production_plan": pln.name},
+ as_list=1,
)
self.assertTrue(len(material_requests), 2)
pln.make_work_order()
- work_orders = frappe.get_all('Work Order', fields = ['name'],
- filters = {'production_plan': pln.name}, as_list=1)
+ work_orders = frappe.get_all(
+ "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
+ )
self.assertTrue(len(work_orders), len(pln.po_items))
for name in material_requests:
- mr = frappe.get_doc('Material Request', name[0])
+ mr = frappe.get_doc("Material Request", name[0])
if mr.docstatus != 0:
mr.cancel()
for name in work_orders:
- mr = frappe.delete_doc('Work Order', name[0])
+ mr = frappe.delete_doc("Work Order", name[0])
- pln = frappe.get_doc('Production Plan', pln.name)
+ pln = frappe.get_doc("Production Plan", pln.name)
pln.cancel()
def test_production_plan_start_date(self):
"Test if Work Order has same Planned Start Date as Prod Plan."
planned_date = add_to_date(date=None, days=3)
plan = create_production_plan(
- item_code='Test Production Item 1',
- planned_start_date=planned_date
+ item_code="Test Production Item 1", planned_start_date=planned_date
)
plan.make_work_order()
work_orders = frappe.get_all(
- 'Work Order',
- fields = ['name', 'planned_start_date'],
- filters = {'production_plan': plan.name}
+ "Work Order", fields=["name", "planned_start_date"], filters={"production_plan": plan.name}
)
self.assertEqual(work_orders[0].planned_start_date, planned_date)
for wo in work_orders:
- frappe.delete_doc('Work Order', wo.name)
+ frappe.delete_doc("Work Order", wo.name)
plan.reload()
plan.cancel()
@@ -104,15 +112,14 @@ class TestProductionPlan(FrappeTestCase):
- Enable 'ignore_existing_ordered_qty'.
- Test if MR Planning table pulls Raw Material Qty even if it is in stock.
"""
- sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
- target="_Test Warehouse - _TC", qty=1, rate=110)
- sr2 = create_stock_reconciliation(item_code="Raw Material Item 2",
- target="_Test Warehouse - _TC", qty=1, rate=120)
-
- pln = create_production_plan(
- item_code='Test Production Item 1',
- ignore_existing_ordered_qty=1
+ sr1 = create_stock_reconciliation(
+ item_code="Raw Material Item 1", target="_Test Warehouse - _TC", qty=1, rate=110
)
+ sr2 = create_stock_reconciliation(
+ item_code="Raw Material Item 2", target="_Test Warehouse - _TC", qty=1, rate=120
+ )
+
+ pln = create_production_plan(item_code="Test Production Item 1", ignore_existing_ordered_qty=1)
self.assertTrue(len(pln.mr_items))
self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
@@ -122,19 +129,13 @@ class TestProductionPlan(FrappeTestCase):
def test_production_plan_with_non_stock_item(self):
"Test if MR Planning table includes Non Stock RM."
- pln = create_production_plan(
- item_code='Test Production Item 1',
- include_non_stock_items=1
- )
+ pln = create_production_plan(item_code="Test Production Item 1", include_non_stock_items=1)
self.assertTrue(len(pln.mr_items), 3)
pln.cancel()
def test_production_plan_without_multi_level(self):
"Test MR Planning table for non exploded BOM."
- pln = create_production_plan(
- item_code='Test Production Item 1',
- use_multi_level_bom=0
- )
+ pln = create_production_plan(item_code="Test Production Item 1", use_multi_level_bom=0)
self.assertTrue(len(pln.mr_items), 2)
pln.cancel()
@@ -144,15 +145,15 @@ class TestProductionPlan(FrappeTestCase):
- Test if MR Planning table avoids pulling Raw Material Qty as it is in stock for
non exploded BOM.
"""
- sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
- target="_Test Warehouse - _TC", qty=1, rate=130)
- sr2 = create_stock_reconciliation(item_code="Subassembly Item 1",
- target="_Test Warehouse - _TC", qty=1, rate=140)
+ sr1 = create_stock_reconciliation(
+ item_code="Raw Material Item 1", target="_Test Warehouse - _TC", qty=1, rate=130
+ )
+ sr2 = create_stock_reconciliation(
+ item_code="Subassembly Item 1", target="_Test Warehouse - _TC", qty=1, rate=140
+ )
pln = create_production_plan(
- item_code='Test Production Item 1',
- use_multi_level_bom=0,
- ignore_existing_ordered_qty=0
+ item_code="Test Production Item 1", use_multi_level_bom=0, ignore_existing_ordered_qty=0
)
self.assertFalse(len(pln.mr_items))
@@ -162,73 +163,86 @@ class TestProductionPlan(FrappeTestCase):
def test_production_plan_sales_orders(self):
"Test if previously fulfilled SO (with WO) is pulled into Prod Plan."
- item = 'Test Production Item 1'
+ item = "Test Production Item 1"
so = make_sales_order(item_code=item, qty=1)
sales_order = so.name
sales_order_item = so.items[0].name
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = so.company
- pln.get_items_from = 'Sales Order'
+ pln.get_items_from = "Sales Order"
- pln.append('sales_orders', {
- 'sales_order': so.name,
- 'sales_order_date': so.transaction_date,
- 'customer': so.customer,
- 'grand_total': so.grand_total
- })
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so.name,
+ "sales_order_date": so.transaction_date,
+ "customer": so.customer,
+ "grand_total": so.grand_total,
+ },
+ )
pln.get_so_items()
pln.submit()
pln.make_work_order()
- work_order = frappe.db.get_value('Work Order', {'sales_order': sales_order,
- 'production_plan': pln.name, 'sales_order_item': sales_order_item}, 'name')
+ work_order = frappe.db.get_value(
+ "Work Order",
+ {"sales_order": sales_order, "production_plan": pln.name, "sales_order_item": sales_order_item},
+ "name",
+ )
- wo_doc = frappe.get_doc('Work Order', work_order)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- 'fg_warehouse': 'Finished Goods - _TC'
- })
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
wo_doc.submit()
- so_wo_qty = frappe.db.get_value('Sales Order Item', sales_order_item, 'work_order_qty')
+ so_wo_qty = frappe.db.get_value("Sales Order Item", sales_order_item, "work_order_qty")
self.assertTrue(so_wo_qty, 5)
- pln = frappe.new_doc('Production Plan')
- pln.update({
- 'from_date': so.transaction_date,
- 'to_date': so.transaction_date,
- 'customer': so.customer,
- 'item_code': item,
- 'sales_order_status': so.status
- })
+ pln = frappe.new_doc("Production Plan")
+ pln.update(
+ {
+ "from_date": so.transaction_date,
+ "to_date": so.transaction_date,
+ "customer": so.customer,
+ "item_code": item,
+ "sales_order_status": so.status,
+ }
+ )
sales_orders = get_sales_orders(pln) or {}
- sales_orders = [d.get('name') for d in sales_orders if d.get('name') == sales_order]
+ sales_orders = [d.get("name") for d in sales_orders if d.get("name") == sales_order]
self.assertEqual(sales_orders, [])
def test_production_plan_combine_items(self):
"Test combining FG items in Production Plan."
- item = 'Test Production Item 1'
+ item = "Test Production Item 1"
so1 = make_sales_order(item_code=item, qty=1)
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = so1.company
- pln.get_items_from = 'Sales Order'
- pln.append('sales_orders', {
- 'sales_order': so1.name,
- 'sales_order_date': so1.transaction_date,
- 'customer': so1.customer,
- 'grand_total': so1.grand_total
- })
+ pln.get_items_from = "Sales Order"
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so1.name,
+ "sales_order_date": so1.transaction_date,
+ "customer": so1.customer,
+ "grand_total": so1.grand_total,
+ },
+ )
so2 = make_sales_order(item_code=item, qty=2)
- pln.append('sales_orders', {
- 'sales_order': so2.name,
- 'sales_order_date': so2.transaction_date,
- 'customer': so2.customer,
- 'grand_total': so2.grand_total
- })
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so2.name,
+ "sales_order_date": so2.transaction_date,
+ "customer": so2.customer,
+ "grand_total": so2.grand_total,
+ },
+ )
pln.combine_items = 1
pln.get_items()
pln.submit()
@@ -236,26 +250,31 @@ class TestProductionPlan(FrappeTestCase):
self.assertTrue(pln.po_items[0].planned_qty, 3)
pln.make_work_order()
- work_order = frappe.db.get_value('Work Order', {
- 'production_plan_item': pln.po_items[0].name,
- 'production_plan': pln.name
- }, 'name')
+ work_order = frappe.db.get_value(
+ "Work Order",
+ {"production_plan_item": pln.po_items[0].name, "production_plan": pln.name},
+ "name",
+ )
- wo_doc = frappe.get_doc('Work Order', work_order)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- })
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ wo_doc.update(
+ {
+ "wip_warehouse": "Work In Progress - _TC",
+ }
+ )
wo_doc.submit()
so_items = []
for plan_reference in pln.prod_plan_references:
so_items.append(plan_reference.sales_order_item)
- so_wo_qty = frappe.db.get_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty')
+ so_wo_qty = frappe.db.get_value(
+ "Sales Order Item", plan_reference.sales_order_item, "work_order_qty"
+ )
self.assertEqual(so_wo_qty, plan_reference.qty)
wo_doc.cancel()
for so_item in so_items:
- so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
+ so_wo_qty = frappe.db.get_value("Sales Order Item", so_item, "work_order_qty")
self.assertEqual(so_wo_qty, 0.0)
pln.reload()
@@ -269,12 +288,8 @@ class TestProductionPlan(FrappeTestCase):
"""
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
- bom_tree_1 = {
- "Red-Car": {"Wheel": {"Rubber": {}}}
- }
- bom_tree_2 = {
- "Green-Car": {"Wheel": {"Rubber": {}}}
- }
+ bom_tree_1 = {"Red-Car": {"Wheel": {"Rubber": {}}}}
+ bom_tree_2 = {"Green-Car": {"Wheel": {"Rubber": {}}}}
parent_bom_1 = create_nested_bom(bom_tree_1, prefix="")
parent_bom_2 = create_nested_bom(bom_tree_2, prefix="")
@@ -284,20 +299,23 @@ class TestProductionPlan(FrappeTestCase):
frappe.db.set_value("BOM Item", parent_bom_2.items[0].name, "bom_no", subassembly_bom)
plan = create_production_plan(item_code="Red-Car", use_multi_level_bom=1, do_not_save=True)
- plan.append("po_items", { # Add Green-Car to Prod Plan
- 'use_multi_level_bom': 1,
- 'item_code': "Green-Car",
- 'bom_no': frappe.db.get_value('Item', "Green-Car", 'default_bom'),
- 'planned_qty': 1,
- 'planned_start_date': now_datetime()
- })
+ plan.append(
+ "po_items",
+ { # Add Green-Car to Prod Plan
+ "use_multi_level_bom": 1,
+ "item_code": "Green-Car",
+ "bom_no": frappe.db.get_value("Item", "Green-Car", "default_bom"),
+ "planned_qty": 1,
+ "planned_start_date": now_datetime(),
+ },
+ )
plan.get_sub_assembly_items()
self.assertTrue(len(plan.sub_assembly_items), 2)
plan.combine_sub_items = 1
plan.get_sub_assembly_items()
- self.assertTrue(len(plan.sub_assembly_items), 1) # check if sub-assembly items merged
+ self.assertTrue(len(plan.sub_assembly_items), 1) # check if sub-assembly items merged
self.assertEqual(plan.sub_assembly_items[0].qty, 2.0)
self.assertEqual(plan.sub_assembly_items[0].stock_qty, 2.0)
@@ -307,25 +325,29 @@ class TestProductionPlan(FrappeTestCase):
self.assertTrue(len(plan.sub_assembly_items), 2)
def test_pp_to_mr_customer_provided(self):
- " Test Material Request from Production Plan for Customer Provided Item."
- create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
- create_item('Production Item CUST')
+ "Test Material Request from Production Plan for Customer Provided Item."
+ create_item(
+ "CUST-0987", is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0
+ )
+ create_item("Production Item CUST")
- for item, raw_materials in {'Production Item CUST': ['Raw Material Item 1', 'CUST-0987']}.items():
- if not frappe.db.get_value('BOM', {'item': item}):
- make_bom(item = item, raw_materials = raw_materials)
- production_plan = create_production_plan(item_code = 'Production Item CUST')
+ for item, raw_materials in {
+ "Production Item CUST": ["Raw Material Item 1", "CUST-0987"]
+ }.items():
+ if not frappe.db.get_value("BOM", {"item": item}):
+ make_bom(item=item, raw_materials=raw_materials)
+ production_plan = create_production_plan(item_code="Production Item CUST")
production_plan.make_material_request()
material_request = frappe.db.get_value(
- 'Material Request Item',
- {'production_plan': production_plan.name, 'item_code': 'CUST-0987'},
- 'parent'
+ "Material Request Item",
+ {"production_plan": production_plan.name, "item_code": "CUST-0987"},
+ "parent",
)
- mr = frappe.get_doc('Material Request', material_request)
+ mr = frappe.get_doc("Material Request", material_request)
- self.assertTrue(mr.material_request_type, 'Customer Provided')
- self.assertTrue(mr.customer, '_Test Customer')
+ self.assertTrue(mr.material_request_type, "Customer Provided")
+ self.assertTrue(mr.customer, "_Test Customer")
def test_production_plan_with_multi_level_bom(self):
"""
@@ -339,33 +361,34 @@ class TestProductionPlan(FrappeTestCase):
create_item(item_code, is_stock_item=1)
# created bom upto 3 level
- if not frappe.db.get_value('BOM', {'item': "Test BOM 3"}):
- make_bom(item = "Test BOM 3", raw_materials = ["Test RM BOM 1"], rm_qty=3)
+ if not frappe.db.get_value("BOM", {"item": "Test BOM 3"}):
+ make_bom(item="Test BOM 3", raw_materials=["Test RM BOM 1"], rm_qty=3)
- if not frappe.db.get_value('BOM', {'item': "Test BOM 2"}):
- make_bom(item = "Test BOM 2", raw_materials = ["Test BOM 3"], rm_qty=3)
+ if not frappe.db.get_value("BOM", {"item": "Test BOM 2"}):
+ make_bom(item="Test BOM 2", raw_materials=["Test BOM 3"], rm_qty=3)
- if not frappe.db.get_value('BOM', {'item': "Test BOM 1"}):
- make_bom(item = "Test BOM 1", raw_materials = ["Test BOM 2"], rm_qty=2)
+ if not frappe.db.get_value("BOM", {"item": "Test BOM 1"}):
+ make_bom(item="Test BOM 1", raw_materials=["Test BOM 2"], rm_qty=2)
item_code = "Test BOM 1"
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = "_Test Company"
- pln.append("po_items", {
- "item_code": item_code,
- "bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
- "planned_qty": 3
- })
+ pln.append(
+ "po_items",
+ {
+ "item_code": item_code,
+ "bom_no": frappe.db.get_value("BOM", {"item": "Test BOM 1"}),
+ "planned_qty": 3,
+ },
+ )
- pln.get_sub_assembly_items('In House')
+ pln.get_sub_assembly_items("In House")
pln.submit()
pln.make_work_order()
- #last level sub-assembly work order produce qty
+ # last level sub-assembly work order produce qty
to_produce_qty = frappe.db.get_value(
- "Work Order",
- {"production_plan": pln.name, "production_item": "Test BOM 3"},
- "qty"
+ "Work Order", {"production_plan": pln.name, "production_item": "Test BOM 3"}, "qty"
)
self.assertEqual(to_produce_qty, 18.0)
@@ -374,70 +397,72 @@ class TestProductionPlan(FrappeTestCase):
def test_get_warehouse_list_group(self):
"Check if required child warehouses are returned."
- warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
+ warehouse_json = '[{"warehouse":"_Test Warehouse Group - _TC"}]'
warehouses = set(get_warehouse_list(warehouse_json))
expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"}
missing_warehouse = expected_warehouses - warehouses
- self.assertTrue(len(missing_warehouse) == 0,
- msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
+ self.assertTrue(
+ len(missing_warehouse) == 0,
+ msg=f"Following warehouses were expected {', '.join(missing_warehouse)}",
+ )
def test_get_warehouse_list_single(self):
"Check if same warehouse is returned in absence of child warehouses."
- warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
+ warehouse_json = '[{"warehouse":"_Test Scrap Warehouse - _TC"}]'
warehouses = set(get_warehouse_list(warehouse_json))
- expected_warehouses = {"_Test Scrap Warehouse - _TC", }
+ expected_warehouses = {
+ "_Test Scrap Warehouse - _TC",
+ }
self.assertEqual(warehouses, expected_warehouses)
def test_get_sales_order_with_variant(self):
"Check if Template BOM is fetched in absence of Variant BOM."
- rm_item = create_item('PIV_RM', valuation_rate = 100)
- if not frappe.db.exists('Item', {"item_code": 'PIV'}):
- item = create_item('PIV', valuation_rate = 100)
+ rm_item = create_item("PIV_RM", valuation_rate=100)
+ if not frappe.db.exists("Item", {"item_code": "PIV"}):
+ item = create_item("PIV", valuation_rate=100)
variant_settings = {
"attributes": [
- {
- "attribute": "Colour"
- },
+ {"attribute": "Colour"},
],
- "has_variants": 1
+ "has_variants": 1,
}
item.update(variant_settings)
item.save()
- parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
- if not frappe.db.exists('BOM', {"item": 'PIV'}):
- parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
+ parent_bom = make_bom(item="PIV", raw_materials=[rm_item.item_code])
+ if not frappe.db.exists("BOM", {"item": "PIV"}):
+ parent_bom = make_bom(item="PIV", raw_materials=[rm_item.item_code])
else:
- parent_bom = frappe.get_doc('BOM', {"item": 'PIV'})
+ parent_bom = frappe.get_doc("BOM", {"item": "PIV"})
- if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}):
+ if not frappe.db.exists("Item", {"item_code": "PIV-RED"}):
variant = create_variant("PIV", {"Colour": "Red"})
variant.save()
- variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
+ variant_bom = make_bom(item=variant.item_code, raw_materials=[rm_item.item_code])
else:
- variant = frappe.get_doc('Item', 'PIV-RED')
- if not frappe.db.exists('BOM', {"item": 'PIV-RED'}):
- variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
+ variant = frappe.get_doc("Item", "PIV-RED")
+ if not frappe.db.exists("BOM", {"item": "PIV-RED"}):
+ variant_bom = make_bom(item=variant.item_code, raw_materials=[rm_item.item_code])
"""Testing when item variant has a BOM"""
so = make_sales_order(item_code="PIV-RED", qty=5)
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = so.company
- pln.get_items_from = 'Sales Order'
- pln.item_code = 'PIV-RED'
+ pln.get_items_from = "Sales Order"
+ pln.item_code = "PIV-RED"
pln.get_open_sales_orders()
self.assertEqual(pln.sales_orders[0].sales_order, so.name)
pln.get_so_items()
- self.assertEqual(pln.po_items[0].item_code, 'PIV-RED')
+ self.assertEqual(pln.po_items[0].item_code, "PIV-RED")
self.assertEqual(pln.po_items[0].bom_no, variant_bom.name)
so.cancel()
- frappe.delete_doc('Sales Order', so.name)
+ frappe.delete_doc("Sales Order", so.name)
variant_bom.cancel()
- frappe.delete_doc('BOM', variant_bom.name)
+ frappe.delete_doc("BOM", variant_bom.name)
"""Testing when item variant doesn't have a BOM"""
so = make_sales_order(item_code="PIV-RED", qty=5)
@@ -445,7 +470,7 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(pln.sales_orders[0].sales_order, so.name)
pln.po_items = []
pln.get_so_items()
- self.assertEqual(pln.po_items[0].item_code, 'PIV-RED')
+ self.assertEqual(pln.po_items[0].item_code, "PIV-RED")
self.assertEqual(pln.po_items[0].bom_no, parent_bom.name)
frappe.db.rollback()
@@ -457,27 +482,35 @@ class TestProductionPlan(FrappeTestCase):
prefix = "_TestLevel_"
boms = {
"Assembly": {
- "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+ "SubAssembly1": {
+ "ChildPart1": {},
+ "ChildPart2": {},
+ },
"ChildPart6": {},
"SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}},
},
"MegaDeepAssy": {
- "SecretSubassy": {"SecretPart": {"VerySecret" : { "SuperSecret": {"Classified": {}}}},},
- # ^ assert that this is
- # first item in subassy table
- }
+ "SecretSubassy": {
+ "SecretPart": {"VerySecret": {"SuperSecret": {"Classified": {}}}},
+ },
+ # ^ assert that this is
+ # first item in subassy table
+ },
}
create_nested_bom(boms, prefix=prefix)
items = [prefix + item_code for item_code in boms.keys()]
plan = create_production_plan(item_code=items[0], do_not_save=True)
- plan.append("po_items", {
- 'use_multi_level_bom': 1,
- 'item_code': items[1],
- 'bom_no': frappe.db.get_value('Item', items[1], 'default_bom'),
- 'planned_qty': 1,
- 'planned_start_date': now_datetime()
- })
+ plan.append(
+ "po_items",
+ {
+ "use_multi_level_bom": 1,
+ "item_code": items[1],
+ "bom_no": frappe.db.get_value("Item", items[1], "default_bom"),
+ "planned_qty": 1,
+ "planned_start_date": now_datetime(),
+ },
+ )
plan.get_sub_assembly_items()
bom_level_order = [d.bom_level for d in plan.sub_assembly_items]
@@ -487,6 +520,7 @@ class TestProductionPlan(FrappeTestCase):
def test_multiple_work_order_for_production_plan_item(self):
"Test producing Prod Plan (making WO) in parts."
+
def create_work_order(item, pln, qty):
# Get Production Items
items_data = pln.get_production_items()
@@ -497,14 +531,13 @@ class TestProductionPlan(FrappeTestCase):
# Create and Submit Work Order for each item in items_data
for key, item in items_data.items():
if pln.sub_assembly_items:
- item['use_multi_level_bom'] = 0
+ item["use_multi_level_bom"] = 0
wo_name = pln.create_work_order(item)
wo_doc = frappe.get_doc("Work Order", wo_name)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- 'fg_warehouse': 'Finished Goods - _TC'
- })
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
wo_doc.submit()
wo_list.append(wo_name)
@@ -554,34 +587,30 @@ class TestProductionPlan(FrappeTestCase):
make_stock_entry as make_se_from_wo,
)
- make_stock_entry(item_code="Raw Material Item 1",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- make_stock_entry(item_code="Raw Material Item 2",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 2", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- item = 'Test Production Item 1'
+ item = "Test Production Item 1"
so = make_sales_order(item_code=item, qty=1)
pln = create_production_plan(
- company=so.company,
- get_items_from="Sales Order",
- sales_order=so,
- skip_getting_mr_items=True
+ company=so.company, get_items_from="Sales Order", sales_order=so, skip_getting_mr_items=True
)
self.assertEqual(pln.po_items[0].pending_qty, 1)
wo = make_wo_order_test_record(
- item_code=item, qty=1,
+ item_code=item,
+ qty=1,
company=so.company,
- wip_warehouse='Work In Progress - _TC',
- fg_warehouse='Finished Goods - _TC',
+ wip_warehouse="Work In Progress - _TC",
+ fg_warehouse="Finished Goods - _TC",
skip_transfer=1,
use_multi_level_bom=1,
- do_not_submit=True
+ do_not_submit=True,
)
wo.production_plan = pln.name
wo.production_plan_item = pln.po_items[0].name
@@ -604,29 +633,25 @@ class TestProductionPlan(FrappeTestCase):
make_stock_entry as make_se_from_wo,
)
- make_stock_entry(item_code="Raw Material Item 1",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- make_stock_entry(item_code="Raw Material Item 2",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 2", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- pln = create_production_plan(
- item_code='Test Production Item 1',
- skip_getting_mr_items=True
- )
+ pln = create_production_plan(item_code="Test Production Item 1", skip_getting_mr_items=True)
self.assertEqual(pln.po_items[0].pending_qty, 1)
wo = make_wo_order_test_record(
- item_code='Test Production Item 1', qty=1,
+ item_code="Test Production Item 1",
+ qty=1,
company=pln.company,
- wip_warehouse='Work In Progress - _TC',
- fg_warehouse='Finished Goods - _TC',
+ wip_warehouse="Work In Progress - _TC",
+ fg_warehouse="Finished Goods - _TC",
skip_transfer=1,
use_multi_level_bom=1,
- do_not_submit=True
+ do_not_submit=True,
)
wo.production_plan = pln.name
wo.production_plan_item = pln.po_items[0].name
@@ -644,26 +669,23 @@ class TestProductionPlan(FrappeTestCase):
def test_qty_based_status(self):
pp = frappe.new_doc("Production Plan")
- pp.po_items = [
- frappe._dict(planned_qty=5, produce_qty=4)
- ]
+ pp.po_items = [frappe._dict(planned_qty=5, produce_qty=4)]
self.assertFalse(pp.all_items_completed())
pp.po_items = [
frappe._dict(planned_qty=5, produce_qty=10),
- frappe._dict(planned_qty=5, produce_qty=4)
+ frappe._dict(planned_qty=5, produce_qty=4),
]
self.assertFalse(pp.all_items_completed())
def test_production_plan_planned_qty(self):
pln = create_production_plan(item_code="_Test FG Item", planned_qty=0.55)
pln.make_work_order()
- work_order = frappe.db.get_value('Work Order', {'production_plan': pln.name}, 'name')
- wo_doc = frappe.get_doc('Work Order', work_order)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- 'fg_warehouse': 'Finished Goods - _TC'
- })
+ work_order = frappe.db.get_value("Work Order", {"production_plan": pln.name}, "name")
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
wo_doc.submit()
self.assertEqual(wo_doc.qty, 0.55)
@@ -674,22 +696,21 @@ class TestProductionPlan(FrappeTestCase):
# this can not be unittested so mocking data that would be expected
# from client side.
for _ in range(10):
- po_item = pp.append("po_items", {
- "name": frappe.generate_hash(length=10),
- "temporary_name": frappe.generate_hash(length=10),
- })
- pp.append("sub_assembly_items", {
- "production_plan_item": po_item.temporary_name
- })
+ po_item = pp.append(
+ "po_items",
+ {
+ "name": frappe.generate_hash(length=10),
+ "temporary_name": frappe.generate_hash(length=10),
+ },
+ )
+ pp.append("sub_assembly_items", {"production_plan_item": po_item.temporary_name})
pp._rename_temporary_references()
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item)
# bad links should be erased
- pp.append("sub_assembly_items", {
- "production_plan_item": frappe.generate_hash(length=16)
- })
+ pp.append("sub_assembly_items", {"production_plan_item": frappe.generate_hash(length=16)})
pp._rename_temporary_references()
self.assertIsNone(pp.sub_assembly_items[-1].production_plan_item)
pp.sub_assembly_items.pop()
@@ -708,40 +729,48 @@ def create_production_plan(**args):
"""
args = frappe._dict(args)
- pln = frappe.get_doc({
- 'doctype': 'Production Plan',
- 'company': args.company or '_Test Company',
- 'customer': args.customer or '_Test Customer',
- 'posting_date': nowdate(),
- 'include_non_stock_items': args.include_non_stock_items or 0,
- 'include_subcontracted_items': args.include_subcontracted_items or 0,
- 'ignore_existing_ordered_qty': args.ignore_existing_ordered_qty or 0,
- 'get_items_from': 'Sales Order'
- })
+ pln = frappe.get_doc(
+ {
+ "doctype": "Production Plan",
+ "company": args.company or "_Test Company",
+ "customer": args.customer or "_Test Customer",
+ "posting_date": nowdate(),
+ "include_non_stock_items": args.include_non_stock_items or 0,
+ "include_subcontracted_items": args.include_subcontracted_items or 0,
+ "ignore_existing_ordered_qty": args.ignore_existing_ordered_qty or 0,
+ "get_items_from": "Sales Order",
+ }
+ )
if not args.get("sales_order"):
- pln.append('po_items', {
- 'use_multi_level_bom': args.use_multi_level_bom or 1,
- 'item_code': args.item_code,
- 'bom_no': frappe.db.get_value('Item', args.item_code, 'default_bom'),
- 'planned_qty': args.planned_qty or 1,
- 'planned_start_date': args.planned_start_date or now_datetime()
- })
+ pln.append(
+ "po_items",
+ {
+ "use_multi_level_bom": args.use_multi_level_bom or 1,
+ "item_code": args.item_code,
+ "bom_no": frappe.db.get_value("Item", args.item_code, "default_bom"),
+ "planned_qty": args.planned_qty or 1,
+ "planned_start_date": args.planned_start_date or now_datetime(),
+ },
+ )
if args.get("get_items_from") == "Sales Order" and args.get("sales_order"):
so = args.get("sales_order")
- pln.append('sales_orders', {
- 'sales_order': so.name,
- 'sales_order_date': so.transaction_date,
- 'customer': so.customer,
- 'grand_total': so.grand_total
- })
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so.name,
+ "sales_order_date": so.transaction_date,
+ "customer": so.customer,
+ "grand_total": so.grand_total,
+ },
+ )
pln.get_items()
if not args.get("skip_getting_mr_items"):
mr_items = get_items_for_material_requests(pln.as_dict())
for d in mr_items:
- pln.append('mr_items', d)
+ pln.append("mr_items", d)
if not args.do_not_save:
pln.insert()
@@ -750,31 +779,37 @@ def create_production_plan(**args):
return pln
+
def make_bom(**args):
args = frappe._dict(args)
- bom = frappe.get_doc({
- 'doctype': 'BOM',
- 'is_default': 1,
- 'item': args.item,
- 'currency': args.currency or 'USD',
- 'quantity': args.quantity or 1,
- 'company': args.company or '_Test Company',
- 'routing': args.routing,
- 'with_operations': args.with_operations or 0
- })
+ bom = frappe.get_doc(
+ {
+ "doctype": "BOM",
+ "is_default": 1,
+ "item": args.item,
+ "currency": args.currency or "USD",
+ "quantity": args.quantity or 1,
+ "company": args.company or "_Test Company",
+ "routing": args.routing,
+ "with_operations": args.with_operations or 0,
+ }
+ )
for item in args.raw_materials:
- item_doc = frappe.get_doc('Item', item)
+ item_doc = frappe.get_doc("Item", item)
- bom.append('items', {
- 'item_code': item,
- 'qty': args.rm_qty or 1.0,
- 'uom': item_doc.stock_uom,
- 'stock_uom': item_doc.stock_uom,
- 'rate': item_doc.valuation_rate or args.rate,
- 'source_warehouse': args.source_warehouse
- })
+ bom.append(
+ "items",
+ {
+ "item_code": item,
+ "qty": args.rm_qty or 1.0,
+ "uom": item_doc.stock_uom,
+ "stock_uom": item_doc.stock_uom,
+ "rate": item_doc.valuation_rate or args.rate,
+ "source_warehouse": args.source_warehouse,
+ },
+ )
if not args.do_not_save:
bom.insert(ignore_permissions=True)
diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py
index b207906c5e..d4c37cf79e 100644
--- a/erpnext/manufacturing/doctype/routing/routing.py
+++ b/erpnext/manufacturing/doctype/routing/routing.py
@@ -19,9 +19,11 @@ class Routing(Document):
def calculate_operating_cost(self):
for operation in self.operations:
if not operation.hour_rate:
- operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, 'hour_rate')
- operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60,
- operation.precision("operating_cost"))
+ operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, "hour_rate")
+ operation.operating_cost = flt(
+ flt(operation.hour_rate) * flt(operation.time_in_mins) / 60,
+ operation.precision("operating_cost"),
+ )
def set_routing_id(self):
sequence_id = 0
@@ -29,7 +31,10 @@ class Routing(Document):
if not row.sequence_id:
row.sequence_id = sequence_id + 1
elif sequence_id and row.sequence_id and cint(sequence_id) > cint(row.sequence_id):
- frappe.throw(_("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}")
- .format(row.idx, row.sequence_id, sequence_id))
+ frappe.throw(
+ _("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}").format(
+ row.idx, row.sequence_id, sequence_id
+ )
+ )
sequence_id = row.sequence_id
diff --git a/erpnext/manufacturing/doctype/routing/routing_dashboard.py b/erpnext/manufacturing/doctype/routing/routing_dashboard.py
index d051e38a34..65d7a45277 100644
--- a/erpnext/manufacturing/doctype/routing/routing_dashboard.py
+++ b/erpnext/manufacturing/doctype/routing/routing_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'routing',
- 'transactions': [
- {
- 'items': ['BOM']
- }
- ]
- }
+ return {"fieldname": "routing", "transactions": [{"items": ["BOM"]}]}
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 696d9bca14..48f1851cb1 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -16,24 +16,27 @@ class TestRouting(FrappeTestCase):
@classmethod
def tearDownClass(cls):
- frappe.db.sql('delete from tabBOM where item=%s', cls.item_code)
+ frappe.db.sql("delete from tabBOM where item=%s", cls.item_code)
def test_sequence_id(self):
- operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
- {"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
+ operations = [
+ {"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
+ {"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20},
+ ]
make_test_records("UOM")
setup_operations(operations)
routing_doc = create_routing(routing_name="Testing Route", operations=operations)
bom_doc = setup_bom(item_code=self.item_code, routing=routing_doc.name)
- wo_doc = make_wo_order_test_record(production_item = self.item_code, bom_no=bom_doc.name)
+ wo_doc = make_wo_order_test_record(production_item=self.item_code, bom_no=bom_doc.name)
for row in routing_doc.operations:
self.assertEqual(row.sequence_id, row.idx)
- for data in frappe.get_all("Job Card",
- filters={"work_order": wo_doc.name}, order_by="sequence_id desc"):
+ for data in frappe.get_all(
+ "Job Card", filters={"work_order": wo_doc.name}, order_by="sequence_id desc"
+ ):
job_card_doc = frappe.get_doc("Job Card", data.name)
job_card_doc.time_logs[0].completed_qty = 10
if job_card_doc.sequence_id != 1:
@@ -52,33 +55,25 @@ class TestRouting(FrappeTestCase):
"operation": "Test Operation A",
"workstation": "_Test Workstation A",
"hour_rate_rent": 300,
- "hour_rate_labour": 750 ,
- "time_in_mins": 30
+ "hour_rate_labour": 750,
+ "time_in_mins": 30,
},
{
"operation": "Test Operation B",
"workstation": "_Test Workstation B",
"hour_rate_labour": 200,
"hour_rate_rent": 1000,
- "time_in_mins": 20
- }
+ "time_in_mins": 20,
+ },
]
test_routing_operations = [
- {
- "operation": "Test Operation A",
- "workstation": "_Test Workstation A",
- "time_in_mins": 30
- },
- {
- "operation": "Test Operation B",
- "workstation": "_Test Workstation A",
- "time_in_mins": 20
- }
+ {"operation": "Test Operation A", "workstation": "_Test Workstation A", "time_in_mins": 30},
+ {"operation": "Test Operation B", "workstation": "_Test Workstation A", "time_in_mins": 20},
]
setup_operations(operations)
routing_doc = create_routing(routing_name="Routing Test", operations=test_routing_operations)
- bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency = 'INR')
+ bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
self.assertEqual(routing_doc.operations[0].time_in_mins, 30)
self.assertEqual(routing_doc.operations[1].time_in_mins, 20)
routing_doc.operations[0].time_in_mins = 90
@@ -93,10 +88,12 @@ class TestRouting(FrappeTestCase):
def setup_operations(rows):
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
+
for row in rows:
make_workstation(row)
make_operation(row)
+
def create_routing(**args):
args = frappe._dict(args)
@@ -108,7 +105,7 @@ def create_routing(**args):
doc.insert()
except frappe.DuplicateEntryError:
doc = frappe.get_doc("Routing", args.routing_name)
- doc.delete_key('operations')
+ doc.delete_key("operations")
for operation in args.operations:
doc.append("operations", operation)
@@ -116,28 +113,35 @@ def create_routing(**args):
return doc
+
def setup_bom(**args):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
args = frappe._dict(args)
- if not frappe.db.exists('Item', args.item_code):
- make_item(args.item_code, {
- 'is_stock_item': 1
- })
+ if not frappe.db.exists("Item", args.item_code):
+ make_item(args.item_code, {"is_stock_item": 1})
if not args.raw_materials:
- if not frappe.db.exists('Item', "Test Extra Item N-1"):
- make_item("Test Extra Item N-1", {
- 'is_stock_item': 1,
- })
+ if not frappe.db.exists("Item", "Test Extra Item N-1"):
+ make_item(
+ "Test Extra Item N-1",
+ {
+ "is_stock_item": 1,
+ },
+ )
- args.raw_materials = ['Test Extra Item N-1']
+ args.raw_materials = ["Test Extra Item N-1"]
- name = frappe.db.get_value('BOM', {'item': args.item_code}, 'name')
+ name = frappe.db.get_value("BOM", {"item": args.item_code}, "name")
if not name:
- bom_doc = make_bom(item = args.item_code, raw_materials = args.get("raw_materials"),
- routing = args.routing, with_operations=1, currency = args.currency)
+ bom_doc = make_bom(
+ item=args.item_code,
+ raw_materials=args.get("raw_materials"),
+ routing=args.routing,
+ with_operations=1,
+ currency=args.currency,
+ )
else:
bom_doc = frappe.get_doc("BOM", name)
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 6a8136de32..3721704840 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -26,29 +26,36 @@ from erpnext.stock.utils import get_bin
class TestWorkOrder(FrappeTestCase):
def setUp(self):
- self.warehouse = '_Test Warehouse 2 - _TC'
- self.item = '_Test Item'
+ self.warehouse = "_Test Warehouse 2 - _TC"
+ self.item = "_Test Item"
def tearDown(self):
frappe.db.rollback()
def check_planned_qty(self):
- planned0 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
- "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") or 0
+ planned0 = (
+ frappe.db.get_value(
+ "Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"
+ )
+ or 0
+ )
wo_order = make_wo_order_test_record()
- planned1 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
- "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty")
+ planned1 = frappe.db.get_value(
+ "Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"
+ )
self.assertEqual(planned1, planned0 + 10)
# add raw materials to stores
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="Stores - _TC", qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="Stores - _TC", qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="Stores - _TC", qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="Stores - _TC", qty=100, basic_rate=100
+ )
# from stores to wip
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
@@ -64,8 +71,9 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(frappe.db.get_value("Work Order", wo_order.name, "produced_qty"), 4)
- planned2 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
- "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty")
+ planned2 = frappe.db.get_value(
+ "Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"
+ )
self.assertEqual(planned2, planned0 + 6)
@@ -74,10 +82,12 @@ class TestWorkOrder(FrappeTestCase):
def test_over_production(self):
wo_doc = self.check_planned_qty()
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", qty=100, basic_rate=100
+ )
s = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 7))
s.insert()
@@ -85,13 +95,14 @@ class TestWorkOrder(FrappeTestCase):
self.assertRaises(StockOverProductionError, s.submit)
def test_planned_operating_cost(self):
- wo_order = make_wo_order_test_record(item="_Test FG Item 2",
- planned_start_date=now(), qty=1, do_not_save=True)
+ wo_order = make_wo_order_test_record(
+ item="_Test FG Item 2", planned_start_date=now(), qty=1, do_not_save=True
+ )
wo_order.set_work_order_operations()
cost = wo_order.planned_operating_cost
wo_order.qty = 2
wo_order.set_work_order_operations()
- self.assertEqual(wo_order.planned_operating_cost, cost*2)
+ self.assertEqual(wo_order.planned_operating_cost, cost * 2)
def test_reserved_qty_for_partial_completion(self):
item = "_Test Item"
@@ -102,27 +113,30 @@ class TestWorkOrder(FrappeTestCase):
# reset to correct value
bin1_at_start.update_reserved_qty_for_production()
- wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=warehouse, skip_transfer=1)
+ wo_order = make_wo_order_test_record(
+ item="_Test FG Item", qty=2, source_warehouse=warehouse, skip_transfer=1
+ )
reserved_qty_on_submission = cint(get_bin(item, warehouse).reserved_qty_for_production)
# reserved qty for production is updated
self.assertEqual(cint(bin1_at_start.reserved_qty_for_production) + 2, reserved_qty_on_submission)
-
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target=warehouse, qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target=warehouse, qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target=warehouse, qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target=warehouse, qty=100, basic_rate=100
+ )
s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 1))
s.submit()
bin1_at_completion = get_bin(item, warehouse)
- self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production),
- reserved_qty_on_submission - 1)
+ self.assertEqual(
+ cint(bin1_at_completion.reserved_qty_for_production), reserved_qty_on_submission - 1
+ )
def test_production_item(self):
wo_order = make_wo_order_test_record(item="_Test FG Item", qty=1, do_not_save=True)
@@ -146,16 +160,20 @@ class TestWorkOrder(FrappeTestCase):
# reset to correct value
self.bin1_at_start.update_reserved_qty_for_production()
- self.wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=self.warehouse)
+ self.wo_order = make_wo_order_test_record(
+ item="_Test FG Item", qty=2, source_warehouse=self.warehouse
+ )
self.bin1_on_submit = get_bin(self.item, self.warehouse)
# reserved qty for production is updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production) + 2,
- cint(self.bin1_on_submit.reserved_qty_for_production))
- self.assertEqual(cint(self.bin1_at_start.projected_qty),
- cint(self.bin1_on_submit.projected_qty) + 2)
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production) + 2,
+ cint(self.bin1_on_submit.reserved_qty_for_production),
+ )
+ self.assertEqual(
+ cint(self.bin1_at_start.projected_qty), cint(self.bin1_on_submit.projected_qty) + 2
+ )
def test_reserved_qty_for_production_cancel(self):
self.test_reserved_qty_for_production_submit()
@@ -165,52 +183,57 @@ class TestWorkOrder(FrappeTestCase):
bin1_on_cancel = get_bin(self.item, self.warehouse)
# reserved_qty_for_producion updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production),
- cint(bin1_on_cancel.reserved_qty_for_production))
- self.assertEqual(self.bin1_at_start.projected_qty,
- cint(bin1_on_cancel.projected_qty))
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production),
+ cint(bin1_on_cancel.reserved_qty_for_production),
+ )
+ self.assertEqual(self.bin1_at_start.projected_qty, cint(bin1_on_cancel.projected_qty))
def test_reserved_qty_for_production_on_stock_entry(self):
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target= self.warehouse, qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target= self.warehouse, qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target=self.warehouse, qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target=self.warehouse, qty=100, basic_rate=100
+ )
self.test_reserved_qty_for_production_submit()
- s = frappe.get_doc(make_stock_entry(self.wo_order.name,
- "Material Transfer for Manufacture", 2))
+ s = frappe.get_doc(make_stock_entry(self.wo_order.name, "Material Transfer for Manufacture", 2))
s.submit()
bin1_on_start_production = get_bin(self.item, self.warehouse)
# reserved_qty_for_producion updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production),
- cint(bin1_on_start_production.reserved_qty_for_production))
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production),
+ cint(bin1_on_start_production.reserved_qty_for_production),
+ )
# projected qty will now be 2 less (becuase of item movement)
- self.assertEqual(cint(self.bin1_at_start.projected_qty),
- cint(bin1_on_start_production.projected_qty) + 2)
+ self.assertEqual(
+ cint(self.bin1_at_start.projected_qty), cint(bin1_on_start_production.projected_qty) + 2
+ )
s = frappe.get_doc(make_stock_entry(self.wo_order.name, "Manufacture", 2))
bin1_on_end_production = get_bin(self.item, self.warehouse)
# no change in reserved / projected
- self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production),
- cint(bin1_on_start_production.reserved_qty_for_production))
+ self.assertEqual(
+ cint(bin1_on_end_production.reserved_qty_for_production),
+ cint(bin1_on_start_production.reserved_qty_for_production),
+ )
def test_reserved_qty_for_production_closed(self):
- wo1 = make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=self.warehouse)
+ wo1 = make_wo_order_test_record(item="_Test FG Item", qty=2, source_warehouse=self.warehouse)
item = wo1.required_items[0].item_code
bin_before = get_bin(item, self.warehouse)
bin_before.update_reserved_qty_for_production()
- make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=self.warehouse)
+ make_wo_order_test_record(item="_Test FG Item", qty=2, source_warehouse=self.warehouse)
close_work_order(wo1.name, "Closed")
bin_after = get_bin(item, self.warehouse)
@@ -220,10 +243,15 @@ class TestWorkOrder(FrappeTestCase):
cancel_stock_entry = []
allow_overproduction("overproduction_percentage_for_work_order", 30)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=240,
+ basic_rate=1000.0,
+ )
cancel_stock_entry.extend([ste1.name, ste2.name])
@@ -253,33 +281,37 @@ class TestWorkOrder(FrappeTestCase):
allow_overproduction("overproduction_percentage_for_work_order", 0)
def test_reserved_qty_for_stopped_production(self):
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target= self.warehouse, qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target= self.warehouse, qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target=self.warehouse, qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target=self.warehouse, qty=100, basic_rate=100
+ )
# 0 0 0
self.test_reserved_qty_for_production_submit()
- #2 0 -2
+ # 2 0 -2
- s = frappe.get_doc(make_stock_entry(self.wo_order.name,
- "Material Transfer for Manufacture", 1))
+ s = frappe.get_doc(make_stock_entry(self.wo_order.name, "Material Transfer for Manufacture", 1))
s.submit()
- #1 -1 0
+ # 1 -1 0
bin1_on_start_production = get_bin(self.item, self.warehouse)
# reserved_qty_for_producion updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production) + 1,
- cint(bin1_on_start_production.reserved_qty_for_production))
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production) + 1,
+ cint(bin1_on_start_production.reserved_qty_for_production),
+ )
# projected qty will now be 2 less (becuase of item movement)
- self.assertEqual(cint(self.bin1_at_start.projected_qty),
- cint(bin1_on_start_production.projected_qty) + 2)
+ self.assertEqual(
+ cint(self.bin1_at_start.projected_qty), cint(bin1_on_start_production.projected_qty) + 2
+ )
# STOP
stop_unstop(self.wo_order.name, "Stopped")
@@ -287,19 +319,24 @@ class TestWorkOrder(FrappeTestCase):
bin1_on_stop_production = get_bin(self.item, self.warehouse)
# no change in reserved / projected
- self.assertEqual(cint(bin1_on_stop_production.reserved_qty_for_production),
- cint(self.bin1_at_start.reserved_qty_for_production))
- self.assertEqual(cint(bin1_on_stop_production.projected_qty) + 1,
- cint(self.bin1_at_start.projected_qty))
+ self.assertEqual(
+ cint(bin1_on_stop_production.reserved_qty_for_production),
+ cint(self.bin1_at_start.reserved_qty_for_production),
+ )
+ self.assertEqual(
+ cint(bin1_on_stop_production.projected_qty) + 1, cint(self.bin1_at_start.projected_qty)
+ )
def test_scrap_material_qty(self):
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
# add raw materials to stores
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="Stores - _TC", qty=10, basic_rate=5000.0)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="Stores - _TC", qty=10, basic_rate=1000.0)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=5000.0
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="Stores - _TC", qty=10, basic_rate=1000.0
+ )
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 2))
for d in s.get("items"):
@@ -311,8 +348,9 @@ class TestWorkOrder(FrappeTestCase):
s.insert()
s.submit()
- wo_order_details = frappe.db.get_value("Work Order", wo_order.name,
- ["scrap_warehouse", "qty", "produced_qty", "bom_no"], as_dict=1)
+ wo_order_details = frappe.db.get_value(
+ "Work Order", wo_order.name, ["scrap_warehouse", "qty", "produced_qty", "bom_no"], as_dict=1
+ )
scrap_item_details = get_scrap_item_details(wo_order_details.bom_no)
@@ -321,15 +359,20 @@ class TestWorkOrder(FrappeTestCase):
for item in s.items:
if item.bom_no and item.item_code in scrap_item_details:
self.assertEqual(wo_order_details.scrap_warehouse, item.t_warehouse)
- self.assertEqual(flt(wo_order_details.qty)*flt(scrap_item_details[item.item_code]), item.qty)
+ self.assertEqual(flt(wo_order_details.qty) * flt(scrap_item_details[item.item_code]), item.qty)
def test_allow_overproduction(self):
allow_overproduction("overproduction_percentage_for_work_order", 0)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=10, basic_rate=1000.0)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=10,
+ basic_rate=1000.0,
+ )
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 3))
s.insert()
@@ -346,42 +389,47 @@ class TestWorkOrder(FrappeTestCase):
so = make_sales_order(item_code="_Test FG Item", qty=2)
allow_overproduction("overproduction_percentage_for_sales_order", 0)
- wo_order = make_wo_order_test_record(planned_start_date=now(),
- sales_order=so.name, qty=3, do_not_save=True)
+ wo_order = make_wo_order_test_record(
+ planned_start_date=now(), sales_order=so.name, qty=3, do_not_save=True
+ )
self.assertRaises(OverProductionError, wo_order.save)
allow_overproduction("overproduction_percentage_for_sales_order", 50)
- wo_order = make_wo_order_test_record(planned_start_date=now(),
- sales_order=so.name, qty=3)
+ wo_order = make_wo_order_test_record(planned_start_date=now(), sales_order=so.name, qty=3)
self.assertEqual(wo_order.docstatus, 1)
allow_overproduction("overproduction_percentage_for_sales_order", 0)
def test_work_order_with_non_stock_item(self):
- items = {'Finished Good Test Item For non stock': 1, '_Test FG Item': 1, '_Test FG Non Stock Item': 0}
+ items = {
+ "Finished Good Test Item For non stock": 1,
+ "_Test FG Item": 1,
+ "_Test FG Non Stock Item": 0,
+ }
for item, is_stock_item in items.items():
- make_item(item, {
- 'is_stock_item': is_stock_item
- })
+ make_item(item, {"is_stock_item": is_stock_item})
- if not frappe.db.get_value('Item Price', {'item_code': '_Test FG Non Stock Item'}):
- frappe.get_doc({
- 'doctype': 'Item Price',
- 'item_code': '_Test FG Non Stock Item',
- 'price_list_rate': 1000,
- 'price_list': 'Standard Buying'
- }).insert(ignore_permissions=True)
+ if not frappe.db.get_value("Item Price", {"item_code": "_Test FG Non Stock Item"}):
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "item_code": "_Test FG Non Stock Item",
+ "price_list_rate": 1000,
+ "price_list": "Standard Buying",
+ }
+ ).insert(ignore_permissions=True)
- fg_item = 'Finished Good Test Item For non stock'
- test_stock_entry.make_stock_entry(item_code="_Test FG Item",
- target="_Test Warehouse - _TC", qty=1, basic_rate=100)
+ fg_item = "Finished Good Test Item For non stock"
+ test_stock_entry.make_stock_entry(
+ item_code="_Test FG Item", target="_Test Warehouse - _TC", qty=1, basic_rate=100
+ )
- if not frappe.db.get_value('BOM', {'item': fg_item}):
- make_bom(item=fg_item, rate=1000, raw_materials = ['_Test FG Item', '_Test FG Non Stock Item'])
+ if not frappe.db.get_value("BOM", {"item": fg_item}):
+ make_bom(item=fg_item, rate=1000, raw_materials=["_Test FG Item", "_Test FG Non Stock Item"])
- wo = make_wo_order_test_record(production_item = fg_item)
+ wo = make_wo_order_test_record(production_item=fg_item)
se = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 1))
se.insert()
@@ -395,25 +443,25 @@ class TestWorkOrder(FrappeTestCase):
@timeout(seconds=60)
def test_job_card(self):
stock_entries = []
- bom = frappe.get_doc('BOM', {
- 'docstatus': 1,
- 'with_operations': 1,
- 'company': '_Test Company'
- })
+ bom = frappe.get_doc("BOM", {"docstatus": 1, "with_operations": 1, "company": "_Test Company"})
- work_order = make_wo_order_test_record(item=bom.item, qty=1,
- bom_no=bom.name, source_warehouse="_Test Warehouse - _TC")
+ work_order = make_wo_order_test_record(
+ item=bom.item, qty=1, bom_no=bom.name, source_warehouse="_Test Warehouse - _TC"
+ )
for row in work_order.required_items:
- stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
- target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
+ stock_entry_doc = test_stock_entry.make_stock_entry(
+ item_code=row.item_code, target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100
+ )
stock_entries.append(stock_entry_doc)
ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
ste.submit()
stock_entries.append(ste)
- job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}, order_by='creation asc')
+ job_cards = frappe.get_all(
+ "Job Card", filters={"work_order": work_order.name}, order_by="creation asc"
+ )
self.assertEqual(len(job_cards), len(bom.operations))
for i, job_card in enumerate(job_cards):
@@ -434,29 +482,33 @@ class TestWorkOrder(FrappeTestCase):
stock_entry.cancel()
def test_capcity_planning(self):
- frappe.db.set_value("Manufacturing Settings", None, {
- "disable_capacity_planning": 0,
- "capacity_planning_for_days": 1
- })
+ frappe.db.set_value(
+ "Manufacturing Settings",
+ None,
+ {"disable_capacity_planning": 0, "capacity_planning_for_days": 1},
+ )
- data = frappe.get_cached_value('BOM', {'docstatus': 1, 'item': '_Test FG Item 2',
- 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+ data = frappe.get_cached_value(
+ "BOM",
+ {"docstatus": 1, "item": "_Test FG Item 2", "with_operations": 1, "company": "_Test Company"},
+ ["name", "item"],
+ )
if data:
bom, bom_item = data
planned_start_date = add_months(today(), months=-1)
- work_order = make_wo_order_test_record(item=bom_item,
- qty=10, bom_no=bom, planned_start_date=planned_start_date)
+ work_order = make_wo_order_test_record(
+ item=bom_item, qty=10, bom_no=bom, planned_start_date=planned_start_date
+ )
- work_order1 = make_wo_order_test_record(item=bom_item,
- qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1)
+ work_order1 = make_wo_order_test_record(
+ item=bom_item, qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1
+ )
self.assertRaises(CapacityError, work_order1.submit)
- frappe.db.set_value("Manufacturing Settings", None, {
- "capacity_planning_for_days": 30
- })
+ frappe.db.set_value("Manufacturing Settings", None, {"capacity_planning_for_days": 30})
work_order1.reload()
work_order1.submit()
@@ -466,22 +518,22 @@ class TestWorkOrder(FrappeTestCase):
work_order.cancel()
def test_work_order_with_non_transfer_item(self):
- items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
+ items = {"Finished Good Transfer Item": 1, "_Test FG Item": 1, "_Test FG Item 1": 0}
for item, allow_transfer in items.items():
- make_item(item, {
- 'include_item_in_manufacturing': allow_transfer
- })
+ make_item(item, {"include_item_in_manufacturing": allow_transfer})
- fg_item = 'Finished Good Transfer Item'
- test_stock_entry.make_stock_entry(item_code="_Test FG Item",
- target="_Test Warehouse - _TC", qty=1, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test FG Item 1",
- target="_Test Warehouse - _TC", qty=1, basic_rate=100)
+ fg_item = "Finished Good Transfer Item"
+ test_stock_entry.make_stock_entry(
+ item_code="_Test FG Item", target="_Test Warehouse - _TC", qty=1, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test FG Item 1", target="_Test Warehouse - _TC", qty=1, basic_rate=100
+ )
- if not frappe.db.get_value('BOM', {'item': fg_item}):
- make_bom(item=fg_item, raw_materials = ['_Test FG Item', '_Test FG Item 1'])
+ if not frappe.db.get_value("BOM", {"item": fg_item}):
+ make_bom(item=fg_item, raw_materials=["_Test FG Item", "_Test FG Item 1"])
- wo = make_wo_order_test_record(production_item = fg_item)
+ wo = make_wo_order_test_record(production_item=fg_item)
ste = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 1))
ste.insert()
ste.submit()
@@ -499,39 +551,42 @@ class TestWorkOrder(FrappeTestCase):
rm1 = "Test Batch Size Item RM 1 For BOM"
for item in ["Test Batch Size Item For BOM", "Test Batch Size Item RM 1 For BOM"]:
- make_item(item, {
- "include_item_in_manufacturing": 1,
- "is_stock_item": 1
- })
+ make_item(item, {"include_item_in_manufacturing": 1, "is_stock_item": 1})
- bom_name = frappe.db.get_value("BOM",
- {"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
+ bom_name = frappe.db.get_value(
+ "BOM", {"item": fg_item, "is_active": 1, "with_operations": 1}, "name"
+ )
if not bom_name:
- bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
+ bom = make_bom(item=fg_item, rate=1000, raw_materials=[rm1], do_not_save=True)
bom.with_operations = 1
- bom.append("operations", {
- "operation": "_Test Operation 1",
- "workstation": "_Test Workstation 1",
- "description": "Test Data",
- "operating_cost": 100,
- "time_in_mins": 40,
- "batch_size": 5
- })
+ bom.append(
+ "operations",
+ {
+ "operation": "_Test Operation 1",
+ "workstation": "_Test Workstation 1",
+ "description": "Test Data",
+ "operating_cost": 100,
+ "time_in_mins": 40,
+ "batch_size": 5,
+ },
+ )
bom.save()
bom.submit()
bom_name = bom.name
- work_order = make_wo_order_test_record(item=fg_item,
- planned_start_date=now(), qty=1, do_not_save=True)
+ work_order = make_wo_order_test_record(
+ item=fg_item, planned_start_date=now(), qty=1, do_not_save=True
+ )
work_order.set_work_order_operations()
work_order.save()
self.assertEqual(work_order.operations[0].time_in_mins, 8.0)
- work_order1 = make_wo_order_test_record(item=fg_item,
- planned_start_date=now(), qty=5, do_not_save=True)
+ work_order1 = make_wo_order_test_record(
+ item=fg_item, planned_start_date=now(), qty=5, do_not_save=True
+ )
work_order1.set_work_order_operations()
work_order1.save()
@@ -541,65 +596,73 @@ class TestWorkOrder(FrappeTestCase):
fg_item = "Test Batch Size Item For BOM 3"
rm1 = "Test Batch Size Item RM 1 For BOM 3"
- frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+ frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)
for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]:
- item_args = {
- "include_item_in_manufacturing": 1,
- "is_stock_item": 1
- }
+ item_args = {"include_item_in_manufacturing": 1, "is_stock_item": 1}
if item == fg_item:
- item_args['has_batch_no'] = 1
- item_args['create_new_batch'] = 1
- item_args['batch_number_series'] = 'TBSI3.#####'
+ item_args["has_batch_no"] = 1
+ item_args["create_new_batch"] = 1
+ item_args["batch_number_series"] = "TBSI3.#####"
make_item(item, item_args)
- bom_name = frappe.db.get_value("BOM",
- {"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
+ bom_name = frappe.db.get_value(
+ "BOM", {"item": fg_item, "is_active": 1, "with_operations": 1}, "name"
+ )
if not bom_name:
- bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
+ bom = make_bom(item=fg_item, rate=1000, raw_materials=[rm1], do_not_save=True)
bom.save()
bom.submit()
bom_name = bom.name
- work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+ work_order = make_wo_order_test_record(
+ item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
+ )
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
- for row in ste1.get('items'):
+ for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
- work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
- frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1)
+ work_order = make_wo_order_test_record(
+ item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
+ )
+ frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 1)
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
- for row in ste1.get('items'):
+ for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
- work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(),
- qty=30, do_not_save = True)
+ work_order = make_wo_order_test_record(
+ item=fg_item, skip_transfer=True, planned_start_date=now(), qty=30, do_not_save=True
+ )
work_order.batch_size = 10
work_order.insert()
work_order.submit()
self.assertEqual(work_order.has_batch_no, 1)
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
- for row in ste1.get('items'):
+ for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10)
- frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+ frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=20,
+ basic_rate=1000.0,
+ )
ste_cancel_list.extend([ste1, ste2])
@@ -625,16 +688,25 @@ class TestWorkOrder(FrappeTestCase):
def test_extra_material_transfer(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
- frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on",
- "Material Transferred for Manufacture")
+ frappe.db.set_value(
+ "Manufacturing Settings",
+ None,
+ "backflush_raw_materials_based_on",
+ "Material Transferred for Manufacture",
+ )
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=20,
+ basic_rate=1000.0,
+ )
ste_cancel_list.extend([ste1, ste2])
@@ -666,30 +738,31 @@ class TestWorkOrder(FrappeTestCase):
frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
def test_make_stock_entry_for_customer_provided_item(self):
- finished_item = 'Test Item for Make Stock Entry 1'
- make_item(finished_item, {
+ finished_item = "Test Item for Make Stock Entry 1"
+ make_item(finished_item, {"include_item_in_manufacturing": 1, "is_stock_item": 1})
+
+ customer_provided_item = "CUST-0987"
+ make_item(
+ customer_provided_item,
+ {
+ "is_purchase_item": 0,
+ "is_customer_provided_item": 1,
+ "is_stock_item": 1,
"include_item_in_manufacturing": 1,
- "is_stock_item": 1
- })
+ "customer": "_Test Customer",
+ },
+ )
- customer_provided_item = 'CUST-0987'
- make_item(customer_provided_item, {
- 'is_purchase_item': 0,
- 'is_customer_provided_item': 1,
- "is_stock_item": 1,
- "include_item_in_manufacturing": 1,
- 'customer': '_Test Customer'
- })
-
- if not frappe.db.exists('BOM', {'item': finished_item}):
+ if not frappe.db.exists("BOM", {"item": finished_item}):
make_bom(item=finished_item, raw_materials=[customer_provided_item], rm_qty=1)
company = "_Test Company with perpetual inventory"
customer_warehouse = create_warehouse("Test Customer Provided Warehouse", company=company)
- wo = make_wo_order_test_record(item=finished_item, qty=1, source_warehouse=customer_warehouse,
- company=company)
+ wo = make_wo_order_test_record(
+ item=finished_item, qty=1, source_warehouse=customer_warehouse, company=company
+ )
- ste = frappe.get_doc(make_stock_entry(wo.name, purpose='Material Transfer for Manufacture'))
+ ste = frappe.get_doc(make_stock_entry(wo.name, purpose="Material Transfer for Manufacture"))
ste.insert()
self.assertEqual(len(ste.items), 1)
@@ -698,26 +771,33 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(item.valuation_rate, 0)
def test_valuation_rate_missing_on_make_stock_entry(self):
- item_name = 'Test Valuation Rate Missing'
- rm_item = '_Test raw material item'
- make_item(item_name, {
- "is_stock_item": 1,
- "include_item_in_manufacturing": 1,
- })
- make_item('_Test raw material item', {
- "is_stock_item": 1,
- "include_item_in_manufacturing": 1,
- })
+ item_name = "Test Valuation Rate Missing"
+ rm_item = "_Test raw material item"
+ make_item(
+ item_name,
+ {
+ "is_stock_item": 1,
+ "include_item_in_manufacturing": 1,
+ },
+ )
+ make_item(
+ "_Test raw material item",
+ {
+ "is_stock_item": 1,
+ "include_item_in_manufacturing": 1,
+ },
+ )
- if not frappe.db.get_value('BOM', {'item': item_name}):
+ if not frappe.db.get_value("BOM", {"item": item_name}):
make_bom(item=item_name, raw_materials=[rm_item], rm_qty=1)
company = "_Test Company with perpetual inventory"
source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company)
- wo = make_wo_order_test_record(item=item_name, qty=1, source_warehouse=source_warehouse,
- company=company)
+ wo = make_wo_order_test_record(
+ item=item_name, qty=1, source_warehouse=source_warehouse, company=company
+ )
- stock_entry = frappe.get_doc(make_stock_entry(wo.name, 'Material Transfer for Manufacture'))
+ stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture"))
self.assertRaises(frappe.ValidationError, stock_entry.save)
def test_wo_completion_with_pl_bom(self):
@@ -727,19 +807,19 @@ class TestWorkOrder(FrappeTestCase):
)
qty = 4
- scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
+ scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
source_warehouse = "Stores - _TC"
wip_warehouse = "_Test Warehouse - _TC"
fg_item_non_whole, _, bom_item = create_process_loss_bom_items()
- test_stock_entry.make_stock_entry(item_code=bom_item.item_code,
- target=source_warehouse, qty=4, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code=bom_item.item_code, target=source_warehouse, qty=4, basic_rate=100
+ )
bom_no = f"BOM-{fg_item_non_whole.item_code}-001"
if not frappe.db.exists("BOM", bom_no):
bom_doc = create_bom_with_process_loss_item(
- fg_item_non_whole, bom_item, scrap_qty=scrap_qty,
- scrap_rate=0, fg_qty=1, is_process_loss=1
+ fg_item_non_whole, bom_item, scrap_qty=scrap_qty, scrap_rate=0, fg_qty=1, is_process_loss=1
)
bom_doc.submit()
@@ -752,16 +832,12 @@ class TestWorkOrder(FrappeTestCase):
stock_uom=fg_item_non_whole.stock_uom,
)
- se = frappe.get_doc(
- make_stock_entry(wo.name, "Material Transfer for Manufacture", qty)
- )
+ se = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", qty))
se.get("items")[0].s_warehouse = "Stores - _TC"
se.insert()
se.submit()
- se = frappe.get_doc(
- make_stock_entry(wo.name, "Manufacture", qty)
- )
+ se = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", qty))
se.insert()
se.submit()
@@ -778,41 +854,52 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(fg_item.qty, actual_fg_qty)
# Testing Work Order values
- self.assertEqual(
- frappe.db.get_value("Work Order", wo.name, "produced_qty"),
- qty
- )
- self.assertEqual(
- frappe.db.get_value("Work Order", wo.name, "process_loss_qty"),
- total_pl_qty
- )
+ self.assertEqual(frappe.db.get_value("Work Order", wo.name, "produced_qty"), qty)
+ self.assertEqual(frappe.db.get_value("Work Order", wo.name, "process_loss_qty"), total_pl_qty)
@timeout(seconds=60)
def test_job_card_scrap_item(self):
- items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test',
- 'Test RM Item 2 for Scrap Item Test']
+ items = [
+ "Test FG Item for Scrap Item Test",
+ "Test RM Item 1 for Scrap Item Test",
+ "Test RM Item 2 for Scrap Item Test",
+ ]
- company = '_Test Company with perpetual inventory'
+ company = "_Test Company with perpetual inventory"
for item_code in items:
- create_item(item_code = item_code, is_stock_item = 1,
- is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1')
+ create_item(
+ item_code=item_code,
+ is_stock_item=1,
+ is_purchase_item=1,
+ opening_stock=100,
+ valuation_rate=10,
+ company=company,
+ warehouse="Stores - TCP1",
+ )
- item = 'Test FG Item for Scrap Item Test'
- raw_materials = ['Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test']
- if not frappe.db.get_value('BOM', {'item': item}):
- bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True)
+ item = "Test FG Item for Scrap Item Test"
+ raw_materials = ["Test RM Item 1 for Scrap Item Test", "Test RM Item 2 for Scrap Item Test"]
+ if not frappe.db.get_value("BOM", {"item": item}):
+ bom = make_bom(
+ item=item, source_warehouse="Stores - TCP1", raw_materials=raw_materials, do_not_save=True
+ )
bom.with_operations = 1
- bom.append('operations', {
- 'operation': '_Test Operation 1',
- 'workstation': '_Test Workstation 1',
- 'hour_rate': 20,
- 'time_in_mins': 60
- })
+ bom.append(
+ "operations",
+ {
+ "operation": "_Test Operation 1",
+ "workstation": "_Test Workstation 1",
+ "hour_rate": 20,
+ "time_in_mins": 60,
+ },
+ )
bom.submit()
- wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1)
- job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
+ wo_order = make_wo_order_test_record(
+ item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1
+ )
+ job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
update_job_card(job_card)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
@@ -821,8 +908,10 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(row.qty, 1)
# Partial Job Card 1 with qty 10
- wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1)
- job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
+ wo_order = make_wo_order_test_record(
+ item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1
+ )
+ job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
update_job_card(job_card, 10)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
@@ -835,12 +924,12 @@ class TestWorkOrder(FrappeTestCase):
wo_order.load_from_db()
for row in wo_order.operations:
n_dict = row.as_dict()
- n_dict['qty'] = 10
- n_dict['pending_qty'] = 10
+ n_dict["qty"] = 10
+ n_dict["pending_qty"] = 10
operations.append(n_dict)
make_job_card(wo_order.name, operations)
- job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name, 'docstatus': 0}, 'name')
+ job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name, "docstatus": 0}, "name")
update_job_card(job_card, 10)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
@@ -849,131 +938,160 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(row.qty, 2)
def test_close_work_order(self):
- items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO',
- 'Test RM Item 2 for Closed WO']
+ items = [
+ "Test FG Item for Closed WO",
+ "Test RM Item 1 for Closed WO",
+ "Test RM Item 2 for Closed WO",
+ ]
- company = '_Test Company with perpetual inventory'
+ company = "_Test Company with perpetual inventory"
for item_code in items:
- create_item(item_code = item_code, is_stock_item = 1,
- is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1')
+ create_item(
+ item_code=item_code,
+ is_stock_item=1,
+ is_purchase_item=1,
+ opening_stock=100,
+ valuation_rate=10,
+ company=company,
+ warehouse="Stores - TCP1",
+ )
- item = 'Test FG Item for Closed WO'
- raw_materials = ['Test RM Item 1 for Closed WO', 'Test RM Item 2 for Closed WO']
- if not frappe.db.get_value('BOM', {'item': item}):
- bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True)
+ item = "Test FG Item for Closed WO"
+ raw_materials = ["Test RM Item 1 for Closed WO", "Test RM Item 2 for Closed WO"]
+ if not frappe.db.get_value("BOM", {"item": item}):
+ bom = make_bom(
+ item=item, source_warehouse="Stores - TCP1", raw_materials=raw_materials, do_not_save=True
+ )
bom.with_operations = 1
- bom.append('operations', {
- 'operation': '_Test Operation 1',
- 'workstation': '_Test Workstation 1',
- 'hour_rate': 20,
- 'time_in_mins': 60
- })
+ bom.append(
+ "operations",
+ {
+ "operation": "_Test Operation 1",
+ "workstation": "_Test Workstation 1",
+ "hour_rate": 20,
+ "time_in_mins": 60,
+ },
+ )
bom.submit()
- wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1)
- job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
+ wo_order = make_wo_order_test_record(
+ item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1
+ )
+ job_cards = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
if len(job_cards) == len(bom.operations):
for jc in job_cards:
- job_card_doc = frappe.get_doc('Job Card', jc)
- job_card_doc.append('time_logs', {
- 'from_time': now(),
- 'time_in_mins': 60,
- 'completed_qty': job_card_doc.for_quantity
- })
+ job_card_doc = frappe.get_doc("Job Card", jc)
+ job_card_doc.append(
+ "time_logs",
+ {"from_time": now(), "time_in_mins": 60, "completed_qty": job_card_doc.for_quantity},
+ )
job_card_doc.submit()
close_work_order(wo_order, "Closed")
- self.assertEqual(wo_order.get('status'), "Closed")
+ self.assertEqual(wo_order.get("status"), "Closed")
def test_fix_time_operations(self):
- bom = frappe.get_doc({
- "doctype": "BOM",
- "item": "_Test FG Item 2",
- "is_active": 1,
- "is_default": 1,
- "quantity": 1.0,
- "with_operations": 1,
- "operations": [
- {
- "operation": "_Test Operation 1",
- "description": "_Test",
- "workstation": "_Test Workstation 1",
- "time_in_mins": 60,
- "operating_cost": 140,
- "fixed_time": 1
- }
- ],
- "items": [
- {
- "amount": 5000.0,
- "doctype": "BOM Item",
- "item_code": "_Test Item",
- "parentfield": "items",
- "qty": 1.0,
- "rate": 5000.0,
- },
- ],
- })
+ bom = frappe.get_doc(
+ {
+ "doctype": "BOM",
+ "item": "_Test FG Item 2",
+ "is_active": 1,
+ "is_default": 1,
+ "quantity": 1.0,
+ "with_operations": 1,
+ "operations": [
+ {
+ "operation": "_Test Operation 1",
+ "description": "_Test",
+ "workstation": "_Test Workstation 1",
+ "time_in_mins": 60,
+ "operating_cost": 140,
+ "fixed_time": 1,
+ }
+ ],
+ "items": [
+ {
+ "amount": 5000.0,
+ "doctype": "BOM Item",
+ "item_code": "_Test Item",
+ "parentfield": "items",
+ "qty": 1.0,
+ "rate": 5000.0,
+ },
+ ],
+ }
+ )
bom.save()
bom.submit()
-
- wo1 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1)
- wo2 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1)
+ wo1 = make_wo_order_test_record(
+ item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1
+ )
+ wo2 = make_wo_order_test_record(
+ item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1
+ )
self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
def test_partial_manufacture_entries(self):
cancel_stock_entry = []
- frappe.db.set_value("Manufacturing Settings", None,
- "backflush_raw_materials_based_on", "Material Transferred for Manufacture")
+ frappe.db.set_value(
+ "Manufacturing Settings",
+ None,
+ "backflush_raw_materials_based_on",
+ "Material Transferred for Manufacture",
+ )
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=240,
+ basic_rate=1000.0,
+ )
cancel_stock_entry.extend([ste1.name, ste2.name])
sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
- for row in sm.get('items'):
- if row.get('item_code') == '_Test Item':
+ for row in sm.get("items"):
+ if row.get("item_code") == "_Test Item":
row.qty = 110
sm.submit()
cancel_stock_entry.append(sm.name)
s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
- for row in s.get('items'):
- if row.get('item_code') == '_Test Item':
- self.assertEqual(row.get('qty'), 100)
+ for row in s.get("items"):
+ if row.get("item_code") == "_Test Item":
+ self.assertEqual(row.get("qty"), 100)
s.submit()
cancel_stock_entry.append(s.name)
s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
- for row in s1.get('items'):
- if row.get('item_code') == '_Test Item':
- self.assertEqual(row.get('qty'), 5)
+ for row in s1.get("items"):
+ if row.get("item_code") == "_Test Item":
+ self.assertEqual(row.get("qty"), 5)
s1.submit()
cancel_stock_entry.append(s1.name)
s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
- for row in s2.get('items'):
- if row.get('item_code') == '_Test Item':
- self.assertEqual(row.get('qty'), 5)
+ for row in s2.get("items"):
+ if row.get("item_code") == "_Test Item":
+ self.assertEqual(row.get("qty"), 5)
cancel_stock_entry.reverse()
for ste in cancel_stock_entry:
doc = frappe.get_doc("Stock Entry", ste)
doc.cancel()
- frappe.db.set_value("Manufacturing Settings", None,
- "backflush_raw_materials_based_on", "BOM")
+ frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
@change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1})
def test_auto_batch_creation(self):
@@ -982,7 +1100,7 @@ class TestWorkOrder(FrappeTestCase):
fg_item = frappe.generate_hash(length=20)
child_item = frappe.generate_hash(length=20)
- bom_tree = {fg_item: {child_item: {}}}
+ bom_tree = {fg_item: {child_item: {}}}
create_nested_bom(bom_tree, prefix="")
@@ -998,54 +1116,55 @@ class TestWorkOrder(FrappeTestCase):
def update_job_card(job_card, jc_qty=None):
- employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name')
- job_card_doc = frappe.get_doc('Job Card', job_card)
- job_card_doc.set('scrap_items', [
- {
- 'item_code': 'Test RM Item 1 for Scrap Item Test',
- 'stock_qty': 2
- },
- {
- 'item_code': 'Test RM Item 2 for Scrap Item Test',
- 'stock_qty': 2
- },
- ])
+ employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
+ job_card_doc = frappe.get_doc("Job Card", job_card)
+ job_card_doc.set(
+ "scrap_items",
+ [
+ {"item_code": "Test RM Item 1 for Scrap Item Test", "stock_qty": 2},
+ {"item_code": "Test RM Item 2 for Scrap Item Test", "stock_qty": 2},
+ ],
+ )
if jc_qty:
job_card_doc.for_quantity = jc_qty
- job_card_doc.append('time_logs', {
- 'from_time': now(),
- 'employee': employee,
- 'time_in_mins': 60,
- 'completed_qty': job_card_doc.for_quantity
- })
+ job_card_doc.append(
+ "time_logs",
+ {
+ "from_time": now(),
+ "employee": employee,
+ "time_in_mins": 60,
+ "completed_qty": job_card_doc.for_quantity,
+ },
+ )
job_card_doc.submit()
def get_scrap_item_details(bom_no):
scrap_items = {}
- for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
- where parent = %s""", bom_no, as_dict=1):
+ for item in frappe.db.sql(
+ """select item_code, stock_qty from `tabBOM Scrap Item`
+ where parent = %s""",
+ bom_no,
+ as_dict=1,
+ ):
scrap_items[item.item_code] = item.stock_qty
return scrap_items
+
def allow_overproduction(fieldname, percentage):
doc = frappe.get_doc("Manufacturing Settings")
- doc.update({
- fieldname: percentage
- })
+ doc.update({fieldname: percentage})
doc.save()
+
def make_wo_order_test_record(**args):
args = frappe._dict(args)
if args.company and args.company != "_Test Company":
- warehouse_map = {
- "fg_warehouse": "_Test FG Warehouse",
- "wip_warehouse": "_Test WIP Warehouse"
- }
+ warehouse_map = {"fg_warehouse": "_Test FG Warehouse", "wip_warehouse": "_Test WIP Warehouse"}
for attr, wh_name in warehouse_map.items():
if not args.get(attr):
@@ -1053,16 +1172,17 @@ def make_wo_order_test_record(**args):
wo_order = frappe.new_doc("Work Order")
wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item"
- wo_order.bom_no = args.bom_no or frappe.db.get_value("BOM", {"item": wo_order.production_item,
- "is_active": 1, "is_default": 1})
+ wo_order.bom_no = args.bom_no or frappe.db.get_value(
+ "BOM", {"item": wo_order.production_item, "is_active": 1, "is_default": 1}
+ )
wo_order.qty = args.qty or 10
wo_order.wip_warehouse = args.wip_warehouse or "_Test Warehouse - _TC"
wo_order.fg_warehouse = args.fg_warehouse or "_Test Warehouse 1 - _TC"
wo_order.scrap_warehouse = args.fg_warehouse or "_Test Scrap Warehouse - _TC"
wo_order.company = args.company or "_Test Company"
wo_order.stock_uom = args.stock_uom or "_Test UOM"
- wo_order.use_multi_level_bom= args.use_multi_level_bom or 0
- wo_order.skip_transfer=args.skip_transfer or 0
+ wo_order.use_multi_level_bom = args.use_multi_level_bom or 0
+ wo_order.skip_transfer = args.skip_transfer or 0
wo_order.get_items_and_operations_from_bom()
wo_order.sales_order = args.sales_order or None
wo_order.planned_start_date = args.planned_start_date or now()
@@ -1079,4 +1199,5 @@ def make_wo_order_test_record(**args):
wo_order.submit()
return wo_order
-test_records = frappe.get_test_records('Work Order')
+
+test_records = frappe.get_test_records("Work Order")
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index e832ac9c7f..c8c2f9a932 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -42,11 +42,26 @@ from erpnext.stock.utils import get_bin, get_latest_stock_qty, validate_warehous
from erpnext.utilities.transaction_base import validate_uom_is_integer
-class OverProductionError(frappe.ValidationError): pass
-class CapacityError(frappe.ValidationError): pass
-class StockOverProductionError(frappe.ValidationError): pass
-class OperationTooLongError(frappe.ValidationError): pass
-class ItemHasVariantError(frappe.ValidationError): pass
+class OverProductionError(frappe.ValidationError):
+ pass
+
+
+class CapacityError(frappe.ValidationError):
+ pass
+
+
+class StockOverProductionError(frappe.ValidationError):
+ pass
+
+
+class OperationTooLongError(frappe.ValidationError):
+ pass
+
+
+class ItemHasVariantError(frappe.ValidationError):
+ pass
+
+
class SerialNoQtyError(frappe.ValidationError):
pass
@@ -74,12 +89,13 @@ class WorkOrder(Document):
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
- self.set_required_items(reset_only_qty = len(self.get("required_items")))
+ self.set_required_items(reset_only_qty=len(self.get("required_items")))
def validate_sales_order(self):
if self.sales_order:
self.check_sales_order_on_hold_or_close()
- so = frappe.db.sql("""
+ so = frappe.db.sql(
+ """
select so.name, so_item.delivery_date, so.project
from `tabSales Order` so
inner join `tabSales Order Item` so_item on so_item.parent = so.name
@@ -88,10 +104,14 @@ class WorkOrder(Document):
and so.skip_delivery_note = 0 and (
so_item.item_code=%s or
pk_item.item_code=%s )
- """, (self.sales_order, self.production_item, self.production_item), as_dict=1)
+ """,
+ (self.sales_order, self.production_item, self.production_item),
+ as_dict=1,
+ )
if not so:
- so = frappe.db.sql("""
+ so = frappe.db.sql(
+ """
select
so.name, so_item.delivery_date, so.project
from
@@ -102,7 +122,10 @@ class WorkOrder(Document):
and so.skip_delivery_note = 0
and so_item.item_code = packed_item.parent_item
and so.docstatus = 1 and packed_item.item_code=%s
- """, (self.sales_order, self.production_item), as_dict=1)
+ """,
+ (self.sales_order, self.production_item),
+ as_dict=1,
+ )
if len(so):
if not self.expected_delivery_date:
@@ -123,7 +146,9 @@ class WorkOrder(Document):
def set_default_warehouse(self):
if not self.wip_warehouse:
- self.wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
+ self.wip_warehouse = frappe.db.get_single_value(
+ "Manufacturing Settings", "default_wip_warehouse"
+ )
if not self.fg_warehouse:
self.fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse")
@@ -145,40 +170,55 @@ class WorkOrder(Document):
self.planned_operating_cost += flt(d.planned_operating_cost)
self.actual_operating_cost += flt(d.actual_operating_cost)
- variable_cost = self.actual_operating_cost if self.actual_operating_cost \
- else self.planned_operating_cost
+ variable_cost = (
+ self.actual_operating_cost if self.actual_operating_cost else self.planned_operating_cost
+ )
- self.total_operating_cost = (flt(self.additional_operating_cost)
- + flt(variable_cost) + flt(self.corrective_operation_cost))
+ self.total_operating_cost = (
+ flt(self.additional_operating_cost) + flt(variable_cost) + flt(self.corrective_operation_cost)
+ )
def validate_work_order_against_so(self):
# already ordered qty
- ordered_qty_against_so = frappe.db.sql("""select sum(qty) from `tabWork Order`
+ ordered_qty_against_so = frappe.db.sql(
+ """select sum(qty) from `tabWork Order`
where production_item = %s and sales_order = %s and docstatus < 2 and name != %s""",
- (self.production_item, self.sales_order, self.name))[0][0]
+ (self.production_item, self.sales_order, self.name),
+ )[0][0]
total_qty = flt(ordered_qty_against_so) + flt(self.qty)
# get qty from Sales Order Item table
- so_item_qty = frappe.db.sql("""select sum(stock_qty) from `tabSales Order Item`
+ so_item_qty = frappe.db.sql(
+ """select sum(stock_qty) from `tabSales Order Item`
where parent = %s and item_code = %s""",
- (self.sales_order, self.production_item))[0][0]
+ (self.sales_order, self.production_item),
+ )[0][0]
# get qty from Packing Item table
- dnpi_qty = frappe.db.sql("""select sum(qty) from `tabPacked Item`
+ dnpi_qty = frappe.db.sql(
+ """select sum(qty) from `tabPacked Item`
where parent = %s and parenttype = 'Sales Order' and item_code = %s""",
- (self.sales_order, self.production_item))[0][0]
+ (self.sales_order, self.production_item),
+ )[0][0]
# total qty in SO
so_qty = flt(so_item_qty) + flt(dnpi_qty)
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_sales_order"))
+ allowance_percentage = flt(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "overproduction_percentage_for_sales_order"
+ )
+ )
- if total_qty > so_qty + (allowance_percentage/100 * so_qty):
- frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}")
- .format(self.production_item, so_qty), OverProductionError)
+ if total_qty > so_qty + (allowance_percentage / 100 * so_qty):
+ frappe.throw(
+ _("Cannot produce more Item {0} than Sales Order quantity {1}").format(
+ self.production_item, so_qty
+ ),
+ OverProductionError,
+ )
def update_status(self, status=None):
- '''Update status of work order if unknown'''
+ """Update status of work order if unknown"""
if status != "Stopped" and status != "Closed":
status = self.get_status(status)
@@ -190,17 +230,22 @@ class WorkOrder(Document):
return status
def get_status(self, status=None):
- '''Return the status based on stock entries against this work order'''
+ """Return the status based on stock entries against this work order"""
if not status:
status = self.status
- if self.docstatus==0:
- status = 'Draft'
- elif self.docstatus==1:
- if status != 'Stopped':
- stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty)
+ if self.docstatus == 0:
+ status = "Draft"
+ elif self.docstatus == 1:
+ if status != "Stopped":
+ stock_entries = frappe._dict(
+ frappe.db.sql(
+ """select purpose, sum(fg_completed_qty)
from `tabStock Entry` where work_order=%s and docstatus=1
- group by purpose""", self.name))
+ group by purpose""",
+ self.name,
+ )
+ )
status = "Not Started"
if stock_entries:
@@ -209,31 +254,46 @@ class WorkOrder(Document):
if flt(produced_qty) >= flt(self.qty):
status = "Completed"
else:
- status = 'Cancelled'
+ status = "Cancelled"
return status
def update_work_order_qty(self):
"""Update **Manufactured Qty** and **Material Transferred for Qty** in Work Order
- based on Stock Entry"""
+ based on Stock Entry"""
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_work_order"))
+ allowance_percentage = flt(
+ frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
+ )
- for purpose, fieldname in (("Manufacture", "produced_qty"),
- ("Material Transfer for Manufacture", "material_transferred_for_manufacturing")):
- if (purpose == 'Material Transfer for Manufacture' and
- self.operations and self.transfer_material_against == 'Job Card'):
+ for purpose, fieldname in (
+ ("Manufacture", "produced_qty"),
+ ("Material Transfer for Manufacture", "material_transferred_for_manufacturing"),
+ ):
+ if (
+ purpose == "Material Transfer for Manufacture"
+ and self.operations
+ and self.transfer_material_against == "Job Card"
+ ):
continue
- qty = flt(frappe.db.sql("""select sum(fg_completed_qty)
+ qty = flt(
+ frappe.db.sql(
+ """select sum(fg_completed_qty)
from `tabStock Entry` where work_order=%s and docstatus=1
- and purpose=%s""", (self.name, purpose))[0][0])
+ and purpose=%s""",
+ (self.name, purpose),
+ )[0][0]
+ )
- completed_qty = self.qty + (allowance_percentage/100 * self.qty)
+ completed_qty = self.qty + (allowance_percentage / 100 * self.qty)
if qty > completed_qty:
- frappe.throw(_("{0} ({1}) cannot be greater than planned quantity ({2}) in Work Order {3}").format(\
- self.meta.get_label(fieldname), qty, completed_qty, self.name), StockOverProductionError)
+ frappe.throw(
+ _("{0} ({1}) cannot be greater than planned quantity ({2}) in Work Order {3}").format(
+ self.meta.get_label(fieldname), qty, completed_qty, self.name
+ ),
+ StockOverProductionError,
+ )
self.db_set(fieldname, qty)
self.set_process_loss_qty()
@@ -247,7 +307,9 @@ class WorkOrder(Document):
self.update_production_plan_status()
def set_process_loss_qty(self):
- process_loss_qty = flt(frappe.db.sql("""
+ process_loss_qty = flt(
+ frappe.db.sql(
+ """
SELECT sum(qty) FROM `tabStock Entry Detail`
WHERE
is_process_loss=1
@@ -258,21 +320,33 @@ class WorkOrder(Document):
AND purpose='Manufacture'
AND docstatus=1
)
- """, (self.name, ))[0][0])
+ """,
+ (self.name,),
+ )[0][0]
+ )
if process_loss_qty is not None:
- self.db_set('process_loss_qty', process_loss_qty)
+ self.db_set("process_loss_qty", process_loss_qty)
def update_production_plan_status(self):
- production_plan = frappe.get_doc('Production Plan', self.production_plan)
+ production_plan = frappe.get_doc("Production Plan", self.production_plan)
produced_qty = 0
if self.production_plan_item:
- total_qty = frappe.get_all("Work Order", fields = "sum(produced_qty) as produced_qty",
- filters = {'docstatus': 1, 'production_plan': self.production_plan,
- 'production_plan_item': self.production_plan_item}, as_list=1)
+ total_qty = frappe.get_all(
+ "Work Order",
+ fields="sum(produced_qty) as produced_qty",
+ filters={
+ "docstatus": 1,
+ "production_plan": self.production_plan,
+ "production_plan_item": self.production_plan_item,
+ },
+ as_list=1,
+ )
produced_qty = total_qty[0][0] if total_qty else 0
- production_plan.run_method("update_produced_pending_qty", produced_qty, self.production_plan_item)
+ production_plan.run_method(
+ "update_produced_pending_qty", produced_qty, self.production_plan_item
+ )
def before_submit(self):
self.create_serial_no_batch_no()
@@ -283,7 +357,9 @@ class WorkOrder(Document):
if not self.fg_warehouse:
frappe.throw(_("For Warehouse is required before Submit"))
- if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ if self.production_plan and frappe.db.exists(
+ "Production Plan Item Reference", {"parent": self.production_plan}
+ ):
self.update_work_order_qty_in_combined_so()
else:
self.update_work_order_qty_in_so()
@@ -296,9 +372,11 @@ class WorkOrder(Document):
def on_cancel(self):
self.validate_cancel()
- frappe.db.set(self,'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
- if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ if self.production_plan and frappe.db.exists(
+ "Production Plan Item Reference", {"parent": self.production_plan}
+ ):
self.update_work_order_qty_in_combined_so()
else:
self.update_work_order_qty_in_so()
@@ -314,16 +392,15 @@ class WorkOrder(Document):
if not (self.has_serial_no or self.has_batch_no):
return
- if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
+ if not cint(
+ frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")
+ ):
return
if self.has_batch_no:
self.create_batch_for_finished_good()
- args = {
- "item_code": self.production_item,
- "work_order": self.name
- }
+ args = {"item_code": self.production_item, "work_order": self.name}
if self.has_serial_no:
self.make_serial_nos(args)
@@ -336,9 +413,12 @@ class WorkOrder(Document):
batch_auto_creation = frappe.get_cached_value("Item", self.production_item, "create_new_batch")
if not batch_auto_creation:
frappe.msgprint(
- _("Batch not created for item {} since it does not have a batch series.")
- .format(frappe.bold(self.production_item)),
- alert=True, indicator="orange")
+ _("Batch not created for item {} since it does not have a batch series.").format(
+ frappe.bold(self.production_item)
+ ),
+ alert=True,
+ indicator="orange",
+ )
return
while total_qty > 0:
@@ -352,19 +432,23 @@ class WorkOrder(Document):
qty = total_qty
total_qty = 0
- make_batch(frappe._dict({
- "item": self.production_item,
- "qty_to_produce": qty,
- "reference_doctype": self.doctype,
- "reference_name": self.name
- }))
+ make_batch(
+ frappe._dict(
+ {
+ "item": self.production_item,
+ "qty_to_produce": qty,
+ "reference_doctype": self.doctype,
+ "reference_name": self.name,
+ }
+ )
+ )
def delete_auto_created_batch_and_serial_no(self):
- for row in frappe.get_all("Serial No", filters = {"work_order": self.name}):
+ for row in frappe.get_all("Serial No", filters={"work_order": self.name}):
frappe.delete_doc("Serial No", row.name)
self.db_set("serial_no", "")
- for row in frappe.get_all("Batch", filters = {"reference_name": self.name}):
+ for row in frappe.get_all("Batch", filters={"reference_name": self.name}):
frappe.delete_doc("Batch", row.name)
def make_serial_nos(self, args):
@@ -379,8 +463,12 @@ class WorkOrder(Document):
serial_nos_length = len(get_serial_nos(self.serial_no))
if serial_nos_length != self.qty:
- frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.")
- .format(self.qty, self.production_item, serial_nos_length), SerialNoQtyError)
+ frappe.throw(
+ _("{0} Serial Numbers required for Item {1}. You have provided {2}.").format(
+ self.qty, self.production_item, serial_nos_length
+ ),
+ SerialNoQtyError,
+ )
def create_job_card(self):
manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings")
@@ -393,8 +481,7 @@ class WorkOrder(Document):
while qty > 0:
qty = split_qty_based_on_batch_size(self, row, qty)
if row.job_card_qty > 0:
- self.prepare_data_for_job_card(row, index,
- plan_days, enable_capacity_planning)
+ self.prepare_data_for_job_card(row, index, plan_days, enable_capacity_planning)
planned_end_date = self.operations and self.operations[-1].planned_end_time
if planned_end_date:
@@ -404,12 +491,14 @@ class WorkOrder(Document):
self.set_operation_start_end_time(index, row)
if not row.workstation:
- frappe.throw(_("Row {0}: select the workstation against the operation {1}")
- .format(row.idx, row.operation))
+ frappe.throw(
+ _("Row {0}: select the workstation against the operation {1}").format(row.idx, row.operation)
+ )
original_start_time = row.planned_start_time
- job_card_doc = create_job_card(self, row, auto_create=True,
- enable_capacity_planning=enable_capacity_planning)
+ job_card_doc = create_job_card(
+ self, row, auto_create=True, enable_capacity_planning=enable_capacity_planning
+ )
if enable_capacity_planning and job_card_doc:
row.planned_start_time = job_card_doc.time_logs[-1].from_time
@@ -417,22 +506,29 @@ class WorkOrder(Document):
if date_diff(row.planned_start_time, original_start_time) > plan_days:
frappe.message_log.pop()
- frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
- .format(plan_days, row.operation), CapacityError)
+ frappe.throw(
+ _("Unable to find the time slot in the next {0} days for the operation {1}.").format(
+ plan_days, row.operation
+ ),
+ CapacityError,
+ )
row.db_update()
def set_operation_start_end_time(self, idx, row):
"""Set start and end time for given operation. If first operation, set start as
`planned_start_date`, else add time diff to end time of earlier operation."""
- if idx==0:
+ if idx == 0:
# first operation at planned_start date
row.planned_start_time = self.planned_start_date
else:
- row.planned_start_time = get_datetime(self.operations[idx-1].planned_end_time)\
- + get_mins_between_operations()
+ row.planned_start_time = (
+ get_datetime(self.operations[idx - 1].planned_end_time) + get_mins_between_operations()
+ )
- row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(minutes = row.time_in_mins)
+ row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(
+ minutes=row.time_in_mins
+ )
if row.planned_start_time == row.planned_end_time:
frappe.throw(_("Capacity Planning Error, planned start time can not be same as end time"))
@@ -442,23 +538,35 @@ class WorkOrder(Document):
frappe.throw(_("Stopped Work Order cannot be cancelled, Unstop it first to cancel"))
# Check whether any stock entry exists against this Work Order
- stock_entry = frappe.db.sql("""select name from `tabStock Entry`
- where work_order = %s and docstatus = 1""", self.name)
+ stock_entry = frappe.db.sql(
+ """select name from `tabStock Entry`
+ where work_order = %s and docstatus = 1""",
+ self.name,
+ )
if stock_entry:
- frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(frappe.utils.get_link_to_form('Stock Entry', stock_entry[0][0])))
+ frappe.throw(
+ _("Cannot cancel because submitted Stock Entry {0} exists").format(
+ frappe.utils.get_link_to_form("Stock Entry", stock_entry[0][0])
+ )
+ )
def update_planned_qty(self):
- update_bin_qty(self.production_item, self.fg_warehouse, {
- "planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)
- })
+ update_bin_qty(
+ self.production_item,
+ self.fg_warehouse,
+ {"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)},
+ )
if self.material_request:
mr_obj = frappe.get_doc("Material Request", self.material_request)
mr_obj.update_requested_qty([self.material_request_item])
def update_ordered_qty(self):
- if self.production_plan and self.production_plan_item \
- and not self.production_plan_sub_assembly_item:
+ if (
+ self.production_plan
+ and self.production_plan_item
+ and not self.production_plan_sub_assembly_item
+ ):
qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
if self.docstatus == 1:
@@ -466,12 +574,11 @@ class WorkOrder(Document):
elif self.docstatus == 2:
qty -= self.qty
- frappe.db.set_value('Production Plan Item',
- self.production_plan_item, 'ordered_qty', qty)
+ frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
- doc = frappe.get_doc('Production Plan', self.production_plan)
+ doc = frappe.get_doc("Production Plan", self.production_plan)
doc.set_status()
- doc.db_set('status', doc.status)
+ doc.db_set("status", doc.status)
def update_work_order_qty_in_so(self):
if not self.sales_order and not self.sales_order_item:
@@ -479,8 +586,11 @@ class WorkOrder(Document):
total_bundle_qty = 1
if self.product_bundle_item:
- total_bundle_qty = frappe.db.sql(""" select sum(qty) from
- `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
+ total_bundle_qty = frappe.db.sql(
+ """ select sum(qty) from
+ `tabProduct Bundle Item` where parent = %s""",
+ (frappe.db.escape(self.product_bundle_item)),
+ )[0][0]
if not total_bundle_qty:
# product bundle is 0 (product bundle allows 0 qty for items)
@@ -488,49 +598,78 @@ class WorkOrder(Document):
cond = "product_bundle_item = %s" if self.product_bundle_item else "production_item = %s"
- qty = frappe.db.sql(""" select sum(qty) from
+ qty = frappe.db.sql(
+ """ select sum(qty) from
`tabWork Order` where sales_order = %s and docstatus = 1 and {0}
- """.format(cond), (self.sales_order, (self.product_bundle_item or self.production_item)), as_list=1)
+ """.format(
+ cond
+ ),
+ (self.sales_order, (self.product_bundle_item or self.production_item)),
+ as_list=1,
+ )
work_order_qty = qty[0][0] if qty and qty[0][0] else 0
- frappe.db.set_value('Sales Order Item',
- self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
+ frappe.db.set_value(
+ "Sales Order Item",
+ self.sales_order_item,
+ "work_order_qty",
+ flt(work_order_qty / total_bundle_qty, 2),
+ )
def update_work_order_qty_in_combined_so(self):
total_bundle_qty = 1
if self.product_bundle_item:
- total_bundle_qty = frappe.db.sql(""" select sum(qty) from
- `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
+ total_bundle_qty = frappe.db.sql(
+ """ select sum(qty) from
+ `tabProduct Bundle Item` where parent = %s""",
+ (frappe.db.escape(self.product_bundle_item)),
+ )[0][0]
if not total_bundle_qty:
# product bundle is 0 (product bundle allows 0 qty for items)
total_bundle_qty = 1
- prod_plan = frappe.get_doc('Production Plan', self.production_plan)
- item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item')
+ prod_plan = frappe.get_doc("Production Plan", self.production_plan)
+ item_reference = frappe.get_value(
+ "Production Plan Item", self.production_plan_item, "sales_order_item"
+ )
for plan_reference in prod_plan.prod_plan_references:
work_order_qty = 0.0
if plan_reference.item_reference == item_reference:
if self.docstatus == 1:
work_order_qty = flt(plan_reference.qty) / total_bundle_qty
- frappe.db.set_value('Sales Order Item',
- plan_reference.sales_order_item, 'work_order_qty', work_order_qty)
+ frappe.db.set_value(
+ "Sales Order Item", plan_reference.sales_order_item, "work_order_qty", work_order_qty
+ )
def update_completed_qty_in_material_request(self):
if self.material_request:
- frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
+ frappe.get_doc("Material Request", self.material_request).update_completed_qty(
+ [self.material_request_item]
+ )
def set_work_order_operations(self):
"""Fetch operations from BOM and set in 'Work Order'"""
def _get_operations(bom_no, qty=1):
- data = frappe.get_all("BOM Operation",
- filters={"parent": bom_no},
- fields=["operation", "description", "workstation", "idx",
- "base_hour_rate as hour_rate", "time_in_mins", "parent as bom",
- "batch_size", "sequence_id", "fixed_time"],
- order_by="idx")
+ data = frappe.get_all(
+ "BOM Operation",
+ filters={"parent": bom_no},
+ fields=[
+ "operation",
+ "description",
+ "workstation",
+ "idx",
+ "base_hour_rate as hour_rate",
+ "time_in_mins",
+ "parent as bom",
+ "batch_size",
+ "sequence_id",
+ "fixed_time",
+ ],
+ order_by="idx",
+ )
for d in data:
if not d.fixed_time:
@@ -539,9 +678,8 @@ class WorkOrder(Document):
return data
-
- self.set('operations', [])
- if not self.bom_no or not frappe.get_cached_value('BOM', self.bom_no, 'with_operations'):
+ self.set("operations", [])
+ if not self.bom_no or not frappe.get_cached_value("BOM", self.bom_no, "with_operations"):
return
operations = []
@@ -555,12 +693,12 @@ class WorkOrder(Document):
operations.extend(_get_operations(node.name, qty=node.exploded_qty))
bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
- operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
+ operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty))
for correct_index, operation in enumerate(operations, start=1):
operation.idx = correct_index
- self.set('operations', operations)
+ self.set("operations", operations)
self.calculate_time()
def calculate_time(self):
@@ -576,16 +714,27 @@ class WorkOrder(Document):
holidays = {}
if holiday_list not in holidays:
- holiday_list_days = [getdate(d[0]) for d in frappe.get_all("Holiday", fields=["holiday_date"],
- filters={"parent": holiday_list}, order_by="holiday_date", limit_page_length=0, as_list=1)]
+ holiday_list_days = [
+ getdate(d[0])
+ for d in frappe.get_all(
+ "Holiday",
+ fields=["holiday_date"],
+ filters={"parent": holiday_list},
+ order_by="holiday_date",
+ limit_page_length=0,
+ as_list=1,
+ )
+ ]
holidays[holiday_list] = holiday_list_days
return holidays[holiday_list]
def update_operation_status(self):
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order"))
- max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage/100 * flt(self.qty))
+ allowance_percentage = flt(
+ frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
+ )
+ max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage / 100 * flt(self.qty))
for d in self.get("operations"):
if not d.completed_qty:
@@ -601,7 +750,9 @@ class WorkOrder(Document):
def set_actual_dates(self):
if self.get("operations"):
- actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
+ actual_start_dates = [
+ d.actual_start_time for d in self.get("operations") if d.actual_start_time
+ ]
if actual_start_dates:
self.actual_start_date = min(actual_start_dates)
@@ -609,20 +760,21 @@ class WorkOrder(Document):
if actual_end_dates:
self.actual_end_date = max(actual_end_dates)
else:
- data = frappe.get_all("Stock Entry",
- fields = ["timestamp(posting_date, posting_time) as posting_datetime"],
- filters = {
+ data = frappe.get_all(
+ "Stock Entry",
+ fields=["timestamp(posting_date, posting_time) as posting_datetime"],
+ filters={
"work_order": self.name,
- "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"])
- }
+ "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"]),
+ },
)
if data and len(data):
dates = [d.posting_datetime for d in data]
- self.db_set('actual_start_date', min(dates))
+ self.db_set("actual_start_date", min(dates))
if self.status == "Completed":
- self.db_set('actual_end_date', max(dates))
+ self.db_set("actual_end_date", max(dates))
self.set_lead_time()
@@ -645,24 +797,39 @@ class WorkOrder(Document):
if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
- if self.production_plan and self.production_plan_item \
- and not self.production_plan_sub_assembly_item:
- qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
+ if (
+ self.production_plan
+ and self.production_plan_item
+ and not self.production_plan_sub_assembly_item
+ ):
+ qty_dict = frappe.db.get_value(
+ "Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1
+ )
if not qty_dict:
return
- allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)
+ allowance_qty = (
+ flt(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "overproduction_percentage_for_work_order"
+ )
+ )
+ / 100
+ * qty_dict.get("planned_qty", 0)
+ )
max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
if not max_qty > 0:
- frappe.throw(_("Cannot produce more item for {0}")
- .format(self.production_item), OverProductionError)
+ frappe.throw(
+ _("Cannot produce more item for {0}").format(self.production_item), OverProductionError
+ )
elif self.qty > max_qty:
- frappe.throw(_("Cannot produce more than {0} items for {1}")
- .format(max_qty, self.production_item), OverProductionError)
+ frappe.throw(
+ _("Cannot produce more than {0} items for {1}").format(max_qty, self.production_item),
+ OverProductionError,
+ )
def validate_transfer_against(self):
if not self.docstatus == 1:
@@ -671,8 +838,10 @@ class WorkOrder(Document):
if not self.operations:
self.transfer_material_against = "Work Order"
if not self.transfer_material_against:
- frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
-
+ frappe.throw(
+ _("Setting {} is required").format(self.meta.get_label("transfer_material_against")),
+ title=_("Missing value"),
+ )
def validate_operation_time(self):
for d in self.operations:
@@ -680,14 +849,14 @@ class WorkOrder(Document):
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
def update_required_items(self):
- '''
+ """
update bin reserved_qty_for_production
called from Stock Entry for production, after submit, cancel
- '''
+ """
# calculate consumed qty based on submitted stock entries
self.update_consumed_qty_for_required_items()
- if self.docstatus==1:
+ if self.docstatus == 1:
# calculate transferred qty based on submitted stock entries
self.update_transferred_qty_for_required_items()
@@ -695,7 +864,7 @@ class WorkOrder(Document):
self.update_reserved_qty_for_production()
def update_reserved_qty_for_production(self, items=None):
- '''update reserved_qty_for_production in bins'''
+ """update reserved_qty_for_production in bins"""
for d in self.required_items:
if d.source_warehouse:
stock_bin = get_bin(d.item_code, d.source_warehouse)
@@ -717,17 +886,18 @@ class WorkOrder(Document):
d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse)
def set_required_items(self, reset_only_qty=False):
- '''set required_items for production to keep track of reserved qty'''
+ """set required_items for production to keep track of reserved qty"""
if not reset_only_qty:
self.required_items = []
operation = None
- if self.get('operations') and len(self.operations) == 1:
+ if self.get("operations") and len(self.operations) == 1:
operation = self.operations[0].operation
if self.bom_no and self.qty:
- item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty,
- fetch_exploded = self.use_multi_level_bom)
+ item_dict = get_bom_items_as_dict(
+ self.bom_no, self.company, qty=self.qty, fetch_exploded=self.use_multi_level_bom
+ )
if reset_only_qty:
for d in self.get("required_items"):
@@ -737,19 +907,22 @@ class WorkOrder(Document):
if not d.operation:
d.operation = operation
else:
- for item in sorted(item_dict.values(), key=lambda d: d['idx'] or float('inf')):
- self.append('required_items', {
- 'rate': item.rate,
- 'amount': item.rate * item.qty,
- 'operation': item.operation or operation,
- 'item_code': item.item_code,
- 'item_name': item.item_name,
- 'description': item.description,
- 'allow_alternative_item': item.allow_alternative_item,
- 'required_qty': item.qty,
- 'source_warehouse': item.source_warehouse or item.default_warehouse,
- 'include_item_in_manufacturing': item.include_item_in_manufacturing
- })
+ for item in sorted(item_dict.values(), key=lambda d: d["idx"] or float("inf")):
+ self.append(
+ "required_items",
+ {
+ "rate": item.rate,
+ "amount": item.rate * item.qty,
+ "operation": item.operation or operation,
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "description": item.description,
+ "allow_alternative_item": item.allow_alternative_item,
+ "required_qty": item.qty,
+ "source_warehouse": item.source_warehouse or item.default_warehouse,
+ "include_item_in_manufacturing": item.include_item_in_manufacturing,
+ },
+ )
if not self.project:
self.project = item.get("project")
@@ -757,32 +930,33 @@ class WorkOrder(Document):
self.set_available_qty()
def update_transferred_qty_for_required_items(self):
- '''update transferred qty from submitted stock entries for that item against
- the work order'''
+ """update transferred qty from submitted stock entries for that item against
+ the work order"""
for d in self.required_items:
- transferred_qty = frappe.db.sql('''select sum(qty)
+ transferred_qty = frappe.db.sql(
+ """select sum(qty)
from `tabStock Entry` entry, `tabStock Entry Detail` detail
where
entry.work_order = %(name)s
and entry.purpose = "Material Transfer for Manufacture"
and entry.docstatus = 1
and detail.parent = entry.name
- and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', {
- 'name': self.name,
- 'item': d.item_code
- })[0][0]
+ and (detail.item_code = %(item)s or detail.original_item = %(item)s)""",
+ {"name": self.name, "item": d.item_code},
+ )[0][0]
- d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
+ d.db_set("transferred_qty", flt(transferred_qty), update_modified=False)
def update_consumed_qty_for_required_items(self):
- '''
- Update consumed qty from submitted stock entries
- against a work order for each stock item
- '''
+ """
+ Update consumed qty from submitted stock entries
+ against a work order for each stock item
+ """
for item in self.required_items:
- consumed_qty = frappe.db.sql('''
+ consumed_qty = frappe.db.sql(
+ """
SELECT
SUM(qty)
FROM
@@ -797,85 +971,97 @@ class WorkOrder(Document):
AND detail.s_warehouse IS NOT null
AND (detail.item_code = %(item)s
OR detail.original_item = %(item)s)
- ''', {
- 'name': self.name,
- 'item': item.item_code
- })[0][0]
+ """,
+ {"name": self.name, "item": item.item_code},
+ )[0][0]
- item.db_set('consumed_qty', flt(consumed_qty), update_modified=False)
+ item.db_set("consumed_qty", flt(consumed_qty), update_modified=False)
@frappe.whitelist()
def make_bom(self):
- data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse
+ data = frappe.db.sql(
+ """ select sed.item_code, sed.qty, sed.s_warehouse
from `tabStock Entry Detail` sed, `tabStock Entry` se
where se.name = sed.parent and se.purpose = 'Manufacture'
and (sed.t_warehouse is null or sed.t_warehouse = '') and se.docstatus = 1
- and se.work_order = %s""", (self.name), as_dict=1)
+ and se.work_order = %s""",
+ (self.name),
+ as_dict=1,
+ )
bom = frappe.new_doc("BOM")
bom.item = self.production_item
bom.conversion_rate = 1
for d in data:
- bom.append('items', {
- 'item_code': d.item_code,
- 'qty': d.qty,
- 'source_warehouse': d.s_warehouse
- })
+ bom.append("items", {"item_code": d.item_code, "qty": d.qty, "source_warehouse": d.s_warehouse})
if self.operations:
- bom.set('operations', self.operations)
+ bom.set("operations", self.operations)
bom.with_operations = 1
bom.set_bom_material_details()
return bom
def update_batch_produced_qty(self, stock_entry_doc):
- if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
+ if not cint(
+ frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")
+ ):
return
for row in stock_entry_doc.items:
if row.batch_no and (row.is_finished_item or row.is_scrap_item):
- qty = frappe.get_all("Stock Entry Detail", filters = {"batch_no": row.batch_no, "docstatus": 1},
- or_filters= {"is_finished_item": 1, "is_scrap_item": 1}, fields = ["sum(qty)"], as_list=1)[0][0]
+ qty = frappe.get_all(
+ "Stock Entry Detail",
+ filters={"batch_no": row.batch_no, "docstatus": 1},
+ or_filters={"is_finished_item": 1, "is_scrap_item": 1},
+ fields=["sum(qty)"],
+ as_list=1,
+ )[0][0]
frappe.db.set_value("Batch", row.batch_no, "produced_qty", flt(qty))
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_bom_operations(doctype, txt, searchfield, start, page_len, filters):
if txt:
- filters['operation'] = ('like', '%%%s%%' % txt)
+ filters["operation"] = ("like", "%%%s%%" % txt)
+
+ return frappe.get_all("BOM Operation", filters=filters, fields=["operation"], as_list=1)
- return frappe.get_all('BOM Operation',
- filters = filters, fields = ['operation'], as_list=1)
@frappe.whitelist()
-def get_item_details(item, project = None, skip_bom_info=False):
- res = frappe.db.sql("""
+def get_item_details(item, project=None, skip_bom_info=False):
+ res = frappe.db.sql(
+ """
select stock_uom, description, item_name, allow_alternative_item,
include_item_in_manufacturing
from `tabItem`
where disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
and name=%s
- """, (nowdate(), item), as_dict=1)
+ """,
+ (nowdate(), item),
+ as_dict=1,
+ )
if not res:
return {}
res = res[0]
- if skip_bom_info: return res
+ if skip_bom_info:
+ return res
filters = {"item": item, "is_default": 1, "docstatus": 1}
if project:
filters = {"item": item, "project": project}
- res["bom_no"] = frappe.db.get_value("BOM", filters = filters)
+ res["bom_no"] = frappe.db.get_value("BOM", filters=filters)
if not res["bom_no"]:
- variant_of= frappe.db.get_value("Item", item, "variant_of")
+ variant_of = frappe.db.get_value("Item", item, "variant_of")
if variant_of:
res["bom_no"] = frappe.db.get_value("BOM", filters={"item": variant_of, "is_default": 1})
@@ -883,19 +1069,26 @@ def get_item_details(item, project = None, skip_bom_info=False):
if not res["bom_no"]:
if project:
res = get_item_details(item)
- frappe.msgprint(_("Default BOM not found for Item {0} and Project {1}").format(item, project), alert=1)
+ frappe.msgprint(
+ _("Default BOM not found for Item {0} and Project {1}").format(item, project), alert=1
+ )
else:
frappe.throw(_("Default BOM for {0} not found").format(item))
- bom_data = frappe.db.get_value('BOM', res['bom_no'],
- ['project', 'allow_alternative_item', 'transfer_material_against', 'item_name'], as_dict=1)
+ bom_data = frappe.db.get_value(
+ "BOM",
+ res["bom_no"],
+ ["project", "allow_alternative_item", "transfer_material_against", "item_name"],
+ as_dict=1,
+ )
- res['project'] = project or bom_data.pop("project")
+ res["project"] = project or bom_data.pop("project")
res.update(bom_data)
res.update(check_if_scrap_warehouse_mandatory(res["bom_no"]))
return res
+
@frappe.whitelist()
def make_work_order(bom_no, item, qty=0, project=None, variant_items=None):
if not frappe.has_permission("Work Order", "write"):
@@ -917,43 +1110,51 @@ def make_work_order(bom_no, item, qty=0, project=None, variant_items=None):
return wo_doc
+
def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"):
if isinstance(variant_items, str):
variant_items = json.loads(variant_items)
for item in variant_items:
- args = frappe._dict({
- "item_code": item.get("variant_item_code"),
- "required_qty": item.get("qty"),
- "qty": item.get("qty"), # for bom
- "source_warehouse": item.get("source_warehouse"),
- "operation": item.get("operation")
- })
+ args = frappe._dict(
+ {
+ "item_code": item.get("variant_item_code"),
+ "required_qty": item.get("qty"),
+ "qty": item.get("qty"), # for bom
+ "source_warehouse": item.get("source_warehouse"),
+ "operation": item.get("operation"),
+ }
+ )
bom_doc = frappe.get_cached_doc("BOM", bom_no)
item_data = get_item_details(args.item_code, skip_bom_info=True)
args.update(item_data)
- args["rate"] = get_bom_item_rate({
- "company": wo_doc.company,
- "item_code": args.get("item_code"),
- "qty": args.get("required_qty"),
- "uom": args.get("stock_uom"),
- "stock_uom": args.get("stock_uom"),
- "conversion_factor": 1
- }, bom_doc)
+ args["rate"] = get_bom_item_rate(
+ {
+ "company": wo_doc.company,
+ "item_code": args.get("item_code"),
+ "qty": args.get("required_qty"),
+ "uom": args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "conversion_factor": 1,
+ },
+ bom_doc,
+ )
if not args.source_warehouse:
- args["source_warehouse"] = get_item_defaults(item.get("variant_item_code"),
- wo_doc.company).default_warehouse
+ args["source_warehouse"] = get_item_defaults(
+ item.get("variant_item_code"), wo_doc.company
+ ).default_warehouse
args["amount"] = flt(args.get("required_qty")) * flt(args.get("rate"))
args["uom"] = item_data.stock_uom
wo_doc.append(table_name, args)
+
@frappe.whitelist()
def check_if_scrap_warehouse_mandatory(bom_no):
- res = {"set_scrap_wh_mandatory": False }
+ res = {"set_scrap_wh_mandatory": False}
if bom_no:
bom = frappe.get_doc("BOM", bom_no)
@@ -962,12 +1163,14 @@ def check_if_scrap_warehouse_mandatory(bom_no):
return res
+
@frappe.whitelist()
def set_work_order_ops(name):
- po = frappe.get_doc('Work Order', name)
+ po = frappe.get_doc("Work Order", name)
po.set_work_order_operations()
po.save()
+
@frappe.whitelist()
def make_stock_entry(work_order_id, purpose, qty=None):
work_order = frappe.get_doc("Work Order", work_order_id)
@@ -985,10 +1188,11 @@ def make_stock_entry(work_order_id, purpose, qty=None):
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(work_order.qty) - flt(work_order.produced_qty))
if work_order.bom_no:
- stock_entry.inspection_required = frappe.db.get_value('BOM',
- work_order.bom_no, 'inspection_required')
+ stock_entry.inspection_required = frappe.db.get_value(
+ "BOM", work_order.bom_no, "inspection_required"
+ )
- if purpose=="Material Transfer for Manufacture":
+ if purpose == "Material Transfer for Manufacture":
stock_entry.to_warehouse = wip_warehouse
stock_entry.project = work_order.project
else:
@@ -1001,6 +1205,7 @@ def make_stock_entry(work_order_id, purpose, qty=None):
stock_entry.set_serial_no_batch_for_finished_good()
return stock_entry.as_dict()
+
@frappe.whitelist()
def get_default_warehouse():
doc = frappe.get_cached_doc("Manufacturing Settings")
@@ -1008,12 +1213,13 @@ def get_default_warehouse():
return {
"wip_warehouse": doc.default_wip_warehouse,
"fg_warehouse": doc.default_fg_warehouse,
- "scrap_warehouse": doc.default_scrap_warehouse
+ "scrap_warehouse": doc.default_scrap_warehouse,
}
+
@frappe.whitelist()
def stop_unstop(work_order, status):
- """ Called from client side on Stop/Unstop event"""
+ """Called from client side on Stop/Unstop event"""
if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
@@ -1030,24 +1236,29 @@ def stop_unstop(work_order, status):
return pro_order.status
+
@frappe.whitelist()
def query_sales_order(production_item):
- out = frappe.db.sql_list("""
+ out = frappe.db.sql_list(
+ """
select distinct so.name from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent=so.name and so_item.item_code=%s and so.docstatus=1
union
select distinct so.name from `tabSales Order` so, `tabPacked Item` pi_item
where pi_item.parent=so.name and pi_item.item_code=%s and so.docstatus=1
- """, (production_item, production_item))
+ """,
+ (production_item, production_item),
+ )
return out
+
@frappe.whitelist()
def make_job_card(work_order, operations):
if isinstance(operations, str):
operations = json.loads(operations)
- work_order = frappe.get_doc('Work Order', work_order)
+ work_order = frappe.get_doc("Work Order", work_order)
for row in operations:
row = frappe._dict(row)
validate_operation_data(row)
@@ -1057,6 +1268,7 @@ def make_job_card(work_order, operations):
if row.job_card_qty > 0:
create_job_card(work_order, row, auto_create=True)
+
@frappe.whitelist()
def close_work_order(work_order, status):
if not frappe.has_permission("Work Order", "write"):
@@ -1064,15 +1276,17 @@ def close_work_order(work_order, status):
work_order = frappe.get_doc("Work Order", work_order)
if work_order.get("operations"):
- job_cards = frappe.get_list("Job Card",
- filters={
- "work_order": work_order.name,
- "status": "Work In Progress"
- }, pluck='name')
+ job_cards = frappe.get_list(
+ "Job Card", filters={"work_order": work_order.name, "status": "Work In Progress"}, pluck="name"
+ )
if job_cards:
job_cards = ", ".join(job_cards)
- frappe.throw(_("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(job_cards))
+ frappe.throw(
+ _("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(
+ job_cards
+ )
+ )
work_order.update_status(status)
work_order.update_planned_qty()
@@ -1080,9 +1294,11 @@ def close_work_order(work_order, status):
work_order.notify_update()
return work_order.status
+
def split_qty_based_on_batch_size(wo_doc, row, qty):
- if not cint(frappe.db.get_value("Operation",
- row.operation, "create_job_card_based_on_batch_size")):
+ if not cint(
+ frappe.db.get_value("Operation", row.operation, "create_job_card_based_on_batch_size")
+ ):
row.batch_size = row.get("qty") or wo_doc.qty
row.job_card_qty = row.batch_size
@@ -1096,55 +1312,63 @@ def split_qty_based_on_batch_size(wo_doc, row, qty):
return qty
+
def get_serial_nos_for_job_card(row, wo_doc):
if not wo_doc.serial_no:
return
serial_nos = get_serial_nos(wo_doc.serial_no)
used_serial_nos = []
- for d in frappe.get_all('Job Card', fields=['serial_no'],
- filters={'docstatus': ('<', 2), 'work_order': wo_doc.name, 'operation_id': row.name}):
+ for d in frappe.get_all(
+ "Job Card",
+ fields=["serial_no"],
+ filters={"docstatus": ("<", 2), "work_order": wo_doc.name, "operation_id": row.name},
+ ):
used_serial_nos.extend(get_serial_nos(d.serial_no))
serial_nos = sorted(list(set(serial_nos) - set(used_serial_nos)))
- row.serial_no = '\n'.join(serial_nos[0:row.job_card_qty])
+ row.serial_no = "\n".join(serial_nos[0 : row.job_card_qty])
+
def validate_operation_data(row):
if row.get("qty") <= 0:
- frappe.throw(_("Quantity to Manufacture can not be zero for the operation {0}")
- .format(
+ frappe.throw(
+ _("Quantity to Manufacture can not be zero for the operation {0}").format(
frappe.bold(row.get("operation"))
)
)
if row.get("qty") > row.get("pending_qty"):
- frappe.throw(_("For operation {0}: Quantity ({1}) can not be greter than pending quantity({2})")
- .format(
+ frappe.throw(
+ _("For operation {0}: Quantity ({1}) can not be greter than pending quantity({2})").format(
frappe.bold(row.get("operation")),
frappe.bold(row.get("qty")),
- frappe.bold(row.get("pending_qty"))
+ frappe.bold(row.get("pending_qty")),
)
)
+
def create_job_card(work_order, row, enable_capacity_planning=False, auto_create=False):
doc = frappe.new_doc("Job Card")
- doc.update({
- 'work_order': work_order.name,
- 'operation': row.get("operation"),
- 'workstation': row.get("workstation"),
- 'posting_date': nowdate(),
- 'for_quantity': row.job_card_qty or work_order.get('qty', 0),
- 'operation_id': row.get("name"),
- 'bom_no': work_order.bom_no,
- 'project': work_order.project,
- 'company': work_order.company,
- 'sequence_id': row.get("sequence_id"),
- 'wip_warehouse': work_order.wip_warehouse,
- 'hour_rate': row.get("hour_rate"),
- 'serial_no': row.get("serial_no")
- })
+ doc.update(
+ {
+ "work_order": work_order.name,
+ "operation": row.get("operation"),
+ "workstation": row.get("workstation"),
+ "posting_date": nowdate(),
+ "for_quantity": row.job_card_qty or work_order.get("qty", 0),
+ "operation_id": row.get("name"),
+ "bom_no": work_order.bom_no,
+ "project": work_order.project,
+ "company": work_order.company,
+ "sequence_id": row.get("sequence_id"),
+ "wip_warehouse": work_order.wip_warehouse,
+ "hour_rate": row.get("hour_rate"),
+ "serial_no": row.get("serial_no"),
+ }
+ )
- if work_order.transfer_material_against == 'Job Card' and not work_order.skip_transfer:
+ if work_order.transfer_material_against == "Job Card" and not work_order.skip_transfer:
doc.get_required_items()
if auto_create:
@@ -1153,7 +1377,9 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create
doc.schedule_time_logs(row)
doc.insert()
- frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True)
+ frappe.msgprint(
+ _("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True
+ )
if enable_capacity_planning:
# automatically added scheduling rows shouldn't change status to WIP
@@ -1161,15 +1387,18 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create
return doc
+
def get_work_order_operation_data(work_order, operation, workstation):
for d in work_order.operations:
if d.operation == operation and d.workstation == workstation:
return d
+
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None, for_qty=None):
- for_qty = for_qty or json.loads(target_doc).get('for_qty')
- max_finished_goods_qty = frappe.db.get_value('Work Order', source_name, 'qty')
+ for_qty = for_qty or json.loads(target_doc).get("for_qty")
+ max_finished_goods_qty = frappe.db.get_value("Work Order", source_name, "qty")
+
def update_item_quantity(source, target, source_parent):
pending_to_issue = flt(source.required_qty) - flt(source.transferred_qty)
desire_to_transfer = flt(source.required_qty) / max_finished_goods_qty * flt(for_qty)
@@ -1183,25 +1412,25 @@ def create_pick_list(source_name, target_doc=None, for_qty=None):
if qty:
target.qty = qty
target.stock_qty = qty
- target.uom = frappe.get_value('Item', source.item_code, 'stock_uom')
+ target.uom = frappe.get_value("Item", source.item_code, "stock_uom")
target.stock_uom = target.uom
target.conversion_factor = 1
else:
target.delete()
- doc = get_mapped_doc('Work Order', source_name, {
- 'Work Order': {
- 'doctype': 'Pick List',
- 'validation': {
- 'docstatus': ['=', 1]
- }
+ doc = get_mapped_doc(
+ "Work Order",
+ source_name,
+ {
+ "Work Order": {"doctype": "Pick List", "validation": {"docstatus": ["=", 1]}},
+ "Work Order Item": {
+ "doctype": "Pick List Item",
+ "postprocess": update_item_quantity,
+ "condition": lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty),
+ },
},
- 'Work Order Item': {
- 'doctype': 'Pick List Item',
- 'postprocess': update_item_quantity,
- 'condition': lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty)
- },
- }, target_doc)
+ target_doc,
+ )
doc.for_qty = for_qty
@@ -1209,26 +1438,31 @@ def create_pick_list(source_name, target_doc=None, for_qty=None):
return doc
+
def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
"""Get total reserved quantity for any item in specified warehouse"""
wo = frappe.qb.DocType("Work Order")
wo_item = frappe.qb.DocType("Work Order Item")
return (
- frappe.qb
- .from_(wo)
+ frappe.qb.from_(wo)
.from_(wo_item)
- .select(Sum(Case()
+ .select(
+ Sum(
+ Case()
.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
- .else_(wo_item.required_qty - wo_item.consumed_qty))
+ .else_(wo_item.required_qty - wo_item.consumed_qty)
)
+ )
.where(
(wo_item.item_code == item_code)
& (wo_item.parent == wo.name)
& (wo.docstatus == 1)
& (wo_item.source_warehouse == warehouse)
& (wo.status.notin(["Stopped", "Completed", "Closed"]))
- & ((wo_item.required_qty > wo_item.transferred_qty)
- | (wo_item.required_qty > wo_item.consumed_qty))
+ & (
+ (wo_item.required_qty > wo_item.transferred_qty)
+ | (wo_item.required_qty > wo_item.consumed_qty)
+ )
)
).run()[0][0] or 0.0
diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
index 37dd11aab4..465460f95d 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
@@ -3,18 +3,10 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'work_order',
- 'non_standard_fieldnames': {
- 'Batch': 'reference_name'
- },
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Stock Entry', 'Job Card', 'Pick List']
- },
- {
- 'label': _('Reference'),
- 'items': ['Serial No', 'Batch']
- }
- ]
+ "fieldname": "work_order",
+ "non_standard_fieldnames": {"Batch": "reference_name"},
+ "transactions": [
+ {"label": _("Transactions"), "items": ["Stock Entry", "Job Card", "Pick List"]},
+ {"label": _("Reference"), "items": ["Serial No", "Batch"]},
+ ],
}
diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py
index 4311d3bf17..179274707e 100644
--- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py
+++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py
@@ -9,5 +9,6 @@ from frappe.model.document import Document
class WorkOrderItem(Document):
pass
+
def on_doctype_update():
frappe.db.add_index("Work Order Item", ["item_code", "source_warehouse"])
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index dd51017bb7..6db985c8c2 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -13,19 +13,42 @@ from erpnext.manufacturing.doctype.workstation.workstation import (
)
test_dependencies = ["Warehouse"]
-test_records = frappe.get_test_records('Workstation')
-make_test_records('Workstation')
+test_records = frappe.get_test_records("Workstation")
+make_test_records("Workstation")
+
class TestWorkstation(FrappeTestCase):
def test_validate_timings(self):
- check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
- check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")
- self.assertRaises(NotInWorkingHoursError, check_if_within_operating_hours,
- "_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00")
- self.assertRaises(NotInWorkingHoursError, check_if_within_operating_hours,
- "_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00")
- self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
- "_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
+ check_if_within_operating_hours(
+ "_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00"
+ )
+ check_if_within_operating_hours(
+ "_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00"
+ )
+ self.assertRaises(
+ NotInWorkingHoursError,
+ check_if_within_operating_hours,
+ "_Test Workstation 1",
+ "Operation 1",
+ "2013-02-02 05:00:00",
+ "2013-02-02 20:00:00",
+ )
+ self.assertRaises(
+ NotInWorkingHoursError,
+ check_if_within_operating_hours,
+ "_Test Workstation 1",
+ "Operation 1",
+ "2013-02-02 05:00:00",
+ "2013-02-02 20:00:00",
+ )
+ self.assertRaises(
+ WorkstationHolidayError,
+ check_if_within_operating_hours,
+ "_Test Workstation 1",
+ "Operation 1",
+ "2013-02-01 10:00:00",
+ "2013-02-02 20:00:00",
+ )
def test_update_bom_operation_rate(self):
operations = [
@@ -33,14 +56,14 @@ class TestWorkstation(FrappeTestCase):
"operation": "Test Operation A",
"workstation": "_Test Workstation A",
"hour_rate_rent": 300,
- "time_in_mins": 60
+ "time_in_mins": 60,
},
{
"operation": "Test Operation B",
"workstation": "_Test Workstation B",
"hour_rate_rent": 1000,
- "time_in_mins": 60
- }
+ "time_in_mins": 60,
+ },
]
for row in operations:
@@ -48,21 +71,13 @@ class TestWorkstation(FrappeTestCase):
make_operation(row)
test_routing_operations = [
- {
- "operation": "Test Operation A",
- "workstation": "_Test Workstation A",
- "time_in_mins": 60
- },
- {
- "operation": "Test Operation B",
- "workstation": "_Test Workstation A",
- "time_in_mins": 60
- }
+ {"operation": "Test Operation A", "workstation": "_Test Workstation A", "time_in_mins": 60},
+ {"operation": "Test Operation B", "workstation": "_Test Workstation A", "time_in_mins": 60},
]
- routing_doc = create_routing(routing_name = "Routing Test", operations=test_routing_operations)
+ routing_doc = create_routing(routing_name="Routing Test", operations=test_routing_operations)
bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
w1 = frappe.get_doc("Workstation", "_Test Workstation A")
- #resets values
+ # resets values
w1.hour_rate_rent = 300
w1.hour_rate_labour = 0
w1.save()
@@ -72,13 +87,14 @@ class TestWorkstation(FrappeTestCase):
self.assertEqual(bom_doc.operations[0].hour_rate, 300)
w1.hour_rate_rent = 250
w1.save()
- #updating after setting new rates in workstations
+ # updating after setting new rates in workstations
bom_doc.update_cost()
bom_doc.reload()
self.assertEqual(w1.hour_rate, 250)
self.assertEqual(bom_doc.operations[0].hour_rate, 250)
self.assertEqual(bom_doc.operations[1].hour_rate, 250)
+
def make_workstation(*args, **kwargs):
args = args if args else kwargs
if isinstance(args, tuple):
@@ -88,10 +104,7 @@ def make_workstation(*args, **kwargs):
workstation_name = args.workstation_name or args.workstation
if not frappe.db.exists("Workstation", workstation_name):
- doc = frappe.get_doc({
- "doctype": "Workstation",
- "workstation_name": workstation_name
- })
+ doc = frappe.get_doc({"doctype": "Workstation", "workstation_name": workstation_name})
doc.hour_rate_rent = args.get("hour_rate_rent")
doc.hour_rate_labour = args.get("hour_rate_labour")
doc.insert()
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py
index 4cfd410ac7..59e5318ab8 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation.py
@@ -19,14 +19,26 @@ from frappe.utils import (
from erpnext.support.doctype.issue.issue import get_holidays
-class WorkstationHolidayError(frappe.ValidationError): pass
-class NotInWorkingHoursError(frappe.ValidationError): pass
-class OverlapError(frappe.ValidationError): pass
+class WorkstationHolidayError(frappe.ValidationError):
+ pass
+
+
+class NotInWorkingHoursError(frappe.ValidationError):
+ pass
+
+
+class OverlapError(frappe.ValidationError):
+ pass
+
class Workstation(Document):
def validate(self):
- self.hour_rate = (flt(self.hour_rate_labour) + flt(self.hour_rate_electricity) +
- flt(self.hour_rate_consumable) + flt(self.hour_rate_rent))
+ self.hour_rate = (
+ flt(self.hour_rate_labour)
+ + flt(self.hour_rate_electricity)
+ + flt(self.hour_rate_consumable)
+ + flt(self.hour_rate_rent)
+ )
def on_update(self):
self.validate_overlap_for_operation_timings()
@@ -35,29 +47,41 @@ class Workstation(Document):
def validate_overlap_for_operation_timings(self):
"""Check if there is no overlap in setting Workstation Operating Hours"""
for d in self.get("working_hours"):
- existing = frappe.db.sql_list("""select idx from `tabWorkstation Working Hour`
+ existing = frappe.db.sql_list(
+ """select idx from `tabWorkstation Working Hour`
where parent = %s and name != %s
and (
(start_time between %s and %s) or
(end_time between %s and %s) or
(%s between start_time and end_time))
- """, (self.name, d.name, d.start_time, d.end_time, d.start_time, d.end_time, d.start_time))
+ """,
+ (self.name, d.name, d.start_time, d.end_time, d.start_time, d.end_time, d.start_time),
+ )
if existing:
- frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError)
+ frappe.throw(
+ _("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError
+ )
def update_bom_operation(self):
- bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation`
- where workstation = %s and parenttype = 'routing' """, self.name)
+ bom_list = frappe.db.sql(
+ """select DISTINCT parent from `tabBOM Operation`
+ where workstation = %s and parenttype = 'routing' """,
+ self.name,
+ )
for bom_no in bom_list:
- frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s
+ frappe.db.sql(
+ """update `tabBOM Operation` set hour_rate = %s
where parent = %s and workstation = %s""",
- (self.hour_rate, bom_no[0], self.name))
+ (self.hour_rate, bom_no[0], self.name),
+ )
def validate_workstation_holiday(self, schedule_date, skip_holiday_list_check=False):
- if not skip_holiday_list_check and (not self.holiday_list or
- cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))):
+ if not skip_holiday_list_check and (
+ not self.holiday_list
+ or cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))
+ ):
return schedule_date
if schedule_date in tuple(get_holidays(self.holiday_list)):
@@ -66,18 +90,25 @@ class Workstation(Document):
return schedule_date
+
@frappe.whitelist()
def get_default_holiday_list():
- return frappe.get_cached_value('Company', frappe.defaults.get_user_default("Company"), "default_holiday_list")
+ return frappe.get_cached_value(
+ "Company", frappe.defaults.get_user_default("Company"), "default_holiday_list"
+ )
+
def check_if_within_operating_hours(workstation, operation, from_datetime, to_datetime):
if from_datetime and to_datetime:
- if not cint(frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")):
+ if not cint(
+ frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")
+ ):
check_workstation_for_holiday(workstation, from_datetime, to_datetime)
if not cint(frappe.db.get_value("Manufacturing Settings", None, "allow_overtime")):
is_within_operating_hours(workstation, operation, from_datetime, to_datetime)
+
def is_within_operating_hours(workstation, operation, from_datetime, to_datetime):
operation_length = time_diff_in_seconds(to_datetime, from_datetime)
workstation = frappe.get_doc("Workstation", workstation)
@@ -87,21 +118,35 @@ def is_within_operating_hours(workstation, operation, from_datetime, to_datetime
for working_hour in workstation.working_hours:
if working_hour.start_time and working_hour.end_time:
- slot_length = (to_timedelta(working_hour.end_time or "") - to_timedelta(working_hour.start_time or "")).total_seconds()
+ slot_length = (
+ to_timedelta(working_hour.end_time or "") - to_timedelta(working_hour.start_time or "")
+ ).total_seconds()
if slot_length >= operation_length:
return
- frappe.throw(_("Operation {0} longer than any available working hours in workstation {1}, break down the operation into multiple operations").format(operation, workstation.name), NotInWorkingHoursError)
+ frappe.throw(
+ _(
+ "Operation {0} longer than any available working hours in workstation {1}, break down the operation into multiple operations"
+ ).format(operation, workstation.name),
+ NotInWorkingHoursError,
+ )
+
def check_workstation_for_holiday(workstation, from_datetime, to_datetime):
holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list")
if holiday_list and from_datetime and to_datetime:
applicable_holidays = []
- for d in frappe.db.sql("""select holiday_date from `tabHoliday` where parent = %s
+ for d in frappe.db.sql(
+ """select holiday_date from `tabHoliday` where parent = %s
and holiday_date between %s and %s """,
- (holiday_list, getdate(from_datetime), getdate(to_datetime))):
- applicable_holidays.append(formatdate(d[0]))
+ (holiday_list, getdate(from_datetime), getdate(to_datetime)),
+ ):
+ applicable_holidays.append(formatdate(d[0]))
if applicable_holidays:
- frappe.throw(_("Workstation is closed on the following dates as per Holiday List: {0}")
- .format(holiday_list) + "\n" + "\n".join(applicable_holidays), WorkstationHolidayError)
+ frappe.throw(
+ _("Workstation is closed on the following dates as per Holiday List: {0}").format(holiday_list)
+ + "\n"
+ + "\n".join(applicable_holidays),
+ WorkstationHolidayError,
+ )
diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
index 1fa14940a4..6d022216bd 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
@@ -3,17 +3,22 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'workstation',
- 'transactions': [
+ "fieldname": "workstation",
+ "transactions": [
+ {"label": _("Master"), "items": ["BOM", "Routing", "Operation"]},
{
- 'label': _('Master'),
- 'items': ['BOM', 'Routing', 'Operation']
+ "label": _("Transaction"),
+ "items": [
+ "Work Order",
+ "Job Card",
+ ],
},
- {
- 'label': _('Transaction'),
- 'items': ['Work Order', 'Job Card',]
- }
],
- 'disable_create_buttons': ['BOM', 'Routing', 'Operation',
- 'Work Order', 'Job Card',]
+ "disable_create_buttons": [
+ "BOM",
+ "Routing",
+ "Operation",
+ "Work Order",
+ "Job Card",
+ ],
}
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index 19a80ab407..c0affd9cad 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -11,30 +11,37 @@ def execute(filters=None):
get_data(filters, data)
return columns, data
+
def get_data(filters, data):
get_exploded_items(filters.bom, data)
+
def get_exploded_items(bom, data, indent=0, qty=1):
- exploded_items = frappe.get_all("BOM Item",
+ exploded_items = frappe.get_all(
+ "BOM Item",
filters={"parent": bom},
- fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
+ fields=["qty", "bom_no", "qty", "scrap", "item_code", "item_name", "description", "uom"],
+ )
for item in exploded_items:
print(item.bom_no, indent)
item["indent"] = indent
- data.append({
- 'item_code': item.item_code,
- 'item_name': item.item_name,
- 'indent': indent,
- 'bom_level': indent,
- 'bom': item.bom_no,
- 'qty': item.qty * qty,
- 'uom': item.uom,
- 'description': item.description,
- 'scrap': item.scrap
- })
+ data.append(
+ {
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "indent": indent,
+ "bom_level": indent,
+ "bom": item.bom_no,
+ "qty": item.qty * qty,
+ "uom": item.uom,
+ "description": item.description,
+ "scrap": item.scrap,
+ }
+ )
if item.bom_no:
- get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty)
+ get_exploded_items(item.bom_no, data, indent=indent + 1, qty=item.qty)
+
def get_columns():
return [
@@ -43,49 +50,13 @@ def get_columns():
"fieldtype": "Link",
"fieldname": "item_code",
"width": 300,
- "options": "Item"
- },
- {
- "label": "Item Name",
- "fieldtype": "data",
- "fieldname": "item_name",
- "width": 100
- },
- {
- "label": "BOM",
- "fieldtype": "Link",
- "fieldname": "bom",
- "width": 150,
- "options": "BOM"
- },
- {
- "label": "Qty",
- "fieldtype": "data",
- "fieldname": "qty",
- "width": 100
- },
- {
- "label": "UOM",
- "fieldtype": "data",
- "fieldname": "uom",
- "width": 100
- },
- {
- "label": "BOM Level",
- "fieldtype": "Int",
- "fieldname": "bom_level",
- "width": 100
- },
- {
- "label": "Standard Description",
- "fieldtype": "data",
- "fieldname": "description",
- "width": 150
- },
- {
- "label": "Scrap",
- "fieldtype": "data",
- "fieldname": "scrap",
- "width": 100
+ "options": "Item",
},
+ {"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100},
+ {"label": "BOM", "fieldtype": "Link", "fieldname": "bom", "width": 150, "options": "BOM"},
+ {"label": "Qty", "fieldtype": "data", "fieldname": "qty", "width": 100},
+ {"label": "UOM", "fieldtype": "data", "fieldname": "uom", "width": 100},
+ {"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
+ {"label": "Standard Description", "fieldtype": "data", "fieldname": "description", "width": 150},
+ {"label": "Scrap", "fieldtype": "data", "fieldname": "scrap", "width": 100},
]
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
index eda9eb9d70..92c69cf3e0 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -11,6 +11,7 @@ def execute(filters=None):
columns = get_columns(filters)
return columns, data
+
def get_data(filters):
bom_wise_data = {}
bom_data, report_data = [], []
@@ -24,11 +25,9 @@ def get_data(filters):
bom_data.append(d.name)
row.update(d)
else:
- row.update({
- "operation": d.operation,
- "workstation": d.workstation,
- "time_in_mins": d.time_in_mins
- })
+ row.update(
+ {"operation": d.operation, "workstation": d.workstation, "time_in_mins": d.time_in_mins}
+ )
# maintain BOM wise data for grouping such as:
# {"BOM A": [{Row1}, {Row2}], "BOM B": ...}
@@ -43,20 +42,25 @@ def get_data(filters):
return report_data
+
def get_filtered_data(filters):
bom = frappe.qb.DocType("BOM")
bom_ops = frappe.qb.DocType("BOM Operation")
bom_ops_query = (
frappe.qb.from_(bom)
- .join(bom_ops).on(bom.name == bom_ops.parent)
+ .join(bom_ops)
+ .on(bom.name == bom_ops.parent)
.select(
- bom.name, bom.item, bom.item_name, bom.uom,
- bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins
- ).where(
- (bom.docstatus == 1)
- & (bom.is_active == 1)
+ bom.name,
+ bom.item,
+ bom.item_name,
+ bom.uom,
+ bom_ops.operation,
+ bom_ops.workstation,
+ bom_ops.time_in_mins,
)
+ .where((bom.docstatus == 1) & (bom.is_active == 1))
)
if filters.get("item_code"):
@@ -66,18 +70,20 @@ def get_filtered_data(filters):
bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id")))
if filters.get("workstation"):
- bom_ops_query = bom_ops_query.where(
- bom_ops.workstation == filters.get("workstation")
- )
+ bom_ops_query = bom_ops_query.where(bom_ops.workstation == filters.get("workstation"))
bom_operation_data = bom_ops_query.run(as_dict=True)
return bom_operation_data
+
def get_bom_count(bom_data):
- data = frappe.get_all("BOM Item",
+ data = frappe.get_all(
+ "BOM Item",
fields=["count(name) as count", "bom_no"],
- filters= {"bom_no": ("in", bom_data)}, group_by = "bom_no")
+ filters={"bom_no": ("in", bom_data)},
+ group_by="bom_no",
+ )
bom_count = {}
for d in data:
@@ -85,58 +91,42 @@ def get_bom_count(bom_data):
return bom_count
+
def get_args():
- return frappe._dict({
- "name": "",
- "item": "",
- "item_name": "",
- "uom": ""
- })
+ return frappe._dict({"name": "", "item": "", "item_name": "", "uom": ""})
+
def get_columns(filters):
- return [{
- "label": _("BOM ID"),
- "options": "BOM",
- "fieldname": "name",
- "fieldtype": "Link",
- "width": 220
- }, {
- "label": _("Item Code"),
- "options": "Item",
- "fieldname": "item",
- "fieldtype": "Link",
- "width": 150
- }, {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 110
- }, {
- "label": _("UOM"),
- "options": "UOM",
- "fieldname": "uom",
- "fieldtype": "Link",
- "width": 100
- }, {
- "label": _("Operation"),
- "options": "Operation",
- "fieldname": "operation",
- "fieldtype": "Link",
- "width": 140
- }, {
- "label": _("Workstation"),
- "options": "Workstation",
- "fieldname": "workstation",
- "fieldtype": "Link",
- "width": 110
- }, {
- "label": _("Time (In Mins)"),
- "fieldname": "time_in_mins",
- "fieldtype": "Float",
- "width": 120
- }, {
- "label": _("Sub-assembly BOM Count"),
- "fieldname": "used_as_subassembly_items",
- "fieldtype": "Int",
- "width": 200
- }]
+ return [
+ {"label": _("BOM ID"), "options": "BOM", "fieldname": "name", "fieldtype": "Link", "width": 220},
+ {
+ "label": _("Item Code"),
+ "options": "Item",
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "width": 150,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 110},
+ {"label": _("UOM"), "options": "UOM", "fieldname": "uom", "fieldtype": "Link", "width": 100},
+ {
+ "label": _("Operation"),
+ "options": "Operation",
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "width": 140,
+ },
+ {
+ "label": _("Workstation"),
+ "options": "Workstation",
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "width": 110,
+ },
+ {"label": _("Time (In Mins)"), "fieldname": "time_in_mins", "fieldtype": "Float", "width": 120},
+ {
+ "label": _("Sub-assembly BOM Count"),
+ "fieldname": "used_as_subassembly_items",
+ "fieldtype": "Int",
+ "width": 200,
+ },
+ ]
diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
index 2693352324..933be3e014 100644
--- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
+++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
@@ -23,14 +23,24 @@ def execute(filters=None):
summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details))
return columns, summ_data
+
def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
to_build = row.to_build if row.to_build > 0 else 0
diff_qty = to_build - reqd_qty
- return [row.item_code, row.description,
- comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer', []), add_quotes=False),
- comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer_part', []), add_quotes=False),
- row.actual_qty, str(to_build),
- reqd_qty, diff_qty, last_pur_price]
+ return [
+ row.item_code,
+ row.description,
+ comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer", []), add_quotes=False),
+ comma_and(
+ manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False
+ ),
+ row.actual_qty,
+ str(to_build),
+ reqd_qty,
+ diff_qty,
+ last_pur_price,
+ ]
+
def get_columns():
"""return columns"""
@@ -41,12 +51,13 @@ def get_columns():
_("Manufacturer Part Number") + "::250",
_("Qty") + ":Float:50",
_("Stock Qty") + ":Float:100",
- _("Reqd Qty")+ ":Float:100",
- _("Diff Qty")+ ":Float:100",
- _("Last Purchase Price")+ ":Float:100",
+ _("Reqd Qty") + ":Float:100",
+ _("Diff Qty") + ":Float:100",
+ _("Last Purchase Price") + ":Float:100",
]
return columns
+
def get_bom_stock(filters):
conditions = ""
bom = filters.get("bom")
@@ -59,18 +70,23 @@ def get_bom_stock(filters):
qty_field = "stock_qty"
if filters.get("warehouse"):
- warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
+ )
if warehouse_details:
- conditions += " and exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft,
- warehouse_details.rgt)
+ conditions += (
+ " and exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ )
else:
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
else:
conditions += ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
bom_item.item_code,
bom_item.description,
@@ -86,14 +102,21 @@ def get_bom_stock(filters):
WHERE
bom_item.parent = '{bom}' and bom_item.parenttype='BOM'
- GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
+ GROUP BY bom_item.item_code""".format(
+ qty_field=qty_field, table=table, conditions=conditions, bom=bom
+ ),
+ as_dict=1,
+ )
+
def get_manufacturer_records():
- details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "item_code"])
+ details = frappe.get_all(
+ "Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"]
+ )
manufacture_details = frappe._dict()
for detail in details:
- dic = manufacture_details.setdefault(detail.get('item_code'), {})
- dic.setdefault('manufacturer', []).append(detail.get('manufacturer'))
- dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no'))
+ dic = manufacture_details.setdefault(detail.get("item_code"), {})
+ dic.setdefault("manufacturer", []).append(detail.get("manufacturer"))
+ dic.setdefault("manufacturer_part", []).append(detail.get("manufacturer_part_no"))
return manufacture_details
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index fa94391261..34e9826305 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -7,7 +7,8 @@ from frappe import _
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns()
@@ -15,6 +16,7 @@ def execute(filters=None):
return columns, data
+
def get_columns():
"""return columns"""
columns = [
@@ -29,6 +31,7 @@ def get_columns():
return columns
+
def get_bom_stock(filters):
conditions = ""
bom = filters.get("bom")
@@ -37,25 +40,30 @@ def get_bom_stock(filters):
qty_field = "stock_qty"
qty_to_produce = filters.get("qty_to_produce", 1)
- if int(qty_to_produce) <= 0:
+ if int(qty_to_produce) <= 0:
frappe.throw(_("Quantity to Produce can not be less than Zero"))
if filters.get("show_exploded_view"):
table = "`tabBOM Explosion Item`"
if filters.get("warehouse"):
- warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
+ )
if warehouse_details:
- conditions += " and exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft,
- warehouse_details.rgt)
+ conditions += (
+ " and exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ )
else:
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
else:
conditions += ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
bom_item.item_code,
bom_item.description ,
@@ -74,9 +82,10 @@ def get_bom_stock(filters):
bom_item.parent = {bom} and bom_item.parenttype='BOM'
GROUP BY bom_item.item_code""".format(
- qty_field=qty_field,
- table=table,
- conditions=conditions,
- bom=frappe.db.escape(bom),
- qty_to_produce=qty_to_produce or 1)
- )
+ qty_field=qty_field,
+ table=table,
+ conditions=conditions,
+ bom=frappe.db.escape(bom),
+ qty_to_produce=qty_to_produce or 1,
+ )
+ )
diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
index a5ae43e9ad..3fe2198966 100644
--- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
+++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
@@ -12,98 +12,99 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_columns(filters):
- columns = [{
+ columns = [
+ {
"label": _("Work Order"),
"fieldname": "work_order",
"fieldtype": "Link",
"options": "Work Order",
- "width": 120
- }]
-
- if not filters.get('bom_no'):
- columns.extend([
- {
- "label": _("BOM No"),
- "fieldname": "bom_no",
- "fieldtype": "Link",
- "options": "BOM",
- "width": 180
- }
- ])
-
- columns.extend([
- {
- "label": _("Finished Good"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- },
- {
- "label": _("Ordered Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Produced Qty"),
- "fieldname": "produced_qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Raw Material"),
- "fieldname": "raw_material_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- },
- {
- "label": _("Required Qty"),
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Consumed Qty"),
- "fieldname": "consumed_qty",
- "fieldtype": "Float",
- "width": 120
+ "width": 120,
}
- ])
+ ]
+
+ if not filters.get("bom_no"):
+ columns.extend(
+ [
+ {
+ "label": _("BOM No"),
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "options": "BOM",
+ "width": 180,
+ }
+ ]
+ )
+
+ columns.extend(
+ [
+ {
+ "label": _("Finished Good"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {"label": _("Ordered Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 120},
+ {"label": _("Produced Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": 120},
+ {
+ "label": _("Raw Material"),
+ "fieldname": "raw_material_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 120},
+ {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120},
+ ]
+ )
return columns
+
def get_data(filters):
cond = "1=1"
- if filters.get('bom_no') and not filters.get('work_order'):
- cond += " and bom_no = '%s'" % filters.get('bom_no')
+ if filters.get("bom_no") and not filters.get("work_order"):
+ cond += " and bom_no = '%s'" % filters.get("bom_no")
- if filters.get('work_order'):
- cond += " and name = '%s'" % filters.get('work_order')
+ if filters.get("work_order"):
+ cond += " and name = '%s'" % filters.get("work_order")
results = []
- for d in frappe.db.sql(""" select name as work_order, qty, produced_qty, production_item, bom_no
- from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format(cond), as_dict=1):
+ for d in frappe.db.sql(
+ """ select name as work_order, qty, produced_qty, production_item, bom_no
+ from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format(
+ cond
+ ),
+ as_dict=1,
+ ):
results.append(d)
- for data in frappe.get_all('Work Order Item', fields=["item_code as raw_material_code",
- "required_qty", "consumed_qty"], filters={'parent': d.work_order, 'parenttype': 'Work Order'}):
+ for data in frappe.get_all(
+ "Work Order Item",
+ fields=["item_code as raw_material_code", "required_qty", "consumed_qty"],
+ filters={"parent": d.work_order, "parenttype": "Work Order"},
+ ):
results.append(data)
return results
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_work_orders(doctype, txt, searchfield, start, page_len, filters):
cond = "1=1"
- if filters.get('bom_no'):
- cond += " and bom_no = '%s'" % filters.get('bom_no')
+ if filters.get("bom_no"):
+ cond += " and bom_no = '%s'" % filters.get("bom_no")
- return frappe.db.sql("""select name from `tabWork Order`
+ return frappe.db.sql(
+ """select name from `tabWork Order`
where name like %(name)s and {0} and produced_qty > qty and docstatus = 1
- order by name limit {1}, {2}""".format(cond, start, page_len),{
- 'name': "%%%s%%" % txt
- }, as_list=1)
+ order by name limit {1}, {2}""".format(
+ cond, start, page_len
+ ),
+ {"name": "%%%s%%" % txt},
+ as_list=1,
+ )
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
index 88b21170e8..481fe51d73 100644
--- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
@@ -11,58 +11,77 @@ def execute(filters=None):
def get_data(report_filters):
data = []
- operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1})
+ operations = frappe.get_all("Operation", filters={"is_corrective_operation": 1})
if operations:
- if report_filters.get('operation'):
- operations = [report_filters.get('operation')]
+ if report_filters.get("operation"):
+ operations = [report_filters.get("operation")]
else:
operations = [d.name for d in operations]
job_card = frappe.qb.DocType("Job Card")
- operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_('operating_cost')
- item_code = (job_card.production_item).as_('item_code')
+ operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_(
+ "operating_cost"
+ )
+ item_code = (job_card.production_item).as_("item_code")
- query = (frappe.qb
- .from_(job_card)
- .select(job_card.name, job_card.work_order, item_code, job_card.item_name,
- job_card.operation, job_card.serial_no, job_card.batch_no,
- job_card.workstation, job_card.total_time_in_mins, job_card.hour_rate,
- operating_cost)
- .where(
- (job_card.docstatus == 1)
- & (job_card.is_corrective_job_card == 1))
- .groupby(job_card.name)
- )
+ query = (
+ frappe.qb.from_(job_card)
+ .select(
+ job_card.name,
+ job_card.work_order,
+ item_code,
+ job_card.item_name,
+ job_card.operation,
+ job_card.serial_no,
+ job_card.batch_no,
+ job_card.workstation,
+ job_card.total_time_in_mins,
+ job_card.hour_rate,
+ operating_cost,
+ )
+ .where((job_card.docstatus == 1) & (job_card.is_corrective_job_card == 1))
+ .groupby(job_card.name)
+ )
query = append_filters(query, report_filters, operations, job_card)
data = query.run(as_dict=True)
return data
-def append_filters(query, report_filters, operations, job_card):
- """Append optional filters to query builder. """
- for field in ("name", "work_order", "operation", "workstation",
- "company", "serial_no", "batch_no", "production_item"):
+def append_filters(query, report_filters, operations, job_card):
+ """Append optional filters to query builder."""
+
+ for field in (
+ "name",
+ "work_order",
+ "operation",
+ "workstation",
+ "company",
+ "serial_no",
+ "batch_no",
+ "production_item",
+ ):
if report_filters.get(field):
- if field == 'serial_no':
- query = query.where(job_card[field].like('%{}%'.format(report_filters.get(field))))
- elif field == 'operation':
+ if field == "serial_no":
+ query = query.where(job_card[field].like("%{}%".format(report_filters.get(field))))
+ elif field == "operation":
query = query.where(job_card[field].isin(operations))
else:
query = query.where(job_card[field] == report_filters.get(field))
- if report_filters.get('from_date') or report_filters.get('to_date'):
+ if report_filters.get("from_date") or report_filters.get("to_date"):
job_card_time_log = frappe.qb.DocType("Job Card Time Log")
query = query.join(job_card_time_log).on(job_card.name == job_card_time_log.parent)
- if report_filters.get('from_date'):
- query = query.where(job_card_time_log.from_time >= report_filters.get('from_date'))
- if report_filters.get('to_date'):
- query = query.where(job_card_time_log.to_time <= report_filters.get('to_date'))
+ if report_filters.get("from_date"):
+ query = query.where(job_card_time_log.from_time >= report_filters.get("from_date"))
+ if report_filters.get("to_date"):
+ query = query.where(job_card_time_log.to_time <= report_filters.get("to_date"))
return query
+
def get_columns(filters):
return [
{
@@ -70,64 +89,49 @@ def get_columns(filters):
"fieldtype": "Link",
"fieldname": "name",
"options": "Job Card",
- "width": "120"
+ "width": "120",
},
{
"label": _("Work Order"),
"fieldtype": "Link",
"fieldname": "work_order",
"options": "Work Order",
- "width": "100"
+ "width": "100",
},
{
"label": _("Item Code"),
"fieldtype": "Link",
"fieldname": "item_code",
"options": "Item",
- "width": "100"
- },
- {
- "label": _("Item Name"),
- "fieldtype": "Data",
- "fieldname": "item_name",
- "width": "100"
+ "width": "100",
},
+ {"label": _("Item Name"), "fieldtype": "Data", "fieldname": "item_name", "width": "100"},
{
"label": _("Operation"),
"fieldtype": "Link",
"fieldname": "operation",
"options": "Operation",
- "width": "100"
- },
- {
- "label": _("Serial No"),
- "fieldtype": "Data",
- "fieldname": "serial_no",
- "width": "100"
- },
- {
- "label": _("Batch No"),
- "fieldtype": "Data",
- "fieldname": "batch_no",
- "width": "100"
+ "width": "100",
},
+ {"label": _("Serial No"), "fieldtype": "Data", "fieldname": "serial_no", "width": "100"},
+ {"label": _("Batch No"), "fieldtype": "Data", "fieldname": "batch_no", "width": "100"},
{
"label": _("Workstation"),
"fieldtype": "Link",
"fieldname": "workstation",
"options": "Workstation",
- "width": "100"
+ "width": "100",
},
{
"label": _("Operating Cost"),
"fieldtype": "Currency",
"fieldname": "operating_cost",
- "width": "150"
+ "width": "150",
},
{
"label": _("Total Time (in Mins)"),
"fieldtype": "Float",
"fieldname": "total_time_in_mins",
- "width": "150"
- }
+ "width": "150",
+ },
]
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
index 2c515d1b36..80a1564867 100644
--- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
@@ -14,10 +14,20 @@ def execute(filters=None):
chart_data = get_chart_data(data, filters)
return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {}
- fields = ["name", "workstation", "operator", "from_time", "to_time", "downtime", "stop_reason", "remarks"]
+ fields = [
+ "name",
+ "workstation",
+ "operator",
+ "from_time",
+ "to_time",
+ "downtime",
+ "stop_reason",
+ "remarks",
+ ]
query_filters["from_time"] = (">=", filters.get("from_date"))
query_filters["to_time"] = ("<=", filters.get("to_date"))
@@ -25,13 +35,14 @@ def get_data(filters):
if filters.get("workstation"):
query_filters["workstation"] = filters.get("workstation")
- data = frappe.get_all("Downtime Entry", fields= fields, filters=query_filters) or []
+ data = frappe.get_all("Downtime Entry", fields=fields, filters=query_filters) or []
for d in data:
if d.downtime:
d.downtime = d.downtime / 60
return data
+
def get_chart_data(data, columns):
labels = sorted(list(set([d.workstation for d in data])))
@@ -47,17 +58,13 @@ def get_chart_data(data, columns):
datasets.append(workstation_wise_data.get(label, 0))
chart = {
- "data": {
- "labels": labels,
- "datasets": [
- {"name": "Machine Downtime", "values": datasets}
- ]
- },
- "type": "bar"
+ "data": {"labels": labels, "datasets": [{"name": "Machine Downtime", "values": datasets}]},
+ "type": "bar",
}
return chart
+
def get_columns(filters):
return [
{
@@ -65,50 +72,25 @@ def get_columns(filters):
"fieldname": "name",
"fieldtype": "Link",
"options": "Downtime Entry",
- "width": 100
+ "width": 100,
},
{
"label": _("Machine"),
"fieldname": "workstation",
"fieldtype": "Link",
"options": "Workstation",
- "width": 100
+ "width": 100,
},
{
"label": _("Operator"),
"fieldname": "operator",
"fieldtype": "Link",
"options": "Employee",
- "width": 130
+ "width": 130,
},
- {
- "label": _("From Time"),
- "fieldname": "from_time",
- "fieldtype": "Datetime",
- "width": 160
- },
- {
- "label": _("To Time"),
- "fieldname": "to_time",
- "fieldtype": "Datetime",
- "width": 160
- },
- {
- "label": _("Downtime (In Hours)"),
- "fieldname": "downtime",
- "fieldtype": "Float",
- "width": 150
- },
- {
- "label": _("Stop Reason"),
- "fieldname": "stop_reason",
- "fieldtype": "Data",
- "width": 220
- },
- {
- "label": _("Remarks"),
- "fieldname": "remarks",
- "fieldtype": "Text",
- "width": 100
- }
+ {"label": _("From Time"), "fieldname": "from_time", "fieldtype": "Datetime", "width": 160},
+ {"label": _("To Time"), "fieldname": "to_time", "fieldtype": "Datetime", "width": 160},
+ {"label": _("Downtime (In Hours)"), "fieldname": "downtime", "fieldtype": "Float", "width": 150},
+ {"label": _("Stop Reason"), "fieldname": "stop_reason", "fieldtype": "Data", "width": 220},
+ {"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Text", "width": 100},
]
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
index 26b3359dee..7500744c22 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -14,6 +14,7 @@ from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
def execute(filters=None):
return ForecastingReport(filters).execute_report()
+
class ExponentialSmoothingForecast(object):
def forecast_future_data(self):
for key, value in self.period_wise_data.items():
@@ -26,24 +27,22 @@ class ExponentialSmoothingForecast(object):
elif forecast_data:
previous_period_data = forecast_data[-1]
- value[forecast_key] = (previous_period_data[1] +
- flt(self.filters.smoothing_constant) * (
- flt(previous_period_data[0]) - flt(previous_period_data[1])
- )
+ value[forecast_key] = previous_period_data[1] + flt(self.filters.smoothing_constant) * (
+ flt(previous_period_data[0]) - flt(previous_period_data[1])
)
if value.get(forecast_key):
# will be use to forecaset next period
forecast_data.append([value.get(period.key), value.get(forecast_key)])
+
class ForecastingReport(ExponentialSmoothingForecast):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
self.data = []
self.doctype = self.filters.based_on_document
self.child_doctype = self.doctype + " Item"
- self.based_on_field = ("qty"
- if self.filters.based_on_field == "Qty" else "amount")
+ self.based_on_field = "qty" if self.filters.based_on_field == "Qty" else "amount"
self.fieldtype = "Float" if self.based_on_field == "qty" else "Currency"
self.company_currency = erpnext.get_company_currency(self.filters.company)
@@ -63,8 +62,15 @@ class ForecastingReport(ExponentialSmoothingForecast):
self.period_wise_data = {}
from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1)
- self.period_list = get_period_list(from_date, self.filters.to_date,
- from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True)
+ self.period_list = get_period_list(
+ from_date,
+ self.filters.to_date,
+ from_date,
+ self.filters.to_date,
+ "Date Range",
+ self.filters.periodicity,
+ ignore_fiscal_year=True,
+ )
order_data = self.get_data_for_forecast() or []
@@ -76,8 +82,10 @@ class ForecastingReport(ExponentialSmoothingForecast):
period_data = self.period_wise_data[key]
for period in self.period_list:
# check if posting date is within the period
- if (entry.posting_date >= period.from_date and entry.posting_date <= period.to_date):
- period_data[period.key] = period_data.get(period.key, 0.0) + flt(entry.get(self.based_on_field))
+ if entry.posting_date >= period.from_date and entry.posting_date <= period.to_date:
+ period_data[period.key] = period_data.get(period.key, 0.0) + flt(
+ entry.get(self.based_on_field)
+ )
for key, value in self.period_wise_data.items():
list_of_period_value = [value.get(p.key, 0) for p in self.period_list]
@@ -90,12 +98,12 @@ class ForecastingReport(ExponentialSmoothingForecast):
def get_data_for_forecast(self):
cond = ""
if self.filters.item_code:
- cond = " AND soi.item_code = %s" %(frappe.db.escape(self.filters.item_code))
+ cond = " AND soi.item_code = %s" % (frappe.db.escape(self.filters.item_code))
warehouses = []
if self.filters.warehouse:
warehouses = get_child_warehouses(self.filters.warehouse)
- cond += " AND soi.warehouse in ({})".format(','.join(['%s'] * len(warehouses)))
+ cond += " AND soi.warehouse in ({})".format(",".join(["%s"] * len(warehouses)))
input_data = [self.filters.from_date, self.filters.company]
if warehouses:
@@ -103,7 +111,8 @@ class ForecastingReport(ExponentialSmoothingForecast):
date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
so.{date_field} as posting_date, soi.item_code, soi.warehouse,
soi.item_name, soi.stock_qty as qty, soi.base_amount as amount
@@ -112,23 +121,27 @@ class ForecastingReport(ExponentialSmoothingForecast):
WHERE
so.docstatus = 1 AND so.name = soi.parent AND
so.{date_field} < %s AND so.company = %s {cond}
- """.format(doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond),
- tuple(input_data), as_dict=1)
+ """.format(
+ doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond
+ ),
+ tuple(input_data),
+ as_dict=1,
+ )
def prepare_final_data(self):
self.data = []
- if not self.period_wise_data: return
+ if not self.period_wise_data:
+ return
for key in self.period_wise_data:
self.data.append(self.period_wise_data.get(key))
def add_total(self):
- if not self.data: return
+ if not self.data:
+ return
- total_row = {
- "item_code": _(frappe.bold("Total Quantity"))
- }
+ total_row = {"item_code": _(frappe.bold("Total Quantity"))}
for value in self.data:
for period in self.period_list:
@@ -145,43 +158,52 @@ class ForecastingReport(ExponentialSmoothingForecast):
self.data.append(total_row)
def get_columns(self):
- columns = [{
- "label": _("Item Code"),
- "options": "Item",
- "fieldname": "item_code",
- "fieldtype": "Link",
- "width": 130
- }, {
- "label": _("Warehouse"),
- "options": "Warehouse",
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "width": 130
- }]
+ columns = [
+ {
+ "label": _("Item Code"),
+ "options": "Item",
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "width": 130,
+ },
+ {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 130,
+ },
+ ]
- width = 180 if self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"] else 100
+ width = 180 if self.filters.periodicity in ["Yearly", "Half-Yearly", "Quarterly"] else 100
for period in self.period_list:
- if (self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"]
- or period.from_date >= getdate(self.filters.from_date)):
+ if self.filters.periodicity in [
+ "Yearly",
+ "Half-Yearly",
+ "Quarterly",
+ ] or period.from_date >= getdate(self.filters.from_date):
forecast_key = period.key
label = _(period.label)
if period.from_date >= getdate(self.filters.from_date):
- forecast_key = 'forecast_' + period.key
+ forecast_key = "forecast_" + period.key
label = _(period.label) + " " + _("(Forecast)")
- columns.append({
- "label": label,
- "fieldname": forecast_key,
- "fieldtype": self.fieldtype,
- "width": width,
- "default": 0.0
- })
+ columns.append(
+ {
+ "label": label,
+ "fieldname": forecast_key,
+ "fieldtype": self.fieldtype,
+ "width": width,
+ "default": 0.0,
+ }
+ )
return columns
def get_chart_data(self):
- if not self.data: return
+ if not self.data:
+ return
labels = []
self.total_demand = []
@@ -206,40 +228,35 @@ class ForecastingReport(ExponentialSmoothingForecast):
"data": {
"labels": labels,
"datasets": [
- {
- "name": "Demand",
- "values": self.total_demand
- },
- {
- "name": "Forecast",
- "values": self.total_forecast
- }
- ]
+ {"name": "Demand", "values": self.total_demand},
+ {"name": "Forecast", "values": self.total_forecast},
+ ],
},
- "type": "line"
+ "type": "line",
}
def get_summary_data(self):
- if not self.data: return
+ if not self.data:
+ return
return [
{
"value": sum(self.total_demand),
"label": _("Total Demand (Past Data)"),
"currency": self.company_currency,
- "datatype": self.fieldtype
+ "datatype": self.fieldtype,
},
{
"value": sum(self.total_history_forecast),
"label": _("Total Forecast (Past Data)"),
"currency": self.company_currency,
- "datatype": self.fieldtype
+ "datatype": self.fieldtype,
},
{
"value": sum(self.total_future_forecast),
"indicator": "Green",
"label": _("Total Forecast (Future Data)"),
"currency": self.company_currency,
- "datatype": self.fieldtype
- }
+ "datatype": self.fieldtype,
+ },
]
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
index 4046bb12b8..a86c7a47c3 100644
--- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
@@ -16,23 +16,34 @@ def execute(filters=None):
chart_data = get_chart_data(data, filters)
return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {
"docstatus": ("<", 2),
- "posting_date": ("between", [filters.from_date, filters.to_date])
+ "posting_date": ("between", [filters.from_date, filters.to_date]),
}
- fields = ["name", "status", "work_order", "production_item", "item_name", "posting_date",
- "total_completed_qty", "workstation", "operation", "total_time_in_mins"]
+ fields = [
+ "name",
+ "status",
+ "work_order",
+ "production_item",
+ "item_name",
+ "posting_date",
+ "total_completed_qty",
+ "workstation",
+ "operation",
+ "total_time_in_mins",
+ ]
for field in ["work_order", "workstation", "operation", "company"]:
if filters.get(field):
query_filters[field] = ("in", filters.get(field))
- data = frappe.get_all("Job Card",
- fields= fields, filters=query_filters)
+ data = frappe.get_all("Job Card", fields=fields, filters=query_filters)
- if not data: return []
+ if not data:
+ return []
job_cards = [d.name for d in data]
@@ -42,9 +53,12 @@ def get_data(filters):
}
job_card_time_details = {}
- for job_card_data in frappe.get_all("Job Card Time Log",
+ for job_card_data in frappe.get_all(
+ "Job Card Time Log",
fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"],
- filters=job_card_time_filter, group_by="parent"):
+ filters=job_card_time_filter,
+ group_by="parent",
+ ):
job_card_time_details[job_card_data.parent] = job_card_data
res = []
@@ -60,6 +74,7 @@ def get_data(filters):
return res
+
def get_chart_data(job_card_details, filters):
labels, periodic_data = prepare_chart_data(job_card_details, filters)
@@ -73,23 +88,15 @@ def get_chart_data(job_card_details, filters):
datasets.append({"name": "Open", "values": open_job_cards})
datasets.append({"name": "Completed", "values": completed})
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- },
- "type": "bar"
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"}
return chart
+
def prepare_chart_data(job_card_details, filters):
labels = []
- periodic_data = {
- "Open": {},
- "Completed": {}
- }
+ periodic_data = {"Open": {}, "Completed": {}}
filters.range = "Monthly"
@@ -110,6 +117,7 @@ def prepare_chart_data(job_card_details, filters):
return labels, periodic_data
+
def get_columns(filters):
columns = [
{
@@ -117,84 +125,62 @@ def get_columns(filters):
"fieldname": "name",
"fieldtype": "Link",
"options": "Job Card",
- "width": 100
- },
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 100
+ "width": 100,
},
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
]
if not filters.get("status"):
columns.append(
- {
- "label": _("Status"),
- "fieldname": "status",
- "width": 100
- },
+ {"label": _("Status"), "fieldname": "status", "width": 100},
)
- columns.extend([
- {
- "label": _("Work Order"),
- "fieldname": "work_order",
- "fieldtype": "Link",
- "options": "Work Order",
- "width": 100
- },
- {
- "label": _("Production Item"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 110
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Workstation"),
- "fieldname": "workstation",
- "fieldtype": "Link",
- "options": "Workstation",
- "width": 110
- },
- {
- "label": _("Operation"),
- "fieldname": "operation",
- "fieldtype": "Link",
- "options": "Operation",
- "width": 110
- },
- {
- "label": _("Total Completed Qty"),
- "fieldname": "total_completed_qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("From Time"),
- "fieldname": "from_time",
- "fieldtype": "Datetime",
- "width": 120
- },
- {
- "label": _("To Time"),
- "fieldname": "to_time",
- "fieldtype": "Datetime",
- "width": 120
- },
- {
- "label": _("Time Required (In Mins)"),
- "fieldname": "total_time_in_mins",
- "fieldtype": "Float",
- "width": 100
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Work Order"),
+ "fieldname": "work_order",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100,
+ },
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 110,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+ {
+ "label": _("Workstation"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 110,
+ },
+ {
+ "label": _("Operation"),
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "options": "Operation",
+ "width": 110,
+ },
+ {
+ "label": _("Total Completed Qty"),
+ "fieldname": "total_completed_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ },
+ {"label": _("From Time"), "fieldname": "from_time", "fieldtype": "Datetime", "width": 120},
+ {"label": _("To Time"), "fieldname": "to_time", "fieldtype": "Datetime", "width": 120},
+ {
+ "label": _("Time Required (In Mins)"),
+ "fieldname": "total_time_in_mins",
+ "fieldtype": "Float",
+ "width": 100,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
index 9b544dafa5..b10e643422 100644
--- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
+++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
@@ -12,87 +12,71 @@ Data = List[Row]
Columns = List[Dict[str, str]]
QueryArgs = Dict[str, str]
+
def execute(filters: Filters) -> Tuple[Columns, Data]:
columns = get_columns()
data = get_data(filters)
return columns, data
+
def get_data(filters: Filters) -> Data:
query_args = get_query_args(filters)
data = run_query(query_args)
update_data_with_total_pl_value(data)
return data
+
def get_columns() -> Columns:
return [
{
- 'label': _('Work Order'),
- 'fieldname': 'name',
- 'fieldtype': 'Link',
- 'options': 'Work Order',
- 'width': '200'
+ "label": _("Work Order"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": "200",
},
{
- 'label': _('Item'),
- 'fieldname': 'production_item',
- 'fieldtype': 'Link',
- 'options': 'Item',
- 'width': '100'
+ "label": _("Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": "100",
},
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": "100"},
{
- 'label': _('Status'),
- 'fieldname': 'status',
- 'fieldtype': 'Data',
- 'width': '100'
+ "label": _("Manufactured Qty"),
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "width": "150",
},
+ {"label": _("Loss Qty"), "fieldname": "process_loss_qty", "fieldtype": "Float", "width": "150"},
{
- 'label': _('Manufactured Qty'),
- 'fieldname': 'produced_qty',
- 'fieldtype': 'Float',
- 'width': '150'
+ "label": _("Actual Manufactured Qty"),
+ "fieldname": "actual_produced_qty",
+ "fieldtype": "Float",
+ "width": "150",
},
+ {"label": _("Loss Value"), "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150"},
+ {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"},
{
- 'label': _('Loss Qty'),
- 'fieldname': 'process_loss_qty',
- 'fieldtype': 'Float',
- 'width': '150'
+ "label": _("Raw Material Value"),
+ "fieldname": "total_rm_value",
+ "fieldtype": "Float",
+ "width": "150",
},
- {
- 'label': _('Actual Manufactured Qty'),
- 'fieldname': 'actual_produced_qty',
- 'fieldtype': 'Float',
- 'width': '150'
- },
- {
- 'label': _('Loss Value'),
- 'fieldname': 'total_pl_value',
- 'fieldtype': 'Float',
- 'width': '150'
- },
- {
- 'label': _('FG Value'),
- 'fieldname': 'total_fg_value',
- 'fieldtype': 'Float',
- 'width': '150'
- },
- {
- 'label': _('Raw Material Value'),
- 'fieldname': 'total_rm_value',
- 'fieldtype': 'Float',
- 'width': '150'
- }
]
+
def get_query_args(filters: Filters) -> QueryArgs:
query_args = {}
query_args.update(filters)
- query_args.update(
- get_filter_conditions(filters)
- )
+ query_args.update(get_filter_conditions(filters))
return query_args
+
def run_query(query_args: QueryArgs) -> Data:
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
wo.name, wo.status, wo.production_item, wo.qty,
wo.produced_qty, wo.process_loss_qty,
@@ -111,23 +95,26 @@ def run_query(query_args: QueryArgs) -> Data:
{work_order_filter}
GROUP BY
se.work_order
- """.format(**query_args), query_args, as_dict=1)
+ """.format(
+ **query_args
+ ),
+ query_args,
+ as_dict=1,
+ )
+
def update_data_with_total_pl_value(data: Data) -> None:
for row in data:
- value_per_unit_fg = row['total_fg_value'] / row['actual_produced_qty']
- row['total_pl_value'] = row['process_loss_qty'] * value_per_unit_fg
+ value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"]
+ row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg
+
def get_filter_conditions(filters: Filters) -> QueryArgs:
filter_conditions = dict(item_filter="", work_order_filter="")
if "item" in filters:
production_item = filters.get("item")
- filter_conditions.update(
- {"item_filter": f"AND wo.production_item='{production_item}'"}
- )
+ filter_conditions.update({"item_filter": f"AND wo.production_item='{production_item}'"})
if "work_order" in filters:
work_order_name = filters.get("work_order")
- filter_conditions.update(
- {"work_order_filter": f"AND wo.name='{work_order_name}'"}
- )
+ filter_conditions.update({"work_order_filter": f"AND wo.name='{work_order_name}'"})
return filter_conditions
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py
index d4743d3a8e..12b5d19ba8 100644
--- a/erpnext/manufacturing/report/production_analytics/production_analytics.py
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py
@@ -12,16 +12,11 @@ from erpnext.stock.report.stock_analytics.stock_analytics import get_period, get
def execute(filters=None):
columns = get_columns(filters)
data, chart = get_data(filters, columns)
- return columns, data, None , chart
+ return columns, data, None, chart
+
def get_columns(filters):
- columns =[
- {
- "label": _("Status"),
- "fieldname": "Status",
- "fieldtype": "Data",
- "width": 140
- }]
+ columns = [{"label": _("Status"), "fieldname": "Status", "fieldtype": "Data", "width": 140}]
ranges = get_period_date_ranges(filters)
@@ -29,22 +24,20 @@ def get_columns(filters):
period = get_period(end_date, filters)
- columns.append({
- "label": _(period),
- "fieldname": scrub(period),
- "fieldtype": "Float",
- "width": 120
- })
+ columns.append(
+ {"label": _(period), "fieldname": scrub(period), "fieldtype": "Float", "width": 120}
+ )
return columns
+
def get_periodic_data(filters, entry):
periodic_data = {
"All Work Orders": {},
"Not Started": {},
"Overdue": {},
"Pending": {},
- "Completed": {}
+ "Completed": {},
}
ranges = get_period_date_ranges(filters)
@@ -52,34 +45,37 @@ def get_periodic_data(filters, entry):
for from_date, end_date in ranges:
period = get_period(end_date, filters)
for d in entry:
- if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date) :
+ if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date):
periodic_data = update_periodic_data(periodic_data, "All Work Orders", period)
- if d.status == 'Completed':
- if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date):
+ if d.status == "Completed":
+ if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(
+ from_date
+ ):
periodic_data = update_periodic_data(periodic_data, "Completed", period)
- elif getdate(d.actual_start_date) < getdate(from_date) :
+ elif getdate(d.actual_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Pending", period)
- elif getdate(d.planned_start_date) < getdate(from_date) :
+ elif getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
- elif d.status == 'In Process':
- if getdate(d.actual_start_date) < getdate(from_date) :
+ elif d.status == "In Process":
+ if getdate(d.actual_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Pending", period)
- elif getdate(d.planned_start_date) < getdate(from_date) :
+ elif getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
- elif d.status == 'Not Started':
- if getdate(d.planned_start_date) < getdate(from_date) :
+ elif d.status == "Not Started":
+ if getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
return periodic_data
+
def update_periodic_data(periodic_data, status, period):
if periodic_data.get(status).get(period):
periodic_data[status][period] += 1
@@ -88,22 +84,33 @@ def update_periodic_data(periodic_data, status, period):
return periodic_data
+
def get_data(filters, columns):
data = []
- entry = frappe.get_all("Work Order",
- fields=["creation", "modified", "actual_start_date", "actual_end_date", "planned_start_date", "planned_end_date", "status"],
- filters={"docstatus": 1, "company": filters["company"] })
+ entry = frappe.get_all(
+ "Work Order",
+ fields=[
+ "creation",
+ "modified",
+ "actual_start_date",
+ "actual_end_date",
+ "planned_start_date",
+ "planned_end_date",
+ "status",
+ ],
+ filters={"docstatus": 1, "company": filters["company"]},
+ )
- periodic_data = get_periodic_data(filters,entry)
+ periodic_data = get_periodic_data(filters, entry)
labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"]
- chart_data = get_chart_data(periodic_data,columns)
+ chart_data = get_chart_data(periodic_data, columns)
ranges = get_period_date_ranges(filters)
for label in labels:
work = {}
work["Status"] = label
- for dummy,end_date in ranges:
+ for dummy, end_date in ranges:
period = get_period(end_date, filters)
if periodic_data.get(label).get(period):
work[scrub(period)] = periodic_data.get(label).get(period)
@@ -113,10 +120,11 @@ def get_data(filters, columns):
return data, chart_data
+
def get_chart_data(periodic_data, columns):
labels = [d.get("label") for d in columns[1:]]
- all_data, not_start, overdue, pending, completed = [], [], [] , [], []
+ all_data, not_start, overdue, pending, completed = [], [], [], [], []
datasets = []
for d in labels:
@@ -126,18 +134,13 @@ def get_chart_data(periodic_data, columns):
pending.append(periodic_data.get("Pending").get(d))
completed.append(periodic_data.get("Completed").get(d))
- datasets.append({'name':'All Work Orders', 'values': all_data})
- datasets.append({'name':'Not Started', 'values': not_start})
- datasets.append({'name':'Overdue', 'values': overdue})
- datasets.append({'name':'Pending', 'values': pending})
- datasets.append({'name':'Completed', 'values': completed})
+ datasets.append({"name": "All Work Orders", "values": all_data})
+ datasets.append({"name": "Not Started", "values": not_start})
+ datasets.append({"name": "Overdue", "values": overdue})
+ datasets.append({"name": "Pending", "values": pending})
+ datasets.append({"name": "Completed", "values": completed})
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- }
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}}
chart["type"] = "line"
return chart
diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
index aaa231466f..17f7f5e51f 100644
--- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
+++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
@@ -13,6 +13,7 @@ def execute(filters=None):
return columns, data
+
def get_data(filters):
data = []
@@ -23,6 +24,7 @@ def get_data(filters):
return data
+
def get_production_plan_item_details(filters, data, order_details):
itemwise_indent = {}
@@ -30,77 +32,85 @@ def get_production_plan_item_details(filters, data, order_details):
for row in production_plan_doc.po_items:
work_order = frappe.get_value(
"Work Order",
- {
- "production_plan_item": row.name,
- "bom_no": row.bom_no,
- "production_item": row.item_code
- },
- "name"
+ {"production_plan_item": row.name, "bom_no": row.bom_no, "production_item": row.item_code},
+ "name",
)
if row.item_code not in itemwise_indent:
itemwise_indent.setdefault(row.item_code, {})
- data.append({
- "indent": 0,
- "item_code": row.item_code,
- "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"),
- "qty": row.planned_qty,
- "document_type": "Work Order",
- "document_name": work_order or "",
- "bom_level": 0,
- "produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0),
- "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0))
- })
+ data.append(
+ {
+ "indent": 0,
+ "item_code": row.item_code,
+ "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"),
+ "qty": row.planned_qty,
+ "document_type": "Work Order",
+ "document_name": work_order or "",
+ "bom_level": 0,
+ "produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0),
+ "pending_qty": flt(row.planned_qty)
+ - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0)),
+ }
+ )
- get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details)
+ get_production_plan_sub_assembly_item_details(
+ filters, row, production_plan_doc, data, order_details
+ )
-def get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details):
+
+def get_production_plan_sub_assembly_item_details(
+ filters, row, production_plan_doc, data, order_details
+):
for item in production_plan_doc.sub_assembly_items:
if row.name == item.production_plan_item:
- subcontracted_item = (item.type_of_manufacturing == 'Subcontract')
+ subcontracted_item = item.type_of_manufacturing == "Subcontract"
if subcontracted_item:
docname = frappe.get_value(
"Purchase Order Item",
- {
- "production_plan_sub_assembly_item": item.name,
- "docstatus": ("<", 2)
- },
- "parent"
+ {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)},
+ "parent",
)
else:
docname = frappe.get_value(
- "Work Order",
- {
- "production_plan_sub_assembly_item": item.name,
- "docstatus": ("<", 2)
- },
- "name"
+ "Work Order", {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name"
)
- data.append({
- "indent": 1,
- "item_code": item.production_item,
- "item_name": item.item_name,
- "qty": item.qty,
- "document_type": "Work Order" if not subcontracted_item else "Purchase Order",
- "document_name": docname or "",
- "bom_level": item.bom_level,
- "produced_qty": order_details.get((docname, item.production_item), {}).get("produced_qty", 0),
- "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item), {}).get("produced_qty", 0))
- })
+ data.append(
+ {
+ "indent": 1,
+ "item_code": item.production_item,
+ "item_name": item.item_name,
+ "qty": item.qty,
+ "document_type": "Work Order" if not subcontracted_item else "Purchase Order",
+ "document_name": docname or "",
+ "bom_level": item.bom_level,
+ "produced_qty": order_details.get((docname, item.production_item), {}).get("produced_qty", 0),
+ "pending_qty": flt(item.qty)
+ - flt(order_details.get((docname, item.production_item), {}).get("produced_qty", 0)),
+ }
+ )
+
def get_work_order_details(filters, order_details):
- for row in frappe.get_all("Work Order", filters = {"production_plan": filters.get("production_plan")},
- fields=["name", "produced_qty", "production_plan", "production_item"]):
+ for row in frappe.get_all(
+ "Work Order",
+ filters={"production_plan": filters.get("production_plan")},
+ fields=["name", "produced_qty", "production_plan", "production_item"],
+ ):
order_details.setdefault((row.name, row.production_item), row)
+
def get_purchase_order_details(filters, order_details):
- for row in frappe.get_all("Purchase Order Item", filters = {"production_plan": filters.get("production_plan")},
- fields=["parent", "received_qty as produced_qty", "item_code"]):
+ for row in frappe.get_all(
+ "Purchase Order Item",
+ filters={"production_plan": filters.get("production_plan")},
+ fields=["parent", "received_qty as produced_qty", "item_code"],
+ ):
order_details.setdefault((row.parent, row.item_code), row)
+
def get_column(filters):
return [
{
@@ -108,49 +118,24 @@ def get_column(filters):
"fieldtype": "Link",
"fieldname": "item_code",
"width": 300,
- "options": "Item"
- },
- {
- "label": "Item Name",
- "fieldtype": "data",
- "fieldname": "item_name",
- "width": 100
+ "options": "Item",
},
+ {"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100},
{
"label": "Document Type",
"fieldtype": "Link",
"fieldname": "document_type",
"width": 150,
- "options": "DocType"
+ "options": "DocType",
},
{
"label": "Document Name",
"fieldtype": "Dynamic Link",
"fieldname": "document_name",
- "width": 150
+ "width": 150,
},
- {
- "label": "BOM Level",
- "fieldtype": "Int",
- "fieldname": "bom_level",
- "width": 100
- },
- {
- "label": "Order Qty",
- "fieldtype": "Float",
- "fieldname": "qty",
- "width": 120
- },
- {
- "label": "Received Qty",
- "fieldtype": "Float",
- "fieldname": "produced_qty",
- "width": 160
- },
- {
- "label": "Pending Qty",
- "fieldtype": "Float",
- "fieldname": "pending_qty",
- "width": 110
- }
+ {"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
+ {"label": "Order Qty", "fieldtype": "Float", "fieldname": "qty", "width": 120},
+ {"label": "Received Qty", "fieldtype": "Float", "fieldname": "produced_qty", "width": 160},
+ {"label": "Pending Qty", "fieldtype": "Float", "fieldname": "pending_qty", "width": 110},
]
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
index e1e7225e05..140488820a 100644
--- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -15,38 +15,36 @@ mapper = {
stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
`tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
"filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
- and `tabSales Order`.per_delivered < 100.0"""
+ and `tabSales Order`.per_delivered < 100.0""",
},
"Material Request": {
"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
`tabMaterial Request Item`.schedule_date """,
"filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
- and `tabMaterial Request`.material_request_type = 'Manufacture' """
+ and `tabMaterial Request`.material_request_type = 'Manufacture' """,
},
"Work Order": {
"fields": """ production_item, item_name as production_item_name, planned_start_date,
stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
- "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')"
+ "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')",
},
}
order_mapper = {
"Sales Order": {
"Delivery Date": "`tabSales Order Item`.delivery_date asc",
- "Total Amount": "`tabSales Order`.base_grand_total desc"
+ "Total Amount": "`tabSales Order`.base_grand_total desc",
},
- "Material Request": {
- "Required Date": "`tabMaterial Request Item`.schedule_date asc"
- },
- "Work Order": {
- "Planned Start Date": "planned_start_date asc"
- }
+ "Material Request": {"Required Date": "`tabMaterial Request Item`.schedule_date asc"},
+ "Work Order": {"Planned Start Date": "planned_start_date asc"},
}
+
def execute(filters=None):
return ProductionPlanReport(filters).execute_report()
+
class ProductionPlanReport(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
@@ -65,46 +63,64 @@ class ProductionPlanReport(object):
return self.columns, self.data
def get_open_orders(self):
- doctype = ("`tabWork Order`" if self.filters.based_on == "Work Order"
- else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on))
+ doctype = (
+ "`tabWork Order`"
+ if self.filters.based_on == "Work Order"
+ else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on)
+ )
filters = mapper.get(self.filters.based_on)["filters"]
filters = self.prepare_other_conditions(filters, self.filters.based_on)
order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
- self.orders = frappe.db.sql(""" SELECT {fields} from {doctype}
+ self.orders = frappe.db.sql(
+ """ SELECT {fields} from {doctype}
WHERE {filters} {order_by}""".format(
- doctype = doctype,
- filters = filters,
- order_by = order_by,
- fields = mapper.get(self.filters.based_on)["fields"]
- ), tuple(self.filters.docnames), as_dict=1)
+ doctype=doctype,
+ filters=filters,
+ order_by=order_by,
+ fields=mapper.get(self.filters.based_on)["fields"],
+ ),
+ tuple(self.filters.docnames),
+ as_dict=1,
+ )
def prepare_other_conditions(self, filters, doctype):
if self.filters.docnames:
field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
- filters += " and %s in (%s)" % (field, ','.join(['%s'] * len(self.filters.docnames)))
+ filters += " and %s in (%s)" % (field, ",".join(["%s"] * len(self.filters.docnames)))
if doctype != "Work Order":
filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
if self.filters.company:
- filters += " and `tab%s`.company = %s" %(doctype, frappe.db.escape(self.filters.company))
+ filters += " and `tab%s`.company = %s" % (doctype, frappe.db.escape(self.filters.company))
return filters
def get_raw_materials(self):
- if not self.orders: return
+ if not self.orders:
+ return
self.warehouses = [d.warehouse for d in self.orders]
self.item_codes = [d.production_item for d in self.orders]
if self.filters.based_on == "Work Order":
work_orders = [d.name for d in self.orders]
- raw_materials = frappe.get_all("Work Order Item",
- fields=["parent", "item_code", "item_name as raw_material_name",
- "source_warehouse as warehouse", "required_qty"],
- filters = {"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")}) or []
+ raw_materials = (
+ frappe.get_all(
+ "Work Order Item",
+ fields=[
+ "parent",
+ "item_code",
+ "item_name as raw_material_name",
+ "source_warehouse as warehouse",
+ "required_qty",
+ ],
+ filters={"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")},
+ )
+ or []
+ )
self.warehouses.extend([d.source_warehouse for d in raw_materials])
else:
@@ -118,21 +134,32 @@ class ProductionPlanReport(object):
bom_nos.append(bom_no)
- bom_doctype = ("BOM Explosion Item"
- if self.filters.include_subassembly_raw_materials else "BOM Item")
+ bom_doctype = (
+ "BOM Explosion Item" if self.filters.include_subassembly_raw_materials else "BOM Item"
+ )
- qty_field = ("qty_consumed_per_unit"
- if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
+ qty_field = (
+ "qty_consumed_per_unit"
+ if self.filters.include_subassembly_raw_materials
+ else "(bom_item.qty / bom.quantity)"
+ )
- raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
+ raw_materials = frappe.db.sql(
+ """ SELECT bom_item.parent, bom_item.item_code,
bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
FROM
`tabBOM` as bom, `tab{1}` as bom_item
WHERE
bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
- """.format(qty_field, bom_doctype, ','.join(["%s"] * len(bom_nos))), tuple(bom_nos), as_dict=1)
+ """.format(
+ qty_field, bom_doctype, ",".join(["%s"] * len(bom_nos))
+ ),
+ tuple(bom_nos),
+ as_dict=1,
+ )
- if not raw_materials: return
+ if not raw_materials:
+ return
self.item_codes.extend([d.item_code for d in raw_materials])
@@ -144,15 +171,20 @@ class ProductionPlanReport(object):
rows.append(d)
def get_item_details(self):
- if not (self.orders and self.item_codes): return
+ if not (self.orders and self.item_codes):
+ return
self.item_details = {}
- for d in frappe.get_all("Item Default", fields = ["parent", "default_warehouse"],
- filters = {"company": self.filters.company, "parent": ("in", self.item_codes)}):
+ for d in frappe.get_all(
+ "Item Default",
+ fields=["parent", "default_warehouse"],
+ filters={"company": self.filters.company, "parent": ("in", self.item_codes)},
+ ):
self.item_details[d.parent] = d
def get_bin_details(self):
- if not (self.orders and self.raw_materials_dict): return
+ if not (self.orders and self.raw_materials_dict):
+ return
self.bin_details = {}
self.mrp_warehouses = []
@@ -160,48 +192,55 @@ class ProductionPlanReport(object):
self.mrp_warehouses.extend(get_child_warehouses(self.filters.raw_material_warehouse))
self.warehouses.extend(self.mrp_warehouses)
- for d in frappe.get_all("Bin",
+ for d in frappe.get_all(
+ "Bin",
fields=["warehouse", "item_code", "actual_qty", "ordered_qty", "projected_qty"],
- filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)}):
+ filters={"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
+ ):
key = (d.item_code, d.warehouse)
if key not in self.bin_details:
self.bin_details.setdefault(key, d)
def get_purchase_details(self):
- if not (self.orders and self.raw_materials_dict): return
+ if not (self.orders and self.raw_materials_dict):
+ return
self.purchase_details = {}
- purchased_items = frappe.get_all("Purchase Order Item",
+ purchased_items = frappe.get_all(
+ "Purchase Order Item",
fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
filters={
"item_code": ("in", self.item_codes),
"warehouse": ("in", self.warehouses),
"docstatus": 1,
},
- group_by = "item_code, warehouse")
+ group_by="item_code, warehouse",
+ )
for d in purchased_items:
key = (d.item_code, d.warehouse)
if key not in self.purchase_details:
self.purchase_details.setdefault(key, d)
def prepare_data(self):
- if not self.orders: return
+ if not self.orders:
+ return
for d in self.orders:
key = d.name if self.filters.based_on == "Work Order" else d.bom_no
- if not self.raw_materials_dict.get(key): continue
+ if not self.raw_materials_dict.get(key):
+ continue
bin_data = self.bin_details.get((d.production_item, d.warehouse)) or {}
- d.update({
- "for_warehouse": d.warehouse,
- "available_qty": 0
- })
+ d.update({"for_warehouse": d.warehouse, "available_qty": 0})
if bin_data and bin_data.get("actual_qty") > 0 and d.qty_to_manufacture:
- d.available_qty = (bin_data.get("actual_qty")
- if (d.qty_to_manufacture > bin_data.get("actual_qty")) else d.qty_to_manufacture)
+ d.available_qty = (
+ bin_data.get("actual_qty")
+ if (d.qty_to_manufacture > bin_data.get("actual_qty"))
+ else d.qty_to_manufacture
+ )
bin_data["actual_qty"] -= d.available_qty
@@ -232,8 +271,9 @@ class ProductionPlanReport(object):
d.remaining_qty = d.required_qty
self.pick_materials_from_warehouses(d, data, warehouses)
- if (d.remaining_qty and self.filters.raw_material_warehouse
- and d.remaining_qty != d.required_qty):
+ if (
+ d.remaining_qty and self.filters.raw_material_warehouse and d.remaining_qty != d.required_qty
+ ):
row = self.get_args()
d.warehouse = self.filters.raw_material_warehouse
d.required_qty = d.remaining_qty
@@ -243,7 +283,8 @@ class ProductionPlanReport(object):
def pick_materials_from_warehouses(self, args, order_data, warehouses):
for index, warehouse in enumerate(warehouses):
- if not args.remaining_qty: return
+ if not args.remaining_qty:
+ return
row = self.get_args()
@@ -255,14 +296,18 @@ class ProductionPlanReport(object):
args.allotted_qty = 0
if bin_data and bin_data.get("actual_qty") > 0:
- args.allotted_qty = (bin_data.get("actual_qty")
- if (args.required_qty > bin_data.get("actual_qty")) else args.required_qty)
+ args.allotted_qty = (
+ bin_data.get("actual_qty")
+ if (args.required_qty > bin_data.get("actual_qty"))
+ else args.required_qty
+ )
args.remaining_qty -= args.allotted_qty
bin_data["actual_qty"] -= args.allotted_qty
- if ((self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1))
- or not self.mrp_warehouses):
+ if (
+ self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1)
+ ) or not self.mrp_warehouses:
if not self.index:
row.update(order_data)
self.index += 1
@@ -275,52 +320,45 @@ class ProductionPlanReport(object):
self.data.append(row)
def get_args(self):
- return frappe._dict({
- "work_order": "",
- "sales_order": "",
- "production_item": "",
- "production_item_name": "",
- "qty_to_manufacture": "",
- "produced_qty": ""
- })
+ return frappe._dict(
+ {
+ "work_order": "",
+ "sales_order": "",
+ "production_item": "",
+ "production_item_name": "",
+ "qty_to_manufacture": "",
+ "produced_qty": "",
+ }
+ )
def get_columns(self):
based_on = self.filters.based_on
- self.columns = [{
- "label": _("ID"),
- "options": based_on,
- "fieldname": "name",
- "fieldtype": "Link",
- "width": 100
- }, {
- "label": _("Item Code"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- }, {
- "label": _("Item Name"),
- "fieldname": "production_item_name",
- "fieldtype": "Data",
- "width": 130
- }, {
- "label": _("Warehouse"),
- "options": "Warehouse",
- "fieldname": "for_warehouse",
- "fieldtype": "Link",
- "width": 100
- }, {
- "label": _("Order Qty"),
- "fieldname": "qty_to_manufacture",
- "fieldtype": "Float",
- "width": 80
- }, {
- "label": _("Available"),
- "fieldname": "available_qty",
- "fieldtype": "Float",
- "width": 80
- }]
+ self.columns = [
+ {"label": _("ID"), "options": based_on, "fieldname": "name", "fieldtype": "Link", "width": 100},
+ {
+ "label": _("Item Code"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "production_item_name",
+ "fieldtype": "Data",
+ "width": 130,
+ },
+ {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "for_warehouse",
+ "fieldtype": "Link",
+ "width": 100,
+ },
+ {"label": _("Order Qty"), "fieldname": "qty_to_manufacture", "fieldtype": "Float", "width": 80},
+ {"label": _("Available"), "fieldname": "available_qty", "fieldtype": "Float", "width": 80},
+ ]
fieldname, fieldtype = "delivery_date", "Date"
if self.filters.based_on == "Sales Order" and self.filters.order_by == "Total Amount":
@@ -330,48 +368,50 @@ class ProductionPlanReport(object):
elif self.filters.based_on == "Work Order":
fieldname = "planned_start_date"
- self.columns.append({
- "label": _(self.filters.order_by),
- "fieldname": fieldname,
- "fieldtype": fieldtype,
- "width": 100
- })
+ self.columns.append(
+ {
+ "label": _(self.filters.order_by),
+ "fieldname": fieldname,
+ "fieldtype": fieldtype,
+ "width": 100,
+ }
+ )
- self.columns.extend([{
- "label": _("Raw Material Code"),
- "fieldname": "item_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- }, {
- "label": _("Raw Material Name"),
- "fieldname": "raw_material_name",
- "fieldtype": "Data",
- "width": 130
- }, {
- "label": _("Warehouse"),
- "options": "Warehouse",
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "width": 110
- }, {
- "label": _("Required Qty"),
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "width": 100
- }, {
- "label": _("Allotted Qty"),
- "fieldname": "allotted_qty",
- "fieldtype": "Float",
- "width": 100
- }, {
- "label": _("Expected Arrival Date"),
- "fieldname": "arrival_date",
- "fieldtype": "Date",
- "width": 160
- }, {
- "label": _("Arrival Quantity"),
- "fieldname": "arrival_qty",
- "fieldtype": "Float",
- "width": 140
- }])
+ self.columns.extend(
+ [
+ {
+ "label": _("Raw Material Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {
+ "label": _("Raw Material Name"),
+ "fieldname": "raw_material_name",
+ "fieldtype": "Data",
+ "width": 130,
+ },
+ {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 110,
+ },
+ {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 100},
+ {"label": _("Allotted Qty"), "fieldname": "allotted_qty", "fieldtype": "Float", "width": 100},
+ {
+ "label": _("Expected Arrival Date"),
+ "fieldname": "arrival_date",
+ "fieldtype": "Date",
+ "width": 160,
+ },
+ {
+ "label": _("Arrival Quantity"),
+ "fieldname": "arrival_qty",
+ "fieldtype": "Float",
+ "width": 140,
+ },
+ ]
+ )
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
index a0c4a43e90..0a79130f1b 100644
--- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
@@ -11,13 +11,24 @@ def execute(filters=None):
data = get_data(filters)
columns = get_columns(filters)
chart_data = get_chart_data(data, filters)
- return columns, data , None, chart_data
+ return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {"docstatus": ("<", 2)}
- fields = ["name", "status", "report_date", "item_code", "item_name", "sample_size",
- "inspection_type", "reference_type", "reference_name", "inspected_by"]
+ fields = [
+ "name",
+ "status",
+ "report_date",
+ "item_code",
+ "item_name",
+ "sample_size",
+ "inspection_type",
+ "reference_type",
+ "reference_name",
+ "inspected_by",
+ ]
for field in ["status", "item_code", "status", "inspected_by"]:
if filters.get(field):
@@ -26,36 +37,33 @@ def get_data(filters):
query_filters["report_date"] = (">=", filters.get("from_date"))
query_filters["report_date"] = ("<=", filters.get("to_date"))
- return frappe.get_all("Quality Inspection",
- fields= fields, filters=query_filters, order_by="report_date asc")
+ return frappe.get_all(
+ "Quality Inspection", fields=fields, filters=query_filters, order_by="report_date asc"
+ )
+
def get_chart_data(periodic_data, columns):
labels = ["Rejected", "Accepted"]
- status_wise_data = {
- "Accepted": 0,
- "Rejected": 0
- }
+ status_wise_data = {"Accepted": 0, "Rejected": 0}
datasets = []
for d in periodic_data:
status_wise_data[d.status] += 1
- datasets.append({'name':'Qty Wise Chart',
- 'values': [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")]})
+ datasets.append(
+ {
+ "name": "Qty Wise Chart",
+ "values": [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")],
+ }
+ )
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- },
- "type": "donut",
- "height": 300
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}, "type": "donut", "height": 300}
return chart
+
def get_columns(filters):
columns = [
{
@@ -63,71 +71,49 @@ def get_columns(filters):
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
- "width": 100
+ "width": 100,
},
- {
- "label": _("Report Date"),
- "fieldname": "report_date",
- "fieldtype": "Date",
- "width": 150
- }
+ {"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150},
]
if not filters.get("status"):
columns.append(
- {
- "label": _("Status"),
- "fieldname": "status",
- "width": 100
- },
+ {"label": _("Status"), "fieldname": "status", "width": 100},
)
- columns.extend([
- {
- "label": _("Item Code"),
- "fieldname": "item_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 130
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 130
- },
- {
- "label": _("Sample Size"),
- "fieldname": "sample_size",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Inspection Type"),
- "fieldname": "inspection_type",
- "fieldtype": "Data",
- "width": 110
- },
- {
- "label": _("Document Type"),
- "fieldname": "reference_type",
- "fieldtype": "Data",
- "width": 90
- },
- {
- "label": _("Document Name"),
- "fieldname": "reference_name",
- "fieldtype": "Dynamic Link",
- "options": "reference_type",
- "width": 150
- },
- {
- "label": _("Inspected By"),
- "fieldname": "inspected_by",
- "fieldtype": "Link",
- "options": "User",
- "width": 150
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 130},
+ {"label": _("Sample Size"), "fieldname": "sample_size", "fieldtype": "Float", "width": 110},
+ {
+ "label": _("Inspection Type"),
+ "fieldname": "inspection_type",
+ "fieldtype": "Data",
+ "width": 110,
+ },
+ {"label": _("Document Type"), "fieldname": "reference_type", "fieldtype": "Data", "width": 90},
+ {
+ "label": _("Document Name"),
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "options": "reference_type",
+ "width": 150,
+ },
+ {
+ "label": _("Inspected By"),
+ "fieldname": "inspected_by",
+ "fieldtype": "Link",
+ "options": "User",
+ "width": 150,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
index 052834807e..8158bc9a02 100644
--- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
@@ -12,12 +12,13 @@ def execute(filters=None):
return columns, data
+
def get_data(report_filters):
fields = get_fields()
filters = get_filter_condition(report_filters)
wo_items = {}
- for d in frappe.get_all("Work Order", filters = filters, fields=fields):
+ for d in frappe.get_all("Work Order", filters=filters, fields=fields):
d.extra_consumed_qty = 0.0
if d.consumed_qty and d.consumed_qty > d.required_qty:
d.extra_consumed_qty = d.consumed_qty - d.required_qty
@@ -29,7 +30,7 @@ def get_data(report_filters):
for key, wo_data in wo_items.items():
for index, row in enumerate(wo_data):
if index != 0:
- #If one work order has multiple raw materials then show parent data in the first row only
+ # If one work order has multiple raw materials then show parent data in the first row only
for field in ["name", "status", "production_item", "qty", "produced_qty"]:
row[field] = ""
@@ -37,17 +38,28 @@ def get_data(report_filters):
return data
+
def get_fields():
- return ["`tabWork Order Item`.`parent`", "`tabWork Order Item`.`item_code` as raw_material_item_code",
- "`tabWork Order Item`.`item_name` as raw_material_name", "`tabWork Order Item`.`required_qty`",
- "`tabWork Order Item`.`transferred_qty`", "`tabWork Order Item`.`consumed_qty`", "`tabWork Order`.`status`",
- "`tabWork Order`.`name`", "`tabWork Order`.`production_item`", "`tabWork Order`.`qty`",
- "`tabWork Order`.`produced_qty`"]
+ return [
+ "`tabWork Order Item`.`parent`",
+ "`tabWork Order Item`.`item_code` as raw_material_item_code",
+ "`tabWork Order Item`.`item_name` as raw_material_name",
+ "`tabWork Order Item`.`required_qty`",
+ "`tabWork Order Item`.`transferred_qty`",
+ "`tabWork Order Item`.`consumed_qty`",
+ "`tabWork Order`.`status`",
+ "`tabWork Order`.`name`",
+ "`tabWork Order`.`production_item`",
+ "`tabWork Order`.`qty`",
+ "`tabWork Order`.`produced_qty`",
+ ]
+
def get_filter_condition(report_filters):
filters = {
- "docstatus": 1, "status": ("in", ["In Process", "Completed", "Stopped"]),
- "creation": ("between", [report_filters.from_date, report_filters.to_date])
+ "docstatus": 1,
+ "status": ("in", ["In Process", "Completed", "Stopped"]),
+ "creation": ("between", [report_filters.from_date, report_filters.to_date]),
}
for field in ["name", "production_item", "company", "status"]:
@@ -58,6 +70,7 @@ def get_filter_condition(report_filters):
return filters
+
def get_columns():
return [
{
@@ -65,67 +78,38 @@ def get_columns():
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
- "width": 80
- },
- {
- "label": _("Status"),
- "fieldname": "status",
- "fieldtype": "Data",
- "width": 80
+ "width": 80,
},
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80},
{
"label": _("Production Item"),
"fieldname": "production_item",
"fieldtype": "Link",
"options": "Item",
- "width": 130
- },
- {
- "label": _("Qty to Produce"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Produced Qty"),
- "fieldname": "produced_qty",
- "fieldtype": "Float",
- "width": 110
+ "width": 130,
},
+ {"label": _("Qty to Produce"), "fieldname": "qty", "fieldtype": "Float", "width": 120},
+ {"label": _("Produced Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": 110},
{
"label": _("Raw Material Item"),
"fieldname": "raw_material_item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 150
- },
- {
- "label": _("Item Name"),
- "fieldname": "raw_material_name",
- "width": 130
- },
- {
- "label": _("Required Qty"),
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "width": 100
+ "width": 150,
},
+ {"label": _("Item Name"), "fieldname": "raw_material_name", "width": 130},
+ {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 100},
{
"label": _("Transferred Qty"),
"fieldname": "transferred_qty",
"fieldtype": "Float",
- "width": 100
- },
- {
- "label": _("Consumed Qty"),
- "fieldname": "consumed_qty",
- "fieldtype": "Float",
- "width": 100
+ "width": 100,
},
+ {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 100},
{
"label": _("Extra Consumed Qty"),
"fieldname": "extra_consumed_qty",
"fieldtype": "Float",
- "width": 100
- }
+ "width": 100,
+ },
]
diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py
index db0b239ae2..c6b7e58d65 100644
--- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py
+++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py
@@ -12,17 +12,20 @@ def execute(filters=None):
columns = get_columns()
return columns, data
+
def get_item_list(wo_list, filters):
out = []
- #Add a row for each item/qty
+ # Add a row for each item/qty
for wo_details in wo_list:
desc = frappe.db.get_value("BOM", wo_details.bom_no, "description")
- for wo_item_details in frappe.db.get_values("Work Order Item",
- {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1):
+ for wo_item_details in frappe.db.get_values(
+ "Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1
+ ):
- item_list = frappe.db.sql("""SELECT
+ item_list = frappe.db.sql(
+ """SELECT
bom_item.item_code as item_code,
ifnull(ledger.actual_qty*bom.quantity/bom_item.stock_qty,0) as build_qty
FROM
@@ -36,8 +39,14 @@ def get_item_list(wo_list, filters):
and bom.name = %(bom)s
GROUP BY
bom_item.item_code""",
- {"bom": wo_details.bom_no, "warehouse": wo_item_details.source_warehouse,
- "filterhouse": filters.warehouse, "item_code": wo_item_details.item_code}, as_dict=1)
+ {
+ "bom": wo_details.bom_no,
+ "warehouse": wo_item_details.source_warehouse,
+ "filterhouse": filters.warehouse,
+ "item_code": wo_item_details.item_code,
+ },
+ as_dict=1,
+ )
stock_qty = 0
count = 0
@@ -54,97 +63,99 @@ def get_item_list(wo_list, filters):
else:
build = "N"
- row = frappe._dict({
- "work_order": wo_details.name,
- "status": wo_details.status,
- "req_items": cint(count),
- "instock": stock_qty,
- "description": desc,
- "source_warehouse": wo_item_details.source_warehouse,
- "item_code": wo_item_details.item_code,
- "bom_no": wo_details.bom_no,
- "qty": wo_details.qty,
- "buildable_qty": buildable_qty,
- "ready_to_build": build
- })
+ row = frappe._dict(
+ {
+ "work_order": wo_details.name,
+ "status": wo_details.status,
+ "req_items": cint(count),
+ "instock": stock_qty,
+ "description": desc,
+ "source_warehouse": wo_item_details.source_warehouse,
+ "item_code": wo_item_details.item_code,
+ "bom_no": wo_details.bom_no,
+ "qty": wo_details.qty,
+ "buildable_qty": buildable_qty,
+ "ready_to_build": build,
+ }
+ )
out.append(row)
return out
+
def get_work_orders():
- out = frappe.get_all("Work Order", filters={"docstatus": 1, "status": ( "!=","Completed")},
- fields=["name","status", "bom_no", "qty", "produced_qty"], order_by='name')
+ out = frappe.get_all(
+ "Work Order",
+ filters={"docstatus": 1, "status": ("!=", "Completed")},
+ fields=["name", "status", "bom_no", "qty", "produced_qty"],
+ order_by="name",
+ )
return out
+
def get_columns():
- columns = [{
- "fieldname": "work_order",
- "label": "Work Order",
- "fieldtype": "Link",
- "options": "Work Order",
- "width": 110
- }, {
- "fieldname": "bom_no",
- "label": "BOM",
- "fieldtype": "Link",
- "options": "BOM",
- "width": 120
- }, {
- "fieldname": "description",
- "label": "Description",
- "fieldtype": "Data",
- "options": "",
- "width": 230
- }, {
- "fieldname": "item_code",
- "label": "Item Code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 110
- },{
- "fieldname": "source_warehouse",
- "label": "Source Warehouse",
- "fieldtype": "Link",
- "options": "Warehouse",
- "width": 110
- },{
- "fieldname": "qty",
- "label": "Qty to Build",
- "fieldtype": "Data",
- "options": "",
- "width": 110
- }, {
- "fieldname": "status",
- "label": "Status",
- "fieldtype": "Data",
- "options": "",
- "width": 100
- }, {
- "fieldname": "req_items",
- "label": "# Req'd Items",
- "fieldtype": "Data",
- "options": "",
- "width": 105
- }, {
- "fieldname": "instock",
- "label": "# In Stock",
- "fieldtype": "Data",
- "options": "",
- "width": 105
- }, {
- "fieldname": "buildable_qty",
- "label": "Buildable Qty",
- "fieldtype": "Data",
- "options": "",
- "width": 100
- }, {
- "fieldname": "ready_to_build",
- "label": "Build All?",
- "fieldtype": "Data",
- "options": "",
- "width": 90
- }]
+ columns = [
+ {
+ "fieldname": "work_order",
+ "label": "Work Order",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 110,
+ },
+ {"fieldname": "bom_no", "label": "BOM", "fieldtype": "Link", "options": "BOM", "width": 120},
+ {
+ "fieldname": "description",
+ "label": "Description",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 230,
+ },
+ {
+ "fieldname": "item_code",
+ "label": "Item Code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 110,
+ },
+ {
+ "fieldname": "source_warehouse",
+ "label": "Source Warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 110,
+ },
+ {"fieldname": "qty", "label": "Qty to Build", "fieldtype": "Data", "options": "", "width": 110},
+ {"fieldname": "status", "label": "Status", "fieldtype": "Data", "options": "", "width": 100},
+ {
+ "fieldname": "req_items",
+ "label": "# Req'd Items",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 105,
+ },
+ {
+ "fieldname": "instock",
+ "label": "# In Stock",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 105,
+ },
+ {
+ "fieldname": "buildable_qty",
+ "label": "Buildable Qty",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 100,
+ },
+ {
+ "fieldname": "ready_to_build",
+ "label": "Build All?",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 90,
+ },
+ ]
return columns
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
index d7469ddfdd..2368bfdf2c 100644
--- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -21,11 +21,23 @@ def execute(filters=None):
chart_data = get_chart_data(data, filters)
return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {"docstatus": ("<", 2)}
- fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
- "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
+ fields = [
+ "name",
+ "status",
+ "sales_order",
+ "production_item",
+ "qty",
+ "produced_qty",
+ "planned_start_date",
+ "planned_end_date",
+ "actual_start_date",
+ "actual_end_date",
+ "lead_time",
+ ]
for field in ["sales_order", "production_item", "status", "company"]:
if filters.get(field):
@@ -34,15 +46,16 @@ def get_data(filters):
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
- data = frappe.get_all("Work Order",
- fields= fields, filters=query_filters, order_by="planned_start_date asc")
+ data = frappe.get_all(
+ "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc"
+ )
res = []
for d in data:
start_date = d.actual_start_date or d.planned_start_date
d.age = 0
- if d.status != 'Completed':
+ if d.status != "Completed":
d.age = date_diff(today(), start_date)
if filters.get("age") <= d.age:
@@ -50,6 +63,7 @@ def get_data(filters):
return res
+
def get_chart_data(data, filters):
if filters.get("charts_based_on") == "Status":
return get_chart_based_on_status(data)
@@ -58,6 +72,7 @@ def get_chart_data(data, filters):
else:
return get_chart_based_on_qty(data, filters)
+
def get_chart_based_on_status(data):
labels = frappe.get_meta("Work Order").get_options("status").split("\n")
if "" in labels:
@@ -71,25 +86,18 @@ def get_chart_based_on_status(data):
values = [status_wise_data[label] for label in labels]
chart = {
- "data": {
- 'labels': labels,
- 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
- },
+ "data": {"labels": labels, "datasets": [{"name": "Qty Wise Chart", "values": values}]},
"type": "donut",
- "height": 300
+ "height": 300,
}
return chart
+
def get_chart_based_on_age(data):
labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"]
- age_wise_data = {
- "0-30 Days": 0,
- "30-60 Days": 0,
- "60-90 Days": 0,
- "90 Above": 0
- }
+ age_wise_data = {"0-30 Days": 0, "30-60 Days": 0, "60-90 Days": 0, "90 Above": 0}
for d in data:
if d.age > 0 and d.age <= 30:
@@ -101,20 +109,22 @@ def get_chart_based_on_age(data):
else:
age_wise_data["90 Above"] += 1
- values = [age_wise_data["0-30 Days"], age_wise_data["30-60 Days"],
- age_wise_data["60-90 Days"], age_wise_data["90 Above"]]
+ values = [
+ age_wise_data["0-30 Days"],
+ age_wise_data["30-60 Days"],
+ age_wise_data["60-90 Days"],
+ age_wise_data["90 Above"],
+ ]
chart = {
- "data": {
- 'labels': labels,
- 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
- },
+ "data": {"labels": labels, "datasets": [{"name": "Qty Wise Chart", "values": values}]},
"type": "donut",
- "height": 300
+ "height": 300,
}
return chart
+
def get_chart_based_on_qty(data, filters):
labels, periodic_data = prepare_chart_data(data, filters)
@@ -129,25 +139,18 @@ def get_chart_based_on_qty(data, filters):
datasets.append({"name": "Completed", "values": completed})
chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- },
+ "data": {"labels": labels, "datasets": datasets},
"type": "bar",
- "barOptions": {
- "stacked": 1
- }
+ "barOptions": {"stacked": 1},
}
return chart
+
def prepare_chart_data(data, filters):
labels = []
- periodic_data = {
- "Pending": {},
- "Completed": {}
- }
+ periodic_data = {"Pending": {}, "Completed": {}}
filters.range = "Monthly"
@@ -165,11 +168,12 @@ def prepare_chart_data(data, filters):
for d in data:
if getdate(d.planned_start_date) >= from_date and getdate(d.planned_start_date) <= end_date:
- periodic_data["Pending"][period] += (flt(d.qty) - flt(d.produced_qty))
+ periodic_data["Pending"][period] += flt(d.qty) - flt(d.produced_qty)
periodic_data["Completed"][period] += flt(d.produced_qty)
return labels, periodic_data
+
def get_columns(filters):
columns = [
{
@@ -177,90 +181,77 @@ def get_columns(filters):
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
- "width": 100
+ "width": 100,
},
]
if not filters.get("status"):
columns.append(
- {
- "label": _("Status"),
- "fieldname": "status",
- "width": 100
- },
+ {"label": _("Status"), "fieldname": "status", "width": 100},
)
- columns.extend([
- {
- "label": _("Production Item"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 130
- },
- {
- "label": _("Produce Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Produced Qty"),
- "fieldname": "produced_qty",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Sales Order"),
- "fieldname": "sales_order",
- "fieldtype": "Link",
- "options": "Sales Order",
- "width": 90
- },
- {
- "label": _("Planned Start Date"),
- "fieldname": "planned_start_date",
- "fieldtype": "Date",
- "width": 150
- },
- {
- "label": _("Planned End Date"),
- "fieldname": "planned_end_date",
- "fieldtype": "Date",
- "width": 150
- }
- ])
-
- if filters.get("status") != 'Not Started':
- columns.extend([
+ columns.extend(
+ [
{
- "label": _("Actual Start Date"),
- "fieldname": "actual_start_date",
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130,
+ },
+ {"label": _("Produce Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 110},
+ {"label": _("Produced Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": 110},
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 90,
+ },
+ {
+ "label": _("Planned Start Date"),
+ "fieldname": "planned_start_date",
"fieldtype": "Date",
- "width": 100
+ "width": 150,
},
{
- "label": _("Actual End Date"),
- "fieldname": "actual_end_date",
+ "label": _("Planned End Date"),
+ "fieldname": "planned_end_date",
"fieldtype": "Date",
- "width": 100
+ "width": 150,
},
- {
- "label": _("Age"),
- "fieldname": "age",
- "fieldtype": "Float",
- "width": 110
- },
- ])
+ ]
+ )
- if filters.get("status") == 'Completed':
- columns.extend([
- {
- "label": _("Lead Time (in mins)"),
- "fieldname": "lead_time",
- "fieldtype": "Float",
- "width": 110
- },
- ])
+ if filters.get("status") != "Not Started":
+ columns.extend(
+ [
+ {
+ "label": _("Actual Start Date"),
+ "fieldname": "actual_start_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {
+ "label": _("Actual End Date"),
+ "fieldname": "actual_end_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {"label": _("Age"), "fieldname": "age", "fieldtype": "Float", "width": 110},
+ ]
+ )
+
+ if filters.get("status") == "Completed":
+ columns.extend(
+ [
+ {
+ "label": _("Lead Time (in mins)"),
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "width": 110,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/patches/v10_0/add_default_cash_flow_mappers.py b/erpnext/patches/v10_0/add_default_cash_flow_mappers.py
index 165ca0243b..5493258e3d 100644
--- a/erpnext/patches/v10_0/add_default_cash_flow_mappers.py
+++ b/erpnext/patches/v10_0/add_default_cash_flow_mappers.py
@@ -8,8 +8,8 @@ from erpnext.setup.install import create_default_cash_flow_mapper_templates
def execute():
- frappe.reload_doc('accounts', 'doctype', frappe.scrub('Cash Flow Mapping'))
- frappe.reload_doc('accounts', 'doctype', frappe.scrub('Cash Flow Mapper'))
- frappe.reload_doc('accounts', 'doctype', frappe.scrub('Cash Flow Mapping Template Details'))
+ frappe.reload_doc("accounts", "doctype", frappe.scrub("Cash Flow Mapping"))
+ frappe.reload_doc("accounts", "doctype", frappe.scrub("Cash Flow Mapper"))
+ frappe.reload_doc("accounts", "doctype", frappe.scrub("Cash Flow Mapping Template Details"))
- create_default_cash_flow_mapper_templates()
+ create_default_cash_flow_mapper_templates()
diff --git a/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py b/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py
index cdf5ba2914..44497299c4 100644
--- a/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py
+++ b/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py
@@ -8,6 +8,6 @@ from erpnext.setup.doctype.company.company import install_country_fixtures
def execute():
- frappe.reload_doc('regional', 'report', 'fichier_des_ecritures_comptables_[fec]')
- for d in frappe.get_all('Company', filters = {'country': 'France'}):
+ frappe.reload_doc("regional", "report", "fichier_des_ecritures_comptables_[fec]")
+ for d in frappe.get_all("Company", filters={"country": "France"}):
install_country_fixtures(d.name)
diff --git a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
index ffff95d223..e2d0943d72 100644
--- a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
+++ b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
@@ -7,26 +7,30 @@ import frappe
def execute():
frappe.reload_doc("stock", "doctype", "item_barcode")
- if frappe.get_all("Item Barcode", limit=1): return
- if "barcode" not in frappe.db.get_table_columns("Item"): return
+ if frappe.get_all("Item Barcode", limit=1):
+ return
+ if "barcode" not in frappe.db.get_table_columns("Item"):
+ return
- items_barcode = frappe.db.sql("select name, barcode from tabItem where barcode is not null", as_dict=True)
+ items_barcode = frappe.db.sql(
+ "select name, barcode from tabItem where barcode is not null", as_dict=True
+ )
frappe.reload_doc("stock", "doctype", "item")
-
-
for item in items_barcode:
barcode = item.barcode.strip()
- if barcode and '<' not in barcode:
+ if barcode and "<" not in barcode:
try:
- frappe.get_doc({
- 'idx': 0,
- 'doctype': 'Item Barcode',
- 'barcode': barcode,
- 'parenttype': 'Item',
- 'parent': item.name,
- 'parentfield': 'barcodes'
- }).insert()
+ frappe.get_doc(
+ {
+ "idx": 0,
+ "doctype": "Item Barcode",
+ "barcode": barcode,
+ "parenttype": "Item",
+ "parent": item.name,
+ "parentfield": "barcodes",
+ }
+ ).insert()
except (frappe.DuplicateEntryError, frappe.UniqueValidationError):
continue
diff --git a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py
index fd511849b2..2cbbe055f6 100644
--- a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py
+++ b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py
@@ -6,13 +6,13 @@ import frappe
def execute():
- if not frappe.db.table_exists('Daily Work Summary Group'):
+ if not frappe.db.table_exists("Daily Work Summary Group"):
frappe.reload_doc("hr", "doctype", "daily_work_summary_group")
frappe.reload_doc("hr", "doctype", "daily_work_summary_group_user")
# check if Daily Work Summary Settings Company table exists
try:
- frappe.db.sql('DESC `tabDaily Work Summary Settings Company`')
+ frappe.db.sql("DESC `tabDaily Work Summary Settings Company`")
except Exception:
return
@@ -20,19 +20,24 @@ def execute():
previous_setting = get_previous_setting()
if previous_setting["companies"]:
for d in previous_setting["companies"]:
- users = frappe.get_list("Employee", dict(
- company=d.company, user_id=("!=", " ")), "user_id as user")
- if(len(users)):
+ users = frappe.get_list(
+ "Employee", dict(company=d.company, user_id=("!=", " ")), "user_id as user"
+ )
+ if len(users):
# create new group entry for each company entry
- new_group = frappe.get_doc(dict(doctype="Daily Work Summary Group",
- name="Daily Work Summary for " + d.company,
- users=users,
- send_emails_at=d.send_emails_at,
- subject=previous_setting["subject"],
- message=previous_setting["message"]))
+ new_group = frappe.get_doc(
+ dict(
+ doctype="Daily Work Summary Group",
+ name="Daily Work Summary for " + d.company,
+ users=users,
+ send_emails_at=d.send_emails_at,
+ subject=previous_setting["subject"],
+ message=previous_setting["message"],
+ )
+ )
new_group.flags.ignore_permissions = True
new_group.flags.ignore_validate = True
- new_group.insert(ignore_if_duplicate = True)
+ new_group.insert(ignore_if_duplicate=True)
frappe.delete_doc("DocType", "Daily Work Summary Settings")
frappe.delete_doc("DocType", "Daily Work Summary Settings Company")
@@ -41,11 +46,13 @@ def execute():
def get_previous_setting():
obj = {}
setting_data = frappe.db.sql(
- "select field, value from tabSingles where doctype='Daily Work Summary Settings'")
+ "select field, value from tabSingles where doctype='Daily Work Summary Settings'"
+ )
for field, value in setting_data:
obj[field] = value
obj["companies"] = get_setting_companies()
return obj
+
def get_setting_companies():
return frappe.db.sql("select * from `tabDaily Work Summary Settings Company`", as_dict=True)
diff --git a/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py b/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py
index 525d1ff204..1d5518f072 100644
--- a/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py
+++ b/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py
@@ -10,5 +10,5 @@ def execute():
rename_field("Pricing Rule", "price", "rate")
except Exception as e:
- if e.args[0]!=1054:
+ if e.args[0] != 1054:
raise
diff --git a/erpnext/patches/v10_0/set_currency_in_pricing_rule.py b/erpnext/patches/v10_0/set_currency_in_pricing_rule.py
index 3f3d42400a..d68148eec1 100644
--- a/erpnext/patches/v10_0/set_currency_in_pricing_rule.py
+++ b/erpnext/patches/v10_0/set_currency_in_pricing_rule.py
@@ -5,8 +5,10 @@ def execute():
frappe.reload_doctype("Pricing Rule")
currency = frappe.db.get_default("currency")
- for doc in frappe.get_all('Pricing Rule', fields = ["company", "name"]):
+ for doc in frappe.get_all("Pricing Rule", fields=["company", "name"]):
if doc.company:
- currency = frappe.get_cached_value('Company', doc.company, "default_currency")
+ currency = frappe.get_cached_value("Company", doc.company, "default_currency")
- frappe.db.sql("""update `tabPricing Rule` set currency = %s where name = %s""",(currency, doc.name))
+ frappe.db.sql(
+ """update `tabPricing Rule` set currency = %s where name = %s""", (currency, doc.name)
+ )
diff --git a/erpnext/patches/v10_0/update_translatable_fields.py b/erpnext/patches/v10_0/update_translatable_fields.py
index 471f53704d..61b4ba5159 100644
--- a/erpnext/patches/v10_0/update_translatable_fields.py
+++ b/erpnext/patches/v10_0/update_translatable_fields.py
@@ -2,37 +2,38 @@ import frappe
def execute():
- '''
+ """
Enable translatable in these fields
- Customer Name
- Supplier Name
- Contact Name
- Item Name/ Description
- Address
- '''
+ """
- frappe.reload_doc('core', 'doctype', 'docfield')
- frappe.reload_doc('custom', 'doctype', 'custom_field')
+ frappe.reload_doc("core", "doctype", "docfield")
+ frappe.reload_doc("custom", "doctype", "custom_field")
enable_for_fields = [
- ['Customer', 'customer_name'],
- ['Supplier', 'supplier_name'],
- ['Contact', 'first_name'],
- ['Contact', 'last_name'],
- ['Item', 'item_name'],
- ['Item', 'description'],
- ['Address', 'address_line1'],
- ['Address', 'address_line2'],
+ ["Customer", "customer_name"],
+ ["Supplier", "supplier_name"],
+ ["Contact", "first_name"],
+ ["Contact", "last_name"],
+ ["Item", "item_name"],
+ ["Item", "description"],
+ ["Address", "address_line1"],
+ ["Address", "address_line2"],
]
-
for f in enable_for_fields:
- frappe.get_doc({
- 'doctype': 'Property Setter',
- 'doc_type': f[0],
- 'doctype_or_field': 'DocField',
- 'field_name': f[1],
- 'property': 'translatable',
- 'propery_type': 'Check',
- 'value': 1
- }).db_insert()
+ frappe.get_doc(
+ {
+ "doctype": "Property Setter",
+ "doc_type": f[0],
+ "doctype_or_field": "DocField",
+ "field_name": f[1],
+ "property": "translatable",
+ "propery_type": "Check",
+ "value": 1,
+ }
+ ).db_insert()
diff --git a/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py b/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py
index 6530b815cc..87151c102b 100644
--- a/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py
+++ b/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py
@@ -3,41 +3,57 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.reload_doc('automation', 'doctype', 'auto_repeat')
+ frappe.reload_doc("automation", "doctype", "auto_repeat")
doctypes_to_rename = {
- 'accounts': ['Journal Entry', 'Payment Entry', 'Purchase Invoice', 'Sales Invoice'],
- 'buying': ['Purchase Order', 'Supplier Quotation'],
- 'selling': ['Quotation', 'Sales Order'],
- 'stock': ['Delivery Note', 'Purchase Receipt']
+ "accounts": ["Journal Entry", "Payment Entry", "Purchase Invoice", "Sales Invoice"],
+ "buying": ["Purchase Order", "Supplier Quotation"],
+ "selling": ["Quotation", "Sales Order"],
+ "stock": ["Delivery Note", "Purchase Receipt"],
}
for module, doctypes in doctypes_to_rename.items():
for doctype in doctypes:
- frappe.reload_doc(module, 'doctype', frappe.scrub(doctype))
+ frappe.reload_doc(module, "doctype", frappe.scrub(doctype))
- if frappe.db.has_column(doctype, 'subscription'):
- rename_field(doctype, 'subscription', 'auto_repeat')
+ if frappe.db.has_column(doctype, "subscription"):
+ rename_field(doctype, "subscription", "auto_repeat")
- subscriptions = frappe.db.sql('select * from `tabSubscription`', as_dict=1)
+ subscriptions = frappe.db.sql("select * from `tabSubscription`", as_dict=1)
for doc in subscriptions:
- doc['doctype'] = 'Auto Repeat'
+ doc["doctype"] = "Auto Repeat"
auto_repeat = frappe.get_doc(doc)
auto_repeat.db_insert()
- frappe.db.sql('delete from `tabSubscription`')
+ frappe.db.sql("delete from `tabSubscription`")
frappe.db.commit()
drop_columns_from_subscription()
+
def drop_columns_from_subscription():
- fields_to_drop = {'Subscription': []}
- for field in ['naming_series', 'reference_doctype', 'reference_document', 'start_date',
- 'end_date', 'submit_on_creation', 'disabled', 'frequency', 'repeat_on_day',
- 'next_schedule_date', 'notify_by_email', 'subject', 'recipients', 'print_format',
- 'message', 'status', 'amended_from']:
+ fields_to_drop = {"Subscription": []}
+ for field in [
+ "naming_series",
+ "reference_doctype",
+ "reference_document",
+ "start_date",
+ "end_date",
+ "submit_on_creation",
+ "disabled",
+ "frequency",
+ "repeat_on_day",
+ "next_schedule_date",
+ "notify_by_email",
+ "subject",
+ "recipients",
+ "print_format",
+ "message",
+ "status",
+ "amended_from",
+ ]:
if field in frappe.db.get_table_columns("Subscription"):
- fields_to_drop['Subscription'].append(field)
+ fields_to_drop["Subscription"].append(field)
frappe.model.delete_fields(fields_to_drop, delete=1)
diff --git a/erpnext/patches/v11_0/add_default_dispatch_notification_template.py b/erpnext/patches/v11_0/add_default_dispatch_notification_template.py
index c7771a5f19..48ca9b9f5b 100644
--- a/erpnext/patches/v11_0/add_default_dispatch_notification_template.py
+++ b/erpnext/patches/v11_0/add_default_dispatch_notification_template.py
@@ -10,15 +10,19 @@ def execute():
if not frappe.db.exists("Email Template", _("Dispatch Notification")):
base_path = frappe.get_app_path("erpnext", "stock", "doctype")
- response = frappe.read_file(os.path.join(base_path, "delivery_trip/dispatch_notification_template.html"))
+ response = frappe.read_file(
+ os.path.join(base_path, "delivery_trip/dispatch_notification_template.html")
+ )
- frappe.get_doc({
- "doctype": "Email Template",
- "name": _("Dispatch Notification"),
- "response": response,
- "subject": _("Your order is out for delivery!"),
- "owner": frappe.session.user,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Dispatch Notification"),
+ "response": response,
+ "subject": _("Your order is out for delivery!"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
delivery_settings = frappe.get_doc("Delivery Settings")
delivery_settings.dispatch_template = _("Dispatch Notification")
diff --git a/erpnext/patches/v11_0/add_default_email_template_for_leave.py b/erpnext/patches/v11_0/add_default_email_template_for_leave.py
index fdf30469bf..1fddc7f11e 100644
--- a/erpnext/patches/v11_0/add_default_email_template_for_leave.py
+++ b/erpnext/patches/v11_0/add_default_email_template_for_leave.py
@@ -7,25 +7,32 @@ from frappe import _
def execute():
frappe.reload_doc("email", "doctype", "email_template")
- if not frappe.db.exists("Email Template", _('Leave Approval Notification')):
+ if not frappe.db.exists("Email Template", _("Leave Approval Notification")):
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
- response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html"))
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _("Leave Approval Notification"),
- 'response': response,
- 'subject': _("Leave Approval Notification"),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ response = frappe.read_file(
+ os.path.join(base_path, "leave_application/leave_application_email_template.html")
+ )
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Leave Approval Notification"),
+ "response": response,
+ "subject": _("Leave Approval Notification"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
-
- if not frappe.db.exists("Email Template", _('Leave Status Notification')):
+ if not frappe.db.exists("Email Template", _("Leave Status Notification")):
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
- response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html"))
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _("Leave Status Notification"),
- 'response': response,
- 'subject': _("Leave Status Notification"),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ response = frappe.read_file(
+ os.path.join(base_path, "leave_application/leave_application_email_template.html")
+ )
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Leave Status Notification"),
+ "response": response,
+ "subject": _("Leave Status Notification"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
diff --git a/erpnext/patches/v11_0/add_expense_claim_default_account.py b/erpnext/patches/v11_0/add_expense_claim_default_account.py
index f5658c5b93..ff393502d7 100644
--- a/erpnext/patches/v11_0/add_expense_claim_default_account.py
+++ b/erpnext/patches/v11_0/add_expense_claim_default_account.py
@@ -8,4 +8,9 @@ def execute():
for company in companies:
if company.default_payable_account is not None:
- frappe.db.set_value("Company", company.name, "default_expense_claim_payable_account", company.default_payable_account)
+ frappe.db.set_value(
+ "Company",
+ company.name,
+ "default_expense_claim_payable_account",
+ company.default_payable_account,
+ )
diff --git a/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py b/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py
index 7c99f580f7..f354616fe7 100644
--- a/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py
+++ b/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py
@@ -7,6 +7,16 @@ import frappe
def execute():
frappe.reload_doc("assets", "doctype", "Location")
- for dt in ("Account", "Cost Center", "File", "Employee", "Location", "Task", "Customer Group", "Sales Person", "Territory"):
+ for dt in (
+ "Account",
+ "Cost Center",
+ "File",
+ "Employee",
+ "Location",
+ "Task",
+ "Customer Group",
+ "Sales Person",
+ "Territory",
+ ):
frappe.reload_doctype(dt)
frappe.get_doc("DocType", dt).run_module_method("on_doctype_update")
diff --git a/erpnext/patches/v11_0/add_item_group_defaults.py b/erpnext/patches/v11_0/add_item_group_defaults.py
index 026047a961..4e6505a356 100644
--- a/erpnext/patches/v11_0/add_item_group_defaults.py
+++ b/erpnext/patches/v11_0/add_item_group_defaults.py
@@ -6,31 +6,36 @@ import frappe
def execute():
- '''
+ """
Fields to move from item group to item defaults child table
[ default_cost_center, default_expense_account, default_income_account ]
- '''
+ """
- frappe.reload_doc('stock', 'doctype', 'item_default')
- frappe.reload_doc('setup', 'doctype', 'item_group')
+ frappe.reload_doc("stock", "doctype", "item_default")
+ frappe.reload_doc("setup", "doctype", "item_group")
companies = frappe.get_all("Company")
- item_groups = frappe.db.sql("""select name, default_income_account, default_expense_account,\
- default_cost_center from `tabItem Group`""", as_dict=True)
+ item_groups = frappe.db.sql(
+ """select name, default_income_account, default_expense_account,\
+ default_cost_center from `tabItem Group`""",
+ as_dict=True,
+ )
if len(companies) == 1:
for item_group in item_groups:
doc = frappe.get_doc("Item Group", item_group.get("name"))
item_group_defaults = []
- item_group_defaults.append({
- "company": companies[0].name,
- "income_account": item_group.get("default_income_account"),
- "expense_account": item_group.get("default_expense_account"),
- "buying_cost_center": item_group.get("default_cost_center"),
- "selling_cost_center": item_group.get("default_cost_center")
- })
+ item_group_defaults.append(
+ {
+ "company": companies[0].name,
+ "income_account": item_group.get("default_income_account"),
+ "expense_account": item_group.get("default_expense_account"),
+ "buying_cost_center": item_group.get("default_cost_center"),
+ "selling_cost_center": item_group.get("default_cost_center"),
+ }
+ )
doc.extend("item_group_defaults", item_group_defaults)
for child_doc in doc.item_group_defaults:
child_doc.db_insert()
@@ -38,10 +43,11 @@ def execute():
item_group_dict = {
"default_expense_account": ["expense_account"],
"default_income_account": ["income_account"],
- "default_cost_center": ["buying_cost_center", "selling_cost_center"]
+ "default_cost_center": ["buying_cost_center", "selling_cost_center"],
}
for item_group in item_groups:
item_group_defaults = []
+
def insert_into_item_defaults(doc_field_name, doc_field_value, company):
for d in item_group_defaults:
if d.get("company") == company:
@@ -50,18 +56,16 @@ def execute():
d[doc_field_name[1]] = doc_field_value
return
- item_group_defaults.append({
- "company": company,
- doc_field_name[0]: doc_field_value
- })
+ item_group_defaults.append({"company": company, doc_field_name[0]: doc_field_value})
- if(len(doc_field_name) > 1):
- item_group_defaults[len(item_group_defaults)-1][doc_field_name[1]] = doc_field_value
+ if len(doc_field_name) > 1:
+ item_group_defaults[len(item_group_defaults) - 1][doc_field_name[1]] = doc_field_value
for d in [
- ["default_expense_account", "Account"], ["default_income_account", "Account"],
- ["default_cost_center", "Cost Center"]
- ]:
+ ["default_expense_account", "Account"],
+ ["default_income_account", "Account"],
+ ["default_cost_center", "Cost Center"],
+ ]:
if item_group.get(d[0]):
company = frappe.get_value(d[1], item_group.get(d[0]), "company", cache=True)
doc_field_name = item_group_dict.get(d[0])
diff --git a/erpnext/patches/v11_0/add_market_segments.py b/erpnext/patches/v11_0/add_market_segments.py
index 6dcbf99e16..d1111c21e0 100644
--- a/erpnext/patches/v11_0/add_market_segments.py
+++ b/erpnext/patches/v11_0/add_market_segments.py
@@ -4,8 +4,8 @@ from erpnext.setup.setup_wizard.operations.install_fixtures import add_market_se
def execute():
- frappe.reload_doc('crm', 'doctype', 'market_segment')
+ frappe.reload_doc("crm", "doctype", "market_segment")
- frappe.local.lang = frappe.db.get_default("lang") or 'en'
+ frappe.local.lang = frappe.db.get_default("lang") or "en"
add_market_segments()
diff --git a/erpnext/patches/v11_0/add_permissions_in_gst_settings.py b/erpnext/patches/v11_0/add_permissions_in_gst_settings.py
index 9df1b586e3..f3429ef1c9 100644
--- a/erpnext/patches/v11_0/add_permissions_in_gst_settings.py
+++ b/erpnext/patches/v11_0/add_permissions_in_gst_settings.py
@@ -4,7 +4,7 @@ from erpnext.regional.india.setup import add_permissions
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
diff --git a/erpnext/patches/v11_0/add_sales_stages.py b/erpnext/patches/v11_0/add_sales_stages.py
index 064b72195f..0dac1e10ed 100644
--- a/erpnext/patches/v11_0/add_sales_stages.py
+++ b/erpnext/patches/v11_0/add_sales_stages.py
@@ -4,8 +4,8 @@ from erpnext.setup.setup_wizard.operations.install_fixtures import add_sale_stag
def execute():
- frappe.reload_doc('crm', 'doctype', 'sales_stage')
+ frappe.reload_doc("crm", "doctype", "sales_stage")
- frappe.local.lang = frappe.db.get_default("lang") or 'en'
+ frappe.local.lang = frappe.db.get_default("lang") or "en"
add_sale_stages()
diff --git a/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py b/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py
index 573021270d..d9d7981965 100644
--- a/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py
+++ b/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py
@@ -2,5 +2,5 @@ import frappe
def execute():
- frappe.reload_doc('setup', 'doctype', 'currency_exchange')
+ frappe.reload_doc("setup", "doctype", "currency_exchange")
frappe.db.sql("""update `tabCurrency Exchange` set for_buying = 1, for_selling = 1""")
diff --git a/erpnext/patches/v11_0/create_department_records_for_each_company.py b/erpnext/patches/v11_0/create_department_records_for_each_company.py
index 034418c137..84be2bee9d 100644
--- a/erpnext/patches/v11_0/create_department_records_for_each_company.py
+++ b/erpnext/patches/v11_0/create_department_records_for_each_company.py
@@ -4,11 +4,11 @@ from frappe.utils.nestedset import rebuild_tree
def execute():
- frappe.local.lang = frappe.db.get_default("lang") or 'en'
+ frappe.local.lang = frappe.db.get_default("lang") or "en"
- for doctype in ['department', 'leave_period', 'staffing_plan', 'job_opening']:
+ for doctype in ["department", "leave_period", "staffing_plan", "job_opening"]:
frappe.reload_doc("hr", "doctype", doctype)
- frappe.reload_doc("Payroll", "doctype", 'payroll_entry')
+ frappe.reload_doc("Payroll", "doctype", "payroll_entry")
companies = frappe.db.get_all("Company", fields=["name", "abbr"])
departments = frappe.db.get_all("Department")
@@ -35,7 +35,7 @@ def execute():
# append list of new department for each company
comp_dict[company.name][department.name] = copy_doc.name
- rebuild_tree('Department', 'parent_department')
+ rebuild_tree("Department", "parent_department")
doctypes = ["Asset", "Employee", "Payroll Entry", "Staffing Plan", "Job Opening"]
for d in doctypes:
@@ -43,7 +43,8 @@ def execute():
update_instructors(comp_dict)
- frappe.local.lang = 'en'
+ frappe.local.lang = "en"
+
def update_records(doctype, comp_dict):
when_then = []
@@ -51,20 +52,27 @@ def update_records(doctype, comp_dict):
records = comp_dict[company]
for department in records:
- when_then.append('''
+ when_then.append(
+ """
WHEN company = "%s" and department = "%s"
THEN "%s"
- '''%(company, department, records[department]))
+ """
+ % (company, department, records[department])
+ )
if not when_then:
return
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update
`tab%s`
set
department = CASE %s END
- """%(doctype, " ".join(when_then)))
+ """
+ % (doctype, " ".join(when_then))
+ )
+
def update_instructors(comp_dict):
when_then = []
@@ -74,17 +82,23 @@ def update_instructors(comp_dict):
records = comp_dict[employee.company] if employee.company else []
for department in records:
- when_then.append('''
+ when_then.append(
+ """
WHEN employee = "%s" and department = "%s"
THEN "%s"
- '''%(employee.name, department, records[department]))
+ """
+ % (employee.name, department, records[department])
+ )
if not when_then:
return
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update
`tabInstructor`
set
department = CASE %s END
- """%(" ".join(when_then)))
+ """
+ % (" ".join(when_then))
+ )
diff --git a/erpnext/patches/v11_0/create_salary_structure_assignments.py b/erpnext/patches/v11_0/create_salary_structure_assignments.py
index 823eca19b0..b81e867b9d 100644
--- a/erpnext/patches/v11_0/create_salary_structure_assignments.py
+++ b/erpnext/patches/v11_0/create_salary_structure_assignments.py
@@ -13,48 +13,62 @@ from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assign
def execute():
- frappe.reload_doc('Payroll', 'doctype', 'Salary Structure')
+ frappe.reload_doc("Payroll", "doctype", "Salary Structure")
frappe.reload_doc("Payroll", "doctype", "Salary Structure Assignment")
- frappe.db.sql("""
+ frappe.db.sql(
+ """
delete from `tabSalary Structure Assignment`
where salary_structure in (select name from `tabSalary Structure` where is_active='No' or docstatus!=1)
- """)
- if frappe.db.table_exists('Salary Structure Employee'):
- ss_details = frappe.db.sql("""
+ """
+ )
+ if frappe.db.table_exists("Salary Structure Employee"):
+ ss_details = frappe.db.sql(
+ """
select sse.employee, sse.employee_name, sse.from_date, sse.to_date,
sse.base, sse.variable, sse.parent as salary_structure, ss.company
from `tabSalary Structure Employee` sse, `tabSalary Structure` ss
where ss.name = sse.parent AND ss.is_active='Yes'
- AND sse.employee in (select name from `tabEmployee` where ifNull(status, '') != 'Left')""", as_dict=1)
+ AND sse.employee in (select name from `tabEmployee` where ifNull(status, '') != 'Left')""",
+ as_dict=1,
+ )
else:
cols = ""
if "base" in frappe.db.get_table_columns("Salary Structure"):
cols = ", base, variable"
- ss_details = frappe.db.sql("""
+ ss_details = frappe.db.sql(
+ """
select name as salary_structure, employee, employee_name, from_date, to_date, company {0}
from `tabSalary Structure`
where is_active='Yes'
AND employee in (select name from `tabEmployee` where ifNull(status, '') != 'Left')
- """.format(cols), as_dict=1)
+ """.format(
+ cols
+ ),
+ as_dict=1,
+ )
all_companies = frappe.db.get_all("Company", fields=["name", "default_currency"])
for d in all_companies:
company = d.name
company_currency = d.default_currency
- frappe.db.sql("""update `tabSalary Structure` set currency = %s where company=%s""", (company_currency, company))
+ frappe.db.sql(
+ """update `tabSalary Structure` set currency = %s where company=%s""",
+ (company_currency, company),
+ )
for d in ss_details:
try:
- joining_date, relieving_date = frappe.db.get_value("Employee", d.employee,
- ["date_of_joining", "relieving_date"])
+ joining_date, relieving_date = frappe.db.get_value(
+ "Employee", d.employee, ["date_of_joining", "relieving_date"]
+ )
from_date = d.from_date
if joining_date and getdate(from_date) < joining_date:
from_date = joining_date
elif relieving_date and getdate(from_date) > relieving_date:
continue
- company_currency = frappe.db.get_value('Company', d.company, 'default_currency')
+ company_currency = frappe.db.get_value("Company", d.company, "default_currency")
s = frappe.new_doc("Salary Structure Assignment")
s.employee = d.employee
diff --git a/erpnext/patches/v11_0/drop_column_max_days_allowed.py b/erpnext/patches/v11_0/drop_column_max_days_allowed.py
index f0803cb5c7..4b4770d809 100644
--- a/erpnext/patches/v11_0/drop_column_max_days_allowed.py
+++ b/erpnext/patches/v11_0/drop_column_max_days_allowed.py
@@ -3,5 +3,5 @@ import frappe
def execute():
if frappe.db.exists("DocType", "Leave Type"):
- if 'max_days_allowed' in frappe.db.get_table_columns("Leave Type"):
+ if "max_days_allowed" in frappe.db.get_table_columns("Leave Type"):
frappe.db.sql("alter table `tabLeave Type` drop column max_days_allowed")
diff --git a/erpnext/patches/v11_0/ewaybill_fields_gst_india.py b/erpnext/patches/v11_0/ewaybill_fields_gst_india.py
index 5974e27059..7a06d52242 100644
--- a/erpnext/patches/v11_0/ewaybill_fields_gst_india.py
+++ b/erpnext/patches/v11_0/ewaybill_fields_gst_india.py
@@ -4,8 +4,8 @@ from erpnext.regional.india.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
- if not company:
- return
+ company = frappe.get_all("Company", filters={"country": "India"})
+ if not company:
+ return
- make_custom_fields()
+ make_custom_fields()
diff --git a/erpnext/patches/v11_0/hr_ux_cleanups.py b/erpnext/patches/v11_0/hr_ux_cleanups.py
index 43c8504294..0749bfc0b9 100644
--- a/erpnext/patches/v11_0/hr_ux_cleanups.py
+++ b/erpnext/patches/v11_0/hr_ux_cleanups.py
@@ -2,11 +2,11 @@ import frappe
def execute():
- frappe.reload_doctype('Employee')
- frappe.db.sql('update tabEmployee set first_name = employee_name')
+ frappe.reload_doctype("Employee")
+ frappe.db.sql("update tabEmployee set first_name = employee_name")
# update holiday list
- frappe.reload_doctype('Holiday List')
- for holiday_list in frappe.get_all('Holiday List'):
- holiday_list = frappe.get_doc('Holiday List', holiday_list.name)
- holiday_list.db_set('total_holidays', len(holiday_list.holidays), update_modified = False)
+ frappe.reload_doctype("Holiday List")
+ for holiday_list in frappe.get_all("Holiday List"):
+ holiday_list = frappe.get_doc("Holiday List", holiday_list.name)
+ holiday_list.db_set("total_holidays", len(holiday_list.holidays), update_modified=False)
diff --git a/erpnext/patches/v11_0/inter_state_field_for_gst.py b/erpnext/patches/v11_0/inter_state_field_for_gst.py
index a1f159483b..b8510297c2 100644
--- a/erpnext/patches/v11_0/inter_state_field_for_gst.py
+++ b/erpnext/patches/v11_0/inter_state_field_for_gst.py
@@ -4,7 +4,7 @@ from erpnext.regional.india.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
frappe.reload_doc("Payroll", "doctype", "Employee Tax Exemption Declaration")
@@ -28,38 +28,64 @@ def execute():
frappe.reload_doc("accounts", "doctype", "purchase_taxes_and_charges_template")
# set is_inter_state in Taxes And Charges Templates
- if frappe.db.has_column("Sales Taxes and Charges Template", "is_inter_state") and\
- frappe.db.has_column("Purchase Taxes and Charges Template", "is_inter_state"):
+ if frappe.db.has_column(
+ "Sales Taxes and Charges Template", "is_inter_state"
+ ) and frappe.db.has_column("Purchase Taxes and Charges Template", "is_inter_state"):
- igst_accounts = set(frappe.db.sql_list('''SELECT igst_account from `tabGST Account` WHERE parent = "GST Settings"'''))
- cgst_accounts = set(frappe.db.sql_list('''SELECT cgst_account FROM `tabGST Account` WHERE parenttype = "GST Settings"'''))
+ igst_accounts = set(
+ frappe.db.sql_list(
+ '''SELECT igst_account from `tabGST Account` WHERE parent = "GST Settings"'''
+ )
+ )
+ cgst_accounts = set(
+ frappe.db.sql_list(
+ '''SELECT cgst_account FROM `tabGST Account` WHERE parenttype = "GST Settings"'''
+ )
+ )
when_then_sales = get_formatted_data("Sales Taxes and Charges", igst_accounts, cgst_accounts)
- when_then_purchase = get_formatted_data("Purchase Taxes and Charges", igst_accounts, cgst_accounts)
+ when_then_purchase = get_formatted_data(
+ "Purchase Taxes and Charges", igst_accounts, cgst_accounts
+ )
if when_then_sales:
- frappe.db.sql('''update `tabSales Taxes and Charges Template`
+ frappe.db.sql(
+ """update `tabSales Taxes and Charges Template`
set is_inter_state = Case {when_then} Else 0 End
- '''.format(when_then=" ".join(when_then_sales)))
+ """.format(
+ when_then=" ".join(when_then_sales)
+ )
+ )
if when_then_purchase:
- frappe.db.sql('''update `tabPurchase Taxes and Charges Template`
+ frappe.db.sql(
+ """update `tabPurchase Taxes and Charges Template`
set is_inter_state = Case {when_then} Else 0 End
- '''.format(when_then=" ".join(when_then_purchase)))
+ """.format(
+ when_then=" ".join(when_then_purchase)
+ )
+ )
+
def get_formatted_data(doctype, igst_accounts, cgst_accounts):
# fetch all the rows data from child table
- all_details = frappe.db.sql('''
+ all_details = frappe.db.sql(
+ '''
select parent, account_head from `tab{doctype}`
- where parenttype="{doctype} Template"'''.format(doctype=doctype), as_dict=True)
+ where parenttype="{doctype} Template"'''.format(
+ doctype=doctype
+ ),
+ as_dict=True,
+ )
# group the data in the form "parent: [list of accounts]""
group_detail = {}
for i in all_details:
- if not i['parent'] in group_detail: group_detail[i['parent']] = []
+ if not i["parent"] in group_detail:
+ group_detail[i["parent"]] = []
for j in all_details:
- if i['parent']==j['parent']:
- group_detail[i['parent']].append(j['account_head'])
+ if i["parent"] == j["parent"]:
+ group_detail[i["parent"]].append(j["account_head"])
# form when_then condition based on - if list of accounts for a document
# matches any account in igst_accounts list and not matches any in cgst_accounts list
@@ -67,6 +93,6 @@ def get_formatted_data(doctype, igst_accounts, cgst_accounts):
for i in group_detail:
temp = set(group_detail[i])
if not temp.isdisjoint(igst_accounts) and temp.isdisjoint(cgst_accounts):
- when_then.append('''When name='{name}' Then 1'''.format(name=i))
+ when_then.append("""When name='{name}' Then 1""".format(name=i))
return when_then
diff --git a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py
index cd3869b360..213145653d 100644
--- a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py
+++ b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py
@@ -6,40 +6,50 @@ import frappe
def execute():
- frappe.reload_doc('assets', 'doctype', 'asset_finance_book')
- frappe.reload_doc('assets', 'doctype', 'depreciation_schedule')
- frappe.reload_doc('assets', 'doctype', 'asset_category')
- frappe.reload_doc('assets', 'doctype', 'asset')
- frappe.reload_doc('assets', 'doctype', 'asset_movement')
- frappe.reload_doc('assets', 'doctype', 'asset_category_account')
+ frappe.reload_doc("assets", "doctype", "asset_finance_book")
+ frappe.reload_doc("assets", "doctype", "depreciation_schedule")
+ frappe.reload_doc("assets", "doctype", "asset_category")
+ frappe.reload_doc("assets", "doctype", "asset")
+ frappe.reload_doc("assets", "doctype", "asset_movement")
+ frappe.reload_doc("assets", "doctype", "asset_category_account")
if frappe.db.has_column("Asset", "warehouse"):
- frappe.db.sql(""" update `tabAsset` ast, `tabWarehouse` wh
- set ast.location = wh.warehouse_name where ast.warehouse = wh.name""")
+ frappe.db.sql(
+ """ update `tabAsset` ast, `tabWarehouse` wh
+ set ast.location = wh.warehouse_name where ast.warehouse = wh.name"""
+ )
- for d in frappe.get_all('Asset'):
- doc = frappe.get_doc('Asset', d.name)
+ for d in frappe.get_all("Asset"):
+ doc = frappe.get_doc("Asset", d.name)
if doc.calculate_depreciation:
- fb = doc.append('finance_books', {
- 'depreciation_method': doc.depreciation_method,
- 'total_number_of_depreciations': doc.total_number_of_depreciations,
- 'frequency_of_depreciation': doc.frequency_of_depreciation,
- 'depreciation_start_date': doc.next_depreciation_date,
- 'expected_value_after_useful_life': doc.expected_value_after_useful_life,
- 'value_after_depreciation': doc.value_after_depreciation
- })
+ fb = doc.append(
+ "finance_books",
+ {
+ "depreciation_method": doc.depreciation_method,
+ "total_number_of_depreciations": doc.total_number_of_depreciations,
+ "frequency_of_depreciation": doc.frequency_of_depreciation,
+ "depreciation_start_date": doc.next_depreciation_date,
+ "expected_value_after_useful_life": doc.expected_value_after_useful_life,
+ "value_after_depreciation": doc.value_after_depreciation,
+ },
+ )
fb.db_update()
- frappe.db.sql(""" update `tabDepreciation Schedule` ds, `tabAsset` ast
- set ds.depreciation_method = ast.depreciation_method, ds.finance_book_id = 1 where ds.parent = ast.name """)
+ frappe.db.sql(
+ """ update `tabDepreciation Schedule` ds, `tabAsset` ast
+ set ds.depreciation_method = ast.depreciation_method, ds.finance_book_id = 1 where ds.parent = ast.name """
+ )
- for category in frappe.get_all('Asset Category'):
+ for category in frappe.get_all("Asset Category"):
asset_category_doc = frappe.get_doc("Asset Category", category)
- row = asset_category_doc.append('finance_books', {
- 'depreciation_method': asset_category_doc.depreciation_method,
- 'total_number_of_depreciations': asset_category_doc.total_number_of_depreciations,
- 'frequency_of_depreciation': asset_category_doc.frequency_of_depreciation
- })
+ row = asset_category_doc.append(
+ "finance_books",
+ {
+ "depreciation_method": asset_category_doc.depreciation_method,
+ "total_number_of_depreciations": asset_category_doc.total_number_of_depreciations,
+ "frequency_of_depreciation": asset_category_doc.frequency_of_depreciation,
+ },
+ )
row.db_update()
diff --git a/erpnext/patches/v11_0/make_italian_localization_fields.py b/erpnext/patches/v11_0/make_italian_localization_fields.py
index 8ff23a50d4..1b9793df80 100644
--- a/erpnext/patches/v11_0/make_italian_localization_fields.py
+++ b/erpnext/patches/v11_0/make_italian_localization_fields.py
@@ -9,11 +9,11 @@ from erpnext.regional.italy.setup import make_custom_fields, setup_report
def execute():
- company = frappe.get_all('Company', filters = {'country': 'Italy'})
+ company = frappe.get_all("Company", filters={"country": "Italy"})
if not company:
return
- frappe.reload_doc('regional', 'report', 'electronic_invoice_register')
+ frappe.reload_doc("regional", "report", "electronic_invoice_register")
make_custom_fields()
setup_report()
@@ -25,15 +25,21 @@ def execute():
if condition:
condition = "state_code = (case state {0} end),".format(condition)
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE tabAddress set {condition} country_code = UPPER(ifnull((select code
from `tabCountry` where name = `tabAddress`.country), ''))
where country_code is null and state_code is null
- """.format(condition=condition))
+ """.format(
+ condition=condition
+ )
+ )
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabSales Invoice Item` si, `tabSales Order` so
set si.customer_po_no = so.po_no, si.customer_po_date = so.po_date
WHERE
si.sales_order = so.name and so.po_no is not null
- """)
+ """
+ )
diff --git a/erpnext/patches/v11_0/make_job_card.py b/erpnext/patches/v11_0/make_job_card.py
index 120e018805..d4b208956b 100644
--- a/erpnext/patches/v11_0/make_job_card.py
+++ b/erpnext/patches/v11_0/make_job_card.py
@@ -8,21 +8,26 @@ from erpnext.manufacturing.doctype.work_order.work_order import create_job_card
def execute():
- frappe.reload_doc('manufacturing', 'doctype', 'work_order')
- frappe.reload_doc('manufacturing', 'doctype', 'work_order_item')
- frappe.reload_doc('manufacturing', 'doctype', 'job_card')
- frappe.reload_doc('manufacturing', 'doctype', 'job_card_item')
+ frappe.reload_doc("manufacturing", "doctype", "work_order")
+ frappe.reload_doc("manufacturing", "doctype", "work_order_item")
+ frappe.reload_doc("manufacturing", "doctype", "job_card")
+ frappe.reload_doc("manufacturing", "doctype", "job_card_item")
- fieldname = frappe.db.get_value('DocField', {'fieldname': 'work_order', 'parent': 'Timesheet'}, 'fieldname')
+ fieldname = frappe.db.get_value(
+ "DocField", {"fieldname": "work_order", "parent": "Timesheet"}, "fieldname"
+ )
if not fieldname:
- fieldname = frappe.db.get_value('DocField', {'fieldname': 'production_order', 'parent': 'Timesheet'}, 'fieldname')
- if not fieldname: return
+ fieldname = frappe.db.get_value(
+ "DocField", {"fieldname": "production_order", "parent": "Timesheet"}, "fieldname"
+ )
+ if not fieldname:
+ return
- for d in frappe.get_all('Timesheet',
- filters={fieldname: ['!=', ""], 'docstatus': 0},
- fields=[fieldname, 'name']):
+ for d in frappe.get_all(
+ "Timesheet", filters={fieldname: ["!=", ""], "docstatus": 0}, fields=[fieldname, "name"]
+ ):
if d[fieldname]:
- doc = frappe.get_doc('Work Order', d[fieldname])
+ doc = frappe.get_doc("Work Order", d[fieldname])
for row in doc.operations:
create_job_card(doc, row, auto_create=True)
- frappe.delete_doc('Timesheet', d.name)
+ frappe.delete_doc("Timesheet", d.name)
diff --git a/erpnext/patches/v11_0/make_location_from_warehouse.py b/erpnext/patches/v11_0/make_location_from_warehouse.py
index ef6262be15..c863bb7ecf 100644
--- a/erpnext/patches/v11_0/make_location_from_warehouse.py
+++ b/erpnext/patches/v11_0/make_location_from_warehouse.py
@@ -7,14 +7,16 @@ from frappe.utils.nestedset import rebuild_tree
def execute():
- if not frappe.db.get_value('Asset', {'docstatus': ('<', 2) }, 'name'): return
- frappe.reload_doc('assets', 'doctype', 'location')
- frappe.reload_doc('stock', 'doctype', 'warehouse')
+ if not frappe.db.get_value("Asset", {"docstatus": ("<", 2)}, "name"):
+ return
+ frappe.reload_doc("assets", "doctype", "location")
+ frappe.reload_doc("stock", "doctype", "warehouse")
- for d in frappe.get_all('Warehouse',
- fields = ['warehouse_name', 'is_group', 'parent_warehouse'], order_by="lft asc"):
+ for d in frappe.get_all(
+ "Warehouse", fields=["warehouse_name", "is_group", "parent_warehouse"], order_by="lft asc"
+ ):
try:
- loc = frappe.new_doc('Location')
+ loc = frappe.new_doc("Location")
loc.location_name = d.warehouse_name
loc.is_group = d.is_group
loc.flags.ignore_mandatory = True
@@ -27,5 +29,6 @@ def execute():
rebuild_tree("Location", "parent_location")
+
def get_parent_warehouse_name(warehouse):
- return frappe.db.get_value('Warehouse', warehouse, 'warehouse_name')
+ return frappe.db.get_value("Warehouse", warehouse, "warehouse_name")
diff --git a/erpnext/patches/v11_0/make_quality_inspection_template.py b/erpnext/patches/v11_0/make_quality_inspection_template.py
index 58c9fb9239..deebfa88e6 100644
--- a/erpnext/patches/v11_0/make_quality_inspection_template.py
+++ b/erpnext/patches/v11_0/make_quality_inspection_template.py
@@ -6,21 +6,29 @@ import frappe
def execute():
- frappe.reload_doc('stock', 'doctype', 'quality_inspection_template')
- frappe.reload_doc('stock', 'doctype', 'item')
+ frappe.reload_doc("stock", "doctype", "quality_inspection_template")
+ frappe.reload_doc("stock", "doctype", "item")
- for data in frappe.get_all('Item Quality Inspection Parameter',
- fields = ["distinct parent"], filters = {'parenttype': 'Item'}):
+ for data in frappe.get_all(
+ "Item Quality Inspection Parameter", fields=["distinct parent"], filters={"parenttype": "Item"}
+ ):
qc_doc = frappe.new_doc("Quality Inspection Template")
- qc_doc.quality_inspection_template_name = 'QIT/%s' % data.parent
+ qc_doc.quality_inspection_template_name = "QIT/%s" % data.parent
qc_doc.flags.ignore_mandatory = True
qc_doc.save(ignore_permissions=True)
- frappe.db.set_value('Item', data.parent, "quality_inspection_template", qc_doc.name, update_modified=False)
- frappe.db.sql(""" update `tabItem Quality Inspection Parameter`
+ frappe.db.set_value(
+ "Item", data.parent, "quality_inspection_template", qc_doc.name, update_modified=False
+ )
+ frappe.db.sql(
+ """ update `tabItem Quality Inspection Parameter`
set parentfield = 'item_quality_inspection_parameter', parenttype = 'Quality Inspection Template',
- parent = %s where parenttype = 'Item' and parent = %s""", (qc_doc.name, data.parent))
+ parent = %s where parenttype = 'Item' and parent = %s""",
+ (qc_doc.name, data.parent),
+ )
# update field in item variant settings
- frappe.db.sql(""" update `tabVariant Field` set field_name = 'quality_inspection_template'
- where field_name = 'quality_parameters'""")
+ frappe.db.sql(
+ """ update `tabVariant Field` set field_name = 'quality_inspection_template'
+ where field_name = 'quality_parameters'"""
+ )
diff --git a/erpnext/patches/v11_0/merge_land_unit_with_location.py b/erpnext/patches/v11_0/merge_land_unit_with_location.py
index e1d0b127b9..c1afef6778 100644
--- a/erpnext/patches/v11_0/merge_land_unit_with_location.py
+++ b/erpnext/patches/v11_0/merge_land_unit_with_location.py
@@ -8,51 +8,55 @@ from frappe.model.utils.rename_field import rename_field
def execute():
# Rename and reload the Land Unit and Linked Land Unit doctypes
- if frappe.db.table_exists('Land Unit') and not frappe.db.table_exists('Location'):
- frappe.rename_doc('DocType', 'Land Unit', 'Location', force=True)
+ if frappe.db.table_exists("Land Unit") and not frappe.db.table_exists("Location"):
+ frappe.rename_doc("DocType", "Land Unit", "Location", force=True)
- frappe.reload_doc('assets', 'doctype', 'location')
+ frappe.reload_doc("assets", "doctype", "location")
- if frappe.db.table_exists('Linked Land Unit') and not frappe.db.table_exists('Linked Location'):
- frappe.rename_doc('DocType', 'Linked Land Unit', 'Linked Location', force=True)
+ if frappe.db.table_exists("Linked Land Unit") and not frappe.db.table_exists("Linked Location"):
+ frappe.rename_doc("DocType", "Linked Land Unit", "Linked Location", force=True)
- frappe.reload_doc('assets', 'doctype', 'linked_location')
+ frappe.reload_doc("assets", "doctype", "linked_location")
- if not frappe.db.table_exists('Crop Cycle'):
- frappe.reload_doc('agriculture', 'doctype', 'crop_cycle')
+ if not frappe.db.table_exists("Crop Cycle"):
+ frappe.reload_doc("agriculture", "doctype", "crop_cycle")
# Rename the fields in related doctypes
- if 'linked_land_unit' in frappe.db.get_table_columns('Crop Cycle'):
- rename_field('Crop Cycle', 'linked_land_unit', 'linked_location')
+ if "linked_land_unit" in frappe.db.get_table_columns("Crop Cycle"):
+ rename_field("Crop Cycle", "linked_land_unit", "linked_location")
- if 'land_unit' in frappe.db.get_table_columns('Linked Location'):
- rename_field('Linked Location', 'land_unit', 'location')
+ if "land_unit" in frappe.db.get_table_columns("Linked Location"):
+ rename_field("Linked Location", "land_unit", "location")
if not frappe.db.exists("Location", "All Land Units"):
- frappe.get_doc({"doctype": "Location", "is_group": True, "location_name": "All Land Units"}).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {"doctype": "Location", "is_group": True, "location_name": "All Land Units"}
+ ).insert(ignore_permissions=True)
- if frappe.db.table_exists('Land Unit'):
- land_units = frappe.get_all('Land Unit', fields=['*'], order_by='lft')
+ if frappe.db.table_exists("Land Unit"):
+ land_units = frappe.get_all("Land Unit", fields=["*"], order_by="lft")
for land_unit in land_units:
- if not frappe.db.exists('Location', land_unit.get('land_unit_name')):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': land_unit.get('land_unit_name'),
- 'parent_location': land_unit.get('parent_land_unit') or "All Land Units",
- 'is_container': land_unit.get('is_container'),
- 'is_group': land_unit.get('is_group'),
- 'latitude': land_unit.get('latitude'),
- 'longitude': land_unit.get('longitude'),
- 'area': land_unit.get('area'),
- 'location': land_unit.get('location'),
- 'lft': land_unit.get('lft'),
- 'rgt': land_unit.get('rgt')
- }).insert(ignore_permissions=True)
+ if not frappe.db.exists("Location", land_unit.get("land_unit_name")):
+ frappe.get_doc(
+ {
+ "doctype": "Location",
+ "location_name": land_unit.get("land_unit_name"),
+ "parent_location": land_unit.get("parent_land_unit") or "All Land Units",
+ "is_container": land_unit.get("is_container"),
+ "is_group": land_unit.get("is_group"),
+ "latitude": land_unit.get("latitude"),
+ "longitude": land_unit.get("longitude"),
+ "area": land_unit.get("area"),
+ "location": land_unit.get("location"),
+ "lft": land_unit.get("lft"),
+ "rgt": land_unit.get("rgt"),
+ }
+ ).insert(ignore_permissions=True)
# Delete the Land Unit and Linked Land Unit doctypes
- if frappe.db.table_exists('Land Unit'):
- frappe.delete_doc('DocType', 'Land Unit', force=1)
+ if frappe.db.table_exists("Land Unit"):
+ frappe.delete_doc("DocType", "Land Unit", force=1)
- if frappe.db.table_exists('Linked Land Unit'):
- frappe.delete_doc('DocType', 'Linked Land Unit', force=1)
+ if frappe.db.table_exists("Linked Land Unit"):
+ frappe.delete_doc("DocType", "Linked Land Unit", force=1)
diff --git a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py
index bfc3fbc608..37c07799dd 100644
--- a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py
+++ b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py
@@ -6,22 +6,23 @@ import frappe
def execute():
- '''
+ """
Fields to move from the item to item defaults child table
[ default_warehouse, buying_cost_center, expense_account, selling_cost_center, income_account ]
- '''
- if not frappe.db.has_column('Item', 'default_warehouse'):
+ """
+ if not frappe.db.has_column("Item", "default_warehouse"):
return
- frappe.reload_doc('stock', 'doctype', 'item_default')
- frappe.reload_doc('stock', 'doctype', 'item')
+ frappe.reload_doc("stock", "doctype", "item_default")
+ frappe.reload_doc("stock", "doctype", "item")
companies = frappe.get_all("Company")
if len(companies) == 1 and not frappe.get_all("Item Default", limit=1):
try:
- frappe.db.sql('''
+ frappe.db.sql(
+ """
INSERT INTO `tabItem Default`
(name, parent, parenttype, parentfield, idx, company, default_warehouse,
buying_cost_center, selling_cost_center, expense_account, income_account, default_supplier)
@@ -30,22 +31,30 @@ def execute():
'item_defaults' as parentfield, 1 as idx, %s as company, default_warehouse,
buying_cost_center, selling_cost_center, expense_account, income_account, default_supplier
FROM `tabItem`;
- ''', companies[0].name)
+ """,
+ companies[0].name,
+ )
except Exception:
pass
else:
- item_details = frappe.db.sql(""" SELECT name, default_warehouse,
+ item_details = frappe.db.sql(
+ """ SELECT name, default_warehouse,
buying_cost_center, expense_account, selling_cost_center, income_account
FROM tabItem
WHERE
- name not in (select distinct parent from `tabItem Default`) and ifnull(disabled, 0) = 0"""
- , as_dict=1)
+ name not in (select distinct parent from `tabItem Default`) and ifnull(disabled, 0) = 0""",
+ as_dict=1,
+ )
items_default_data = {}
for item_data in item_details:
- for d in [["default_warehouse", "Warehouse"], ["expense_account", "Account"],
- ["income_account", "Account"], ["buying_cost_center", "Cost Center"],
- ["selling_cost_center", "Cost Center"]]:
+ for d in [
+ ["default_warehouse", "Warehouse"],
+ ["expense_account", "Account"],
+ ["income_account", "Account"],
+ ["buying_cost_center", "Cost Center"],
+ ["selling_cost_center", "Cost Center"],
+ ]:
if item_data.get(d[0]):
company = frappe.get_value(d[1], item_data.get(d[0]), "company", cache=True)
@@ -73,25 +82,32 @@ def execute():
for item_code, companywise_item_data in items_default_data.items():
for company, item_default_data in companywise_item_data.items():
- to_insert_data.append((
- frappe.generate_hash("", 10),
- item_code,
- 'Item',
- 'item_defaults',
- company,
- item_default_data.get('default_warehouse'),
- item_default_data.get('expense_account'),
- item_default_data.get('income_account'),
- item_default_data.get('buying_cost_center'),
- item_default_data.get('selling_cost_center'),
- ))
+ to_insert_data.append(
+ (
+ frappe.generate_hash("", 10),
+ item_code,
+ "Item",
+ "item_defaults",
+ company,
+ item_default_data.get("default_warehouse"),
+ item_default_data.get("expense_account"),
+ item_default_data.get("income_account"),
+ item_default_data.get("buying_cost_center"),
+ item_default_data.get("selling_cost_center"),
+ )
+ )
if to_insert_data:
- frappe.db.sql('''
+ frappe.db.sql(
+ """
INSERT INTO `tabItem Default`
(
`name`, `parent`, `parenttype`, `parentfield`, `company`, `default_warehouse`,
`expense_account`, `income_account`, `buying_cost_center`, `selling_cost_center`
)
VALUES {}
- '''.format(', '.join(['%s'] * len(to_insert_data))), tuple(to_insert_data))
+ """.format(
+ ", ".join(["%s"] * len(to_insert_data))
+ ),
+ tuple(to_insert_data),
+ )
diff --git a/erpnext/patches/v11_0/move_leave_approvers_from_employee.py b/erpnext/patches/v11_0/move_leave_approvers_from_employee.py
index 80e5ef7f37..f91a7db2a3 100644
--- a/erpnext/patches/v11_0/move_leave_approvers_from_employee.py
+++ b/erpnext/patches/v11_0/move_leave_approvers_from_employee.py
@@ -7,20 +7,23 @@ def execute():
frappe.reload_doc("hr", "doctype", "employee")
frappe.reload_doc("hr", "doctype", "department")
- if frappe.db.has_column('Department', 'leave_approver'):
- rename_field('Department', "leave_approver", "leave_approvers")
+ if frappe.db.has_column("Department", "leave_approver"):
+ rename_field("Department", "leave_approver", "leave_approvers")
- if frappe.db.has_column('Department', 'expense_approver'):
- rename_field('Department', "expense_approver", "expense_approvers")
+ if frappe.db.has_column("Department", "expense_approver"):
+ rename_field("Department", "expense_approver", "expense_approvers")
if not frappe.db.table_exists("Employee Leave Approver"):
return
- approvers = frappe.db.sql("""select distinct app.leave_approver, emp.department from
+ approvers = frappe.db.sql(
+ """select distinct app.leave_approver, emp.department from
`tabEmployee Leave Approver` app, `tabEmployee` emp
where app.parenttype = 'Employee'
and emp.name = app.parent
- """, as_dict=True)
+ """,
+ as_dict=True,
+ )
for record in approvers:
if record.department:
@@ -28,6 +31,4 @@ def execute():
if not department:
return
if not len(department.leave_approvers):
- department.append("leave_approvers",{
- "approver": record.leave_approver
- }).db_insert()
+ department.append("leave_approvers", {"approver": record.leave_approver}).db_insert()
diff --git a/erpnext/patches/v11_0/rebuild_tree_for_company.py b/erpnext/patches/v11_0/rebuild_tree_for_company.py
index cad9c6cd78..fc06c5d30d 100644
--- a/erpnext/patches/v11_0/rebuild_tree_for_company.py
+++ b/erpnext/patches/v11_0/rebuild_tree_for_company.py
@@ -4,4 +4,4 @@ from frappe.utils.nestedset import rebuild_tree
def execute():
frappe.reload_doc("setup", "doctype", "company")
- rebuild_tree('Company', 'parent_company')
+ rebuild_tree("Company", "parent_company")
diff --git a/erpnext/patches/v11_0/refactor_autoname_naming.py b/erpnext/patches/v11_0/refactor_autoname_naming.py
index 1c4d8f1f79..de453ccf21 100644
--- a/erpnext/patches/v11_0/refactor_autoname_naming.py
+++ b/erpnext/patches/v11_0/refactor_autoname_naming.py
@@ -6,99 +6,102 @@ import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
doctype_series_map = {
- 'Activity Cost': 'PROJ-ACC-.#####',
- 'Agriculture Task': 'AG-TASK-.#####',
- 'Assessment Plan': 'EDU-ASP-.YYYY.-.#####',
- 'Assessment Result': 'EDU-RES-.YYYY.-.#####',
- 'Asset Movement': 'ACC-ASM-.YYYY.-.#####',
- 'Attendance Request': 'HR-ARQ-.YY.-.MM.-.#####',
- 'Authorization Rule': 'HR-ARU-.#####',
- 'Bank Guarantee': 'ACC-BG-.YYYY.-.#####',
- 'Bin': 'MAT-BIN-.YYYY.-.#####',
- 'Certification Application': 'NPO-CAPP-.YYYY.-.#####',
- 'Certified Consultant': 'NPO-CONS-.YYYY.-.#####',
- 'Chat Room': 'CHAT-ROOM-.#####',
- 'Compensatory Leave Request': 'HR-CMP-.YY.-.MM.-.#####',
- 'Client Script': 'SYS-SCR-.#####',
- 'Employee Benefit Application': 'HR-BEN-APP-.YY.-.MM.-.#####',
- 'Employee Benefit Application Detail': '',
- 'Employee Benefit Claim': 'HR-BEN-CLM-.YY.-.MM.-.#####',
- 'Employee Incentive': 'HR-EINV-.YY.-.MM.-.#####',
- 'Employee Onboarding': 'HR-EMP-ONB-.YYYY.-.#####',
- 'Employee Onboarding Template': 'HR-EMP-ONT-.#####',
- 'Employee Promotion': 'HR-EMP-PRO-.YYYY.-.#####',
- 'Employee Separation': 'HR-EMP-SEP-.YYYY.-.#####',
- 'Employee Separation Template': 'HR-EMP-STP-.#####',
- 'Employee Tax Exemption Declaration': 'HR-TAX-DEC-.YYYY.-.#####',
- 'Employee Tax Exemption Proof Submission': 'HR-TAX-PRF-.YYYY.-.#####',
- 'Employee Transfer': 'HR-EMP-TRN-.YYYY.-.#####',
- 'Event': 'EVENT-.YYYY.-.#####',
- 'Exchange Rate Revaluation': 'ACC-ERR-.YYYY.-.#####',
- 'GL Entry': 'ACC-GLE-.YYYY.-.#####',
- 'Guardian': 'EDU-GRD-.YYYY.-.#####',
- 'Hotel Room Reservation': 'HTL-RES-.YYYY.-.#####',
- 'Item Price': '',
- 'Job Applicant': 'HR-APP-.YYYY.-.#####',
- 'Job Offer': 'HR-OFF-.YYYY.-.#####',
- 'Leave Encashment': 'HR-ENC-.YYYY.-.#####',
- 'Leave Period': 'HR-LPR-.YYYY.-.#####',
- 'Leave Policy': 'HR-LPOL-.YYYY.-.#####',
- 'Loan': 'ACC-LOAN-.YYYY.-.#####',
- 'Loan Application': 'ACC-LOAP-.YYYY.-.#####',
- 'Loyalty Point Entry': '',
- 'Membership': 'NPO-MSH-.YYYY.-.#####',
- 'Packing Slip': 'MAT-PAC-.YYYY.-.#####',
- 'Patient Appointment': 'HLC-APP-.YYYY.-.#####',
- 'Payment Terms Template Detail': '',
- 'Payroll Entry': 'HR-PRUN-.YYYY.-.#####',
- 'Period Closing Voucher': 'ACC-PCV-.YYYY.-.#####',
- 'Plant Analysis': 'AG-PLA-.YYYY.-.#####',
- 'POS Closing Entry': 'POS-CLO-.YYYY.-.#####',
- 'Prepared Report': 'SYS-PREP-.YYYY.-.#####',
- 'Program Enrollment': 'EDU-ENR-.YYYY.-.#####',
- 'Quotation Item': '',
- 'Restaurant Reservation': 'RES-RES-.YYYY.-.#####',
- 'Retention Bonus': 'HR-RTB-.YYYY.-.#####',
- 'Room': 'HTL-ROOM-.YYYY.-.#####',
- 'Salary Structure Assignment': 'HR-SSA-.YY.-.MM.-.#####',
- 'Sales Taxes and Charges': '',
- 'Share Transfer': 'ACC-SHT-.YYYY.-.#####',
- 'Shift Assignment': 'HR-SHA-.YY.-.MM.-.#####',
- 'Shift Request': 'HR-SHR-.YY.-.MM.-.#####',
- 'SMS Log': 'SYS-SMS-.#####',
- 'Soil Analysis': 'AG-ANA-.YY.-.MM.-.#####',
- 'Soil Texture': 'AG-TEX-.YYYY.-.#####',
- 'Stock Ledger Entry': 'MAT-SLE-.YYYY.-.#####',
- 'Student Leave Application': 'EDU-SLA-.YYYY.-.#####',
- 'Student Log': 'EDU-SLOG-.YYYY.-.#####',
- 'Subscription': 'ACC-SUB-.YYYY.-.#####',
- 'Task': 'TASK-.YYYY.-.#####',
- 'Tax Rule': 'ACC-TAX-RULE-.YYYY.-.#####',
- 'Training Feedback': 'HR-TRF-.YYYY.-.#####',
- 'Training Result': 'HR-TRR-.YYYY.-.#####',
- 'Travel Request': 'HR-TRQ-.YYYY.-.#####',
- 'UOM Conversion Factor': 'MAT-UOM-CNV-.#####',
- 'Water Analysis': 'HR-WAT-.YYYY.-.#####',
- 'Workflow Action': 'SYS-WACT-.#####',
+ "Activity Cost": "PROJ-ACC-.#####",
+ "Agriculture Task": "AG-TASK-.#####",
+ "Assessment Plan": "EDU-ASP-.YYYY.-.#####",
+ "Assessment Result": "EDU-RES-.YYYY.-.#####",
+ "Asset Movement": "ACC-ASM-.YYYY.-.#####",
+ "Attendance Request": "HR-ARQ-.YY.-.MM.-.#####",
+ "Authorization Rule": "HR-ARU-.#####",
+ "Bank Guarantee": "ACC-BG-.YYYY.-.#####",
+ "Bin": "MAT-BIN-.YYYY.-.#####",
+ "Certification Application": "NPO-CAPP-.YYYY.-.#####",
+ "Certified Consultant": "NPO-CONS-.YYYY.-.#####",
+ "Chat Room": "CHAT-ROOM-.#####",
+ "Compensatory Leave Request": "HR-CMP-.YY.-.MM.-.#####",
+ "Client Script": "SYS-SCR-.#####",
+ "Employee Benefit Application": "HR-BEN-APP-.YY.-.MM.-.#####",
+ "Employee Benefit Application Detail": "",
+ "Employee Benefit Claim": "HR-BEN-CLM-.YY.-.MM.-.#####",
+ "Employee Incentive": "HR-EINV-.YY.-.MM.-.#####",
+ "Employee Onboarding": "HR-EMP-ONB-.YYYY.-.#####",
+ "Employee Onboarding Template": "HR-EMP-ONT-.#####",
+ "Employee Promotion": "HR-EMP-PRO-.YYYY.-.#####",
+ "Employee Separation": "HR-EMP-SEP-.YYYY.-.#####",
+ "Employee Separation Template": "HR-EMP-STP-.#####",
+ "Employee Tax Exemption Declaration": "HR-TAX-DEC-.YYYY.-.#####",
+ "Employee Tax Exemption Proof Submission": "HR-TAX-PRF-.YYYY.-.#####",
+ "Employee Transfer": "HR-EMP-TRN-.YYYY.-.#####",
+ "Event": "EVENT-.YYYY.-.#####",
+ "Exchange Rate Revaluation": "ACC-ERR-.YYYY.-.#####",
+ "GL Entry": "ACC-GLE-.YYYY.-.#####",
+ "Guardian": "EDU-GRD-.YYYY.-.#####",
+ "Hotel Room Reservation": "HTL-RES-.YYYY.-.#####",
+ "Item Price": "",
+ "Job Applicant": "HR-APP-.YYYY.-.#####",
+ "Job Offer": "HR-OFF-.YYYY.-.#####",
+ "Leave Encashment": "HR-ENC-.YYYY.-.#####",
+ "Leave Period": "HR-LPR-.YYYY.-.#####",
+ "Leave Policy": "HR-LPOL-.YYYY.-.#####",
+ "Loan": "ACC-LOAN-.YYYY.-.#####",
+ "Loan Application": "ACC-LOAP-.YYYY.-.#####",
+ "Loyalty Point Entry": "",
+ "Membership": "NPO-MSH-.YYYY.-.#####",
+ "Packing Slip": "MAT-PAC-.YYYY.-.#####",
+ "Patient Appointment": "HLC-APP-.YYYY.-.#####",
+ "Payment Terms Template Detail": "",
+ "Payroll Entry": "HR-PRUN-.YYYY.-.#####",
+ "Period Closing Voucher": "ACC-PCV-.YYYY.-.#####",
+ "Plant Analysis": "AG-PLA-.YYYY.-.#####",
+ "POS Closing Entry": "POS-CLO-.YYYY.-.#####",
+ "Prepared Report": "SYS-PREP-.YYYY.-.#####",
+ "Program Enrollment": "EDU-ENR-.YYYY.-.#####",
+ "Quotation Item": "",
+ "Restaurant Reservation": "RES-RES-.YYYY.-.#####",
+ "Retention Bonus": "HR-RTB-.YYYY.-.#####",
+ "Room": "HTL-ROOM-.YYYY.-.#####",
+ "Salary Structure Assignment": "HR-SSA-.YY.-.MM.-.#####",
+ "Sales Taxes and Charges": "",
+ "Share Transfer": "ACC-SHT-.YYYY.-.#####",
+ "Shift Assignment": "HR-SHA-.YY.-.MM.-.#####",
+ "Shift Request": "HR-SHR-.YY.-.MM.-.#####",
+ "SMS Log": "SYS-SMS-.#####",
+ "Soil Analysis": "AG-ANA-.YY.-.MM.-.#####",
+ "Soil Texture": "AG-TEX-.YYYY.-.#####",
+ "Stock Ledger Entry": "MAT-SLE-.YYYY.-.#####",
+ "Student Leave Application": "EDU-SLA-.YYYY.-.#####",
+ "Student Log": "EDU-SLOG-.YYYY.-.#####",
+ "Subscription": "ACC-SUB-.YYYY.-.#####",
+ "Task": "TASK-.YYYY.-.#####",
+ "Tax Rule": "ACC-TAX-RULE-.YYYY.-.#####",
+ "Training Feedback": "HR-TRF-.YYYY.-.#####",
+ "Training Result": "HR-TRR-.YYYY.-.#####",
+ "Travel Request": "HR-TRQ-.YYYY.-.#####",
+ "UOM Conversion Factor": "MAT-UOM-CNV-.#####",
+ "Water Analysis": "HR-WAT-.YYYY.-.#####",
+ "Workflow Action": "SYS-WACT-.#####",
}
+
def execute():
series_to_set = get_series()
for doctype, opts in series_to_set.items():
- set_series(doctype, opts['value'])
+ set_series(doctype, opts["value"])
+
def set_series(doctype, value):
- doc = frappe.db.exists('Property Setter', {'doc_type': doctype, 'property': 'autoname'})
+ doc = frappe.db.exists("Property Setter", {"doc_type": doctype, "property": "autoname"})
if doc:
- frappe.db.set_value('Property Setter', doc, 'value', value)
+ frappe.db.set_value("Property Setter", doc, "value", value)
else:
- make_property_setter(doctype, '', 'autoname', value, '', for_doctype = True)
+ make_property_setter(doctype, "", "autoname", value, "", for_doctype=True)
+
def get_series():
series_to_set = {}
for doctype in doctype_series_map:
- if not frappe.db.exists('DocType', doctype):
+ if not frappe.db.exists("DocType", doctype):
continue
if not frappe.db.a_row_exists(doctype):
@@ -110,10 +113,11 @@ def get_series():
# set autoname property setter
if series_to_preserve:
- series_to_set[doctype] = {'value': series_to_preserve}
+ series_to_set[doctype] = {"value": series_to_preserve}
return series_to_set
+
def get_series_to_preserve(doctype):
- series_to_preserve = frappe.db.get_value('DocType', doctype, 'autoname')
+ series_to_preserve = frappe.db.get_value("DocType", doctype, "autoname")
return series_to_preserve
diff --git a/erpnext/patches/v11_0/refactor_naming_series.py b/erpnext/patches/v11_0/refactor_naming_series.py
index e0aa004e47..4945860797 100644
--- a/erpnext/patches/v11_0/refactor_naming_series.py
+++ b/erpnext/patches/v11_0/refactor_naming_series.py
@@ -6,82 +6,88 @@ import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
doctype_series_map = {
- 'Additional Salary': 'HR-ADS-.YY.-.MM.-',
- 'Appraisal': 'HR-APR-.YY.-.MM.',
- 'Asset': 'ACC-ASS-.YYYY.-',
- 'Attendance': 'HR-ATT-.YYYY.-',
- 'Auto Repeat': 'SYS-ARP-.YYYY.-',
- 'Blanket Order': 'MFG-BLR-.YYYY.-',
- 'C-Form': 'ACC-CF-.YYYY.-',
- 'Campaign': 'SAL-CAM-.YYYY.-',
- 'Course Schedule': 'EDU-CSH-.YYYY.-',
- 'Customer': 'CUST-.YYYY.-',
- 'Delivery Note': 'MAT-DN-.YYYY.-',
- 'Delivery Trip': 'MAT-DT-.YYYY.-',
- 'Driver': 'HR-DRI-.YYYY.-',
- 'Employee': 'HR-EMP-',
- 'Employee Advance': 'HR-EAD-.YYYY.-',
- 'Expense Claim': 'HR-EXP-.YYYY.-',
- 'Fee Schedule': 'EDU-FSH-.YYYY.-',
- 'Fee Structure': 'EDU-FST-.YYYY.-',
- 'Fees': 'EDU-FEE-.YYYY.-',
- 'Installation Note': 'MAT-INS-.YYYY.-',
- 'Instructor': 'EDU-INS-.YYYY.-',
- 'Issue': 'ISS-.YYYY.-',
- 'Journal Entry': 'ACC-JV-.YYYY.-',
- 'Landed Cost Voucher': 'MAT-LCV-.YYYY.-',
- 'Lead': 'CRM-LEAD-.YYYY.-',
- 'Leave Allocation': 'HR-LAL-.YYYY.-',
- 'Leave Application': 'HR-LAP-.YYYY.-',
- 'Maintenance Schedule': 'MAT-MSH-.YYYY.-',
- 'Maintenance Visit': 'MAT-MVS-.YYYY.-',
- 'Material Request': 'MAT-MR-.YYYY.-',
- 'Member': 'NPO-MEM-.YYYY.-',
- 'Opportunity': 'CRM-OPP-.YYYY.-',
- 'Packing Slip': 'MAT-PAC-.YYYY.-',
- 'Payment Entry': 'ACC-PAY-.YYYY.-',
- 'Payment Request': 'ACC-PRQ-.YYYY.-',
- 'Production Plan': 'MFG-PP-.YYYY.-',
- 'Project Update': 'PROJ-UPD-.YYYY.-',
- 'Purchase Invoice': 'ACC-PINV-.YYYY.-',
- 'Purchase Order': 'PUR-ORD-.YYYY.-',
- 'Purchase Receipt': 'MAT-PRE-.YYYY.-',
- 'Quality Inspection': 'MAT-QA-.YYYY.-',
- 'Quotation': 'SAL-QTN-.YYYY.-',
- 'Request for Quotation': 'PUR-RFQ-.YYYY.-',
- 'Sales Invoice': 'ACC-SINV-.YYYY.-',
- 'Sales Order': 'SAL-ORD-.YYYY.-',
- 'Sample Collection': 'HLC-SC-.YYYY.-',
- 'Shareholder': 'ACC-SH-.YYYY.-',
- 'Stock Entry': 'MAT-STE-.YYYY.-',
- 'Stock Reconciliation': 'MAT-RECO-.YYYY.-',
- 'Student': 'EDU-STU-.YYYY.-',
- 'Student Applicant': 'EDU-APP-.YYYY.-',
- 'Supplier': 'SUP-.YYYY.-',
- 'Supplier Quotation': 'PUR-SQTN-.YYYY.-',
- 'Supplier Scorecard Period': 'PU-SSP-.YYYY.-',
- 'Timesheet': 'TS-.YYYY.-',
- 'Vehicle Log': 'HR-VLOG-.YYYY.-',
- 'Warranty Claim': 'SER-WRN-.YYYY.-',
- 'Work Order': 'MFG-WO-.YYYY.-'
+ "Additional Salary": "HR-ADS-.YY.-.MM.-",
+ "Appraisal": "HR-APR-.YY.-.MM.",
+ "Asset": "ACC-ASS-.YYYY.-",
+ "Attendance": "HR-ATT-.YYYY.-",
+ "Auto Repeat": "SYS-ARP-.YYYY.-",
+ "Blanket Order": "MFG-BLR-.YYYY.-",
+ "C-Form": "ACC-CF-.YYYY.-",
+ "Campaign": "SAL-CAM-.YYYY.-",
+ "Course Schedule": "EDU-CSH-.YYYY.-",
+ "Customer": "CUST-.YYYY.-",
+ "Delivery Note": "MAT-DN-.YYYY.-",
+ "Delivery Trip": "MAT-DT-.YYYY.-",
+ "Driver": "HR-DRI-.YYYY.-",
+ "Employee": "HR-EMP-",
+ "Employee Advance": "HR-EAD-.YYYY.-",
+ "Expense Claim": "HR-EXP-.YYYY.-",
+ "Fee Schedule": "EDU-FSH-.YYYY.-",
+ "Fee Structure": "EDU-FST-.YYYY.-",
+ "Fees": "EDU-FEE-.YYYY.-",
+ "Installation Note": "MAT-INS-.YYYY.-",
+ "Instructor": "EDU-INS-.YYYY.-",
+ "Issue": "ISS-.YYYY.-",
+ "Journal Entry": "ACC-JV-.YYYY.-",
+ "Landed Cost Voucher": "MAT-LCV-.YYYY.-",
+ "Lead": "CRM-LEAD-.YYYY.-",
+ "Leave Allocation": "HR-LAL-.YYYY.-",
+ "Leave Application": "HR-LAP-.YYYY.-",
+ "Maintenance Schedule": "MAT-MSH-.YYYY.-",
+ "Maintenance Visit": "MAT-MVS-.YYYY.-",
+ "Material Request": "MAT-MR-.YYYY.-",
+ "Member": "NPO-MEM-.YYYY.-",
+ "Opportunity": "CRM-OPP-.YYYY.-",
+ "Packing Slip": "MAT-PAC-.YYYY.-",
+ "Payment Entry": "ACC-PAY-.YYYY.-",
+ "Payment Request": "ACC-PRQ-.YYYY.-",
+ "Production Plan": "MFG-PP-.YYYY.-",
+ "Project Update": "PROJ-UPD-.YYYY.-",
+ "Purchase Invoice": "ACC-PINV-.YYYY.-",
+ "Purchase Order": "PUR-ORD-.YYYY.-",
+ "Purchase Receipt": "MAT-PRE-.YYYY.-",
+ "Quality Inspection": "MAT-QA-.YYYY.-",
+ "Quotation": "SAL-QTN-.YYYY.-",
+ "Request for Quotation": "PUR-RFQ-.YYYY.-",
+ "Sales Invoice": "ACC-SINV-.YYYY.-",
+ "Sales Order": "SAL-ORD-.YYYY.-",
+ "Sample Collection": "HLC-SC-.YYYY.-",
+ "Shareholder": "ACC-SH-.YYYY.-",
+ "Stock Entry": "MAT-STE-.YYYY.-",
+ "Stock Reconciliation": "MAT-RECO-.YYYY.-",
+ "Student": "EDU-STU-.YYYY.-",
+ "Student Applicant": "EDU-APP-.YYYY.-",
+ "Supplier": "SUP-.YYYY.-",
+ "Supplier Quotation": "PUR-SQTN-.YYYY.-",
+ "Supplier Scorecard Period": "PU-SSP-.YYYY.-",
+ "Timesheet": "TS-.YYYY.-",
+ "Vehicle Log": "HR-VLOG-.YYYY.-",
+ "Warranty Claim": "SER-WRN-.YYYY.-",
+ "Work Order": "MFG-WO-.YYYY.-",
}
+
def execute():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabProperty Setter`
set name=concat(doc_type, '-', field_name, '-', property)
where property='fetch_from'
- """)
+ """
+ )
series_to_set = get_series()
for doctype, opts in series_to_set.items():
set_series(doctype, opts["options"], opts["default"])
+
def set_series(doctype, options, default):
def _make_property_setter(property_name, value):
- property_setter = frappe.db.exists('Property Setter',
- {'doc_type': doctype, 'field_name': 'naming_series', 'property': property_name})
+ property_setter = frappe.db.exists(
+ "Property Setter",
+ {"doc_type": doctype, "field_name": "naming_series", "property": property_name},
+ )
if property_setter:
- frappe.db.set_value('Property Setter', property_setter, 'value', value)
+ frappe.db.set_value("Property Setter", property_setter, "value", value)
else:
make_property_setter(doctype, "naming_series", "options", value, "Text")
@@ -89,17 +95,18 @@ def set_series(doctype, options, default):
if default:
_make_property_setter("default", default)
+
def get_series():
series_to_set = {}
for doctype in doctype_series_map:
- if not frappe.db.exists('DocType', doctype):
+ if not frappe.db.exists("DocType", doctype):
continue
if not frappe.db.a_row_exists(doctype):
continue
- if not frappe.db.has_column(doctype, 'naming_series'):
+ if not frappe.db.has_column(doctype, "naming_series"):
continue
- if not frappe.get_meta(doctype).has_field('naming_series'):
+ if not frappe.get_meta(doctype).has_field("naming_series"):
continue
series_to_preserve = list(filter(None, get_series_to_preserve(doctype)))
default_series = get_default_series(doctype)
@@ -117,12 +124,18 @@ def get_series():
return series_to_set
+
def get_series_to_preserve(doctype):
- series_to_preserve = frappe.db.sql_list("""select distinct naming_series from `tab{doctype}` where ifnull(naming_series, '') != ''""".format(doctype=doctype))
+ series_to_preserve = frappe.db.sql_list(
+ """select distinct naming_series from `tab{doctype}` where ifnull(naming_series, '') != ''""".format(
+ doctype=doctype
+ )
+ )
series_to_preserve.sort()
return series_to_preserve
+
def get_default_series(doctype):
field = frappe.get_meta(doctype).get_field("naming_series")
- default_series = field.get('default', '') if field else ''
+ default_series = field.get("default", "") if field else ""
return default_series
diff --git a/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py b/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py
index caf74f578d..2e0204c22b 100644
--- a/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py
+++ b/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py
@@ -2,7 +2,7 @@ import frappe
def execute():
- '''Remove barcodes field from "Copy Fields to Variants" table because barcodes must be unique'''
+ """Remove barcodes field from "Copy Fields to Variants" table because barcodes must be unique"""
- settings = frappe.get_doc('Item Variant Settings')
+ settings = frappe.get_doc("Item Variant Settings")
settings.remove_invalid_fields_for_copy_fields_in_variants()
diff --git a/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py b/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py
index 8fa876dd74..036ae8ebfc 100644
--- a/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py
+++ b/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py
@@ -2,9 +2,10 @@ import frappe
# this patch should have been included with this PR https://github.com/frappe/erpnext/pull/14302
+
def execute():
if frappe.db.table_exists("Additional Salary Component"):
if not frappe.db.table_exists("Additional Salary"):
frappe.rename_doc("DocType", "Additional Salary Component", "Additional Salary")
- frappe.delete_doc('DocType', "Additional Salary Component")
+ frappe.delete_doc("DocType", "Additional Salary Component")
diff --git a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py
index c7a3aa2abd..c444c16a59 100644
--- a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py
+++ b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py
@@ -6,6 +6,8 @@ import frappe
def execute():
- if frappe.db.table_exists("Asset Adjustment") and not frappe.db.table_exists("Asset Value Adjustment"):
- frappe.rename_doc('DocType', 'Asset Adjustment', 'Asset Value Adjustment', force=True)
- frappe.reload_doc('assets', 'doctype', 'asset_value_adjustment')
+ if frappe.db.table_exists("Asset Adjustment") and not frappe.db.table_exists(
+ "Asset Value Adjustment"
+ ):
+ frappe.rename_doc("DocType", "Asset Adjustment", "Asset Value Adjustment", force=True)
+ frappe.reload_doc("assets", "doctype", "asset_value_adjustment")
diff --git a/erpnext/patches/v11_0/rename_bom_wo_fields.py b/erpnext/patches/v11_0/rename_bom_wo_fields.py
index cab7d0a673..fb25eeb6fc 100644
--- a/erpnext/patches/v11_0/rename_bom_wo_fields.py
+++ b/erpnext/patches/v11_0/rename_bom_wo_fields.py
@@ -7,28 +7,36 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- # updating column value to handle field change from Data to Currency
- changed_field = "base_scrap_material_cost"
- frappe.db.sql(f"update `tabBOM` set {changed_field} = '0' where trim(coalesce({changed_field}, ''))= ''")
+ # updating column value to handle field change from Data to Currency
+ changed_field = "base_scrap_material_cost"
+ frappe.db.sql(
+ f"update `tabBOM` set {changed_field} = '0' where trim(coalesce({changed_field}, ''))= ''"
+ )
- for doctype in ['BOM Explosion Item', 'BOM Item', 'Work Order Item', 'Item']:
- if frappe.db.has_column(doctype, 'allow_transfer_for_manufacture'):
- if doctype != 'Item':
- frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))
- else:
- frappe.reload_doc('stock', 'doctype', frappe.scrub(doctype))
+ for doctype in ["BOM Explosion Item", "BOM Item", "Work Order Item", "Item"]:
+ if frappe.db.has_column(doctype, "allow_transfer_for_manufacture"):
+ if doctype != "Item":
+ frappe.reload_doc("manufacturing", "doctype", frappe.scrub(doctype))
+ else:
+ frappe.reload_doc("stock", "doctype", frappe.scrub(doctype))
- rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
+ rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
- for doctype in ['BOM', 'Work Order']:
- frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))
+ for doctype in ["BOM", "Work Order"]:
+ frappe.reload_doc("manufacturing", "doctype", frappe.scrub(doctype))
- if frappe.db.has_column(doctype, 'transfer_material_against_job_card'):
- frappe.db.sql(""" UPDATE `tab%s`
+ if frappe.db.has_column(doctype, "transfer_material_against_job_card"):
+ frappe.db.sql(
+ """ UPDATE `tab%s`
SET transfer_material_against = CASE WHEN
transfer_material_against_job_card = 1 then 'Job Card' Else 'Work Order' END
- WHERE docstatus < 2""" % (doctype))
- else:
- frappe.db.sql(""" UPDATE `tab%s`
+ WHERE docstatus < 2"""
+ % (doctype)
+ )
+ else:
+ frappe.db.sql(
+ """ UPDATE `tab%s`
SET transfer_material_against = 'Work Order'
- WHERE docstatus < 2""" % (doctype))
+ WHERE docstatus < 2"""
+ % (doctype)
+ )
diff --git a/erpnext/patches/v11_0/rename_duplicate_item_code_values.py b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py
index 61f3856e8e..1f65e14814 100644
--- a/erpnext/patches/v11_0/rename_duplicate_item_code_values.py
+++ b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py
@@ -3,7 +3,9 @@ import frappe
def execute():
items = []
- items = frappe.db.sql("""select item_code from `tabItem` group by item_code having count(*) > 1""", as_dict=True)
+ items = frappe.db.sql(
+ """select item_code from `tabItem` group by item_code having count(*) > 1""", as_dict=True
+ )
if items:
for item in items:
frappe.db.sql("""update `tabItem` set item_code=name where item_code = %s""", (item.item_code))
diff --git a/erpnext/patches/v11_0/rename_field_max_days_allowed.py b/erpnext/patches/v11_0/rename_field_max_days_allowed.py
index 4b55aa06bb..0813770efc 100644
--- a/erpnext/patches/v11_0/rename_field_max_days_allowed.py
+++ b/erpnext/patches/v11_0/rename_field_max_days_allowed.py
@@ -3,11 +3,13 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabLeave Type`
SET max_days_allowed = '0'
WHERE trim(coalesce(max_days_allowed, '')) = ''
- """)
+ """
+ )
frappe.db.sql_ddl("""ALTER table `tabLeave Type` modify max_days_allowed int(8) NOT NULL""")
frappe.reload_doc("hr", "doctype", "leave_type")
rename_field("Leave Type", "max_days_allowed", "max_continuous_days_allowed")
diff --git a/erpnext/patches/v11_0/rename_members_with_naming_series.py b/erpnext/patches/v11_0/rename_members_with_naming_series.py
index 95fb55d9b4..4dffbc8fe8 100644
--- a/erpnext/patches/v11_0/rename_members_with_naming_series.py
+++ b/erpnext/patches/v11_0/rename_members_with_naming_series.py
@@ -3,8 +3,8 @@ import frappe
def execute():
frappe.reload_doc("non_profit", "doctype", "member")
- old_named_members = frappe.get_all("Member", filters = {"name": ("not like", "MEM-%")})
- correctly_named_members = frappe.get_all("Member", filters = {"name": ("like", "MEM-%")})
+ old_named_members = frappe.get_all("Member", filters={"name": ("not like", "MEM-%")})
+ correctly_named_members = frappe.get_all("Member", filters={"name": ("like", "MEM-%")})
current_index = len(correctly_named_members)
for member in old_named_members:
diff --git a/erpnext/patches/v11_0/rename_overproduction_percent_field.py b/erpnext/patches/v11_0/rename_overproduction_percent_field.py
index c78ec5d012..74699db41e 100644
--- a/erpnext/patches/v11_0/rename_overproduction_percent_field.py
+++ b/erpnext/patches/v11_0/rename_overproduction_percent_field.py
@@ -7,5 +7,9 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
- rename_field('Manufacturing Settings', 'over_production_allowance_percentage', 'overproduction_percentage_for_sales_order')
+ frappe.reload_doc("manufacturing", "doctype", "manufacturing_settings")
+ rename_field(
+ "Manufacturing Settings",
+ "over_production_allowance_percentage",
+ "overproduction_percentage_for_sales_order",
+ )
diff --git a/erpnext/patches/v11_0/rename_production_order_to_work_order.py b/erpnext/patches/v11_0/rename_production_order_to_work_order.py
index 453a5710a1..b58ac4e72f 100644
--- a/erpnext/patches/v11_0/rename_production_order_to_work_order.py
+++ b/erpnext/patches/v11_0/rename_production_order_to_work_order.py
@@ -7,22 +7,28 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.rename_doc('DocType', 'Production Order', 'Work Order', force=True)
- frappe.reload_doc('manufacturing', 'doctype', 'work_order')
+ frappe.rename_doc("DocType", "Production Order", "Work Order", force=True)
+ frappe.reload_doc("manufacturing", "doctype", "work_order")
- frappe.rename_doc('DocType', 'Production Order Item', 'Work Order Item', force=True)
- frappe.reload_doc('manufacturing', 'doctype', 'work_order_item')
+ frappe.rename_doc("DocType", "Production Order Item", "Work Order Item", force=True)
+ frappe.reload_doc("manufacturing", "doctype", "work_order_item")
- frappe.rename_doc('DocType', 'Production Order Operation', 'Work Order Operation', force=True)
- frappe.reload_doc('manufacturing', 'doctype', 'work_order_operation')
+ frappe.rename_doc("DocType", "Production Order Operation", "Work Order Operation", force=True)
+ frappe.reload_doc("manufacturing", "doctype", "work_order_operation")
- frappe.reload_doc('projects', 'doctype', 'timesheet')
- frappe.reload_doc('stock', 'doctype', 'stock_entry')
+ frappe.reload_doc("projects", "doctype", "timesheet")
+ frappe.reload_doc("stock", "doctype", "stock_entry")
rename_field("Timesheet", "production_order", "work_order")
rename_field("Stock Entry", "production_order", "work_order")
- frappe.rename_doc("Report", "Production Orders in Progress", "Work Orders in Progress", force=True)
+ frappe.rename_doc(
+ "Report", "Production Orders in Progress", "Work Orders in Progress", force=True
+ )
frappe.rename_doc("Report", "Completed Production Orders", "Completed Work Orders", force=True)
frappe.rename_doc("Report", "Open Production Orders", "Open Work Orders", force=True)
- frappe.rename_doc("Report", "Issued Items Against Production Order", "Issued Items Against Work Order", force=True)
- frappe.rename_doc("Report", "Production Order Stock Report", "Work Order Stock Report", force=True)
+ frappe.rename_doc(
+ "Report", "Issued Items Against Production Order", "Issued Items Against Work Order", force=True
+ )
+ frappe.rename_doc(
+ "Report", "Production Order Stock Report", "Work Order Stock Report", force=True
+ )
diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
index 3f87550224..96daba7d36 100644
--- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
+++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
@@ -6,10 +6,10 @@ from frappe.utils.nestedset import rebuild_tree
def execute():
if frappe.db.table_exists("Supplier Group"):
- frappe.reload_doc('setup', 'doctype', 'supplier_group')
+ frappe.reload_doc("setup", "doctype", "supplier_group")
elif frappe.db.table_exists("Supplier Type"):
frappe.rename_doc("DocType", "Supplier Type", "Supplier Group", force=True)
- frappe.reload_doc('setup', 'doctype', 'supplier_group')
+ frappe.reload_doc("setup", "doctype", "supplier_group")
frappe.reload_doc("accounts", "doctype", "pricing_rule")
frappe.reload_doc("accounts", "doctype", "tax_rule")
frappe.reload_doc("buying", "doctype", "buying_settings")
@@ -22,16 +22,23 @@ def execute():
build_tree()
-def build_tree():
- frappe.db.sql("""update `tabSupplier Group` set parent_supplier_group = '{0}'
- where is_group = 0""".format(_('All Supplier Groups')))
- if not frappe.db.exists("Supplier Group", _('All Supplier Groups')):
- frappe.get_doc({
- 'doctype': 'Supplier Group',
- 'supplier_group_name': _('All Supplier Groups'),
- 'is_group': 1,
- 'parent_supplier_group': ''
- }).insert(ignore_permissions=True)
+def build_tree():
+ frappe.db.sql(
+ """update `tabSupplier Group` set parent_supplier_group = '{0}'
+ where is_group = 0""".format(
+ _("All Supplier Groups")
+ )
+ )
+
+ if not frappe.db.exists("Supplier Group", _("All Supplier Groups")):
+ frappe.get_doc(
+ {
+ "doctype": "Supplier Group",
+ "supplier_group_name": _("All Supplier Groups"),
+ "is_group": 1,
+ "parent_supplier_group": "",
+ }
+ ).insert(ignore_permissions=True)
rebuild_tree("Supplier Group", "parent_supplier_group")
diff --git a/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py
index f23a81494b..4dc2521d39 100644
--- a/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py
+++ b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py
@@ -7,8 +7,8 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.reload_doc('projects', 'doctype', 'project')
+ frappe.reload_doc("projects", "doctype", "project")
- if frappe.db.has_column('Project', 'from'):
- rename_field('Project', 'from', 'from_time')
- rename_field('Project', 'to', 'to_time')
+ if frappe.db.has_column("Project", "from"):
+ rename_field("Project", "from", "from_time")
+ rename_field("Project", "to", "to_time")
diff --git a/erpnext/patches/v11_0/set_department_for_doctypes.py b/erpnext/patches/v11_0/set_department_for_doctypes.py
index b1098abb57..1e14b9ceb0 100644
--- a/erpnext/patches/v11_0/set_department_for_doctypes.py
+++ b/erpnext/patches/v11_0/set_department_for_doctypes.py
@@ -2,22 +2,36 @@ import frappe
# Set department value based on employee value
+
def execute():
doctypes_to_update = {
- 'hr': ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Salary Slip',
- 'Attendance', 'Training Feedback', 'Training Result Employee','Leave Application',
- 'Employee Advance', 'Training Event Employee', 'Payroll Employee Detail'],
- 'education': ['Instructor'],
- 'projects': ['Activity Cost', 'Timesheet'],
- 'setup': ['Sales Person']
+ "hr": [
+ "Appraisal",
+ "Leave Allocation",
+ "Expense Claim",
+ "Salary Slip",
+ "Attendance",
+ "Training Feedback",
+ "Training Result Employee",
+ "Leave Application",
+ "Employee Advance",
+ "Training Event Employee",
+ "Payroll Employee Detail",
+ ],
+ "education": ["Instructor"],
+ "projects": ["Activity Cost", "Timesheet"],
+ "setup": ["Sales Person"],
}
for module, doctypes in doctypes_to_update.items():
for doctype in doctypes:
if frappe.db.table_exists(doctype):
- frappe.reload_doc(module, 'doctype', frappe.scrub(doctype))
- frappe.db.sql("""
+ frappe.reload_doc(module, "doctype", frappe.scrub(doctype))
+ frappe.db.sql(
+ """
update `tab%s` dt
set department=(select department from `tabEmployee` where name=dt.employee)
- """ % doctype)
+ """
+ % doctype
+ )
diff --git a/erpnext/patches/v11_0/set_missing_gst_hsn_code.py b/erpnext/patches/v11_0/set_missing_gst_hsn_code.py
index ec75d454aa..d9356e758d 100644
--- a/erpnext/patches/v11_0/set_missing_gst_hsn_code.py
+++ b/erpnext/patches/v11_0/set_missing_gst_hsn_code.py
@@ -8,15 +8,24 @@ def execute():
if not company:
return
- doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice",
- "Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"]
+ doctypes = [
+ "Quotation",
+ "Sales Order",
+ "Delivery Note",
+ "Sales Invoice",
+ "Supplier Quotation",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ ]
for dt in doctypes:
date_field = "posting_date"
if dt in ["Quotation", "Sales Order", "Supplier Quotation", "Purchase Order"]:
date_field = "transaction_date"
- transactions = frappe.db.sql("""
+ transactions = frappe.db.sql(
+ """
select dt.name, dt_item.name as child_name
from `tab{dt}` dt, `tab{dt} Item` dt_item
where dt.name = dt_item.parent
@@ -25,18 +34,28 @@ def execute():
and ifnull(dt_item.gst_hsn_code, '') = ''
and ifnull(dt_item.item_code, '') != ''
and dt.company in ({company})
- """.format(dt=dt, date_field=date_field, company=", ".join(['%s']*len(company))), tuple(company), as_dict=1)
+ """.format(
+ dt=dt, date_field=date_field, company=", ".join(["%s"] * len(company))
+ ),
+ tuple(company),
+ as_dict=1,
+ )
if not transactions:
continue
transaction_rows_name = [d.child_name for d in transactions]
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tab{dt} Item` dt_item
set dt_item.gst_hsn_code = (select gst_hsn_code from tabItem where name=dt_item.item_code)
where dt_item.name in ({rows_name})
- """.format(dt=dt, rows_name=", ".join(['%s']*len(transaction_rows_name))), tuple(transaction_rows_name))
+ """.format(
+ dt=dt, rows_name=", ".join(["%s"] * len(transaction_rows_name))
+ ),
+ tuple(transaction_rows_name),
+ )
parent = set([d.name for d in transactions])
for t in list(parent):
diff --git a/erpnext/patches/v11_0/set_salary_component_properties.py b/erpnext/patches/v11_0/set_salary_component_properties.py
index 99c3b0b7c4..3ec9f8ab29 100644
--- a/erpnext/patches/v11_0/set_salary_component_properties.py
+++ b/erpnext/patches/v11_0/set_salary_component_properties.py
@@ -2,15 +2,21 @@ import frappe
def execute():
- frappe.reload_doc('Payroll', 'doctype', 'salary_detail')
- frappe.reload_doc('Payroll', 'doctype', 'salary_component')
+ frappe.reload_doc("Payroll", "doctype", "salary_detail")
+ frappe.reload_doc("Payroll", "doctype", "salary_component")
frappe.db.sql("update `tabSalary Component` set is_tax_applicable=1 where type='Earning'")
- frappe.db.sql("""update `tabSalary Component` set variable_based_on_taxable_salary=1
- where type='Deduction' and name in ('TDS', 'Tax Deducted at Source')""")
+ frappe.db.sql(
+ """update `tabSalary Component` set variable_based_on_taxable_salary=1
+ where type='Deduction' and name in ('TDS', 'Tax Deducted at Source')"""
+ )
- frappe.db.sql("""update `tabSalary Detail` set is_tax_applicable=1
- where parentfield='earnings' and statistical_component=0""")
- frappe.db.sql("""update `tabSalary Detail` set variable_based_on_taxable_salary=1
- where parentfield='deductions' and salary_component in ('TDS', 'Tax Deducted at Source')""")
+ frappe.db.sql(
+ """update `tabSalary Detail` set is_tax_applicable=1
+ where parentfield='earnings' and statistical_component=0"""
+ )
+ frappe.db.sql(
+ """update `tabSalary Detail` set variable_based_on_taxable_salary=1
+ where parentfield='deductions' and salary_component in ('TDS', 'Tax Deducted at Source')"""
+ )
diff --git a/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py b/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py
index a44daaa197..548a7cb158 100644
--- a/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py
+++ b/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py
@@ -3,17 +3,19 @@ from frappe.model.workflow import get_workflow_name
def execute():
- for doctype in ['Expense Claim', 'Leave Application']:
+ for doctype in ["Expense Claim", "Leave Application"]:
active_workflow = get_workflow_name(doctype)
- if not active_workflow: continue
+ if not active_workflow:
+ continue
- workflow_states = frappe.get_all('Workflow Document State',
- filters=[['parent', '=', active_workflow]],
- fields=['*'])
+ workflow_states = frappe.get_all(
+ "Workflow Document State", filters=[["parent", "=", active_workflow]], fields=["*"]
+ )
for state in workflow_states:
- if state.update_field: continue
- status_field = 'approval_status' if doctype=="Expense Claim" else 'status'
- frappe.set_value('Workflow Document State', state.name, 'update_field', status_field)
- frappe.set_value('Workflow Document State', state.name, 'update_value', state.state)
+ if state.update_field:
+ continue
+ status_field = "approval_status" if doctype == "Expense Claim" else "status"
+ frappe.set_value("Workflow Document State", state.name, "update_field", status_field)
+ frappe.set_value("Workflow Document State", state.name, "update_value", state.state)
diff --git a/erpnext/patches/v11_0/set_user_permissions_for_department.py b/erpnext/patches/v11_0/set_user_permissions_for_department.py
index bb7ef87747..9b5cb24372 100644
--- a/erpnext/patches/v11_0/set_user_permissions_for_department.py
+++ b/erpnext/patches/v11_0/set_user_permissions_for_department.py
@@ -2,18 +2,24 @@ import frappe
def execute():
- user_permissions = frappe.db.sql("""select name, for_value from `tabUser Permission`
- where allow='Department'""", as_dict=1)
- for d in user_permissions:
- user_permission = frappe.get_doc("User Permission", d.name)
- for new_dept in frappe.db.sql("""select name from tabDepartment
- where ifnull(company, '') != '' and department_name=%s""", d.for_value):
- try:
- new_user_permission = frappe.copy_doc(user_permission)
- new_user_permission.for_value = new_dept[0]
- new_user_permission.save()
- except frappe.DuplicateEntryError:
- pass
+ user_permissions = frappe.db.sql(
+ """select name, for_value from `tabUser Permission`
+ where allow='Department'""",
+ as_dict=1,
+ )
+ for d in user_permissions:
+ user_permission = frappe.get_doc("User Permission", d.name)
+ for new_dept in frappe.db.sql(
+ """select name from tabDepartment
+ where ifnull(company, '') != '' and department_name=%s""",
+ d.for_value,
+ ):
+ try:
+ new_user_permission = frappe.copy_doc(user_permission)
+ new_user_permission.for_value = new_dept[0]
+ new_user_permission.save()
+ except frappe.DuplicateEntryError:
+ pass
- frappe.reload_doc("hr", "doctype", "department")
- frappe.db.sql("update tabDepartment set disabled=1 where ifnull(company, '') = ''")
+ frappe.reload_doc("hr", "doctype", "department")
+ frappe.db.sql("update tabDepartment set disabled=1 where ifnull(company, '') = ''")
diff --git a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py
index d387577310..1327da981e 100644
--- a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py
+++ b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py
@@ -4,20 +4,37 @@ from frappe.desk.form.linked_with import get_linked_doctypes
# Skips user permission check for doctypes where department link field was recently added
# https://github.com/frappe/erpnext/pull/14121
+
def execute():
doctypes_to_skip = []
- for doctype in ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Instructor', 'Salary Slip',
- 'Attendance', 'Training Feedback', 'Training Result Employee',
- 'Leave Application', 'Employee Advance', 'Activity Cost', 'Training Event Employee',
- 'Timesheet', 'Sales Person', 'Payroll Employee Detail']:
- if frappe.db.exists('Custom Field', { 'dt': doctype, 'fieldname': 'department'}): continue
+ for doctype in [
+ "Appraisal",
+ "Leave Allocation",
+ "Expense Claim",
+ "Instructor",
+ "Salary Slip",
+ "Attendance",
+ "Training Feedback",
+ "Training Result Employee",
+ "Leave Application",
+ "Employee Advance",
+ "Activity Cost",
+ "Training Event Employee",
+ "Timesheet",
+ "Sales Person",
+ "Payroll Employee Detail",
+ ]:
+ if frappe.db.exists("Custom Field", {"dt": doctype, "fieldname": "department"}):
+ continue
doctypes_to_skip.append(doctype)
- frappe.reload_doctype('User Permission')
+ frappe.reload_doctype("User Permission")
- user_permissions = frappe.get_all("User Permission",
- filters=[['allow', '=', 'Department'], ['applicable_for', 'in', [None] + doctypes_to_skip]],
- fields=['name', 'applicable_for'])
+ user_permissions = frappe.get_all(
+ "User Permission",
+ filters=[["allow", "=", "Department"], ["applicable_for", "in", [None] + doctypes_to_skip]],
+ fields=["name", "applicable_for"],
+ )
user_permissions_to_delete = []
new_user_permissions_list = []
@@ -37,24 +54,32 @@ def execute():
for doctype in applicable_for_doctypes:
if doctype:
# Maintain sequence (name, user, allow, for_value, applicable_for, apply_to_all_doctypes)
- new_user_permissions_list.append((
- frappe.generate_hash("", 10),
- user_permission.user,
- user_permission.allow,
- user_permission.for_value,
- doctype,
- 0
- ))
+ new_user_permissions_list.append(
+ (
+ frappe.generate_hash("", 10),
+ user_permission.user,
+ user_permission.allow,
+ user_permission.for_value,
+ doctype,
+ 0,
+ )
+ )
if new_user_permissions_list:
- frappe.db.sql('''
+ frappe.db.sql(
+ """
INSERT INTO `tabUser Permission`
(`name`, `user`, `allow`, `for_value`, `applicable_for`, `apply_to_all_doctypes`)
- VALUES {}'''.format(', '.join(['%s'] * len(new_user_permissions_list))), # nosec
- tuple(new_user_permissions_list)
+ VALUES {}""".format(
+ ", ".join(["%s"] * len(new_user_permissions_list))
+ ), # nosec
+ tuple(new_user_permissions_list),
)
if user_permissions_to_delete:
- frappe.db.sql('DELETE FROM `tabUser Permission` WHERE `name` IN ({})'.format( # nosec
- ','.join(['%s'] * len(user_permissions_to_delete))
- ), tuple(user_permissions_to_delete))
+ frappe.db.sql(
+ "DELETE FROM `tabUser Permission` WHERE `name` IN ({})".format( # nosec
+ ",".join(["%s"] * len(user_permissions_to_delete))
+ ),
+ tuple(user_permissions_to_delete),
+ )
diff --git a/erpnext/patches/v11_0/uom_conversion_data.py b/erpnext/patches/v11_0/uom_conversion_data.py
index 81e547b16a..5dee0840eb 100644
--- a/erpnext/patches/v11_0/uom_conversion_data.py
+++ b/erpnext/patches/v11_0/uom_conversion_data.py
@@ -14,8 +14,8 @@ def execute():
# delete conversion data and insert again
frappe.db.sql("delete from `tabUOM Conversion Factor`")
try:
- frappe.delete_doc('UOM', 'Hundredweight')
- frappe.delete_doc('UOM', 'Pound Cubic Yard')
+ frappe.delete_doc("UOM", "Hundredweight")
+ frappe.delete_doc("UOM", "Pound Cubic Yard")
except frappe.LinkExistsError:
pass
diff --git a/erpnext/patches/v11_0/update_account_type_in_party_type.py b/erpnext/patches/v11_0/update_account_type_in_party_type.py
index c66cef042d..e55f9f20cc 100644
--- a/erpnext/patches/v11_0/update_account_type_in_party_type.py
+++ b/erpnext/patches/v11_0/update_account_type_in_party_type.py
@@ -6,9 +6,15 @@ import frappe
def execute():
- frappe.reload_doc('setup', 'doctype', 'party_type')
- party_types = {'Customer': 'Receivable', 'Supplier': 'Payable',
- 'Employee': 'Payable', 'Member': 'Receivable', 'Shareholder': 'Payable', 'Student': 'Receivable'}
+ frappe.reload_doc("setup", "doctype", "party_type")
+ party_types = {
+ "Customer": "Receivable",
+ "Supplier": "Payable",
+ "Employee": "Payable",
+ "Member": "Receivable",
+ "Shareholder": "Payable",
+ "Student": "Receivable",
+ }
for party_type, account_type in party_types.items():
- frappe.db.set_value('Party Type', party_type, 'account_type', account_type)
+ frappe.db.set_value("Party Type", party_type, "account_type", account_type)
diff --git a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py
index 3e36a4bb90..a7351d2759 100644
--- a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py
+++ b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py
@@ -6,16 +6,22 @@ import frappe
def execute():
- frappe.reload_doc('stock', 'doctype', 'item')
- frappe.db.sql(""" update `tabItem` set include_item_in_manufacturing = 1
- where ifnull(is_stock_item, 0) = 1""")
+ frappe.reload_doc("stock", "doctype", "item")
+ frappe.db.sql(
+ """ update `tabItem` set include_item_in_manufacturing = 1
+ where ifnull(is_stock_item, 0) = 1"""
+ )
- for doctype in ['BOM Item', 'Work Order Item', 'BOM Explosion Item']:
- frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))
+ for doctype in ["BOM Item", "Work Order Item", "BOM Explosion Item"]:
+ frappe.reload_doc("manufacturing", "doctype", frappe.scrub(doctype))
- frappe.db.sql(""" update `tab{0}` child, tabItem item
+ frappe.db.sql(
+ """ update `tab{0}` child, tabItem item
set
child.include_item_in_manufacturing = 1
where
child.item_code = item.name and ifnull(item.is_stock_item, 0) = 1
- """.format(doctype))
+ """.format(
+ doctype
+ )
+ )
diff --git a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py
index f3a2ac6a65..51ba706dcf 100644
--- a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py
+++ b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py
@@ -6,15 +6,19 @@ import frappe
def execute():
- frappe.reload_doc('buying', 'doctype', 'buying_settings')
- frappe.db.set_value('Buying Settings', None, 'backflush_raw_materials_of_subcontract_based_on', 'BOM')
+ frappe.reload_doc("buying", "doctype", "buying_settings")
+ frappe.db.set_value(
+ "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM"
+ )
- frappe.reload_doc('stock', 'doctype', 'stock_entry_detail')
- frappe.db.sql(""" update `tabStock Entry Detail` as sed,
+ frappe.reload_doc("stock", "doctype", "stock_entry_detail")
+ frappe.db.sql(
+ """ update `tabStock Entry Detail` as sed,
`tabStock Entry` as se, `tabPurchase Order Item Supplied` as pois
set
sed.subcontracted_item = pois.main_item_code
where
se.purpose = 'Send to Subcontractor' and sed.parent = se.name
and pois.rm_item_code = sed.item_code and se.docstatus = 1
- and pois.parenttype = 'Purchase Order'""")
+ and pois.parenttype = 'Purchase Order'"""
+ )
diff --git a/erpnext/patches/v11_0/update_brand_in_item_price.py b/erpnext/patches/v11_0/update_brand_in_item_price.py
index ce1df78083..f4859ae1c7 100644
--- a/erpnext/patches/v11_0/update_brand_in_item_price.py
+++ b/erpnext/patches/v11_0/update_brand_in_item_price.py
@@ -6,11 +6,13 @@ import frappe
def execute():
- frappe.reload_doc('stock', 'doctype', 'item_price')
+ frappe.reload_doc("stock", "doctype", "item_price")
- frappe.db.sql(""" update `tabItem Price`, `tabItem`
+ frappe.db.sql(
+ """ update `tabItem Price`, `tabItem`
set
`tabItem Price`.brand = `tabItem`.brand
where
`tabItem Price`.item_code = `tabItem`.name
- and `tabItem`.brand is not null and `tabItem`.brand != ''""")
+ and `tabItem`.brand is not null and `tabItem`.brand != ''"""
+ )
diff --git a/erpnext/patches/v11_0/update_delivery_trip_status.py b/erpnext/patches/v11_0/update_delivery_trip_status.py
index 35b95353b1..1badfab502 100755
--- a/erpnext/patches/v11_0/update_delivery_trip_status.py
+++ b/erpnext/patches/v11_0/update_delivery_trip_status.py
@@ -6,18 +6,14 @@ import frappe
def execute():
- frappe.reload_doc('setup', 'doctype', 'global_defaults', force=True)
- frappe.reload_doc('stock', 'doctype', 'delivery_trip')
- frappe.reload_doc('stock', 'doctype', 'delivery_stop', force=True)
+ frappe.reload_doc("setup", "doctype", "global_defaults", force=True)
+ frappe.reload_doc("stock", "doctype", "delivery_trip")
+ frappe.reload_doc("stock", "doctype", "delivery_stop", force=True)
for trip in frappe.get_all("Delivery Trip"):
trip_doc = frappe.get_doc("Delivery Trip", trip.name)
- status = {
- 0: "Draft",
- 1: "Scheduled",
- 2: "Cancelled"
- }[trip_doc.docstatus]
+ status = {0: "Draft", 1: "Scheduled", 2: "Cancelled"}[trip_doc.docstatus]
if trip_doc.docstatus == 1:
visited_stops = [stop.visited for stop in trip_doc.delivery_stops]
diff --git a/erpnext/patches/v11_0/update_department_lft_rgt.py b/erpnext/patches/v11_0/update_department_lft_rgt.py
index aff8e1556b..bca5e9e882 100644
--- a/erpnext/patches/v11_0/update_department_lft_rgt.py
+++ b/erpnext/patches/v11_0/update_department_lft_rgt.py
@@ -4,16 +4,18 @@ from frappe.utils.nestedset import rebuild_tree
def execute():
- """ assign lft and rgt appropriately """
+ """assign lft and rgt appropriately"""
frappe.reload_doc("hr", "doctype", "department")
- if not frappe.db.exists("Department", _('All Departments')):
- frappe.get_doc({
- 'doctype': 'Department',
- 'department_name': _('All Departments'),
- 'is_group': 1
- }).insert(ignore_permissions=True, ignore_mandatory=True)
+ if not frappe.db.exists("Department", _("All Departments")):
+ frappe.get_doc(
+ {"doctype": "Department", "department_name": _("All Departments"), "is_group": 1}
+ ).insert(ignore_permissions=True, ignore_mandatory=True)
- frappe.db.sql("""update `tabDepartment` set parent_department = '{0}'
- where is_group = 0""".format(_('All Departments')))
+ frappe.db.sql(
+ """update `tabDepartment` set parent_department = '{0}'
+ where is_group = 0""".format(
+ _("All Departments")
+ )
+ )
rebuild_tree("Department", "parent_department")
diff --git a/erpnext/patches/v11_0/update_sales_partner_type.py b/erpnext/patches/v11_0/update_sales_partner_type.py
index ef58499f0f..2d37fd69b1 100644
--- a/erpnext/patches/v11_0/update_sales_partner_type.py
+++ b/erpnext/patches/v11_0/update_sales_partner_type.py
@@ -5,25 +5,28 @@ from frappe import _
def execute():
from erpnext.setup.setup_wizard.operations.install_fixtures import default_sales_partner_type
- frappe.reload_doc('selling', 'doctype', 'sales_partner_type')
+ frappe.reload_doc("selling", "doctype", "sales_partner_type")
- frappe.local.lang = frappe.db.get_default("lang") or 'en'
+ frappe.local.lang = frappe.db.get_default("lang") or "en"
for s in default_sales_partner_type:
insert_sales_partner_type(_(s))
# get partner type in existing forms (customized)
# and create a document if not created
- for d in ['Sales Partner']:
- partner_type = frappe.db.sql_list('select distinct partner_type from `tab{0}`'.format(d))
+ for d in ["Sales Partner"]:
+ partner_type = frappe.db.sql_list("select distinct partner_type from `tab{0}`".format(d))
for s in partner_type:
if s and s not in default_sales_partner_type:
insert_sales_partner_type(s)
# remove customization for partner type
- for p in frappe.get_all('Property Setter', {'doc_type':d, 'field_name':'partner_type', 'property':'options'}):
- frappe.delete_doc('Property Setter', p.name)
+ for p in frappe.get_all(
+ "Property Setter", {"doc_type": d, "field_name": "partner_type", "property": "options"}
+ ):
+ frappe.delete_doc("Property Setter", p.name)
+
def insert_sales_partner_type(s):
- if not frappe.db.exists('Sales Partner Type', s):
- frappe.get_doc(dict(doctype='Sales Partner Type', sales_partner_type=s)).insert()
+ if not frappe.db.exists("Sales Partner Type", s):
+ frappe.get_doc(dict(doctype="Sales Partner Type", sales_partner_type=s)).insert()
diff --git a/erpnext/patches/v11_0/update_total_qty_field.py b/erpnext/patches/v11_0/update_total_qty_field.py
index 4e807b4f13..09fcdb8723 100644
--- a/erpnext/patches/v11_0/update_total_qty_field.py
+++ b/erpnext/patches/v11_0/update_total_qty_field.py
@@ -2,33 +2,46 @@ import frappe
def execute():
- frappe.reload_doc('buying', 'doctype', 'purchase_order')
- frappe.reload_doc('buying', 'doctype', 'supplier_quotation')
- frappe.reload_doc('selling', 'doctype', 'sales_order')
- frappe.reload_doc('selling', 'doctype', 'quotation')
- frappe.reload_doc('stock', 'doctype', 'delivery_note')
- frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
- frappe.reload_doc('accounts', 'doctype', 'sales_invoice')
- frappe.reload_doc('accounts', 'doctype', 'purchase_invoice')
+ frappe.reload_doc("buying", "doctype", "purchase_order")
+ frappe.reload_doc("buying", "doctype", "supplier_quotation")
+ frappe.reload_doc("selling", "doctype", "sales_order")
+ frappe.reload_doc("selling", "doctype", "quotation")
+ frappe.reload_doc("stock", "doctype", "delivery_note")
+ frappe.reload_doc("stock", "doctype", "purchase_receipt")
+ frappe.reload_doc("accounts", "doctype", "sales_invoice")
+ frappe.reload_doc("accounts", "doctype", "purchase_invoice")
- doctypes = ["Sales Order", "Sales Invoice", "Delivery Note",\
- "Purchase Order", "Purchase Invoice", "Purchase Receipt", "Quotation", "Supplier Quotation"]
+ doctypes = [
+ "Sales Order",
+ "Sales Invoice",
+ "Delivery Note",
+ "Purchase Order",
+ "Purchase Invoice",
+ "Purchase Receipt",
+ "Quotation",
+ "Supplier Quotation",
+ ]
for doctype in doctypes:
- total_qty = frappe.db.sql('''
+ total_qty = frappe.db.sql(
+ """
SELECT
parent, SUM(qty) as qty
FROM
`tab{0} Item`
where parenttype = '{0}'
GROUP BY parent
- '''.format(doctype), as_dict = True)
+ """.format(
+ doctype
+ ),
+ as_dict=True,
+ )
# Query to update total_qty might become too big, Update in batches
# batch_size is chosen arbitrarily, Don't try too hard to reason about it
batch_size = 100000
for i in range(0, len(total_qty), batch_size):
- batch_transactions = total_qty[i:i + batch_size]
+ batch_transactions = total_qty[i : i + batch_size]
# UPDATE with CASE for some reason cannot use PRIMARY INDEX,
# causing all rows to be examined, leading to a very slow update
@@ -42,7 +55,11 @@ def execute():
for d in batch_transactions:
values.append("({0}, {1})".format(frappe.db.escape(d.parent), d.qty))
conditions = ",".join(values)
- frappe.db.sql("""
+ frappe.db.sql(
+ """
INSERT INTO `tab{}` (name, total_qty) VALUES {}
ON DUPLICATE KEY UPDATE name = VALUES(name), total_qty = VALUES(total_qty)
- """.format(doctype, conditions))
+ """.format(
+ doctype, conditions
+ )
+ )
diff --git a/erpnext/patches/v11_1/delete_bom_browser.py b/erpnext/patches/v11_1/delete_bom_browser.py
index 9b5c169717..09ee1695b9 100644
--- a/erpnext/patches/v11_1/delete_bom_browser.py
+++ b/erpnext/patches/v11_1/delete_bom_browser.py
@@ -6,4 +6,4 @@ import frappe
def execute():
- frappe.delete_doc_if_exists('Page', 'bom-browser')
+ frappe.delete_doc_if_exists("Page", "bom-browser")
diff --git a/erpnext/patches/v11_1/make_job_card_time_logs.py b/erpnext/patches/v11_1/make_job_card_time_logs.py
index 100cd64f8f..beb2c4e534 100644
--- a/erpnext/patches/v11_1/make_job_card_time_logs.py
+++ b/erpnext/patches/v11_1/make_job_card_time_logs.py
@@ -6,25 +6,45 @@ import frappe
def execute():
- frappe.reload_doc('manufacturing', 'doctype', 'job_card_time_log')
+ frappe.reload_doc("manufacturing", "doctype", "job_card_time_log")
- if (frappe.db.table_exists("Job Card")
- and frappe.get_meta("Job Card").has_field("actual_start_date")):
- time_logs = []
- for d in frappe.get_all('Job Card',
- fields = ["actual_start_date", "actual_end_date", "time_in_mins", "name", "for_quantity"],
- filters = {'docstatus': ("<", 2)}):
- if d.actual_start_date:
- time_logs.append([d.actual_start_date, d.actual_end_date, d.time_in_mins,
- d.for_quantity, d.name, 'Job Card', 'time_logs', frappe.generate_hash("", 10)])
+ if frappe.db.table_exists("Job Card") and frappe.get_meta("Job Card").has_field(
+ "actual_start_date"
+ ):
+ time_logs = []
+ for d in frappe.get_all(
+ "Job Card",
+ fields=["actual_start_date", "actual_end_date", "time_in_mins", "name", "for_quantity"],
+ filters={"docstatus": ("<", 2)},
+ ):
+ if d.actual_start_date:
+ time_logs.append(
+ [
+ d.actual_start_date,
+ d.actual_end_date,
+ d.time_in_mins,
+ d.for_quantity,
+ d.name,
+ "Job Card",
+ "time_logs",
+ frappe.generate_hash("", 10),
+ ]
+ )
- if time_logs:
- frappe.db.sql(""" INSERT INTO
+ if time_logs:
+ frappe.db.sql(
+ """ INSERT INTO
`tabJob Card Time Log`
(from_time, to_time, time_in_mins, completed_qty, parent, parenttype, parentfield, name)
values {values}
- """.format(values = ','.join(['%s'] * len(time_logs))), tuple(time_logs))
+ """.format(
+ values=",".join(["%s"] * len(time_logs))
+ ),
+ tuple(time_logs),
+ )
- frappe.reload_doc('manufacturing', 'doctype', 'job_card')
- frappe.db.sql(""" update `tabJob Card` set total_completed_qty = for_quantity,
- total_time_in_mins = time_in_mins where docstatus < 2 """)
+ frappe.reload_doc("manufacturing", "doctype", "job_card")
+ frappe.db.sql(
+ """ update `tabJob Card` set total_completed_qty = for_quantity,
+ total_time_in_mins = time_in_mins where docstatus < 2 """
+ )
diff --git a/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py b/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py
index d292d7ae43..b681f25d84 100644
--- a/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py
+++ b/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py
@@ -8,8 +8,14 @@ import frappe
def execute():
frappe.reload_doctype("Quotation")
frappe.db.sql(""" UPDATE `tabQuotation` set party_name = lead WHERE quotation_to = 'Lead' """)
- frappe.db.sql(""" UPDATE `tabQuotation` set party_name = customer WHERE quotation_to = 'Customer' """)
+ frappe.db.sql(
+ """ UPDATE `tabQuotation` set party_name = customer WHERE quotation_to = 'Customer' """
+ )
frappe.reload_doctype("Opportunity")
- frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = lead WHERE opportunity_from = 'Lead' """)
- frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = customer WHERE opportunity_from = 'Customer' """)
+ frappe.db.sql(
+ """ UPDATE `tabOpportunity` set party_name = lead WHERE opportunity_from = 'Lead' """
+ )
+ frappe.db.sql(
+ """ UPDATE `tabOpportunity` set party_name = customer WHERE opportunity_from = 'Customer' """
+ )
diff --git a/erpnext/patches/v11_1/renamed_delayed_item_report.py b/erpnext/patches/v11_1/renamed_delayed_item_report.py
index c160b79d0e..86247815e2 100644
--- a/erpnext/patches/v11_1/renamed_delayed_item_report.py
+++ b/erpnext/patches/v11_1/renamed_delayed_item_report.py
@@ -6,6 +6,6 @@ import frappe
def execute():
- for report in ["Delayed Order Item Summary", "Delayed Order Summary"]:
- if frappe.db.exists("Report", report):
- frappe.delete_doc("Report", report)
+ for report in ["Delayed Order Item Summary", "Delayed Order Summary"]:
+ if frappe.db.exists("Report", report):
+ frappe.delete_doc("Report", report)
diff --git a/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py b/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py
index 672b7628bb..39aa6dd8e8 100644
--- a/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py
+++ b/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py
@@ -6,11 +6,13 @@ import frappe
def execute():
- stock_settings = frappe.get_doc('Stock Settings')
- if stock_settings.default_warehouse and not frappe.db.exists("Warehouse", stock_settings.default_warehouse):
- stock_settings.default_warehouse = None
- if stock_settings.stock_uom and not frappe.db.exists("UOM", stock_settings.stock_uom):
- stock_settings.stock_uom = None
- stock_settings.flags.ignore_mandatory = True
- stock_settings.action_if_quality_inspection_is_not_submitted = "Stop"
- stock_settings.save()
+ stock_settings = frappe.get_doc("Stock Settings")
+ if stock_settings.default_warehouse and not frappe.db.exists(
+ "Warehouse", stock_settings.default_warehouse
+ ):
+ stock_settings.default_warehouse = None
+ if stock_settings.stock_uom and not frappe.db.exists("UOM", stock_settings.stock_uom):
+ stock_settings.stock_uom = None
+ stock_settings.flags.ignore_mandatory = True
+ stock_settings.action_if_quality_inspection_is_not_submitted = "Stop"
+ stock_settings.save()
diff --git a/erpnext/patches/v11_1/set_missing_opportunity_from.py b/erpnext/patches/v11_1/set_missing_opportunity_from.py
index beec63af4b..ae5f620014 100644
--- a/erpnext/patches/v11_1/set_missing_opportunity_from.py
+++ b/erpnext/patches/v11_1/set_missing_opportunity_from.py
@@ -5,13 +5,23 @@ def execute():
frappe.reload_doctype("Opportunity")
if frappe.db.has_column("Opportunity", "enquiry_from"):
- frappe.db.sql(""" UPDATE `tabOpportunity` set opportunity_from = enquiry_from
- where ifnull(opportunity_from, '') = '' and ifnull(enquiry_from, '') != ''""")
+ frappe.db.sql(
+ """ UPDATE `tabOpportunity` set opportunity_from = enquiry_from
+ where ifnull(opportunity_from, '') = '' and ifnull(enquiry_from, '') != ''"""
+ )
- if frappe.db.has_column("Opportunity", "lead") and frappe.db.has_column("Opportunity", "enquiry_from"):
- frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = lead
- where enquiry_from = 'Lead' and ifnull(party_name, '') = '' and ifnull(lead, '') != ''""")
+ if frappe.db.has_column("Opportunity", "lead") and frappe.db.has_column(
+ "Opportunity", "enquiry_from"
+ ):
+ frappe.db.sql(
+ """ UPDATE `tabOpportunity` set party_name = lead
+ where enquiry_from = 'Lead' and ifnull(party_name, '') = '' and ifnull(lead, '') != ''"""
+ )
- if frappe.db.has_column("Opportunity", "customer") and frappe.db.has_column("Opportunity", "enquiry_from"):
- frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = customer
- where enquiry_from = 'Customer' and ifnull(party_name, '') = '' and ifnull(customer, '') != ''""")
+ if frappe.db.has_column("Opportunity", "customer") and frappe.db.has_column(
+ "Opportunity", "enquiry_from"
+ ):
+ frappe.db.sql(
+ """ UPDATE `tabOpportunity` set party_name = customer
+ where enquiry_from = 'Customer' and ifnull(party_name, '') = '' and ifnull(customer, '') != ''"""
+ )
diff --git a/erpnext/patches/v11_1/set_missing_title_for_quotation.py b/erpnext/patches/v11_1/set_missing_title_for_quotation.py
index 93d9f0e7d8..6e7e2c9d8b 100644
--- a/erpnext/patches/v11_1/set_missing_title_for_quotation.py
+++ b/erpnext/patches/v11_1/set_missing_title_for_quotation.py
@@ -4,7 +4,8 @@ import frappe
def execute():
frappe.reload_doctype("Quotation")
# update customer_name from Customer document if quotation_to is set to Customer
- frappe.db.sql('''
+ frappe.db.sql(
+ """
update tabQuotation, tabCustomer
set
tabQuotation.customer_name = tabCustomer.customer_name,
@@ -13,11 +14,13 @@ def execute():
tabQuotation.customer_name is null
and tabQuotation.party_name = tabCustomer.name
and tabQuotation.quotation_to = 'Customer'
- ''')
+ """
+ )
# update customer_name from Lead document if quotation_to is set to Lead
- frappe.db.sql('''
+ frappe.db.sql(
+ """
update tabQuotation, tabLead
set
tabQuotation.customer_name = case when ifnull(tabLead.company_name, '') != '' then tabLead.company_name else tabLead.lead_name end,
@@ -26,4 +29,5 @@ def execute():
tabQuotation.customer_name is null
and tabQuotation.party_name = tabLead.name
and tabQuotation.quotation_to = 'Lead'
- ''')
+ """
+ )
diff --git a/erpnext/patches/v11_1/set_salary_details_submittable.py b/erpnext/patches/v11_1/set_salary_details_submittable.py
index ac082b1ae2..e5ecce6486 100644
--- a/erpnext/patches/v11_1/set_salary_details_submittable.py
+++ b/erpnext/patches/v11_1/set_salary_details_submittable.py
@@ -2,8 +2,10 @@ import frappe
def execute():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabSalary Structure` ss, `tabSalary Detail` sd
set sd.docstatus=1
where ss.name=sd.parent and ss.docstatus=1 and sd.parenttype='Salary Structure'
- """)
+ """
+ )
diff --git a/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py b/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py
index 0d4f3d2ce1..6e2edfea87 100644
--- a/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py
+++ b/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py
@@ -2,8 +2,10 @@ import frappe
def execute():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabMaterial Request`
set status='Manufactured'
where docstatus=1 and material_request_type='Manufacture' and per_ordered=100 and status != 'Stopped'
- """)
+ """
+ )
diff --git a/erpnext/patches/v11_1/set_variant_based_on.py b/erpnext/patches/v11_1/set_variant_based_on.py
index 2e06e63a8a..1d57527b1f 100644
--- a/erpnext/patches/v11_1/set_variant_based_on.py
+++ b/erpnext/patches/v11_1/set_variant_based_on.py
@@ -6,7 +6,9 @@ import frappe
def execute():
- frappe.db.sql("""update tabItem set variant_based_on = 'Item Attribute'
+ frappe.db.sql(
+ """update tabItem set variant_based_on = 'Item Attribute'
where ifnull(variant_based_on, '') = ''
and (has_variants=1 or ifnull(variant_of, '') != '')
- """)
+ """
+ )
diff --git a/erpnext/patches/v11_1/setup_guardian_role.py b/erpnext/patches/v11_1/setup_guardian_role.py
index dd9c1d2887..2b25e13254 100644
--- a/erpnext/patches/v11_1/setup_guardian_role.py
+++ b/erpnext/patches/v11_1/setup_guardian_role.py
@@ -2,11 +2,8 @@ import frappe
def execute():
- if 'Education' in frappe.get_active_domains() and not frappe.db.exists("Role", "Guardian"):
+ if "Education" in frappe.get_active_domains() and not frappe.db.exists("Role", "Guardian"):
doc = frappe.new_doc("Role")
- doc.update({
- "role_name": "Guardian",
- "desk_access": 0
- })
+ doc.update({"role_name": "Guardian", "desk_access": 0})
doc.insert(ignore_permissions=True)
diff --git a/erpnext/patches/v11_1/update_bank_transaction_status.py b/erpnext/patches/v11_1/update_bank_transaction_status.py
index 9b8be3de1b..0615b04a57 100644
--- a/erpnext/patches/v11_1/update_bank_transaction_status.py
+++ b/erpnext/patches/v11_1/update_bank_transaction_status.py
@@ -6,22 +6,26 @@ import frappe
def execute():
- frappe.reload_doc("accounts", "doctype", "bank_transaction")
+ frappe.reload_doc("accounts", "doctype", "bank_transaction")
- bank_transaction_fields = frappe.get_meta("Bank Transaction").get_valid_columns()
+ bank_transaction_fields = frappe.get_meta("Bank Transaction").get_valid_columns()
- if 'debit' in bank_transaction_fields:
- frappe.db.sql(""" UPDATE `tabBank Transaction`
+ if "debit" in bank_transaction_fields:
+ frappe.db.sql(
+ """ UPDATE `tabBank Transaction`
SET status = 'Reconciled'
WHERE
status = 'Settled' and (debit = allocated_amount or credit = allocated_amount)
and ifnull(allocated_amount, 0) > 0
- """)
+ """
+ )
- elif 'deposit' in bank_transaction_fields:
- frappe.db.sql(""" UPDATE `tabBank Transaction`
+ elif "deposit" in bank_transaction_fields:
+ frappe.db.sql(
+ """ UPDATE `tabBank Transaction`
SET status = 'Reconciled'
WHERE
status = 'Settled' and (deposit = allocated_amount or withdrawal = allocated_amount)
and ifnull(allocated_amount, 0) > 0
- """)
+ """
+ )
diff --git a/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py
index 902df201a4..16e11ed7be 100644
--- a/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py
+++ b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py
@@ -6,21 +6,23 @@ import frappe
def execute():
- '''
- default supplier was not set in the item defaults for multi company instance,
- this patch will set the default supplier
+ """
+ default supplier was not set in the item defaults for multi company instance,
+ this patch will set the default supplier
- '''
- if not frappe.db.has_column('Item', 'default_supplier'):
+ """
+ if not frappe.db.has_column("Item", "default_supplier"):
return
- frappe.reload_doc('stock', 'doctype', 'item_default')
- frappe.reload_doc('stock', 'doctype', 'item')
+ frappe.reload_doc("stock", "doctype", "item_default")
+ frappe.reload_doc("stock", "doctype", "item")
companies = frappe.get_all("Company")
if len(companies) > 1:
- frappe.db.sql(""" UPDATE `tabItem Default`, `tabItem`
+ frappe.db.sql(
+ """ UPDATE `tabItem Default`, `tabItem`
SET `tabItem Default`.default_supplier = `tabItem`.default_supplier
WHERE
`tabItem Default`.parent = `tabItem`.name and `tabItem Default`.default_supplier is null
- and `tabItem`.default_supplier is not null and `tabItem`.default_supplier != '' """)
+ and `tabItem`.default_supplier is not null and `tabItem`.default_supplier != '' """
+ )
diff --git a/erpnext/patches/v11_1/woocommerce_set_creation_user.py b/erpnext/patches/v11_1/woocommerce_set_creation_user.py
index 19958eef14..e2d9e3ef38 100644
--- a/erpnext/patches/v11_1/woocommerce_set_creation_user.py
+++ b/erpnext/patches/v11_1/woocommerce_set_creation_user.py
@@ -3,7 +3,7 @@ from frappe.utils import cint
def execute():
- frappe.reload_doc("erpnext_integrations", "doctype","woocommerce_settings")
+ frappe.reload_doc("erpnext_integrations", "doctype", "woocommerce_settings")
doc = frappe.get_doc("Woocommerce Settings")
if cint(doc.enable_sync):
diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
index e498b673fb..40965fa824 100644
--- a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
+++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
@@ -4,15 +4,18 @@ import frappe
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
- if not company or not frappe.db.count('E Invoice User'):
+ company = frappe.get_all("Company", filters={"country": "India"})
+ if not company or not frappe.db.count("E Invoice User"):
return
frappe.reload_doc("regional", "doctype", "e_invoice_user")
- for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']):
- company_name = frappe.db.sql("""
+ for creds in frappe.db.get_all("E Invoice User", fields=["name", "gstin"]):
+ company_name = frappe.db.sql(
+ """
select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
- """, (creds.get('gstin')))
+ """,
+ (creds.get("gstin")),
+ )
if company_name and len(company_name) > 0:
- frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])
+ frappe.db.set_value("E Invoice User", creds.get("name"), "company", company_name[0][0])
diff --git a/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py
index 80187d834a..284b616bbd 100644
--- a/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py
+++ b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py
@@ -8,12 +8,16 @@ from frappe.model.utils.rename_field import rename_field
def execute():
frappe.reload_doc("setup", "doctype", "company")
- if frappe.db.has_column('Company', 'default_terms'):
- rename_field('Company', "default_terms", "default_selling_terms")
+ if frappe.db.has_column("Company", "default_terms"):
+ rename_field("Company", "default_terms", "default_selling_terms")
- for company in frappe.get_all("Company", ["name", "default_selling_terms", "default_buying_terms"]):
+ for company in frappe.get_all(
+ "Company", ["name", "default_selling_terms", "default_buying_terms"]
+ ):
if company.default_selling_terms and not company.default_buying_terms:
- frappe.db.set_value("Company", company.name, "default_buying_terms", company.default_selling_terms)
+ frappe.db.set_value(
+ "Company", company.name, "default_buying_terms", company.default_selling_terms
+ )
frappe.reload_doc("setup", "doctype", "terms_and_conditions")
frappe.db.sql("update `tabTerms and Conditions` set selling=1, buying=1, hr=1")
diff --git a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py
index f860cb4693..a98976a968 100644
--- a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py
+++ b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py
@@ -3,15 +3,19 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'Italy'})
+ company = frappe.get_all("Company", filters={"country": "Italy"})
if not company:
return
custom_fields = {
- 'Sales Invoice': [
- dict(fieldname='type_of_document', label='Type of Document',
- fieldtype='Select', insert_after='customer_fiscal_code',
- options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'),
+ "Sales Invoice": [
+ dict(
+ fieldname="type_of_document",
+ label="Type of Document",
+ fieldtype="Select",
+ insert_after="customer_fiscal_code",
+ options="\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27",
+ ),
]
}
diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py
index aeff9ca841..8c7abc6199 100644
--- a/erpnext/patches/v12_0/add_einvoice_status_field.py
+++ b/erpnext/patches/v12_0/add_einvoice_status_field.py
@@ -7,66 +7,154 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
# move hidden einvoice fields to a different section
custom_fields = {
- 'Sales Invoice': [
- dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
- print_hide=1, hidden=1),
-
- dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
- no_copy=1, print_hide=1),
-
- dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
-
- dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
- no_copy=1, print_hide=1),
-
- dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
- no_copy=1, print_hide=1, read_only=1),
-
- dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
- no_copy=1, print_hide=1, read_only=1),
-
- dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
- no_copy=1, print_hide=1, read_only=1),
-
- dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
- options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
-
- dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
- hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
+ "Sales Invoice": [
+ dict(
+ fieldname="einvoice_section",
+ label="E-Invoice Fields",
+ fieldtype="Section Break",
+ insert_after="gst_vehicle_type",
+ print_hide=1,
+ hidden=1,
+ ),
+ dict(
+ fieldname="ack_no",
+ label="Ack. No.",
+ fieldtype="Data",
+ read_only=1,
+ hidden=1,
+ insert_after="einvoice_section",
+ no_copy=1,
+ print_hide=1,
+ ),
+ dict(
+ fieldname="ack_date",
+ label="Ack. Date",
+ fieldtype="Data",
+ read_only=1,
+ hidden=1,
+ insert_after="ack_no",
+ no_copy=1,
+ print_hide=1,
+ ),
+ dict(
+ fieldname="irn_cancel_date",
+ label="Cancel Date",
+ fieldtype="Data",
+ read_only=1,
+ hidden=1,
+ insert_after="ack_date",
+ no_copy=1,
+ print_hide=1,
+ ),
+ dict(
+ fieldname="signed_einvoice",
+ label="Signed E-Invoice",
+ fieldtype="Code",
+ options="JSON",
+ hidden=1,
+ insert_after="irn_cancel_date",
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="signed_qr_code",
+ label="Signed QRCode",
+ fieldtype="Code",
+ options="JSON",
+ hidden=1,
+ insert_after="signed_einvoice",
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="qrcode_image",
+ label="QRCode",
+ fieldtype="Attach Image",
+ hidden=1,
+ insert_after="signed_qr_code",
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="einvoice_status",
+ label="E-Invoice Status",
+ fieldtype="Select",
+ insert_after="qrcode_image",
+ options="\nPending\nGenerated\nCancelled\nFailed",
+ default=None,
+ hidden=1,
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="failure_description",
+ label="E-Invoice Failure Description",
+ fieldtype="Code",
+ options="JSON",
+ hidden=1,
+ insert_after="einvoice_status",
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
]
}
create_custom_fields(custom_fields, update=True)
- if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'):
- frappe.db.sql('''
+ if frappe.db.exists("E Invoice Settings") and frappe.db.get_single_value(
+ "E Invoice Settings", "enable"
+ ):
+ frappe.db.sql(
+ """
UPDATE `tabSales Invoice` SET einvoice_status = 'Pending'
WHERE
posting_date >= '2021-04-01'
AND ifnull(irn, '') = ''
AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '')
AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export')
- ''')
+ """
+ )
# set appropriate statuses
- frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
- WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''')
+ frappe.db.sql(
+ """UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
+ WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0"""
+ )
- frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
- WHERE ifnull(irn_cancelled, 0) = 1''')
+ frappe.db.sql(
+ """UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
+ WHERE ifnull(irn_cancelled, 0) = 1"""
+ )
# set correct acknowledgement in e-invoices
- einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice'])
+ einvoices = frappe.get_all("Sales Invoice", {"irn": ["is", "set"]}, ["name", "signed_einvoice"])
if einvoices:
for inv in einvoices:
- signed_einvoice = inv.get('signed_einvoice')
+ signed_einvoice = inv.get("signed_einvoice")
if signed_einvoice:
signed_einvoice = json.loads(signed_einvoice)
- frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False)
- frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False)
+ frappe.db.set_value(
+ "Sales Invoice",
+ inv.get("name"),
+ "ack_no",
+ signed_einvoice.get("AckNo"),
+ update_modified=False,
+ )
+ frappe.db.set_value(
+ "Sales Invoice",
+ inv.get("name"),
+ "ack_date",
+ signed_einvoice.get("AckDt"),
+ update_modified=False,
+ )
diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
index e837786138..e4277ba50f 100644
--- a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
+++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
@@ -4,17 +4,17 @@ import frappe
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- if frappe.db.exists('Report', 'E-Invoice Summary') and \
- not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')):
- frappe.get_doc(dict(
- doctype='Custom Role',
- report='E-Invoice Summary',
- roles= [
- dict(role='Accounts User'),
- dict(role='Accounts Manager')
- ]
- )).insert()
+ if frappe.db.exists("Report", "E-Invoice Summary") and not frappe.db.get_value(
+ "Custom Role", dict(report="E-Invoice Summary")
+ ):
+ frappe.get_doc(
+ dict(
+ doctype="Custom Role",
+ report="E-Invoice Summary",
+ roles=[dict(role="Accounts User"), dict(role="Accounts Manager")],
+ )
+ ).insert()
diff --git a/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py b/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py
index 973da89562..db9fa247ed 100644
--- a/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py
+++ b/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py
@@ -3,18 +3,21 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
- if not company:
- return
+ if not company:
+ return
- create_custom_field('Delivery Note', {
- 'fieldname': 'ewaybill',
- 'label': 'E-Way Bill No.',
- 'fieldtype': 'Data',
- 'depends_on': 'eval:(doc.docstatus === 1)',
- 'allow_on_submit': 1,
- 'insert_after': 'customer_name_in_arabic',
- 'translatable': 0,
- 'owner': 'Administrator'
- })
+ create_custom_field(
+ "Delivery Note",
+ {
+ "fieldname": "ewaybill",
+ "label": "E-Way Bill No.",
+ "fieldtype": "Data",
+ "depends_on": "eval:(doc.docstatus === 1)",
+ "allow_on_submit": 1,
+ "insert_after": "customer_name_in_arabic",
+ "translatable": 0,
+ "owner": "Administrator",
+ },
+ )
diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
index 247140d21d..ca3e2540a4 100644
--- a/erpnext/patches/v12_0/add_ewaybill_validity_field.py
+++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
@@ -5,14 +5,23 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
custom_fields = {
- 'Sales Invoice': [
- dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
- depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
+ "Sales Invoice": [
+ dict(
+ fieldname="eway_bill_validity",
+ label="E-Way Bill Validity",
+ fieldtype="Data",
+ no_copy=1,
+ print_hide=1,
+ depends_on="ewaybill",
+ read_only=1,
+ allow_on_submit=1,
+ insert_after="ewaybill",
+ )
]
}
create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py
index dc9e884918..b14ffd2d34 100644
--- a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py
+++ b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py
@@ -5,37 +5,38 @@ from erpnext.regional.india.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
make_custom_fields()
- frappe.reload_doctype('Tax Category')
- frappe.reload_doctype('Sales Taxes and Charges Template')
- frappe.reload_doctype('Purchase Taxes and Charges Template')
+ frappe.reload_doctype("Tax Category")
+ frappe.reload_doctype("Sales Taxes and Charges Template")
+ frappe.reload_doctype("Purchase Taxes and Charges Template")
# Create tax category with inter state field checked
- tax_category = frappe.db.get_value('Tax Category', {'name': 'OUT OF STATE'}, 'name')
+ tax_category = frappe.db.get_value("Tax Category", {"name": "OUT OF STATE"}, "name")
if not tax_category:
- inter_state_category = frappe.get_doc({
- 'doctype': 'Tax Category',
- 'title': 'OUT OF STATE',
- 'is_inter_state': 1
- }).insert()
+ inter_state_category = frappe.get_doc(
+ {"doctype": "Tax Category", "title": "OUT OF STATE", "is_inter_state": 1}
+ ).insert()
tax_category = inter_state_category.name
- for doctype in ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template'):
- if not frappe.get_meta(doctype).has_field('is_inter_state'): continue
+ for doctype in ("Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"):
+ if not frappe.get_meta(doctype).has_field("is_inter_state"):
+ continue
- template = frappe.db.get_value(doctype, {'is_inter_state': 1, 'disabled': 0}, ['name'])
+ template = frappe.db.get_value(doctype, {"is_inter_state": 1, "disabled": 0}, ["name"])
if template:
- frappe.db.set_value(doctype, template, 'tax_category', tax_category)
+ frappe.db.set_value(doctype, template, "tax_category", tax_category)
- frappe.db.sql("""
+ frappe.db.sql(
+ """
DELETE FROM `tabCustom Field`
WHERE fieldname = 'is_inter_state'
AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template')
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py
index 6316bb3da9..5944889b63 100644
--- a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py
+++ b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py
@@ -3,16 +3,22 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
custom_fields = {
- 'Delivery Note': [
- dict(fieldname='gst_category', label='GST Category',
- fieldtype='Select', insert_after='gst_vehicle_type', print_hide=1,
- options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
- fetch_from='customer.gst_category', fetch_if_empty=1),
+ "Delivery Note": [
+ dict(
+ fieldname="gst_category",
+ label="GST Category",
+ fieldtype="Select",
+ insert_after="gst_vehicle_type",
+ print_hide=1,
+ options="\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders",
+ fetch_from="customer.gst_category",
+ fetch_if_empty=1,
+ ),
]
}
diff --git a/erpnext/patches/v12_0/add_item_name_in_work_orders.py b/erpnext/patches/v12_0/add_item_name_in_work_orders.py
index d765b93d21..0e5cd4eed5 100644
--- a/erpnext/patches/v12_0/add_item_name_in_work_orders.py
+++ b/erpnext/patches/v12_0/add_item_name_in_work_orders.py
@@ -4,11 +4,13 @@ import frappe
def execute():
frappe.reload_doc("manufacturing", "doctype", "work_order")
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabWork Order` wo
JOIN `tabItem` item ON wo.production_item = item.item_code
SET
wo.item_name = item.item_name
- """)
+ """
+ )
frappe.db.commit()
diff --git a/erpnext/patches/v12_0/add_permission_in_lower_deduction.py b/erpnext/patches/v12_0/add_permission_in_lower_deduction.py
index 1d77e5ad3d..24748b2cc6 100644
--- a/erpnext/patches/v12_0/add_permission_in_lower_deduction.py
+++ b/erpnext/patches/v12_0/add_permission_in_lower_deduction.py
@@ -3,12 +3,12 @@ from frappe.permissions import add_permission, update_permission_property
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- frappe.reload_doc('regional', 'doctype', 'Lower Deduction Certificate')
+ frappe.reload_doc("regional", "doctype", "Lower Deduction Certificate")
- add_permission('Lower Deduction Certificate', 'Accounts Manager', 0)
- update_permission_property('Lower Deduction Certificate', 'Accounts Manager', 0, 'write', 1)
- update_permission_property('Lower Deduction Certificate', 'Accounts Manager', 0, 'create', 1)
+ add_permission("Lower Deduction Certificate", "Accounts Manager", 0)
+ update_permission_property("Lower Deduction Certificate", "Accounts Manager", 0, "write", 1)
+ update_permission_property("Lower Deduction Certificate", "Accounts Manager", 0, "create", 1)
diff --git a/erpnext/patches/v12_0/add_state_code_for_ladakh.py b/erpnext/patches/v12_0/add_state_code_for_ladakh.py
index 6722b7bcef..4b06eb1921 100644
--- a/erpnext/patches/v12_0/add_state_code_for_ladakh.py
+++ b/erpnext/patches/v12_0/add_state_code_for_ladakh.py
@@ -5,15 +5,15 @@ from erpnext.regional.india import states
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- custom_fields = ['Address-gst_state', 'Tax Category-gst_state']
+ custom_fields = ["Address-gst_state", "Tax Category-gst_state"]
# Update options in gst_state custom fields
for field in custom_fields:
- if frappe.db.exists('Custom Field', field):
- gst_state_field = frappe.get_doc('Custom Field', field)
- gst_state_field.options = '\n'.join(states)
+ if frappe.db.exists("Custom Field", field):
+ gst_state_field = frappe.get_doc("Custom Field", field)
+ gst_state_field.options = "\n".join(states)
gst_state_field.save()
diff --git a/erpnext/patches/v12_0/add_taxjar_integration_field.py b/erpnext/patches/v12_0/add_taxjar_integration_field.py
index b0ddf001a6..9217384b81 100644
--- a/erpnext/patches/v12_0/add_taxjar_integration_field.py
+++ b/erpnext/patches/v12_0/add_taxjar_integration_field.py
@@ -4,7 +4,7 @@ from erpnext.regional.united_states.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters={'country': 'United States'})
+ company = frappe.get_all("Company", filters={"country": "United States"})
if not company:
return
diff --git a/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py
index c3a422c49e..7f044c8d47 100644
--- a/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py
+++ b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py
@@ -2,9 +2,11 @@ import frappe
def execute():
- frappe.reload_doc('stock', 'doctype', 'item_variant_attribute')
- frappe.db.sql('''
+ frappe.reload_doc("stock", "doctype", "item_variant_attribute")
+ frappe.db.sql(
+ """
UPDATE `tabItem Variant Attribute` t1
INNER JOIN `tabItem` t2 ON t2.name = t1.parent
SET t1.variant_of = t2.variant_of
- ''')
+ """
+ )
diff --git a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py
index aec9cb8b26..744ea1ccd8 100644
--- a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py
+++ b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py
@@ -4,10 +4,13 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
- frappe.reload_doc('accounts', 'doctype', 'accounting_dimension')
+ frappe.reload_doc("accounts", "doctype", "accounting_dimension")
- accounting_dimensions = frappe.db.sql("""select fieldname, label, document_type, disabled from
- `tabAccounting Dimension`""", as_dict=1)
+ accounting_dimensions = frappe.db.sql(
+ """select fieldname, label, document_type, disabled from
+ `tabAccounting Dimension`""",
+ as_dict=1,
+ )
if not accounting_dimensions:
return
@@ -15,13 +18,19 @@ def execute():
count = 1
for d in accounting_dimensions:
- if count%2 == 0:
- insert_after_field = 'dimension_col_break'
+ if count % 2 == 0:
+ insert_after_field = "dimension_col_break"
else:
- insert_after_field = 'accounting_dimensions_section'
+ insert_after_field = "accounting_dimensions_section"
- for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item",
- "Expense Claim Detail", "Expense Taxes and Charges"]:
+ for doctype in [
+ "Subscription Plan",
+ "Subscription",
+ "Opening Invoice Creation Tool",
+ "Opening Invoice Creation Tool Item",
+ "Expense Claim Detail",
+ "Expense Taxes and Charges",
+ ]:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
@@ -33,7 +42,7 @@ def execute():
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
- "insert_after": insert_after_field
+ "insert_after": insert_after_field,
}
create_custom_field(doctype, df)
diff --git a/erpnext/patches/v12_0/create_default_energy_point_rules.py b/erpnext/patches/v12_0/create_default_energy_point_rules.py
index 35eaca7f40..da200de9b7 100644
--- a/erpnext/patches/v12_0/create_default_energy_point_rules.py
+++ b/erpnext/patches/v12_0/create_default_energy_point_rules.py
@@ -4,5 +4,5 @@ from erpnext.setup.install import create_default_energy_point_rules
def execute():
- frappe.reload_doc('social', 'doctype', 'energy_point_rule')
+ frappe.reload_doc("social", "doctype", "energy_point_rule")
create_default_energy_point_rules()
diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py
index efcc7cf0f7..80e9047cf0 100644
--- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py
+++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py
@@ -5,12 +5,12 @@ from erpnext.regional.united_states.setup import make_custom_fields
def execute():
- frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True)
- frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True)
- frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True)
- frappe.reload_doc('setup', 'doctype', 'quotation_lost_reason_detail', force=True)
+ frappe.reload_doc("accounts", "doctype", "allowed_to_transact_with", force=True)
+ frappe.reload_doc("accounts", "doctype", "pricing_rule_detail", force=True)
+ frappe.reload_doc("crm", "doctype", "lost_reason_detail", force=True)
+ frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail", force=True)
- company = frappe.get_all('Company', filters = {'country': 'United States'})
+ company = frappe.get_all("Company", filters={"country": "United States"})
if not company:
return
diff --git a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
index d4fbded5a3..906baf22cc 100644
--- a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
+++ b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
@@ -6,44 +6,76 @@ from erpnext.regional.india.utils import get_gst_accounts
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name'])
+ company = frappe.get_all("Company", filters={"country": "India"}, fields=["name"])
if not company:
return
frappe.reload_doc("regional", "doctype", "gst_settings")
frappe.reload_doc("accounts", "doctype", "gst_account")
- journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
- make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
+ journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + [
+ "Reversal Of ITC"
+ ]
+ make_property_setter(
+ "Journal Entry", "voucher_type", "options", "\n".join(journal_entry_types), ""
+ )
custom_fields = {
- 'Journal Entry': [
- dict(fieldname='reversal_type', label='Reversal Type',
- fieldtype='Select', insert_after='voucher_type', print_hide=1,
+ "Journal Entry": [
+ dict(
+ fieldname="reversal_type",
+ label="Reversal Type",
+ fieldtype="Select",
+ insert_after="voucher_type",
+ print_hide=1,
options="As per rules 42 & 43 of CGST Rules\nOthers",
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
- mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
- dict(fieldname='company_address', label='Company Address',
- fieldtype='Link', options='Address', insert_after='reversal_type',
- print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
- mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
- dict(fieldname='company_gstin', label='Company GSTIN',
- fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1,
- fetch_from='company_address.gstin',
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ ),
+ dict(
+ fieldname="company_address",
+ label="Company Address",
+ fieldtype="Link",
+ options="Address",
+ insert_after="reversal_type",
+ print_hide=1,
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
- mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'")
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ ),
+ dict(
+ fieldname="company_gstin",
+ label="Company GSTIN",
+ fieldtype="Data",
+ read_only=1,
+ insert_after="company_address",
+ print_hide=1,
+ fetch_from="company_address.gstin",
+ depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ ),
],
- 'Purchase Invoice': [
- dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
- fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
- options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC',
- default="All Other ITC")
+ "Purchase Invoice": [
+ dict(
+ fieldname="eligibility_for_itc",
+ label="Eligibility For ITC",
+ fieldtype="Select",
+ insert_after="reason_for_issuing_document",
+ print_hide=1,
+ options="Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC",
+ default="All Other ITC",
+ )
+ ],
+ "Purchase Invoice Item": [
+ dict(
+ fieldname="taxable_value",
+ label="Taxable Value",
+ fieldtype="Currency",
+ insert_after="base_net_amount",
+ hidden=1,
+ options="Company:company:default_currency",
+ print_hide=1,
+ )
],
- 'Purchase Invoice Item': [
- dict(fieldname='taxable_value', label='Taxable Value',
- fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
- print_hide=1)
- ]
}
create_custom_fields(custom_fields, update=True)
@@ -53,28 +85,40 @@ def execute():
gst_accounts = get_gst_accounts(only_non_reverse_charge=1)
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabCustom Field` SET fieldtype='Currency', options='Company:company:default_currency'
WHERE dt = 'Purchase Invoice' and fieldname in ('itc_integrated_tax', 'itc_state_tax', 'itc_central_tax',
'itc_cess_amount')
- """)
+ """
+ )
- frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0'
- WHERE trim(coalesce(itc_integrated_tax, '')) = '' """)
+ frappe.db.sql(
+ """UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0'
+ WHERE trim(coalesce(itc_integrated_tax, '')) = '' """
+ )
- frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_state_tax = '0'
- WHERE trim(coalesce(itc_state_tax, '')) = '' """)
+ frappe.db.sql(
+ """UPDATE `tabPurchase Invoice` set itc_state_tax = '0'
+ WHERE trim(coalesce(itc_state_tax, '')) = '' """
+ )
- frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_central_tax = '0'
- WHERE trim(coalesce(itc_central_tax, '')) = '' """)
+ frappe.db.sql(
+ """UPDATE `tabPurchase Invoice` set itc_central_tax = '0'
+ WHERE trim(coalesce(itc_central_tax, '')) = '' """
+ )
- frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_cess_amount = '0'
- WHERE trim(coalesce(itc_cess_amount, '')) = '' """)
+ frappe.db.sql(
+ """UPDATE `tabPurchase Invoice` set itc_cess_amount = '0'
+ WHERE trim(coalesce(itc_cess_amount, '')) = '' """
+ )
# Get purchase invoices
- invoices = frappe.get_all('Purchase Invoice',
- {'posting_date': ('>=', '2021-04-01'), 'eligibility_for_itc': ('!=', 'Ineligible')},
- ['name'])
+ invoices = frappe.get_all(
+ "Purchase Invoice",
+ {"posting_date": (">=", "2021-04-01"), "eligibility_for_itc": ("!=", "Ineligible")},
+ ["name"],
+ )
amount_map = {}
@@ -82,37 +126,42 @@ def execute():
invoice_list = set([d.name for d in invoices])
# Get GST applied
- amounts = frappe.db.sql("""
+ amounts = frappe.db.sql(
+ """
SELECT parent, account_head, sum(base_tax_amount_after_discount_amount) as amount
FROM `tabPurchase Taxes and Charges`
where parent in %s
GROUP BY parent, account_head
- """, (invoice_list), as_dict=1)
+ """,
+ (invoice_list),
+ as_dict=1,
+ )
for d in amounts:
- amount_map.setdefault(d.parent,
- {
- 'itc_integrated_tax': 0,
- 'itc_state_tax': 0,
- 'itc_central_tax': 0,
- 'itc_cess_amount': 0
- })
+ amount_map.setdefault(
+ d.parent,
+ {"itc_integrated_tax": 0, "itc_state_tax": 0, "itc_central_tax": 0, "itc_cess_amount": 0},
+ )
if not gst_accounts:
continue
- if d.account_head in gst_accounts.get('igst_account'):
- amount_map[d.parent]['itc_integrated_tax'] += d.amount
- if d.account_head in gst_accounts.get('cgst_account'):
- amount_map[d.parent]['itc_central_tax'] += d.amount
- if d.account_head in gst_accounts.get('sgst_account'):
- amount_map[d.parent]['itc_state_tax'] += d.amount
- if d.account_head in gst_accounts.get('cess_account'):
- amount_map[d.parent]['itc_cess_amount'] += d.amount
+ if d.account_head in gst_accounts.get("igst_account"):
+ amount_map[d.parent]["itc_integrated_tax"] += d.amount
+ if d.account_head in gst_accounts.get("cgst_account"):
+ amount_map[d.parent]["itc_central_tax"] += d.amount
+ if d.account_head in gst_accounts.get("sgst_account"):
+ amount_map[d.parent]["itc_state_tax"] += d.amount
+ if d.account_head in gst_accounts.get("cess_account"):
+ amount_map[d.parent]["itc_cess_amount"] += d.amount
for invoice, values in amount_map.items():
- frappe.db.set_value('Purchase Invoice', invoice, {
- 'itc_integrated_tax': values.get('itc_integrated_tax'),
- 'itc_central_tax': values.get('itc_central_tax'),
- 'itc_state_tax': values['itc_state_tax'],
- 'itc_cess_amount': values['itc_cess_amount'],
- })
+ frappe.db.set_value(
+ "Purchase Invoice",
+ invoice,
+ {
+ "itc_integrated_tax": values.get("itc_integrated_tax"),
+ "itc_central_tax": values.get("itc_central_tax"),
+ "itc_state_tax": values["itc_state_tax"],
+ "itc_cess_amount": values["itc_cess_amount"],
+ },
+ )
diff --git a/erpnext/patches/v12_0/create_taxable_value_field.py b/erpnext/patches/v12_0/create_taxable_value_field.py
index 55717ccab2..ad953032e7 100644
--- a/erpnext/patches/v12_0/create_taxable_value_field.py
+++ b/erpnext/patches/v12_0/create_taxable_value_field.py
@@ -3,15 +3,21 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
custom_fields = {
- 'Sales Invoice Item': [
- dict(fieldname='taxable_value', label='Taxable Value',
- fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
- print_hide=1)
+ "Sales Invoice Item": [
+ dict(
+ fieldname="taxable_value",
+ label="Taxable Value",
+ fieldtype="Currency",
+ insert_after="base_net_amount",
+ hidden=1,
+ options="Company:company:default_currency",
+ print_hide=1,
+ )
]
}
diff --git a/erpnext/patches/v12_0/delete_priority_property_setter.py b/erpnext/patches/v12_0/delete_priority_property_setter.py
index cacc463d4b..fbb02430f4 100644
--- a/erpnext/patches/v12_0/delete_priority_property_setter.py
+++ b/erpnext/patches/v12_0/delete_priority_property_setter.py
@@ -2,9 +2,11 @@ import frappe
def execute():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
DELETE FROM `tabProperty Setter`
WHERE `tabProperty Setter`.doc_type='Issue'
AND `tabProperty Setter`.field_name='priority'
AND `tabProperty Setter`.property='options'
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/fix_percent_complete_for_projects.py b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py
index 36f51bca60..1fbcfa4e59 100644
--- a/erpnext/patches/v12_0/fix_percent_complete_for_projects.py
+++ b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py
@@ -4,10 +4,13 @@ from frappe.utils import flt
def execute():
for project in frappe.get_all("Project", fields=["name", "percent_complete_method"]):
- total = frappe.db.count('Task', dict(project=project.name))
+ total = frappe.db.count("Task", dict(project=project.name))
if project.percent_complete_method == "Task Completion" and total > 0:
- completed = frappe.db.sql("""select count(name) from tabTask where
- project=%s and status in ('Cancelled', 'Completed')""", project.name)[0][0]
+ completed = frappe.db.sql(
+ """select count(name) from tabTask where
+ project=%s and status in ('Cancelled', 'Completed')""",
+ project.name,
+ )[0][0]
percent_complete = flt(flt(completed) / total * 100, 2)
if project.percent_complete != percent_complete:
frappe.db.set_value("Project", project.name, "percent_complete", percent_complete)
diff --git a/erpnext/patches/v12_0/fix_quotation_expired_status.py b/erpnext/patches/v12_0/fix_quotation_expired_status.py
index e5c4b8c524..285183bfa2 100644
--- a/erpnext/patches/v12_0/fix_quotation_expired_status.py
+++ b/erpnext/patches/v12_0/fix_quotation_expired_status.py
@@ -13,12 +13,13 @@ def execute():
so_item.docstatus = 1 and so.docstatus = 1
and so_item.parent = so.name
and so_item.prevdoc_docname = qo.name
- and qo.valid_till < so.transaction_date""" # check if SO was created after quotation expired
+ and qo.valid_till < so.transaction_date""" # check if SO was created after quotation expired
frappe.db.sql(
- """UPDATE `tabQuotation` qo SET qo.status = 'Expired' WHERE {cond} and exists({invalid_so_against_quo})"""
- .format(cond=cond, invalid_so_against_quo=invalid_so_against_quo)
+ """UPDATE `tabQuotation` qo SET qo.status = 'Expired' WHERE {cond} and exists({invalid_so_against_quo})""".format(
+ cond=cond, invalid_so_against_quo=invalid_so_against_quo
)
+ )
valid_so_against_quo = """
SELECT
@@ -27,9 +28,10 @@ def execute():
so_item.docstatus = 1 and so.docstatus = 1
and so_item.parent = so.name
and so_item.prevdoc_docname = qo.name
- and qo.valid_till >= so.transaction_date""" # check if SO was created before quotation expired
+ and qo.valid_till >= so.transaction_date""" # check if SO was created before quotation expired
frappe.db.sql(
- """UPDATE `tabQuotation` qo SET qo.status = 'Closed' WHERE {cond} and exists({valid_so_against_quo})"""
- .format(cond=cond, valid_so_against_quo=valid_so_against_quo)
+ """UPDATE `tabQuotation` qo SET qo.status = 'Closed' WHERE {cond} and exists({valid_so_against_quo})""".format(
+ cond=cond, valid_so_against_quo=valid_so_against_quo
)
+ )
diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py
index 90e46d07e4..354c5096c0 100644
--- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py
+++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py
@@ -7,8 +7,8 @@ from frappe.utils import getdate, today
def execute():
- """ Generates leave ledger entries for leave allocation/application/encashment
- for last allocation """
+ """Generates leave ledger entries for leave allocation/application/encashment
+ for last allocation"""
frappe.reload_doc("HR", "doctype", "Leave Ledger Entry")
frappe.reload_doc("HR", "doctype", "Leave Encashment")
frappe.reload_doc("HR", "doctype", "Leave Type")
@@ -22,55 +22,79 @@ def execute():
generate_encashment_leave_ledger_entries()
generate_expiry_allocation_ledger_entries()
+
def update_leave_allocation_fieldname():
- ''' maps data from old field to the new field '''
- frappe.db.sql("""
+ """maps data from old field to the new field"""
+ frappe.db.sql(
+ """
UPDATE `tabLeave Allocation`
SET `unused_leaves` = `carry_forwarded_leaves`
- """)
+ """
+ )
+
def generate_allocation_ledger_entries():
- ''' fix ledger entries for missing leave allocation transaction '''
+ """fix ledger entries for missing leave allocation transaction"""
allocation_list = get_allocation_records()
for allocation in allocation_list:
- if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}):
+ if not frappe.db.exists(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Allocation", "transaction_name": allocation.name},
+ ):
allocation_obj = frappe.get_doc("Leave Allocation", allocation)
allocation_obj.create_leave_ledger_entry()
+
def generate_application_leave_ledger_entries():
- ''' fix ledger entries for missing leave application transaction '''
+ """fix ledger entries for missing leave application transaction"""
leave_applications = get_leaves_application_records()
for application in leave_applications:
- if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': application.name}):
+ if not frappe.db.exists(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Application", "transaction_name": application.name},
+ ):
frappe.get_doc("Leave Application", application.name).create_leave_ledger_entry()
+
def generate_encashment_leave_ledger_entries():
- ''' fix ledger entries for missing leave encashment transaction '''
+ """fix ledger entries for missing leave encashment transaction"""
leave_encashments = get_leave_encashment_records()
for encashment in leave_encashments:
- if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}):
+ if not frappe.db.exists(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Encashment", "transaction_name": encashment.name},
+ ):
frappe.get_doc("Leave Encashment", encashment).create_leave_ledger_entry()
+
def generate_expiry_allocation_ledger_entries():
- ''' fix ledger entries for missing leave allocation transaction '''
+ """fix ledger entries for missing leave allocation transaction"""
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation
+
allocation_list = get_allocation_records()
for allocation in allocation_list:
- if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}):
+ if not frappe.db.exists(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Allocation", "transaction_name": allocation.name, "is_expired": 1},
+ ):
allocation_obj = frappe.get_doc("Leave Allocation", allocation)
if allocation_obj.to_date <= getdate(today()):
expire_allocation(allocation_obj)
+
def get_allocation_records():
- return frappe.get_all("Leave Allocation", filters={"docstatus": 1},
- fields=['name'], order_by='to_date ASC')
+ return frappe.get_all(
+ "Leave Allocation", filters={"docstatus": 1}, fields=["name"], order_by="to_date ASC"
+ )
+
def get_leaves_application_records():
- return frappe.get_all("Leave Application", filters={"docstatus": 1}, fields=['name'])
+ return frappe.get_all("Leave Application", filters={"docstatus": 1}, fields=["name"])
+
def get_leave_encashment_records():
- return frappe.get_all("Leave Encashment", filters={"docstatus": 1}, fields=['name'])
+ return frappe.get_all("Leave Encashment", filters={"docstatus": 1}, fields=["name"])
diff --git a/erpnext/patches/v12_0/make_item_manufacturer.py b/erpnext/patches/v12_0/make_item_manufacturer.py
index d66f429de3..3f233659d0 100644
--- a/erpnext/patches/v12_0/make_item_manufacturer.py
+++ b/erpnext/patches/v12_0/make_item_manufacturer.py
@@ -9,20 +9,29 @@ def execute():
frappe.reload_doc("stock", "doctype", "item_manufacturer")
item_manufacturer = []
- for d in frappe.db.sql(""" SELECT name, manufacturer, manufacturer_part_no, creation, owner
- FROM `tabItem` WHERE manufacturer is not null and manufacturer != ''""", as_dict=1):
- item_manufacturer.append((
- frappe.generate_hash("", 10),
- d.name,
- d.manufacturer,
- d.manufacturer_part_no,
- d.creation,
- d.owner
- ))
+ for d in frappe.db.sql(
+ """ SELECT name, manufacturer, manufacturer_part_no, creation, owner
+ FROM `tabItem` WHERE manufacturer is not null and manufacturer != ''""",
+ as_dict=1,
+ ):
+ item_manufacturer.append(
+ (
+ frappe.generate_hash("", 10),
+ d.name,
+ d.manufacturer,
+ d.manufacturer_part_no,
+ d.creation,
+ d.owner,
+ )
+ )
if item_manufacturer:
- frappe.db.sql('''
+ frappe.db.sql(
+ """
INSERT INTO `tabItem Manufacturer`
(`name`, `item_code`, `manufacturer`, `manufacturer_part_no`, `creation`, `owner`)
- VALUES {}'''.format(', '.join(['%s'] * len(item_manufacturer))), tuple(item_manufacturer)
+ VALUES {}""".format(
+ ", ".join(["%s"] * len(item_manufacturer))
+ ),
+ tuple(item_manufacturer),
)
diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
index 7ae4c42cec..c069c24cfa 100644
--- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -2,16 +2,22 @@ import frappe
def execute():
- frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
+ frappe.reload_doc("accounts", "doctype", "bank", force=1)
- if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'):
+ if (
+ frappe.db.table_exists("Bank")
+ and frappe.db.table_exists("Bank Account")
+ and frappe.db.has_column("Bank Account", "swift_number")
+ ):
try:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabBank` b, `tabBank Account` ba
SET b.swift_number = ba.swift_number WHERE b.name = ba.bank
- """)
+ """
+ )
except Exception as e:
frappe.log_error(e, title="Patch Migration Failed")
- frappe.reload_doc('accounts', 'doctype', 'bank_account')
- frappe.reload_doc('accounts', 'doctype', 'payment_request')
+ frappe.reload_doc("accounts", "doctype", "bank_account")
+ frappe.reload_doc("accounts", "doctype", "payment_request")
diff --git a/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py b/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py
index 82dfba52c9..17c1966624 100644
--- a/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py
+++ b/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py
@@ -6,7 +6,7 @@ import frappe
def execute():
- ''' Move credit limit and bypass credit limit to the child table of customer credit limit '''
+ """Move credit limit and bypass credit limit to the child table of customer credit limit"""
frappe.reload_doc("Selling", "doctype", "Customer Credit Limit")
frappe.reload_doc("Selling", "doctype", "Customer")
frappe.reload_doc("Setup", "doctype", "Customer Group")
@@ -16,28 +16,32 @@ def execute():
move_credit_limit_to_child_table()
-def move_credit_limit_to_child_table():
- ''' maps data from old field to the new field in the child table '''
- companies = frappe.get_all("Company", 'name')
+def move_credit_limit_to_child_table():
+ """maps data from old field to the new field in the child table"""
+
+ companies = frappe.get_all("Company", "name")
for doctype in ("Customer", "Customer Group"):
fields = ""
- if doctype == "Customer" \
- and frappe.db.has_column('Customer', 'bypass_credit_limit_check_at_sales_order'):
+ if doctype == "Customer" and frappe.db.has_column(
+ "Customer", "bypass_credit_limit_check_at_sales_order"
+ ):
fields = ", bypass_credit_limit_check_at_sales_order"
- credit_limit_records = frappe.db.sql('''
+ credit_limit_records = frappe.db.sql(
+ """
SELECT name, credit_limit {0}
FROM `tab{1}` where credit_limit > 0
- '''.format(fields, doctype), as_dict=1) #nosec
+ """.format(
+ fields, doctype
+ ),
+ as_dict=1,
+ ) # nosec
for record in credit_limit_records:
doc = frappe.get_doc(doctype, record.name)
for company in companies:
- row = frappe._dict({
- 'credit_limit': record.credit_limit,
- 'company': company.name
- })
+ row = frappe._dict({"credit_limit": record.credit_limit, "company": company.name})
if doctype == "Customer":
row.bypass_credit_limit_check = record.bypass_credit_limit_check_at_sales_order
diff --git a/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
index 5de7e69620..8b8d9637f2 100644
--- a/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
+++ b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
@@ -6,7 +6,7 @@ import frappe
def execute():
- ''' Move from due_advance_amount to pending_amount '''
+ """Move from due_advance_amount to pending_amount"""
- if frappe.db.has_column("Employee Advance", "due_advance_amount"):
- frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')
+ if frappe.db.has_column("Employee Advance", "due_advance_amount"):
+ frappe.db.sql(""" UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount """)
diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
index 1224d7512e..c4c3b692e2 100644
--- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
+++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
@@ -12,17 +12,22 @@ def execute():
frappe.reload_doc("accounts", "doctype", "item_tax_template_detail", force=1)
frappe.reload_doc("accounts", "doctype", "item_tax_template", force=1)
- existing_templates = frappe.db.sql("""select template.name, details.tax_type, details.tax_rate
+ existing_templates = frappe.db.sql(
+ """select template.name, details.tax_type, details.tax_rate
from `tabItem Tax Template` template, `tabItem Tax Template Detail` details
where details.parent=template.name
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
if len(existing_templates):
for d in existing_templates:
item_tax_templates.setdefault(d.name, {})
item_tax_templates[d.name][d.tax_type] = d.tax_rate
- for d in frappe.db.sql("""select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1):
+ for d in frappe.db.sql(
+ """select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1
+ ):
old_item_taxes.setdefault(d.item_code, [])
old_item_taxes[d.item_code].append(d)
@@ -49,7 +54,9 @@ def execute():
item_tax_map[d.tax_type] = d.tax_rate
tax_types = []
- item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code, tax_types=tax_types)
+ item_tax_template_name = get_item_tax_template(
+ item_tax_templates, item_tax_map, item_code, tax_types=tax_types
+ )
# update the item tax table
frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code)
@@ -61,17 +68,29 @@ def execute():
d.db_insert()
doctypes = [
- 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice',
- 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'
+ "Quotation",
+ "Sales Order",
+ "Delivery Note",
+ "Sales Invoice",
+ "Supplier Quotation",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
]
for dt in doctypes:
- for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item`
+ for d in frappe.db.sql(
+ """select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item`
where ifnull(item_tax_rate, '') not in ('', '{{}}')
- and item_tax_template is NULL""".format(dt), as_dict=1):
+ and item_tax_template is NULL""".format(
+ dt
+ ),
+ as_dict=1,
+ ):
item_tax_map = json.loads(d.item_tax_rate)
- item_tax_template_name = get_item_tax_template(item_tax_templates,
- item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types)
+ item_tax_template_name = get_item_tax_template(
+ item_tax_templates, item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types
+ )
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
frappe.db.auto_commit_on_many_writes = False
@@ -81,7 +100,10 @@ def execute():
settings.determine_address_tax_category_from = "Billing Address"
settings.save()
-def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None):
+
+def get_item_tax_template(
+ item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None
+):
# search for previously created item tax template by comparing tax maps
for template, item_tax_template_map in item_tax_templates.items():
if item_tax_map == item_tax_template_map:
@@ -93,15 +115,23 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp
item_tax_template_name = item_tax_template.title
for tax_type, tax_rate in item_tax_map.items():
- account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type', 'company'], as_dict=1)
+ account_details = frappe.db.get_value(
+ "Account", tax_type, ["name", "account_type", "company"], as_dict=1
+ )
if account_details:
item_tax_template.company = account_details.company
if not item_tax_template_name:
# set name once company is set as name is generated from company & title
# setting name is required to update `item_tax_templates` dict
item_tax_template_name = item_tax_template.set_new_name()
- if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
- frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable')
+ if account_details.account_type not in (
+ "Tax",
+ "Chargeable",
+ "Income Account",
+ "Expense Account",
+ "Expenses Included In Valuation",
+ ):
+ frappe.db.set_value("Account", account_details.name, "account_type", "Chargeable")
else:
parts = tax_type.strip().split(" - ")
account_name = " - ".join(parts[:-1])
@@ -109,18 +139,25 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp
tax_type = None
else:
company = get_company(parts[-1], parenttype, parent)
- parent_account = frappe.get_value("Account", {"account_name": account_name, "company": company}, "parent_account")
+ parent_account = frappe.get_value(
+ "Account", {"account_name": account_name, "company": company}, "parent_account"
+ )
if not parent_account:
- parent_account = frappe.db.get_value("Account",
- filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
+ parent_account = frappe.db.get_value(
+ "Account",
+ filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company},
+ fieldname="parent_account",
+ )
if not parent_account:
- parent_account = frappe.db.get_value("Account",
- filters={"account_type": "Tax", "root_type": "Liability", "is_group": 1, "company": company})
+ parent_account = frappe.db.get_value(
+ "Account",
+ filters={"account_type": "Tax", "root_type": "Liability", "is_group": 1, "company": company},
+ )
filters = {
"account_name": account_name,
"company": company,
"account_type": "Tax",
- "parent_account": parent_account
+ "parent_account": parent_account,
}
tax_type = frappe.db.get_value("Account", filters)
if not tax_type:
@@ -130,11 +167,19 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp
account.insert()
tax_type = account.name
except frappe.DuplicateEntryError:
- tax_type = frappe.db.get_value("Account", {"account_name": account_name, "company": company}, "name")
+ tax_type = frappe.db.get_value(
+ "Account", {"account_name": account_name, "company": company}, "name"
+ )
account_type = frappe.get_cached_value("Account", tax_type, "account_type")
- if tax_type and account_type in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
+ if tax_type and account_type in (
+ "Tax",
+ "Chargeable",
+ "Income Account",
+ "Expense Account",
+ "Expenses Included In Valuation",
+ ):
if tax_type not in tax_types:
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
tax_types.append(tax_type)
@@ -145,14 +190,15 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp
item_tax_template.save()
return item_tax_template.name
+
def get_company(company_abbr, parenttype=None, parent=None):
if parenttype and parent:
- company = frappe.get_cached_value(parenttype, parent, 'company')
+ company = frappe.get_cached_value(parenttype, parent, "company")
else:
company = frappe.db.get_value("Company", filters={"abbr": company_abbr})
if not company:
- companies = frappe.get_all('Company')
+ companies = frappe.get_all("Company")
if len(companies) == 1:
company = companies[0].name
diff --git a/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py
index c396891b59..6788cb2e26 100644
--- a/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py
+++ b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py
@@ -12,10 +12,12 @@ def execute():
if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env and frappe.conf.plaid_secret):
plaid_settings.enabled = 0
else:
- plaid_settings.update({
- "plaid_client_id": frappe.conf.plaid_client_id,
- "plaid_env": frappe.conf.plaid_env,
- "plaid_secret": frappe.conf.plaid_secret
- })
+ plaid_settings.update(
+ {
+ "plaid_client_id": frappe.conf.plaid_client_id,
+ "plaid_env": frappe.conf.plaid_env,
+ "plaid_secret": frappe.conf.plaid_secret,
+ }
+ )
plaid_settings.flags.ignore_mandatory = True
plaid_settings.save()
diff --git a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py
index 36fe18d8b7..71926107bd 100644
--- a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py
+++ b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py
@@ -6,19 +6,23 @@ import frappe
def execute():
- frappe.reload_doc("setup", "doctype", "target_detail")
- frappe.reload_doc("core", "doctype", "prepared_report")
+ frappe.reload_doc("setup", "doctype", "target_detail")
+ frappe.reload_doc("core", "doctype", "prepared_report")
- for d in ['Sales Person', 'Sales Partner', 'Territory']:
- frappe.db.sql("""
+ for d in ["Sales Person", "Sales Partner", "Territory"]:
+ frappe.db.sql(
+ """
UPDATE `tab{child_doc}`, `tab{parent_doc}`
SET
`tab{child_doc}`.distribution_id = `tab{parent_doc}`.distribution_id
WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name
and `tab{parent_doc}`.distribution_id is not null and `tab{parent_doc}`.distribution_id != ''
- """.format(parent_doc = d, child_doc = "Target Detail"))
+ """.format(
+ parent_doc=d, child_doc="Target Detail"
+ )
+ )
- frappe.delete_doc("Report", "Sales Partner-wise Transaction Summary")
- frappe.delete_doc("Report", "Sales Person Target Variance Item Group-Wise")
- frappe.delete_doc("Report", "Territory Target Variance Item Group-Wise")
+ frappe.delete_doc("Report", "Sales Partner-wise Transaction Summary")
+ frappe.delete_doc("Report", "Sales Person Target Variance Item Group-Wise")
+ frappe.delete_doc("Report", "Territory Target Variance Item Group-Wise")
diff --git a/erpnext/patches/v12_0/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py
index 459221e769..3b828d69cb 100644
--- a/erpnext/patches/v12_0/purchase_receipt_status.py
+++ b/erpnext/patches/v12_0/purchase_receipt_status.py
@@ -7,6 +7,7 @@ import frappe
logger = frappe.logger("patch", allow_site=True, file_count=50)
+
def execute():
affected_purchase_receipts = frappe.db.sql(
"""select name from `tabPurchase Receipt`
@@ -16,13 +17,13 @@ def execute():
if not affected_purchase_receipts:
return
- logger.info("purchase_receipt_status: begin patch, PR count: {}"
- .format(len(affected_purchase_receipts)))
+ logger.info(
+ "purchase_receipt_status: begin patch, PR count: {}".format(len(affected_purchase_receipts))
+ )
frappe.reload_doc("stock", "doctype", "Purchase Receipt")
frappe.reload_doc("stock", "doctype", "Purchase Receipt Item")
-
for pr in affected_purchase_receipts:
pr_name = pr[0]
logger.info("purchase_receipt_status: patching PR - {}".format(pr_name))
diff --git a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py
index 8dec9ff381..661152bef3 100644
--- a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py
+++ b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py
@@ -4,13 +4,18 @@ from erpnext.stock.stock_balance import get_indented_qty, update_bin_qty
def execute():
- bin_details = frappe.db.sql("""
+ bin_details = frappe.db.sql(
+ """
SELECT item_code, warehouse
- FROM `tabBin`""",as_dict=1)
+ FROM `tabBin`""",
+ as_dict=1,
+ )
for entry in bin_details:
if not (entry.item_code and entry.warehouse):
continue
- update_bin_qty(entry.get("item_code"), entry.get("warehouse"), {
- "indented_qty": get_indented_qty(entry.get("item_code"), entry.get("warehouse"))
- })
+ update_bin_qty(
+ entry.get("item_code"),
+ entry.get("warehouse"),
+ {"indented_qty": get_indented_qty(entry.get("item_code"), entry.get("warehouse"))},
+ )
diff --git a/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py
index 12768a6f95..b18f4ebe2e 100644
--- a/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py
+++ b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py
@@ -4,10 +4,15 @@ import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "tax_category")
frappe.reload_doc("stock", "doctype", "item_manufacturer")
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
if frappe.db.exists("Custom Field", "Company-bank_remittance_section"):
- deprecated_fields = ['bank_remittance_section', 'client_code', 'remittance_column_break', 'product_code']
+ deprecated_fields = [
+ "bank_remittance_section",
+ "client_code",
+ "remittance_column_break",
+ "product_code",
+ ]
for i in range(len(deprecated_fields)):
- frappe.delete_doc("Custom Field", 'Company-'+deprecated_fields[i])
+ frappe.delete_doc("Custom Field", "Company-" + deprecated_fields[i])
diff --git a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py
index d1d4bcc140..4029a3f0e2 100644
--- a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py
+++ b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py
@@ -6,8 +6,8 @@ import frappe
def execute():
- ''' Delete leave ledger entry created
- via leave applications with status != Approved '''
+ """Delete leave ledger entry created
+ via leave applications with status != Approved"""
if not frappe.db.a_row_exists("Leave Ledger Entry"):
return
@@ -15,14 +15,21 @@ def execute():
if leave_application_list:
delete_denied_leaves_from_leave_ledger_entry(leave_application_list)
+
def get_denied_leave_application_list():
- return frappe.db.sql_list(''' Select name from `tabLeave Application` where status <> 'Approved' ''')
+ return frappe.db.sql_list(
+ """ Select name from `tabLeave Application` where status <> 'Approved' """
+ )
+
def delete_denied_leaves_from_leave_ledger_entry(leave_application_list):
if leave_application_list:
- frappe.db.sql(''' Delete
+ frappe.db.sql(
+ """ Delete
FROM `tabLeave Ledger Entry`
WHERE
transaction_type = 'Leave Application'
- AND transaction_name in (%s) ''' % (', '.join(['%s'] * len(leave_application_list))), #nosec
- tuple(leave_application_list))
+ AND transaction_name in (%s) """
+ % (", ".join(["%s"] * len(leave_application_list))), # nosec
+ tuple(leave_application_list),
+ )
diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
index 6ad68ccc6e..8247734a4a 100644
--- a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
+++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
@@ -7,16 +7,18 @@ import frappe
def execute():
"""Delete duplicate leave ledger entries of type allocation created."""
- frappe.reload_doc('hr', 'doctype', 'leave_ledger_entry')
+ frappe.reload_doc("hr", "doctype", "leave_ledger_entry")
if not frappe.db.a_row_exists("Leave Ledger Entry"):
return
duplicate_records_list = get_duplicate_records()
delete_duplicate_ledger_entries(duplicate_records_list)
+
def get_duplicate_records():
"""Fetch all but one duplicate records from the list of expired leave allocation."""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT name, employee, transaction_name, leave_type, is_carry_forward, from_date, to_date
FROM `tabLeave Ledger Entry`
WHERE
@@ -29,13 +31,17 @@ def get_duplicate_records():
count(name) > 1
ORDER BY
creation
- """)
+ """
+ )
+
def delete_duplicate_ledger_entries(duplicate_records_list):
"""Delete duplicate leave ledger entries."""
- if not duplicate_records_list: return
+ if not duplicate_records_list:
+ return
for d in duplicate_records_list:
- frappe.db.sql('''
+ frappe.db.sql(
+ """
DELETE FROM `tabLeave Ledger Entry`
WHERE name != %s
AND employee = %s
@@ -44,4 +50,6 @@ def delete_duplicate_ledger_entries(duplicate_records_list):
AND is_carry_forward = %s
AND from_date = %s
AND to_date = %s
- ''', tuple(d))
+ """,
+ tuple(d),
+ )
diff --git a/erpnext/patches/v12_0/rename_account_type_doctype.py b/erpnext/patches/v12_0/rename_account_type_doctype.py
index e33a1d010d..ab195549a4 100644
--- a/erpnext/patches/v12_0/rename_account_type_doctype.py
+++ b/erpnext/patches/v12_0/rename_account_type_doctype.py
@@ -2,6 +2,6 @@ import frappe
def execute():
- frappe.rename_doc('DocType', 'Account Type', 'Bank Account Type', force=True)
- frappe.rename_doc('DocType', 'Account Subtype', 'Bank Account Subtype', force=True)
- frappe.reload_doc('accounts', 'doctype', 'bank_account')
+ frappe.rename_doc("DocType", "Account Type", "Bank Account Type", force=True)
+ frappe.rename_doc("DocType", "Account Subtype", "Bank Account Subtype", force=True)
+ frappe.reload_doc("accounts", "doctype", "bank_account")
diff --git a/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py b/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py
index a5d986a0a1..92687530eb 100644
--- a/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py
+++ b/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py
@@ -7,12 +7,13 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- ''' Change the fieldname from bank_account_no to bank_account '''
+ """Change the fieldname from bank_account_no to bank_account"""
if not frappe.get_meta("Journal Entry Account").has_field("bank_account"):
frappe.reload_doc("Accounts", "doctype", "Journal Entry Account")
update_journal_entry_account_fieldname()
+
def update_journal_entry_account_fieldname():
- ''' maps data from old field to the new field '''
- if frappe.db.has_column('Journal Entry Account', 'bank_account_no'):
+ """maps data from old field to the new field"""
+ if frappe.db.has_column("Journal Entry Account", "bank_account_no"):
rename_field("Journal Entry Account", "bank_account_no", "bank_account")
diff --git a/erpnext/patches/v12_0/rename_bank_reconciliation.py b/erpnext/patches/v12_0/rename_bank_reconciliation.py
index 51ff0c8c94..aacd6e8161 100644
--- a/erpnext/patches/v12_0/rename_bank_reconciliation.py
+++ b/erpnext/patches/v12_0/rename_bank_reconciliation.py
@@ -7,8 +7,8 @@ import frappe
def execute():
if frappe.db.table_exists("Bank Reconciliation"):
- frappe.rename_doc('DocType', 'Bank Reconciliation', 'Bank Clearance', force=True)
- frappe.reload_doc('Accounts', 'doctype', 'Bank Clearance')
+ frappe.rename_doc("DocType", "Bank Reconciliation", "Bank Clearance", force=True)
+ frappe.reload_doc("Accounts", "doctype", "Bank Clearance")
- frappe.rename_doc('DocType', 'Bank Reconciliation Detail', 'Bank Clearance Detail', force=True)
- frappe.reload_doc('Accounts', 'doctype', 'Bank Clearance Detail')
+ frappe.rename_doc("DocType", "Bank Reconciliation Detail", "Bank Clearance Detail", force=True)
+ frappe.reload_doc("Accounts", "doctype", "Bank Clearance Detail")
diff --git a/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py b/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py
index 629cd5bda6..e2a3887b9a 100644
--- a/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py
+++ b/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py
@@ -5,11 +5,24 @@ import frappe
def _rename_single_field(**kwargs):
- count = frappe.db.sql("SELECT COUNT(*) FROM tabSingles WHERE doctype='{doctype}' AND field='{new_name}';".format(**kwargs))[0][0] #nosec
+ count = frappe.db.sql(
+ "SELECT COUNT(*) FROM tabSingles WHERE doctype='{doctype}' AND field='{new_name}';".format(
+ **kwargs
+ )
+ )[0][
+ 0
+ ] # nosec
if count == 0:
- frappe.db.sql("UPDATE tabSingles SET field='{new_name}' WHERE doctype='{doctype}' AND field='{old_name}';".format(**kwargs)) #nosec
+ frappe.db.sql(
+ "UPDATE tabSingles SET field='{new_name}' WHERE doctype='{doctype}' AND field='{old_name}';".format(
+ **kwargs
+ )
+ ) # nosec
+
def execute():
- _rename_single_field(doctype = "Bank Clearance", old_name = "bank_account" , new_name = "account")
- _rename_single_field(doctype = "Bank Clearance", old_name = "bank_account_no", new_name = "bank_account")
+ _rename_single_field(doctype="Bank Clearance", old_name="bank_account", new_name="account")
+ _rename_single_field(
+ doctype="Bank Clearance", old_name="bank_account_no", new_name="bank_account"
+ )
frappe.reload_doc("Accounts", "doctype", "Bank Clearance")
diff --git a/erpnext/patches/v12_0/rename_lost_reason_detail.py b/erpnext/patches/v12_0/rename_lost_reason_detail.py
index 96ae9798c6..2f7f842848 100644
--- a/erpnext/patches/v12_0/rename_lost_reason_detail.py
+++ b/erpnext/patches/v12_0/rename_lost_reason_detail.py
@@ -2,17 +2,23 @@ import frappe
def execute():
- if frappe.db.exists("DocType", "Lost Reason Detail"):
- frappe.reload_doc("crm", "doctype", "opportunity_lost_reason")
- frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail")
- frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail")
+ if frappe.db.exists("DocType", "Lost Reason Detail"):
+ frappe.reload_doc("crm", "doctype", "opportunity_lost_reason")
+ frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail")
+ frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail")
- frappe.db.sql("""INSERT INTO `tabOpportunity Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Opportunity'""")
+ frappe.db.sql(
+ """INSERT INTO `tabOpportunity Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Opportunity'"""
+ )
- frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Quotation'""")
+ frappe.db.sql(
+ """INSERT INTO `tabQuotation Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Quotation'"""
+ )
- frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`)
+ frappe.db.sql(
+ """INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`)
SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason`
- FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL""")
+ FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL"""
+ )
- frappe.delete_doc("DocType", "Lost Reason Detail")
+ frappe.delete_doc("DocType", "Lost Reason Detail")
diff --git a/erpnext/patches/v12_0/rename_pos_closing_doctype.py b/erpnext/patches/v12_0/rename_pos_closing_doctype.py
index f5f0112e03..fb80f8dc61 100644
--- a/erpnext/patches/v12_0/rename_pos_closing_doctype.py
+++ b/erpnext/patches/v12_0/rename_pos_closing_doctype.py
@@ -7,17 +7,19 @@ import frappe
def execute():
if frappe.db.table_exists("POS Closing Voucher"):
if not frappe.db.exists("DocType", "POS Closing Entry"):
- frappe.rename_doc('DocType', 'POS Closing Voucher', 'POS Closing Entry', force=True)
+ frappe.rename_doc("DocType", "POS Closing Voucher", "POS Closing Entry", force=True)
- if not frappe.db.exists('DocType', 'POS Closing Entry Taxes'):
- frappe.rename_doc('DocType', 'POS Closing Voucher Taxes', 'POS Closing Entry Taxes', force=True)
+ if not frappe.db.exists("DocType", "POS Closing Entry Taxes"):
+ frappe.rename_doc("DocType", "POS Closing Voucher Taxes", "POS Closing Entry Taxes", force=True)
- if not frappe.db.exists('DocType', 'POS Closing Voucher Details'):
- frappe.rename_doc('DocType', 'POS Closing Voucher Details', 'POS Closing Entry Detail', force=True)
+ if not frappe.db.exists("DocType", "POS Closing Voucher Details"):
+ frappe.rename_doc(
+ "DocType", "POS Closing Voucher Details", "POS Closing Entry Detail", force=True
+ )
- frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry')
- frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Taxes')
- frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Detail')
+ frappe.reload_doc("Accounts", "doctype", "POS Closing Entry")
+ frappe.reload_doc("Accounts", "doctype", "POS Closing Entry Taxes")
+ frappe.reload_doc("Accounts", "doctype", "POS Closing Entry Detail")
if frappe.db.exists("DocType", "POS Closing Voucher"):
frappe.delete_doc("DocType", "POS Closing Voucher")
diff --git a/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py b/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py
index 87630fbcaf..8d4c01359d 100644
--- a/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py
+++ b/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py
@@ -5,16 +5,17 @@
import frappe
doctypes = {
- 'Price Discount Slab': 'Promotional Scheme Price Discount',
- 'Product Discount Slab': 'Promotional Scheme Product Discount',
- 'Apply Rule On Item Code': 'Pricing Rule Item Code',
- 'Apply Rule On Item Group': 'Pricing Rule Item Group',
- 'Apply Rule On Brand': 'Pricing Rule Brand'
+ "Price Discount Slab": "Promotional Scheme Price Discount",
+ "Product Discount Slab": "Promotional Scheme Product Discount",
+ "Apply Rule On Item Code": "Pricing Rule Item Code",
+ "Apply Rule On Item Group": "Pricing Rule Item Group",
+ "Apply Rule On Brand": "Pricing Rule Brand",
}
+
def execute():
- for old_doc, new_doc in doctypes.items():
- if not frappe.db.table_exists(new_doc) and frappe.db.table_exists(old_doc):
- frappe.rename_doc('DocType', old_doc, new_doc)
- frappe.reload_doc("accounts", "doctype", frappe.scrub(new_doc))
- frappe.delete_doc("DocType", old_doc)
+ for old_doc, new_doc in doctypes.items():
+ if not frappe.db.table_exists(new_doc) and frappe.db.table_exists(old_doc):
+ frappe.rename_doc("DocType", old_doc, new_doc)
+ frappe.reload_doc("accounts", "doctype", frappe.scrub(new_doc))
+ frappe.delete_doc("DocType", old_doc)
diff --git a/erpnext/patches/v12_0/rename_tolerance_fields.py b/erpnext/patches/v12_0/rename_tolerance_fields.py
index ca2427bc3d..ef1ba655a9 100644
--- a/erpnext/patches/v12_0/rename_tolerance_fields.py
+++ b/erpnext/patches/v12_0/rename_tolerance_fields.py
@@ -7,8 +7,8 @@ def execute():
frappe.reload_doc("stock", "doctype", "stock_settings")
frappe.reload_doc("accounts", "doctype", "accounts_settings")
- rename_field('Stock Settings', "tolerance", "over_delivery_receipt_allowance")
- rename_field('Item', "tolerance", "over_delivery_receipt_allowance")
+ rename_field("Stock Settings", "tolerance", "over_delivery_receipt_allowance")
+ rename_field("Item", "tolerance", "over_delivery_receipt_allowance")
qty_allowance = frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
frappe.db.set_value("Accounts Settings", None, "over_delivery_receipt_allowance", qty_allowance)
diff --git a/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py b/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py
index ff332f771d..21dd258ead 100644
--- a/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py
+++ b/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py
@@ -2,5 +2,7 @@ import frappe
def execute():
- frappe.db.sql("""UPDATE `tabUser` SET `home_settings` = REPLACE(`home_settings`, 'Accounting', 'Accounts')""")
- frappe.cache().delete_key('home_settings')
+ frappe.db.sql(
+ """UPDATE `tabUser` SET `home_settings` = REPLACE(`home_settings`, 'Accounting', 'Accounts')"""
+ )
+ frappe.cache().delete_key("home_settings")
diff --git a/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py
index 198963df71..a4a85871ea 100644
--- a/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py
+++ b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py
@@ -6,51 +6,79 @@ import frappe
def execute():
- warehouse_perm = frappe.get_all("User Permission",
- fields=["count(*) as p_count", "is_default", "user"], filters={"allow": "Warehouse"}, group_by="user")
+ warehouse_perm = frappe.get_all(
+ "User Permission",
+ fields=["count(*) as p_count", "is_default", "user"],
+ filters={"allow": "Warehouse"},
+ group_by="user",
+ )
if not warehouse_perm:
return
execute_patch = False
for perm_data in warehouse_perm:
- if perm_data.p_count == 1 or (perm_data.p_count > 1 and frappe.get_all("User Permission",
- filters = {"user": perm_data.user, "allow": "warehouse", "is_default": 1}, limit=1)):
+ if perm_data.p_count == 1 or (
+ perm_data.p_count > 1
+ and frappe.get_all(
+ "User Permission",
+ filters={"user": perm_data.user, "allow": "warehouse", "is_default": 1},
+ limit=1,
+ )
+ ):
execute_patch = True
break
- if not execute_patch: return
+ if not execute_patch:
+ return
for doctype in ["Sales Invoice", "Delivery Note"]:
- if not frappe.get_meta(doctype + ' Item').get_field("target_warehouse").hidden: continue
+ if not frappe.get_meta(doctype + " Item").get_field("target_warehouse").hidden:
+ continue
cond = ""
if doctype == "Sales Invoice":
cond = " AND parent_doc.update_stock = 1"
- data = frappe.db.sql(""" SELECT parent_doc.name as name, child_doc.name as child_name
+ data = frappe.db.sql(
+ """ SELECT parent_doc.name as name, child_doc.name as child_name
FROM
`tab{doctype}` parent_doc, `tab{doctype} Item` child_doc
WHERE
parent_doc.name = child_doc.parent AND parent_doc.docstatus < 2
AND child_doc.target_warehouse is not null AND child_doc.target_warehouse != ''
AND child_doc.creation > '2020-04-16' {cond}
- """.format(doctype=doctype, cond=cond), as_dict=1)
+ """.format(
+ doctype=doctype, cond=cond
+ ),
+ as_dict=1,
+ )
if data:
names = [d.child_name for d in data]
- frappe.db.sql(""" UPDATE `tab{0} Item` set target_warehouse = null
- WHERE name in ({1}) """.format(doctype, ','.join(["%s"] * len(names) )), tuple(names))
+ frappe.db.sql(
+ """ UPDATE `tab{0} Item` set target_warehouse = null
+ WHERE name in ({1}) """.format(
+ doctype, ",".join(["%s"] * len(names))
+ ),
+ tuple(names),
+ )
- frappe.db.sql(""" UPDATE `tabPacked Item` set target_warehouse = null
+ frappe.db.sql(
+ """ UPDATE `tabPacked Item` set target_warehouse = null
WHERE parenttype = '{0}' and parent_detail_docname in ({1})
- """.format(doctype, ','.join(["%s"] * len(names) )), tuple(names))
+ """.format(
+ doctype, ",".join(["%s"] * len(names))
+ ),
+ tuple(names),
+ )
parent_names = list(set([d.name for d in data]))
for d in parent_names:
doc = frappe.get_doc(doctype, d)
- if doc.docstatus != 1: continue
+ if doc.docstatus != 1:
+ continue
doc.docstatus = 2
doc.update_stock_ledger()
@@ -61,9 +89,13 @@ def execute():
doc.update_stock_ledger()
doc.make_gl_entries()
- if frappe.get_meta('Sales Order Item').get_field("target_warehouse").hidden:
- frappe.db.sql(""" UPDATE `tabSales Order Item` set target_warehouse = null
- WHERE creation > '2020-04-16' and docstatus < 2 """)
+ if frappe.get_meta("Sales Order Item").get_field("target_warehouse").hidden:
+ frappe.db.sql(
+ """ UPDATE `tabSales Order Item` set target_warehouse = null
+ WHERE creation > '2020-04-16' and docstatus < 2 """
+ )
- frappe.db.sql(""" UPDATE `tabPacked Item` set target_warehouse = null
- WHERE creation > '2020-04-16' and docstatus < 2 and parenttype = 'Sales Order' """)
+ frappe.db.sql(
+ """ UPDATE `tabPacked Item` set target_warehouse = null
+ WHERE creation > '2020-04-16' and docstatus < 2 and parenttype = 'Sales Order' """
+ )
diff --git a/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py
index b76e34abe1..d88593b498 100644
--- a/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py
+++ b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py
@@ -3,12 +3,16 @@ import frappe
def execute():
- frappe.reload_doc('selling', 'doctype', 'sales_order_item', force=True)
- frappe.reload_doc('buying', 'doctype', 'purchase_order_item', force=True)
+ frappe.reload_doc("selling", "doctype", "sales_order_item", force=True)
+ frappe.reload_doc("buying", "doctype", "purchase_order_item", force=True)
- for doctype in ('Sales Order Item', 'Purchase Order Item'):
- frappe.db.sql("""
+ for doctype in ("Sales Order Item", "Purchase Order Item"):
+ frappe.db.sql(
+ """
UPDATE `tab{0}`
SET against_blanket_order = 1
WHERE ifnull(blanket_order, '') != ''
- """.format(doctype))
+ """.format(
+ doctype
+ )
+ )
diff --git a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py
index 8f29fc888e..37af989549 100644
--- a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py
+++ b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py
@@ -4,4 +4,6 @@ import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "accounts_settings")
- frappe.db.set_value("Accounts Settings", None, "automatically_process_deferred_accounting_entry", 1)
+ frappe.db.set_value(
+ "Accounts Settings", None, "automatically_process_deferred_accounting_entry", 1
+ )
diff --git a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py
index d3045a1a57..a5b4c66ce8 100644
--- a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py
+++ b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py
@@ -2,9 +2,11 @@ import frappe
def execute():
- frappe.reload_doc('hr', 'doctype', 'expense_claim_detail')
- frappe.db.sql("""
+ frappe.reload_doc("hr", "doctype", "expense_claim_detail")
+ frappe.db.sql(
+ """
UPDATE `tabExpense Claim Detail` child, `tabExpense Claim` par
SET child.cost_center = par.cost_center
WHERE child.parent = par.name
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py
index d1e0e4550e..952f64be2e 100644
--- a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py
+++ b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py
@@ -3,8 +3,8 @@ from frappe.utils import cint
def execute():
- '''Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field
- in Company, delete Asset Settings '''
+ """Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field
+ in Company, delete Asset Settings"""
if frappe.db.exists("DocType", "Asset Settings"):
frappe.reload_doctype("Asset Category")
diff --git a/erpnext/patches/v12_0/set_default_batch_size.py b/erpnext/patches/v12_0/set_default_batch_size.py
index 6fb69456dd..ac3e2f47ee 100644
--- a/erpnext/patches/v12_0/set_default_batch_size.py
+++ b/erpnext/patches/v12_0/set_default_batch_size.py
@@ -2,18 +2,22 @@ import frappe
def execute():
- frappe.reload_doc("manufacturing", "doctype", "bom_operation")
- frappe.reload_doc("manufacturing", "doctype", "work_order_operation")
+ frappe.reload_doc("manufacturing", "doctype", "bom_operation")
+ frappe.reload_doc("manufacturing", "doctype", "work_order_operation")
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabBOM Operation` bo
SET
bo.batch_size = 1
- """)
- frappe.db.sql("""
+ """
+ )
+ frappe.db.sql(
+ """
UPDATE
`tabWork Order Operation` wop
SET
wop.batch_size = 1
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/set_default_homepage_type.py b/erpnext/patches/v12_0/set_default_homepage_type.py
index 1e4333aa46..d70b28efd8 100644
--- a/erpnext/patches/v12_0/set_default_homepage_type.py
+++ b/erpnext/patches/v12_0/set_default_homepage_type.py
@@ -2,4 +2,4 @@ import frappe
def execute():
- frappe.db.set_value('Homepage', 'Homepage', 'hero_section_based_on', 'Default')
+ frappe.db.set_value("Homepage", "Homepage", "hero_section_based_on", "Default")
diff --git a/erpnext/patches/v12_0/set_employee_preferred_emails.py b/erpnext/patches/v12_0/set_employee_preferred_emails.py
index f6eb12e2b9..a6159c6b6b 100644
--- a/erpnext/patches/v12_0/set_employee_preferred_emails.py
+++ b/erpnext/patches/v12_0/set_employee_preferred_emails.py
@@ -2,9 +2,11 @@ import frappe
def execute():
- employees = frappe.get_all("Employee",
+ employees = frappe.get_all(
+ "Employee",
filters={"prefered_email": ""},
- fields=["name", "prefered_contact_email", "company_email", "personal_email", "user_id"])
+ fields=["name", "prefered_contact_email", "company_email", "personal_email", "user_id"],
+ )
for employee in employees:
if not employee.prefered_contact_email:
@@ -13,4 +15,6 @@ def execute():
preferred_email_field = frappe.scrub(employee.prefered_contact_email)
preferred_email = employee.get(preferred_email_field)
- frappe.db.set_value("Employee", employee.name, "prefered_email", preferred_email, update_modified=False)
+ frappe.db.set_value(
+ "Employee", employee.name, "prefered_email", preferred_email, update_modified=False
+ )
diff --git a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py
index 50d9fee099..9588e026d3 100644
--- a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py
+++ b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py
@@ -2,14 +2,19 @@ import frappe
def execute():
- frappe.reload_doctype('Landed Cost Taxes and Charges')
+ frappe.reload_doctype("Landed Cost Taxes and Charges")
- company_account_map = frappe._dict(frappe.db.sql("""
+ company_account_map = frappe._dict(
+ frappe.db.sql(
+ """
SELECT name, expenses_included_in_valuation from `tabCompany`
- """))
+ """
+ )
+ )
for company, account in company_account_map.items():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabLanded Cost Taxes and Charges` t, `tabLanded Cost Voucher` l
SET
@@ -18,9 +23,12 @@ def execute():
l.docstatus = 1
AND l.company = %s
AND t.parent = l.name
- """, (account, company))
+ """,
+ (account, company),
+ )
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabLanded Cost Taxes and Charges` t, `tabStock Entry` s
SET
@@ -29,4 +37,6 @@ def execute():
s.docstatus = 1
AND s.company = %s
AND t.parent = s.name
- """, (account, company))
+ """,
+ (account, company),
+ )
diff --git a/erpnext/patches/v12_0/set_gst_category.py b/erpnext/patches/v12_0/set_gst_category.py
index 094e2a3134..126a73bd3d 100644
--- a/erpnext/patches/v12_0/set_gst_category.py
+++ b/erpnext/patches/v12_0/set_gst_category.py
@@ -5,48 +5,62 @@ from erpnext.regional.india.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- frappe.reload_doc('accounts', 'doctype', 'Tax Category')
+ frappe.reload_doc("accounts", "doctype", "Tax Category")
make_custom_fields()
- for doctype in ['Sales Invoice', 'Purchase Invoice']:
- has_column = frappe.db.has_column(doctype,'invoice_type')
+ for doctype in ["Sales Invoice", "Purchase Invoice"]:
+ has_column = frappe.db.has_column(doctype, "invoice_type")
if has_column:
update_map = {
- 'Regular': 'Registered Regular',
- 'Export': 'Overseas',
- 'SEZ': 'SEZ',
- 'Deemed Export': 'Deemed Export',
+ "Regular": "Registered Regular",
+ "Export": "Overseas",
+ "SEZ": "SEZ",
+ "Deemed Export": "Deemed Export",
}
for old, new in update_map.items():
- frappe.db.sql("UPDATE `tab{doctype}` SET gst_category = %s where invoice_type = %s".format(doctype=doctype), (new, old)) #nosec
+ frappe.db.sql(
+ "UPDATE `tab{doctype}` SET gst_category = %s where invoice_type = %s".format(doctype=doctype),
+ (new, old),
+ ) # nosec
- frappe.delete_doc('Custom Field', 'Sales Invoice-invoice_type')
- frappe.delete_doc('Custom Field', 'Purchase Invoice-invoice_type')
+ frappe.delete_doc("Custom Field", "Sales Invoice-invoice_type")
+ frappe.delete_doc("Custom Field", "Purchase Invoice-invoice_type")
itc_update_map = {
"ineligible": "Ineligible",
"input service": "Input Service Distributor",
"capital goods": "Import Of Capital Goods",
- "input": "All Other ITC"
+ "input": "All Other ITC",
}
- has_gst_fields = frappe.db.has_column('Purchase Invoice','eligibility_for_itc')
+ has_gst_fields = frappe.db.has_column("Purchase Invoice", "eligibility_for_itc")
if has_gst_fields:
for old, new in itc_update_map.items():
- frappe.db.sql("UPDATE `tabPurchase Invoice` SET eligibility_for_itc = %s where eligibility_for_itc = %s ", (new, old))
+ frappe.db.sql(
+ "UPDATE `tabPurchase Invoice` SET eligibility_for_itc = %s where eligibility_for_itc = %s ",
+ (new, old),
+ )
for doctype in ["Customer", "Supplier"]:
- frappe.db.sql(""" UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Registered Regular"
- where t3.link_name = t1.name and t3.parent = t2.name and t2.gstin IS NOT NULL and t2.gstin != '' """.format(doctype=doctype)) #nosec
+ frappe.db.sql(
+ """ UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Registered Regular"
+ where t3.link_name = t1.name and t3.parent = t2.name and t2.gstin IS NOT NULL and t2.gstin != '' """.format(
+ doctype=doctype
+ )
+ ) # nosec
- frappe.db.sql(""" UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Overseas"
- where t3.link_name = t1.name and t3.parent = t2.name and t2.country != 'India' """.format(doctype=doctype)) #nosec
+ frappe.db.sql(
+ """ UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Overseas"
+ where t3.link_name = t1.name and t3.parent = t2.name and t2.country != 'India' """.format(
+ doctype=doctype
+ )
+ ) # nosec
diff --git a/erpnext/patches/v12_0/set_job_offer_applicant_email.py b/erpnext/patches/v12_0/set_job_offer_applicant_email.py
index 7dd8492081..0e3b5c4d2a 100644
--- a/erpnext/patches/v12_0/set_job_offer_applicant_email.py
+++ b/erpnext/patches/v12_0/set_job_offer_applicant_email.py
@@ -4,9 +4,11 @@ import frappe
def execute():
frappe.reload_doc("hr", "doctype", "job_offer")
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabJob Offer` AS offer
SET
applicant_email = (SELECT email_id FROM `tabJob Applicant` WHERE name = offer.job_applicant)
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/set_lead_title_field.py b/erpnext/patches/v12_0/set_lead_title_field.py
index 86e00038f6..eda3007e29 100644
--- a/erpnext/patches/v12_0/set_lead_title_field.py
+++ b/erpnext/patches/v12_0/set_lead_title_field.py
@@ -3,9 +3,11 @@ import frappe
def execute():
frappe.reload_doc("crm", "doctype", "lead")
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabLead`
SET
title = IF(organization_lead = 1, company_name, lead_name)
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py
index a8e0ec1f81..4d19007313 100644
--- a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py
+++ b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py
@@ -6,10 +6,12 @@ import frappe
def execute():
- frappe.reload_doc('buying', 'doctype', 'request_for_quotation_item')
+ frappe.reload_doc("buying", "doctype", "request_for_quotation_item")
- frappe.db.sql("""UPDATE `tabRequest for Quotation Item`
+ frappe.db.sql(
+ """UPDATE `tabRequest for Quotation Item`
SET
stock_uom = uom,
conversion_factor = 1,
- stock_qty = qty""")
+ stock_qty = qty"""
+ )
diff --git a/erpnext/patches/v12_0/set_payment_entry_status.py b/erpnext/patches/v12_0/set_payment_entry_status.py
index f8792952d8..2a3a3ad45e 100644
--- a/erpnext/patches/v12_0/set_payment_entry_status.py
+++ b/erpnext/patches/v12_0/set_payment_entry_status.py
@@ -3,8 +3,10 @@ import frappe
def execute():
frappe.reload_doctype("Payment Entry")
- frappe.db.sql("""update `tabPayment Entry` set status = CASE
+ frappe.db.sql(
+ """update `tabPayment Entry` set status = CASE
WHEN docstatus = 1 THEN 'Submitted'
WHEN docstatus = 2 THEN 'Cancelled'
ELSE 'Draft'
- END;""")
+ END;"""
+ )
diff --git a/erpnext/patches/v12_0/set_permission_einvoicing.py b/erpnext/patches/v12_0/set_permission_einvoicing.py
index 01cab14db9..65d70977d2 100644
--- a/erpnext/patches/v12_0/set_permission_einvoicing.py
+++ b/erpnext/patches/v12_0/set_permission_einvoicing.py
@@ -5,7 +5,7 @@ from erpnext.regional.italy.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'Italy'})
+ company = frappe.get_all("Company", filters={"country": "Italy"})
if not company:
return
@@ -14,6 +14,6 @@ def execute():
frappe.reload_doc("regional", "doctype", "import_supplier_invoice")
- add_permission('Import Supplier Invoice', 'Accounts Manager', 0)
- update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'write', 1)
- update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'create', 1)
+ add_permission("Import Supplier Invoice", "Accounts Manager", 0)
+ update_permission_property("Import Supplier Invoice", "Accounts Manager", 0, "write", 1)
+ update_permission_property("Import Supplier Invoice", "Accounts Manager", 0, "create", 1)
diff --git a/erpnext/patches/v12_0/set_priority_for_support.py b/erpnext/patches/v12_0/set_priority_for_support.py
index 6d7d099346..a8a07e76ea 100644
--- a/erpnext/patches/v12_0/set_priority_for_support.py
+++ b/erpnext/patches/v12_0/set_priority_for_support.py
@@ -4,21 +4,20 @@ import frappe
def execute():
frappe.reload_doc("support", "doctype", "issue_priority")
frappe.reload_doc("support", "doctype", "service_level_priority")
- frappe.reload_doc('support', 'doctype', 'issue')
+ frappe.reload_doc("support", "doctype", "issue")
set_issue_priority()
set_priority_for_issue()
set_priorities_service_level()
set_priorities_service_level_agreement()
+
def set_issue_priority():
# Adds priority from issue to Issue Priority DocType as Priority is a new DocType.
for priority in frappe.get_meta("Issue").get_field("priority").options.split("\n"):
if priority and not frappe.db.exists("Issue Priority", priority):
- frappe.get_doc({
- "doctype": "Issue Priority",
- "name": priority
- }).insert(ignore_permissions=True)
+ frappe.get_doc({"doctype": "Issue Priority", "name": priority}).insert(ignore_permissions=True)
+
def set_priority_for_issue():
# Sets priority for Issues as Select field is changed to Link field.
@@ -28,38 +27,63 @@ def set_priority_for_issue():
for issue in issue_priority:
frappe.db.set_value("Issue", issue.name, "priority", issue.priority)
+
def set_priorities_service_level():
# Migrates "priority", "response_time", "response_time_period", "resolution_time", "resolution_time_period" to Child Table
# as a Service Level can have multiple priorities
try:
- service_level_priorities = frappe.get_list("Service Level", fields=["name", "priority", "response_time", "response_time_period", "resolution_time", "resolution_time_period"])
+ service_level_priorities = frappe.get_list(
+ "Service Level",
+ fields=[
+ "name",
+ "priority",
+ "response_time",
+ "response_time_period",
+ "resolution_time",
+ "resolution_time_period",
+ ],
+ )
frappe.reload_doc("support", "doctype", "service_level")
frappe.reload_doc("support", "doctype", "support_settings")
- frappe.db.set_value('Support Settings', None, 'track_service_level_agreement', 1)
+ frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
for service_level in service_level_priorities:
if service_level:
doc = frappe.get_doc("Service Level", service_level.name)
if not doc.priorities:
- doc.append("priorities", {
- "priority": service_level.priority,
- "default_priority": 1,
- "response_time": service_level.response_time,
- "response_time_period": service_level.response_time_period,
- "resolution_time": service_level.resolution_time,
- "resolution_time_period": service_level.resolution_time_period
- })
+ doc.append(
+ "priorities",
+ {
+ "priority": service_level.priority,
+ "default_priority": 1,
+ "response_time": service_level.response_time,
+ "response_time_period": service_level.response_time_period,
+ "resolution_time": service_level.resolution_time,
+ "resolution_time_period": service_level.resolution_time_period,
+ },
+ )
doc.flags.ignore_validate = True
doc.save(ignore_permissions=True)
except frappe.db.TableMissingError:
frappe.reload_doc("support", "doctype", "service_level")
+
def set_priorities_service_level_agreement():
# Migrates "priority", "response_time", "response_time_period", "resolution_time", "resolution_time_period" to Child Table
# as a Service Level Agreement can have multiple priorities
try:
- service_level_agreement_priorities = frappe.get_list("Service Level Agreement", fields=["name", "priority", "response_time", "response_time_period", "resolution_time", "resolution_time_period"])
+ service_level_agreement_priorities = frappe.get_list(
+ "Service Level Agreement",
+ fields=[
+ "name",
+ "priority",
+ "response_time",
+ "response_time_period",
+ "resolution_time",
+ "resolution_time_period",
+ ],
+ )
frappe.reload_doc("support", "doctype", "service_level_agreement")
@@ -71,14 +95,17 @@ def set_priorities_service_level_agreement():
doc.entity_type = "Customer"
doc.entity = doc.customer
- doc.append("priorities", {
- "priority": service_level_agreement.priority,
- "default_priority": 1,
- "response_time": service_level_agreement.response_time,
- "response_time_period": service_level_agreement.response_time_period,
- "resolution_time": service_level_agreement.resolution_time,
- "resolution_time_period": service_level_agreement.resolution_time_period
- })
+ doc.append(
+ "priorities",
+ {
+ "priority": service_level_agreement.priority,
+ "default_priority": 1,
+ "response_time": service_level_agreement.response_time,
+ "response_time_period": service_level_agreement.response_time_period,
+ "resolution_time": service_level_agreement.resolution_time,
+ "resolution_time_period": service_level_agreement.resolution_time_period,
+ },
+ )
doc.flags.ignore_validate = True
doc.save(ignore_permissions=True)
except frappe.db.TableMissingError:
diff --git a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py
index 9c851ddcee..562ebed757 100644
--- a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py
+++ b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py
@@ -4,12 +4,14 @@ from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_
def execute():
- frappe.reload_doctype('Sales Order Item')
- frappe.reload_doctype('Sales Order')
+ frappe.reload_doctype("Sales Order Item")
+ frappe.reload_doctype("Sales Order")
- for d in frappe.get_all('Work Order',
- fields = ['sales_order', 'sales_order_item'],
- filters={'sales_order': ('!=', ''), 'sales_order_item': ('!=', '')}):
+ for d in frappe.get_all(
+ "Work Order",
+ fields=["sales_order", "sales_order_item"],
+ filters={"sales_order": ("!=", ""), "sales_order_item": ("!=", "")},
+ ):
# update produced qty in sales order
update_produced_qty_in_so_item(d.sales_order, d.sales_order_item)
diff --git a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py
index bd2f7e2dec..0246c35447 100644
--- a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py
+++ b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py
@@ -2,7 +2,9 @@ import frappe
def execute():
- frappe.reload_doc("manufacturing", "doctype", "workstation")
+ frappe.reload_doc("manufacturing", "doctype", "workstation")
- frappe.db.sql(""" UPDATE `tabWorkstation`
- SET production_capacity = 1 """)
+ frappe.db.sql(
+ """ UPDATE `tabWorkstation`
+ SET production_capacity = 1 """
+ )
diff --git a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
index a15166ed5f..2edf0f54fc 100644
--- a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
+++ b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
@@ -5,29 +5,36 @@ import frappe
def execute():
- frappe.reload_doc('stock', 'doctype', 'delivery_note_item', force=True)
- frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item', force=True)
+ frappe.reload_doc("stock", "doctype", "delivery_note_item", force=True)
+ frappe.reload_doc("stock", "doctype", "purchase_receipt_item", force=True)
def map_rows(doc_row, return_doc_row, detail_field, doctype):
"""Map rows after identifying similar ones."""
- frappe.db.sql(""" UPDATE `tab{doctype} Item` set {detail_field} = '{doc_row_name}'
- where name = '{return_doc_row_name}'""" \
- .format(doctype=doctype,
- detail_field=detail_field,
- doc_row_name=doc_row.get('name'),
- return_doc_row_name=return_doc_row.get('name'))) #nosec
+ frappe.db.sql(
+ """ UPDATE `tab{doctype} Item` set {detail_field} = '{doc_row_name}'
+ where name = '{return_doc_row_name}'""".format(
+ doctype=doctype,
+ detail_field=detail_field,
+ doc_row_name=doc_row.get("name"),
+ return_doc_row_name=return_doc_row.get("name"),
+ )
+ ) # nosec
def row_is_mappable(doc_row, return_doc_row, detail_field):
"""Checks if two rows are similar enough to be mapped."""
if doc_row.item_code == return_doc_row.item_code and not return_doc_row.get(detail_field):
- if doc_row.get('batch_no') and return_doc_row.get('batch_no') and doc_row.batch_no == return_doc_row.batch_no:
+ if (
+ doc_row.get("batch_no")
+ and return_doc_row.get("batch_no")
+ and doc_row.batch_no == return_doc_row.batch_no
+ ):
return True
- elif doc_row.get('serial_no') and return_doc_row.get('serial_no'):
- doc_sn = doc_row.serial_no.split('\n')
- return_doc_sn = return_doc_row.serial_no.split('\n')
+ elif doc_row.get("serial_no") and return_doc_row.get("serial_no"):
+ doc_sn = doc_row.serial_no.split("\n")
+ return_doc_sn = return_doc_row.serial_no.split("\n")
if set(doc_sn) & set(return_doc_sn):
# if two rows have serial nos in common, map them
@@ -42,12 +49,17 @@ def execute():
"""Returns a map of documents and it's return documents.
Format => { 'document' : ['return_document_1','return_document_2'] }"""
- return_against_documents = frappe.db.sql("""
+ return_against_documents = frappe.db.sql(
+ """
SELECT
return_against as document, name as return_document
FROM `tab{doctype}`
WHERE
- is_return = 1 and docstatus = 1""".format(doctype=doctype),as_dict=1) #nosec
+ is_return = 1 and docstatus = 1""".format(
+ doctype=doctype
+ ),
+ as_dict=1,
+ ) # nosec
for entry in return_against_documents:
return_document_map[entry.document].append(entry.return_document)
@@ -58,7 +70,7 @@ def execute():
"""Map each row of the original document in the return document."""
mapped = []
return_document_map = defaultdict(list)
- detail_field = "purchase_receipt_item" if doctype=="Purchase Receipt" else "dn_detail"
+ detail_field = "purchase_receipt_item" if doctype == "Purchase Receipt" else "dn_detail"
child_doc = frappe.scrub("{0} Item".format(doctype))
frappe.reload_doc("stock", "doctype", child_doc)
@@ -67,25 +79,27 @@ def execute():
count = 0
- #iterate through original documents and its return documents
+ # iterate through original documents and its return documents
for docname in return_document_map:
doc_items = frappe.get_cached_doc(doctype, docname).get("items")
for return_doc in return_document_map[docname]:
return_doc_items = frappe.get_cached_doc(doctype, return_doc).get("items")
- #iterate through return document items and original document items for mapping
+ # iterate through return document items and original document items for mapping
for return_item in return_doc_items:
for doc_item in doc_items:
- if row_is_mappable(doc_item, return_item, detail_field) and doc_item.get('name') not in mapped:
+ if (
+ row_is_mappable(doc_item, return_item, detail_field) and doc_item.get("name") not in mapped
+ ):
map_rows(doc_item, return_item, detail_field, doctype)
- mapped.append(doc_item.get('name'))
+ mapped.append(doc_item.get("name"))
break
else:
continue
# commit after every 100 sql updates
count += 1
- if count%100 == 0:
+ if count % 100 == 0:
frappe.db.commit()
set_document_detail_in_return_document("Purchase Receipt")
diff --git a/erpnext/patches/v12_0/set_quotation_status.py b/erpnext/patches/v12_0/set_quotation_status.py
index 91e77e4e0d..bebedd3a49 100644
--- a/erpnext/patches/v12_0/set_quotation_status.py
+++ b/erpnext/patches/v12_0/set_quotation_status.py
@@ -3,5 +3,7 @@ import frappe
def execute():
- frappe.db.sql(""" UPDATE `tabQuotation` set status = 'Open'
- where docstatus = 1 and status = 'Submitted' """)
+ frappe.db.sql(
+ """ UPDATE `tabQuotation` set status = 'Open'
+ where docstatus = 1 and status = 'Submitted' """
+ )
diff --git a/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py b/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py
index d41134d4db..dcdd19fbb6 100644
--- a/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py
+++ b/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py
@@ -2,13 +2,16 @@ import frappe
def execute():
- purchase_receipts = frappe.db.sql("""
+ purchase_receipts = frappe.db.sql(
+ """
SELECT
parent from `tabPurchase Receipt Item`
WHERE
material_request is not null
AND docstatus=1
- """,as_dict=1)
+ """,
+ as_dict=1,
+ )
purchase_receipts = set([d.parent for d in purchase_receipts])
@@ -16,15 +19,15 @@ def execute():
doc = frappe.get_doc("Purchase Receipt", pr)
doc.status_updater = [
{
- 'source_dt': 'Purchase Receipt Item',
- 'target_dt': 'Material Request Item',
- 'join_field': 'material_request_item',
- 'target_field': 'received_qty',
- 'target_parent_dt': 'Material Request',
- 'target_parent_field': 'per_received',
- 'target_ref_field': 'stock_qty',
- 'source_field': 'stock_qty',
- 'percent_join_field': 'material_request'
+ "source_dt": "Purchase Receipt Item",
+ "target_dt": "Material Request Item",
+ "join_field": "material_request_item",
+ "target_field": "received_qty",
+ "target_parent_dt": "Material Request",
+ "target_parent_field": "per_received",
+ "target_ref_field": "stock_qty",
+ "source_field": "stock_qty",
+ "percent_join_field": "material_request",
}
]
doc.update_qty()
diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py
index 8c136e6663..8ab342e9f2 100644
--- a/erpnext/patches/v12_0/set_serial_no_status.py
+++ b/erpnext/patches/v12_0/set_serial_no_status.py
@@ -3,17 +3,22 @@ from frappe.utils import getdate, nowdate
def execute():
- frappe.reload_doc('stock', 'doctype', 'serial_no')
+ frappe.reload_doc("stock", "doctype", "serial_no")
- serial_no_list = frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date, warehouse from `tabSerial No`
- where (status is NULL OR status='')""", as_dict = 1)
+ serial_no_list = frappe.db.sql(
+ """select name, delivery_document_type, warranty_expiry_date, warehouse from `tabSerial No`
+ where (status is NULL OR status='')""",
+ as_dict=1,
+ )
if len(serial_no_list) > 20000:
frappe.db.auto_commit_on_many_writes = True
for serial_no in serial_no_list:
if serial_no.get("delivery_document_type"):
status = "Delivered"
- elif serial_no.get("warranty_expiry_date") and getdate(serial_no.get("warranty_expiry_date")) <= getdate(nowdate()):
+ elif serial_no.get("warranty_expiry_date") and getdate(
+ serial_no.get("warranty_expiry_date")
+ ) <= getdate(nowdate()):
status = "Expired"
elif not serial_no.get("warehouse"):
status = "Inactive"
diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py
index 1b4955a75b..1c6654e57a 100644
--- a/erpnext/patches/v12_0/set_task_status.py
+++ b/erpnext/patches/v12_0/set_task_status.py
@@ -2,14 +2,16 @@ import frappe
def execute():
- frappe.reload_doctype('Task')
+ frappe.reload_doctype("Task")
# add "Completed" if customized
- property_setter_name = frappe.db.exists('Property Setter', dict(doc_type='Task', field_name = 'status', property = 'options'))
+ property_setter_name = frappe.db.exists(
+ "Property Setter", dict(doc_type="Task", field_name="status", property="options")
+ )
if property_setter_name:
- property_setter = frappe.get_doc('Property Setter', property_setter_name)
+ property_setter = frappe.get_doc("Property Setter", property_setter_name)
if not "Completed" in property_setter.value:
- property_setter.value = property_setter.value + '\nCompleted'
+ property_setter.value = property_setter.value + "\nCompleted"
property_setter.save()
# renamed default status to Completed as status "Closed" is ambiguous
diff --git a/erpnext/patches/v12_0/set_total_batch_quantity.py b/erpnext/patches/v12_0/set_total_batch_quantity.py
index 7296eaa33d..068e0a6a4c 100644
--- a/erpnext/patches/v12_0/set_total_batch_quantity.py
+++ b/erpnext/patches/v12_0/set_total_batch_quantity.py
@@ -5,7 +5,12 @@ def execute():
frappe.reload_doc("stock", "doctype", "batch")
for batch in frappe.get_all("Batch", fields=["name", "batch_id"]):
- batch_qty = frappe.db.get_value("Stock Ledger Entry",
- {"docstatus": 1, "batch_no": batch.batch_id, "is_cancelled": 0},
- "sum(actual_qty)") or 0.0
+ batch_qty = (
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"docstatus": 1, "batch_no": batch.batch_id, "is_cancelled": 0},
+ "sum(actual_qty)",
+ )
+ or 0.0
+ )
frappe.db.set_value("Batch", batch.name, "batch_qty", batch_qty, update_modified=False)
diff --git a/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py
index 300d0f2ba4..1e390819cd 100644
--- a/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py
+++ b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py
@@ -6,6 +6,8 @@ import frappe
def execute():
- frappe.reload_doc("stock", "doctype", "pick_list")
- frappe.db.sql("""UPDATE `tabPick List` set purpose = 'Delivery'
- WHERE docstatus = 1 and purpose = 'Delivery against Sales Order' """)
+ frappe.reload_doc("stock", "doctype", "pick_list")
+ frappe.db.sql(
+ """UPDATE `tabPick List` set purpose = 'Delivery'
+ WHERE docstatus = 1 and purpose = 'Delivery against Sales Order' """
+ )
diff --git a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py
index 154d7ba176..94322cd197 100644
--- a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py
+++ b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py
@@ -3,6 +3,8 @@ import frappe
def execute():
frappe.reload_doc("buying", "doctype", "supplier_quotation")
- frappe.db.sql("""UPDATE `tabSupplier Quotation`
+ frappe.db.sql(
+ """UPDATE `tabSupplier Quotation`
SET valid_till = DATE_ADD(transaction_date , INTERVAL 1 MONTH)
- WHERE docstatus < 2""")
+ WHERE docstatus < 2"""
+ )
diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py
index c17666add1..72fa663089 100644
--- a/erpnext/patches/v12_0/setup_einvoice_fields.py
+++ b/erpnext/patches/v12_0/setup_einvoice_fields.py
@@ -7,53 +7,128 @@ from erpnext.regional.india.setup import add_permissions, add_print_formats
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
frappe.reload_doc("custom", "doctype", "custom_field")
frappe.reload_doc("regional", "doctype", "e_invoice_settings")
custom_fields = {
- 'Sales Invoice': [
- dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
- depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
-
- dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
-
- dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
-
- dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
- depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
-
- dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
- depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
-
- dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
-
- dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
-
- dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+ "Sales Invoice": [
+ dict(
+ fieldname="irn",
+ label="IRN",
+ fieldtype="Data",
+ read_only=1,
+ insert_after="customer",
+ no_copy=1,
+ print_hide=1,
+ depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0',
+ ),
+ dict(
+ fieldname="ack_no",
+ label="Ack. No.",
+ fieldtype="Data",
+ read_only=1,
+ hidden=1,
+ insert_after="irn",
+ no_copy=1,
+ print_hide=1,
+ ),
+ dict(
+ fieldname="ack_date",
+ label="Ack. Date",
+ fieldtype="Data",
+ read_only=1,
+ hidden=1,
+ insert_after="ack_no",
+ no_copy=1,
+ print_hide=1,
+ ),
+ dict(
+ fieldname="irn_cancelled",
+ label="IRN Cancelled",
+ fieldtype="Check",
+ no_copy=1,
+ print_hide=1,
+ depends_on="eval:(doc.irn_cancelled === 1)",
+ read_only=1,
+ allow_on_submit=1,
+ insert_after="customer",
+ ),
+ dict(
+ fieldname="eway_bill_cancelled",
+ label="E-Way Bill Cancelled",
+ fieldtype="Check",
+ no_copy=1,
+ print_hide=1,
+ depends_on="eval:(doc.eway_bill_cancelled === 1)",
+ read_only=1,
+ allow_on_submit=1,
+ insert_after="customer",
+ ),
+ dict(
+ fieldname="signed_einvoice",
+ fieldtype="Code",
+ options="JSON",
+ hidden=1,
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="signed_qr_code",
+ fieldtype="Code",
+ options="JSON",
+ hidden=1,
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="qrcode_image",
+ label="QRCode",
+ fieldtype="Attach Image",
+ hidden=1,
+ no_copy=1,
+ print_hide=1,
+ read_only=1,
+ ),
]
}
create_custom_fields(custom_fields, update=True)
add_permissions()
add_print_formats()
- einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
+ einvoice_cond = (
+ 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
+ )
t = {
- 'mode_of_transport': [{'default': None}],
- 'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}],
- 'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
- 'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
- 'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
- 'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
- 'ewaybill': [
- {'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'},
- {'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
- ]
+ "mode_of_transport": [{"default": None}],
+ "distance": [{"mandatory_depends_on": f"eval:{einvoice_cond} && doc.transporter"}],
+ "gst_vehicle_type": [
+ {"mandatory_depends_on": f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}
+ ],
+ "lr_date": [
+ {
+ "mandatory_depends_on": f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'
+ }
+ ],
+ "lr_no": [
+ {
+ "mandatory_depends_on": f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'
+ }
+ ],
+ "vehicle_no": [
+ {"mandatory_depends_on": f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}
+ ],
+ "ewaybill": [
+ {"read_only_depends_on": "eval:doc.irn && doc.ewaybill"},
+ {"depends_on": "eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)"},
+ ],
}
for field, conditions in t.items():
for c in conditions:
[(prop, value)] = c.items()
- frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)
+ frappe.db.set_value("Custom Field", {"fieldname": field}, prop, value)
diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py
index 3f90a03020..459aeea1ec 100644
--- a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py
+++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py
@@ -4,11 +4,13 @@ import frappe
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'})
+ irn_cancelled_field = frappe.db.exists(
+ "Custom Field", {"dt": "Sales Invoice", "fieldname": "irn_cancelled"}
+ )
if irn_cancelled_field:
- frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn')
- frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0)
+ frappe.db.set_value("Custom Field", irn_cancelled_field, "depends_on", "eval: doc.irn")
+ frappe.db.set_value("Custom Field", irn_cancelled_field, "read_only", 0)
diff --git a/erpnext/patches/v12_0/stock_entry_enhancements.py b/erpnext/patches/v12_0/stock_entry_enhancements.py
index 94d8ff9cde..db099a304c 100644
--- a/erpnext/patches/v12_0/stock_entry_enhancements.py
+++ b/erpnext/patches/v12_0/stock_entry_enhancements.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
@@ -10,44 +9,61 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
create_stock_entry_types()
- company = frappe.db.get_value("Company", {'country': 'India'}, 'name')
+ company = frappe.db.get_value("Company", {"country": "India"}, "name")
if company:
add_gst_hsn_code_field()
+
def create_stock_entry_types():
- frappe.reload_doc('stock', 'doctype', 'stock_entry_type')
- frappe.reload_doc('stock', 'doctype', 'stock_entry')
+ frappe.reload_doc("stock", "doctype", "stock_entry_type")
+ frappe.reload_doc("stock", "doctype", "stock_entry")
- for purpose in ["Material Issue", "Material Receipt", "Material Transfer",
- "Material Transfer for Manufacture", "Material Consumption for Manufacture", "Manufacture",
- "Repack", "Send to Subcontractor"]:
+ for purpose in [
+ "Material Issue",
+ "Material Receipt",
+ "Material Transfer",
+ "Material Transfer for Manufacture",
+ "Material Consumption for Manufacture",
+ "Manufacture",
+ "Repack",
+ "Send to Subcontractor",
+ ]:
- ste_type = frappe.get_doc({
- 'doctype': 'Stock Entry Type',
- 'name': purpose,
- 'purpose': purpose
- })
+ ste_type = frappe.get_doc({"doctype": "Stock Entry Type", "name": purpose, "purpose": purpose})
try:
ste_type.insert()
except frappe.DuplicateEntryError:
pass
- frappe.db.sql(" UPDATE `tabStock Entry` set purpose = 'Send to Subcontractor' where purpose = 'Subcontract'")
+ frappe.db.sql(
+ " UPDATE `tabStock Entry` set purpose = 'Send to Subcontractor' where purpose = 'Subcontract'"
+ )
frappe.db.sql(" UPDATE `tabStock Entry` set stock_entry_type = purpose ")
+
def add_gst_hsn_code_field():
custom_fields = {
- 'Stock Entry Detail': [dict(fieldname='gst_hsn_code', label='HSN/SAC',
- fieldtype='Data', fetch_from='item_code.gst_hsn_code',
- insert_after='description', allow_on_submit=1, print_hide=0)]
+ "Stock Entry Detail": [
+ dict(
+ fieldname="gst_hsn_code",
+ label="HSN/SAC",
+ fieldtype="Data",
+ fetch_from="item_code.gst_hsn_code",
+ insert_after="description",
+ allow_on_submit=1,
+ print_hide=0,
+ )
+ ]
}
- create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=True)
+ create_custom_fields(custom_fields, ignore_validate=frappe.flags.in_patch, update=True)
- frappe.db.sql(""" update `tabStock Entry Detail`, `tabItem`
+ frappe.db.sql(
+ """ update `tabStock Entry Detail`, `tabItem`
SET
`tabStock Entry Detail`.gst_hsn_code = `tabItem`.gst_hsn_code
Where
`tabItem`.name = `tabStock Entry Detail`.item_code and `tabItem`.gst_hsn_code is not null
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/unhide_cost_center_field.py b/erpnext/patches/v12_0/unhide_cost_center_field.py
index 7245021287..5f91ef6260 100644
--- a/erpnext/patches/v12_0/unhide_cost_center_field.py
+++ b/erpnext/patches/v12_0/unhide_cost_center_field.py
@@ -6,9 +6,11 @@ import frappe
def execute():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
DELETE FROM `tabProperty Setter`
WHERE doc_type in ('Sales Invoice', 'Purchase Invoice', 'Payment Entry')
AND field_name = 'cost_center'
AND property = 'hidden'
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
index 5b5f623b48..332609b846 100644
--- a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
+++ b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
@@ -2,27 +2,29 @@ import frappe
def execute():
- """
- set proper customer and supplier details for item price
- based on selling and buying values
- """
+ """
+ set proper customer and supplier details for item price
+ based on selling and buying values
+ """
- # update for selling
- frappe.db.sql(
- """UPDATE `tabItem Price` ip, `tabPrice List` pl
+ # update for selling
+ frappe.db.sql(
+ """UPDATE `tabItem Price` ip, `tabPrice List` pl
SET ip.`reference` = ip.`customer`, ip.`supplier` = NULL
WHERE ip.`selling` = 1
AND ip.`buying` = 0
AND (ip.`supplier` IS NOT NULL OR ip.`supplier` = '')
AND ip.`price_list` = pl.`name`
- AND pl.`enabled` = 1""")
+ AND pl.`enabled` = 1"""
+ )
- # update for buying
- frappe.db.sql(
- """UPDATE `tabItem Price` ip, `tabPrice List` pl
+ # update for buying
+ frappe.db.sql(
+ """UPDATE `tabItem Price` ip, `tabPrice List` pl
SET ip.`reference` = ip.`supplier`, ip.`customer` = NULL
WHERE ip.`selling` = 0
AND ip.`buying` = 1
AND (ip.`customer` IS NOT NULL OR ip.`customer` = '')
AND ip.`price_list` = pl.`name`
- AND pl.`enabled` = 1""")
+ AND pl.`enabled` = 1"""
+ )
diff --git a/erpnext/patches/v12_0/update_address_template_for_india.py b/erpnext/patches/v12_0/update_address_template_for_india.py
index 64a2e41587..27b1bb6dc1 100644
--- a/erpnext/patches/v12_0/update_address_template_for_india.py
+++ b/erpnext/patches/v12_0/update_address_template_for_india.py
@@ -8,7 +8,7 @@ from erpnext.regional.address_template.setup import set_up_address_templates
def execute():
- if frappe.db.get_value('Company', {'country': 'India'}, 'name'):
- address_template = frappe.db.get_value('Address Template', 'India', 'template')
+ if frappe.db.get_value("Company", {"country": "India"}, "name"):
+ address_template = frappe.db.get_value("Address Template", "India", "template")
if not address_template or "gstin" not in address_template:
- set_up_address_templates(default_country='India')
+ set_up_address_templates(default_country="India")
diff --git a/erpnext/patches/v12_0/update_bom_in_so_mr.py b/erpnext/patches/v12_0/update_bom_in_so_mr.py
index 132f3bd3b1..114f65d100 100644
--- a/erpnext/patches/v12_0/update_bom_in_so_mr.py
+++ b/erpnext/patches/v12_0/update_bom_in_so_mr.py
@@ -10,11 +10,15 @@ def execute():
if doctype == "Material Request":
condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
- frappe.db.sql(""" UPDATE `tab{doc}` as doc, `tab{doc} Item` as child_doc, tabItem as item
+ frappe.db.sql(
+ """ UPDATE `tab{doc}` as doc, `tab{doc} Item` as child_doc, tabItem as item
SET
child_doc.bom_no = item.default_bom
WHERE
child_doc.item_code = item.name and child_doc.docstatus < 2
and child_doc.parent = doc.name
and item.default_bom is not null and item.default_bom != '' {cond}
- """.format(doc = doctype, cond = condition))
+ """.format(
+ doc=doctype, cond=condition
+ )
+ )
diff --git a/erpnext/patches/v12_0/update_due_date_in_gle.py b/erpnext/patches/v12_0/update_due_date_in_gle.py
index e4418b0103..a1c4f51ad0 100644
--- a/erpnext/patches/v12_0/update_due_date_in_gle.py
+++ b/erpnext/patches/v12_0/update_due_date_in_gle.py
@@ -2,16 +2,19 @@ import frappe
def execute():
- frappe.reload_doc("accounts", "doctype", "gl_entry")
+ frappe.reload_doc("accounts", "doctype", "gl_entry")
- for doctype in ["Sales Invoice", "Purchase Invoice", "Journal Entry"]:
- frappe.reload_doc("accounts", "doctype", frappe.scrub(doctype))
+ for doctype in ["Sales Invoice", "Purchase Invoice", "Journal Entry"]:
+ frappe.reload_doc("accounts", "doctype", frappe.scrub(doctype))
- frappe.db.sql(""" UPDATE `tabGL Entry`, `tab{doctype}`
+ frappe.db.sql(
+ """ UPDATE `tabGL Entry`, `tab{doctype}`
SET
`tabGL Entry`.due_date = `tab{doctype}`.due_date
WHERE
`tabGL Entry`.voucher_no = `tab{doctype}`.name and `tabGL Entry`.party is not null
and `tabGL Entry`.voucher_type in ('Sales Invoice', 'Purchase Invoice', 'Journal Entry')
- and `tabGL Entry`.account in (select name from `tabAccount` where account_type in ('Receivable', 'Payable'))""" #nosec
- .format(doctype=doctype))
+ and `tabGL Entry`.account in (select name from `tabAccount` where account_type in ('Receivable', 'Payable'))""".format( # nosec
+ doctype=doctype
+ )
+ )
diff --git a/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py b/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py
index ef4204fc9c..570b77b88e 100644
--- a/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py
+++ b/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py
@@ -3,22 +3,22 @@ from frappe.utils import add_days, getdate, today
def execute():
- if frappe.db.exists('DocType', 'Email Campaign'):
- email_campaign = frappe.get_all('Email Campaign')
- for campaign in email_campaign:
- doc = frappe.get_doc("Email Campaign",campaign["name"])
- send_after_days = []
+ if frappe.db.exists("DocType", "Email Campaign"):
+ email_campaign = frappe.get_all("Email Campaign")
+ for campaign in email_campaign:
+ doc = frappe.get_doc("Email Campaign", campaign["name"])
+ send_after_days = []
- camp = frappe.get_doc("Campaign", doc.campaign_name)
- for entry in camp.get("campaign_schedules"):
- send_after_days.append(entry.send_after_days)
- if send_after_days:
- end_date = add_days(getdate(doc.start_date), max(send_after_days))
- doc.db_set("end_date", end_date)
- today_date = getdate(today())
- if doc.start_date > today_date:
- doc.db_set("status", "Scheduled")
- elif end_date >= today_date:
- doc.db_set("status", "In Progress")
- elif end_date < today_date:
- doc.db_set("status", "Completed")
+ camp = frappe.get_doc("Campaign", doc.campaign_name)
+ for entry in camp.get("campaign_schedules"):
+ send_after_days.append(entry.send_after_days)
+ if send_after_days:
+ end_date = add_days(getdate(doc.start_date), max(send_after_days))
+ doc.db_set("end_date", end_date)
+ today_date = getdate(today())
+ if doc.start_date > today_date:
+ doc.db_set("status", "Scheduled")
+ elif end_date >= today_date:
+ doc.db_set("status", "In Progress")
+ elif end_date < today_date:
+ doc.db_set("status", "Completed")
diff --git a/erpnext/patches/v12_0/update_ewaybill_field_position.py b/erpnext/patches/v12_0/update_ewaybill_field_position.py
index 132fd900bd..24a834b05c 100644
--- a/erpnext/patches/v12_0/update_ewaybill_field_position.py
+++ b/erpnext/patches/v12_0/update_ewaybill_field_position.py
@@ -2,7 +2,7 @@ import frappe
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
@@ -14,14 +14,16 @@ def execute():
ewaybill_field.flags.ignore_validate = True
- ewaybill_field.update({
- 'fieldname': 'ewaybill',
- 'label': 'e-Way Bill No.',
- 'fieldtype': 'Data',
- 'depends_on': 'eval:(doc.docstatus === 1)',
- 'allow_on_submit': 1,
- 'insert_after': 'tax_id',
- 'translatable': 0
- })
+ ewaybill_field.update(
+ {
+ "fieldname": "ewaybill",
+ "label": "e-Way Bill No.",
+ "fieldtype": "Data",
+ "depends_on": "eval:(doc.docstatus === 1)",
+ "allow_on_submit": 1,
+ "insert_after": "tax_id",
+ "translatable": 0,
+ }
+ )
ewaybill_field.save()
diff --git a/erpnext/patches/v12_0/update_gst_category.py b/erpnext/patches/v12_0/update_gst_category.py
index 8b15370b09..16168f022c 100644
--- a/erpnext/patches/v12_0/update_gst_category.py
+++ b/erpnext/patches/v12_0/update_gst_category.py
@@ -3,17 +3,21 @@ import frappe
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
- if not company:
- return
+ company = frappe.get_all("Company", filters={"country": "India"})
+ if not company:
+ return
- frappe.db.sql(""" UPDATE `tabSales Invoice` set gst_category = 'Unregistered'
+ frappe.db.sql(
+ """ UPDATE `tabSales Invoice` set gst_category = 'Unregistered'
where gst_category = 'Registered Regular'
and ifnull(customer_gstin, '')=''
and ifnull(billing_address_gstin,'')=''
- """)
+ """
+ )
- frappe.db.sql(""" UPDATE `tabPurchase Invoice` set gst_category = 'Unregistered'
+ frappe.db.sql(
+ """ UPDATE `tabPurchase Invoice` set gst_category = 'Unregistered'
where gst_category = 'Registered Regular'
and ifnull(supplier_gstin, '')=''
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/update_healthcare_refactored_changes.py b/erpnext/patches/v12_0/update_healthcare_refactored_changes.py
index f553ee9d85..5ca0d5d47d 100644
--- a/erpnext/patches/v12_0/update_healthcare_refactored_changes.py
+++ b/erpnext/patches/v12_0/update_healthcare_refactored_changes.py
@@ -3,76 +3,79 @@ from frappe.model.utils.rename_field import rename_field
from frappe.modules import get_doctype_module, scrub
field_rename_map = {
- 'Healthcare Settings': [
- ['patient_master_name', 'patient_name_by'],
- ['max_visit', 'max_visits'],
- ['reg_sms', 'send_registration_msg'],
- ['reg_msg', 'registration_msg'],
- ['app_con', 'send_appointment_confirmation'],
- ['app_con_msg', 'appointment_confirmation_msg'],
- ['no_con', 'avoid_confirmation'],
- ['app_rem', 'send_appointment_reminder'],
- ['app_rem_msg', 'appointment_reminder_msg'],
- ['rem_before', 'remind_before'],
- ['manage_customer', 'link_customer_to_patient'],
- ['create_test_on_si_submit', 'create_lab_test_on_si_submit'],
- ['require_sample_collection', 'create_sample_collection_for_lab_test'],
- ['require_test_result_approval', 'lab_test_approval_required'],
- ['manage_appointment_invoice_automatically', 'automate_appointment_invoicing']
+ "Healthcare Settings": [
+ ["patient_master_name", "patient_name_by"],
+ ["max_visit", "max_visits"],
+ ["reg_sms", "send_registration_msg"],
+ ["reg_msg", "registration_msg"],
+ ["app_con", "send_appointment_confirmation"],
+ ["app_con_msg", "appointment_confirmation_msg"],
+ ["no_con", "avoid_confirmation"],
+ ["app_rem", "send_appointment_reminder"],
+ ["app_rem_msg", "appointment_reminder_msg"],
+ ["rem_before", "remind_before"],
+ ["manage_customer", "link_customer_to_patient"],
+ ["create_test_on_si_submit", "create_lab_test_on_si_submit"],
+ ["require_sample_collection", "create_sample_collection_for_lab_test"],
+ ["require_test_result_approval", "lab_test_approval_required"],
+ ["manage_appointment_invoice_automatically", "automate_appointment_invoicing"],
],
- 'Drug Prescription':[
- ['use_interval', 'usage_interval'],
- ['in_every', 'interval_uom']
+ "Drug Prescription": [["use_interval", "usage_interval"], ["in_every", "interval_uom"]],
+ "Lab Test Template": [
+ ["sample_quantity", "sample_qty"],
+ ["sample_collection_details", "sample_details"],
],
- 'Lab Test Template':[
- ['sample_quantity', 'sample_qty'],
- ['sample_collection_details', 'sample_details']
+ "Sample Collection": [
+ ["sample_quantity", "sample_qty"],
+ ["sample_collection_details", "sample_details"],
],
- 'Sample Collection':[
- ['sample_quantity', 'sample_qty'],
- ['sample_collection_details', 'sample_details']
- ],
- 'Fee Validity': [
- ['max_visit', 'max_visits']
- ]
+ "Fee Validity": [["max_visit", "max_visits"]],
}
+
def execute():
for dn in field_rename_map:
- if frappe.db.exists('DocType', dn):
- if dn == 'Healthcare Settings':
- frappe.reload_doctype('Healthcare Settings')
+ if frappe.db.exists("DocType", dn):
+ if dn == "Healthcare Settings":
+ frappe.reload_doctype("Healthcare Settings")
else:
frappe.reload_doc(get_doctype_module(dn), "doctype", scrub(dn))
for dt, field_list in field_rename_map.items():
- if frappe.db.exists('DocType', dt):
+ if frappe.db.exists("DocType", dt):
for field in field_list:
- if dt == 'Healthcare Settings':
+ if dt == "Healthcare Settings":
rename_field(dt, field[0], field[1])
elif frappe.db.has_column(dt, field[0]):
rename_field(dt, field[0], field[1])
# first name mandatory in Patient
- if frappe.db.exists('DocType', 'Patient'):
+ if frappe.db.exists("DocType", "Patient"):
patients = frappe.db.sql("select name, patient_name from `tabPatient`", as_dict=1)
- frappe.reload_doc('healthcare', 'doctype', 'patient')
+ frappe.reload_doc("healthcare", "doctype", "patient")
for entry in patients:
- name = entry.patient_name.split(' ')
- frappe.db.set_value('Patient', entry.name, 'first_name', name[0])
+ name = entry.patient_name.split(" ")
+ frappe.db.set_value("Patient", entry.name, "first_name", name[0])
# mark Healthcare Practitioner status as Disabled
- if frappe.db.exists('DocType', 'Healthcare Practitioner'):
- practitioners = frappe.db.sql("select name from `tabHealthcare Practitioner` where 'active'= 0", as_dict=1)
+ if frappe.db.exists("DocType", "Healthcare Practitioner"):
+ practitioners = frappe.db.sql(
+ "select name from `tabHealthcare Practitioner` where 'active'= 0", as_dict=1
+ )
practitioners_lst = [p.name for p in practitioners]
- frappe.reload_doc('healthcare', 'doctype', 'healthcare_practitioner')
+ frappe.reload_doc("healthcare", "doctype", "healthcare_practitioner")
if practitioners_lst:
- frappe.db.sql("update `tabHealthcare Practitioner` set status = 'Disabled' where name IN %(practitioners)s""", {"practitioners": practitioners_lst})
+ frappe.db.sql(
+ "update `tabHealthcare Practitioner` set status = 'Disabled' where name IN %(practitioners)s"
+ "",
+ {"practitioners": practitioners_lst},
+ )
# set Clinical Procedure status
- if frappe.db.exists('DocType', 'Clinical Procedure'):
- frappe.reload_doc('healthcare', 'doctype', 'clinical_procedure')
- frappe.db.sql("""
+ if frappe.db.exists("DocType", "Clinical Procedure"):
+ frappe.reload_doc("healthcare", "doctype", "clinical_procedure")
+ frappe.db.sql(
+ """
UPDATE
`tabClinical Procedure`
SET
@@ -80,57 +83,49 @@ def execute():
WHEN status = 'Draft' THEN 0
ELSE 1
END)
- """)
+ """
+ )
# set complaints and diagnosis in table multiselect in Patient Encounter
- if frappe.db.exists('DocType', 'Patient Encounter'):
- field_list = [
- ['visit_department', 'medical_department'],
- ['type', 'appointment_type']
- ]
- encounter_details = frappe.db.sql("""select symptoms, diagnosis, name from `tabPatient Encounter`""", as_dict=True)
- frappe.reload_doc('healthcare', 'doctype', 'patient_encounter')
- frappe.reload_doc('healthcare', 'doctype', 'patient_encounter_symptom')
- frappe.reload_doc('healthcare', 'doctype', 'patient_encounter_diagnosis')
+ if frappe.db.exists("DocType", "Patient Encounter"):
+ field_list = [["visit_department", "medical_department"], ["type", "appointment_type"]]
+ encounter_details = frappe.db.sql(
+ """select symptoms, diagnosis, name from `tabPatient Encounter`""", as_dict=True
+ )
+ frappe.reload_doc("healthcare", "doctype", "patient_encounter")
+ frappe.reload_doc("healthcare", "doctype", "patient_encounter_symptom")
+ frappe.reload_doc("healthcare", "doctype", "patient_encounter_diagnosis")
for field in field_list:
if frappe.db.has_column(dt, field[0]):
rename_field(dt, field[0], field[1])
for entry in encounter_details:
- doc = frappe.get_doc('Patient Encounter', entry.name)
- symptoms = entry.symptoms.split('\n') if entry.symptoms else []
+ doc = frappe.get_doc("Patient Encounter", entry.name)
+ symptoms = entry.symptoms.split("\n") if entry.symptoms else []
for symptom in symptoms:
- if not frappe.db.exists('Complaint', symptom):
- frappe.get_doc({
- 'doctype': 'Complaint',
- 'complaints': symptom
- }).insert()
- row = doc.append('symptoms', {
- 'complaint': symptom
- })
+ if not frappe.db.exists("Complaint", symptom):
+ frappe.get_doc({"doctype": "Complaint", "complaints": symptom}).insert()
+ row = doc.append("symptoms", {"complaint": symptom})
row.db_update()
- diagnosis = entry.diagnosis.split('\n') if entry.diagnosis else []
+ diagnosis = entry.diagnosis.split("\n") if entry.diagnosis else []
for d in diagnosis:
- if not frappe.db.exists('Diagnosis', d):
- frappe.get_doc({
- 'doctype': 'Diagnosis',
- 'diagnosis': d
- }).insert()
- row = doc.append('diagnosis', {
- 'diagnosis': d
- })
+ if not frappe.db.exists("Diagnosis", d):
+ frappe.get_doc({"doctype": "Diagnosis", "diagnosis": d}).insert()
+ row = doc.append("diagnosis", {"diagnosis": d})
row.db_update()
doc.db_update()
- if frappe.db.exists('DocType', 'Fee Validity'):
+ if frappe.db.exists("DocType", "Fee Validity"):
# update fee validity status
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabFee Validity`
SET
status = (CASE WHEN visited >= max_visits THEN 'Completed'
ELSE 'Pending'
END)
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/update_is_cancelled_field.py b/erpnext/patches/v12_0/update_is_cancelled_field.py
index 0401034874..b567823b06 100644
--- a/erpnext/patches/v12_0/update_is_cancelled_field.py
+++ b/erpnext/patches/v12_0/update_is_cancelled_field.py
@@ -2,28 +2,35 @@ import frappe
def execute():
- #handle type casting for is_cancelled field
+ # handle type casting for is_cancelled field
module_doctypes = (
- ('stock', 'Stock Ledger Entry'),
- ('stock', 'Serial No'),
- ('accounts', 'GL Entry')
+ ("stock", "Stock Ledger Entry"),
+ ("stock", "Serial No"),
+ ("accounts", "GL Entry"),
)
for module, doctype in module_doctypes:
- if (not frappe.db.has_column(doctype, "is_cancelled")
+ if (
+ not frappe.db.has_column(doctype, "is_cancelled")
or frappe.db.get_column_type(doctype, "is_cancelled").lower() == "int(1)"
):
continue
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tab{doctype}`
SET is_cancelled = 0
- where is_cancelled in ('', NULL, 'No')"""
- .format(doctype=doctype))
- frappe.db.sql("""
+ where is_cancelled in ('', NULL, 'No')""".format(
+ doctype=doctype
+ )
+ )
+ frappe.db.sql(
+ """
UPDATE `tab{doctype}`
SET is_cancelled = 1
- where is_cancelled = 'Yes'"""
- .format(doctype=doctype))
+ where is_cancelled = 'Yes'""".format(
+ doctype=doctype
+ )
+ )
frappe.reload_doc(module, "doctype", frappe.scrub(doctype))
diff --git a/erpnext/patches/v12_0/update_item_tax_template_company.py b/erpnext/patches/v12_0/update_item_tax_template_company.py
index abd4f6fb18..489f70d449 100644
--- a/erpnext/patches/v12_0/update_item_tax_template_company.py
+++ b/erpnext/patches/v12_0/update_item_tax_template_company.py
@@ -2,12 +2,12 @@ import frappe
def execute():
- frappe.reload_doc('accounts', 'doctype', 'item_tax_template')
+ frappe.reload_doc("accounts", "doctype", "item_tax_template")
- item_tax_template_list = frappe.get_list('Item Tax Template')
- for template in item_tax_template_list:
- doc = frappe.get_doc('Item Tax Template', template.name)
- for tax in doc.taxes:
- doc.company = frappe.get_value('Account', tax.tax_type, 'company')
- break
- doc.save()
+ item_tax_template_list = frappe.get_list("Item Tax Template")
+ for template in item_tax_template_list:
+ doc = frappe.get_doc("Item Tax Template", template.name)
+ for tax in doc.taxes:
+ doc.company = frappe.get_value("Account", tax.tax_type, "company")
+ break
+ doc.save()
diff --git a/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py
index e5f24d4b88..7dc0af9a1a 100644
--- a/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py
+++ b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py
@@ -6,15 +6,21 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
def execute():
- accounting_dimensions = frappe.db.sql("""select fieldname from
- `tabAccounting Dimension`""", as_dict=1)
+ accounting_dimensions = frappe.db.sql(
+ """select fieldname from
+ `tabAccounting Dimension`""",
+ as_dict=1,
+ )
doclist = get_doctypes_with_dimensions()
for dimension in accounting_dimensions:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabCustom Field`
SET owner = 'Administrator'
WHERE fieldname = %s
- AND dt IN (%s)""" % #nosec
- ('%s', ', '.join(['%s']* len(doclist))), tuple([dimension.fieldname] + doclist))
+ AND dt IN (%s)"""
+ % ("%s", ", ".join(["%s"] * len(doclist))), # nosec
+ tuple([dimension.fieldname] + doclist),
+ )
diff --git a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py
index ea3e002e5d..5710320e62 100644
--- a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py
+++ b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py
@@ -8,16 +8,19 @@ def execute():
frappe.reload_doc("manufacturing", "doctype", "bom")
frappe.reload_doc("manufacturing", "doctype", "bom_item")
- frappe.db.sql(""" UPDATE `tabBOM`, `tabPrice List`
+ frappe.db.sql(
+ """ UPDATE `tabBOM`, `tabPrice List`
SET
`tabBOM`.price_list_currency = `tabPrice List`.currency,
`tabBOM`.plc_conversion_rate = 1.0
WHERE
`tabBOM`.buying_price_list = `tabPrice List`.name AND `tabBOM`.docstatus < 2
AND `tabBOM`.rm_cost_as_per = 'Price List'
- """)
+ """
+ )
- for d in frappe.db.sql("""
+ for d in frappe.db.sql(
+ """
SELECT
bom.creation, bom.name, bom.price_list_currency as currency,
company.default_currency as company_currency
@@ -25,8 +28,11 @@ def execute():
`tabBOM` as bom, `tabCompany` as company
WHERE
bom.company = company.name AND bom.rm_cost_as_per = 'Price List' AND
- bom.price_list_currency != company.default_currency AND bom.docstatus < 2""", as_dict=1):
- plc_conversion_rate = get_exchange_rate(d.currency,
- d.company_currency, getdate(d.creation), "for_buying")
+ bom.price_list_currency != company.default_currency AND bom.docstatus < 2""",
+ as_dict=1,
+ ):
+ plc_conversion_rate = get_exchange_rate(
+ d.currency, d.company_currency, getdate(d.creation), "for_buying"
+ )
- frappe.db.set_value("BOM", d.name, "plc_conversion_rate", plc_conversion_rate)
+ frappe.db.set_value("BOM", d.name, "plc_conversion_rate", plc_conversion_rate)
diff --git a/erpnext/patches/v12_0/update_price_or_product_discount.py b/erpnext/patches/v12_0/update_price_or_product_discount.py
index 53c0ba525b..64344c8cd4 100644
--- a/erpnext/patches/v12_0/update_price_or_product_discount.py
+++ b/erpnext/patches/v12_0/update_price_or_product_discount.py
@@ -4,5 +4,7 @@ import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "pricing_rule")
- frappe.db.sql(""" UPDATE `tabPricing Rule` SET price_or_product_discount = 'Price'
- WHERE ifnull(price_or_product_discount,'') = '' """)
+ frappe.db.sql(
+ """ UPDATE `tabPricing Rule` SET price_or_product_discount = 'Price'
+ WHERE ifnull(price_or_product_discount,'') = '' """
+ )
diff --git a/erpnext/patches/v12_0/update_pricing_rule_fields.py b/erpnext/patches/v12_0/update_pricing_rule_fields.py
index b7c36ae778..8da06b0bda 100644
--- a/erpnext/patches/v12_0/update_pricing_rule_fields.py
+++ b/erpnext/patches/v12_0/update_pricing_rule_fields.py
@@ -4,68 +4,120 @@
import frappe
-parentfield = {
- 'item_code': 'items',
- 'item_group': 'item_groups',
- 'brand': 'brands'
-}
+parentfield = {"item_code": "items", "item_group": "item_groups", "brand": "brands"}
+
def execute():
- if not frappe.get_all('Pricing Rule', limit=1):
+ if not frappe.get_all("Pricing Rule", limit=1):
return
- frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail')
- doctypes = {'Supplier Quotation': 'buying', 'Purchase Order': 'buying', 'Purchase Invoice': 'accounts',
- 'Purchase Receipt': 'stock', 'Quotation': 'selling', 'Sales Order': 'selling',
- 'Sales Invoice': 'accounts', 'Delivery Note': 'stock'}
+ frappe.reload_doc("accounts", "doctype", "pricing_rule_detail")
+ doctypes = {
+ "Supplier Quotation": "buying",
+ "Purchase Order": "buying",
+ "Purchase Invoice": "accounts",
+ "Purchase Receipt": "stock",
+ "Quotation": "selling",
+ "Sales Order": "selling",
+ "Sales Invoice": "accounts",
+ "Delivery Note": "stock",
+ }
for doctype, module in doctypes.items():
- frappe.reload_doc(module, 'doctype', frappe.scrub(doctype))
+ frappe.reload_doc(module, "doctype", frappe.scrub(doctype))
- child_doc = frappe.scrub(doctype) + '_item'
- frappe.reload_doc(module, 'doctype', child_doc, force=True)
+ child_doc = frappe.scrub(doctype) + "_item"
+ frappe.reload_doc(module, "doctype", child_doc, force=True)
- child_doctype = doctype + ' Item'
+ child_doctype = doctype + " Item"
- frappe.db.sql(""" UPDATE `tab{child_doctype}` SET pricing_rules = pricing_rule
+ frappe.db.sql(
+ """ UPDATE `tab{child_doctype}` SET pricing_rules = pricing_rule
WHERE docstatus < 2 and pricing_rule is not null and pricing_rule != ''
- """.format(child_doctype= child_doctype))
+ """.format(
+ child_doctype=child_doctype
+ )
+ )
- data = frappe.db.sql(""" SELECT pricing_rule, name, parent,
+ data = frappe.db.sql(
+ """ SELECT pricing_rule, name, parent,
parenttype, creation, modified, docstatus, modified_by, owner, name
FROM `tab{child_doc}` where docstatus < 2 and pricing_rule is not null
- and pricing_rule != ''""".format(child_doc=child_doctype), as_dict=1)
+ and pricing_rule != ''""".format(
+ child_doc=child_doctype
+ ),
+ as_dict=1,
+ )
values = []
for d in data:
- values.append((d.pricing_rule, d.name, d.parent, 'pricing_rules', d.parenttype,
- d.creation, d.modified, d.docstatus, d.modified_by, d.owner, frappe.generate_hash("", 10)))
+ values.append(
+ (
+ d.pricing_rule,
+ d.name,
+ d.parent,
+ "pricing_rules",
+ d.parenttype,
+ d.creation,
+ d.modified,
+ d.docstatus,
+ d.modified_by,
+ d.owner,
+ frappe.generate_hash("", 10),
+ )
+ )
if values:
- frappe.db.sql(""" INSERT INTO
+ frappe.db.sql(
+ """ INSERT INTO
`tabPricing Rule Detail` (`pricing_rule`, `child_docname`, `parent`, `parentfield`, `parenttype`,
`creation`, `modified`, `docstatus`, `modified_by`, `owner`, `name`)
- VALUES {values} """.format(values=', '.join(['%s'] * len(values))), tuple(values))
+ VALUES {values} """.format(
+ values=", ".join(["%s"] * len(values))
+ ),
+ tuple(values),
+ )
- frappe.reload_doc('accounts', 'doctype', 'pricing_rule')
+ frappe.reload_doc("accounts", "doctype", "pricing_rule")
- for doctype, apply_on in {'Pricing Rule Item Code': 'Item Code',
- 'Pricing Rule Item Group': 'Item Group', 'Pricing Rule Brand': 'Brand'}.items():
- frappe.reload_doc('accounts', 'doctype', frappe.scrub(doctype))
+ for doctype, apply_on in {
+ "Pricing Rule Item Code": "Item Code",
+ "Pricing Rule Item Group": "Item Group",
+ "Pricing Rule Brand": "Brand",
+ }.items():
+ frappe.reload_doc("accounts", "doctype", frappe.scrub(doctype))
field = frappe.scrub(apply_on)
- data = frappe.get_all('Pricing Rule', fields=[field, "name", "creation", "modified",
- "owner", "modified_by"], filters= {'apply_on': apply_on})
+ data = frappe.get_all(
+ "Pricing Rule",
+ fields=[field, "name", "creation", "modified", "owner", "modified_by"],
+ filters={"apply_on": apply_on},
+ )
values = []
for d in data:
- values.append((d.get(field), d.name, parentfield.get(field), 'Pricing Rule',
- d.creation, d.modified, d.owner, d.modified_by, frappe.generate_hash("", 10)))
+ values.append(
+ (
+ d.get(field),
+ d.name,
+ parentfield.get(field),
+ "Pricing Rule",
+ d.creation,
+ d.modified,
+ d.owner,
+ d.modified_by,
+ frappe.generate_hash("", 10),
+ )
+ )
if values:
- frappe.db.sql(""" INSERT INTO
+ frappe.db.sql(
+ """ INSERT INTO
`tab{doctype}` ({field}, parent, parentfield, parenttype, creation, modified,
owner, modified_by, name)
- VALUES {values} """.format(doctype=doctype,
- field=field, values=', '.join(['%s'] * len(values))), tuple(values))
+ VALUES {values} """.format(
+ doctype=doctype, field=field, values=", ".join(["%s"] * len(values))
+ ),
+ tuple(values),
+ )
diff --git a/erpnext/patches/v12_0/update_production_plan_status.py b/erpnext/patches/v12_0/update_production_plan_status.py
index 06fc503a33..dc65ec25f2 100644
--- a/erpnext/patches/v12_0/update_production_plan_status.py
+++ b/erpnext/patches/v12_0/update_production_plan_status.py
@@ -6,7 +6,8 @@ import frappe
def execute():
frappe.reload_doc("manufacturing", "doctype", "production_plan")
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabProduction Plan` ppl
SET status = "Completed"
WHERE ppl.name IN (
@@ -28,4 +29,5 @@ def execute():
HAVING should_set = 1
) ss
)
- """)
+ """
+ )
diff --git a/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py
index 25cf6b97e3..d7e96fafd6 100644
--- a/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py
+++ b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py
@@ -5,20 +5,22 @@ from erpnext.regional.india import states
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
# Update options in gst_state custom field
- gst_state = frappe.get_doc('Custom Field', 'Address-gst_state')
- gst_state.options = '\n'.join(states)
+ gst_state = frappe.get_doc("Custom Field", "Address-gst_state")
+ gst_state.options = "\n".join(states)
gst_state.save()
# Update gst_state and state code in existing address
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabAddress`
SET
gst_state = 'Dadra and Nagar Haveli and Daman and Diu',
gst_state_number = 26
WHERE gst_state = 'Daman and Diu'
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/add_bin_unique_constraint.py b/erpnext/patches/v13_0/add_bin_unique_constraint.py
index 57fbaae9d8..38a8500ac7 100644
--- a/erpnext/patches/v13_0/add_bin_unique_constraint.py
+++ b/erpnext/patches/v13_0/add_bin_unique_constraint.py
@@ -14,13 +14,16 @@ def execute():
delete_broken_bins()
delete_and_patch_duplicate_bins()
+
def delete_broken_bins():
# delete useless bins
frappe.db.sql("delete from `tabBin` where item_code is null or warehouse is null")
+
def delete_and_patch_duplicate_bins():
- duplicate_bins = frappe.db.sql("""
+ duplicate_bins = frappe.db.sql(
+ """
SELECT
item_code, warehouse, count(*) as bin_count
FROM
@@ -29,18 +32,19 @@ def delete_and_patch_duplicate_bins():
item_code, warehouse
HAVING
bin_count > 1
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
for duplicate_bin in duplicate_bins:
item_code = duplicate_bin.item_code
warehouse = duplicate_bin.warehouse
- existing_bins = frappe.get_list("Bin",
- filters={
- "item_code": item_code,
- "warehouse": warehouse
- },
- fields=["name"],
- order_by="creation",)
+ existing_bins = frappe.get_list(
+ "Bin",
+ filters={"item_code": item_code, "warehouse": warehouse},
+ fields=["name"],
+ order_by="creation",
+ )
# keep last one
existing_bins.pop()
@@ -53,7 +57,7 @@ def delete_and_patch_duplicate_bins():
"indented_qty": get_indented_qty(item_code, warehouse),
"ordered_qty": get_ordered_qty(item_code, warehouse),
"planned_qty": get_planned_qty(item_code, warehouse),
- "actual_qty": get_balance_qty_from_sle(item_code, warehouse)
+ "actual_qty": get_balance_qty_from_sle(item_code, warehouse),
}
bin = get_bin(item_code, warehouse)
diff --git a/erpnext/patches/v13_0/add_cost_center_in_loans.py b/erpnext/patches/v13_0/add_cost_center_in_loans.py
index 25e1722a4f..e293cf2874 100644
--- a/erpnext/patches/v13_0/add_cost_center_in_loans.py
+++ b/erpnext/patches/v13_0/add_cost_center_in_loans.py
@@ -2,15 +2,11 @@ import frappe
def execute():
- frappe.reload_doc('loan_management', 'doctype', 'loan')
- loan = frappe.qb.DocType('Loan')
+ frappe.reload_doc("loan_management", "doctype", "loan")
+ loan = frappe.qb.DocType("Loan")
- for company in frappe.get_all('Company', pluck='name'):
- default_cost_center = frappe.db.get_value('Company', company, 'cost_center')
- frappe.qb.update(
- loan
- ).set(
- loan.cost_center, default_cost_center
- ).where(
+ for company in frappe.get_all("Company", pluck="name"):
+ default_cost_center = frappe.db.get_value("Company", company, "cost_center")
+ frappe.qb.update(loan).set(loan.cost_center, default_cost_center).where(
loan.company == company
- ).run()
\ No newline at end of file
+ ).run()
diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
index b34b5c1801..353376b603 100644
--- a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
+++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
@@ -7,13 +7,13 @@ from erpnext.regional.south_africa.setup import add_permissions, make_custom_fie
def execute():
- company = frappe.get_all('Company', filters = {'country': 'South Africa'})
+ company = frappe.get_all("Company", filters={"country": "South Africa"})
if not company:
return
- frappe.reload_doc('regional', 'doctype', 'south_africa_vat_settings')
- frappe.reload_doc('regional', 'report', 'vat_audit_report')
- frappe.reload_doc('accounts', 'doctype', 'south_africa_vat_account')
+ frappe.reload_doc("regional", "doctype", "south_africa_vat_settings")
+ frappe.reload_doc("regional", "report", "vat_audit_report")
+ frappe.reload_doc("accounts", "doctype", "south_africa_vat_account")
make_custom_fields()
add_permissions()
diff --git a/erpnext/patches/v13_0/add_default_interview_notification_templates.py b/erpnext/patches/v13_0/add_default_interview_notification_templates.py
index 6b5de52e2b..9a47efe870 100644
--- a/erpnext/patches/v13_0/add_default_interview_notification_templates.py
+++ b/erpnext/patches/v13_0/add_default_interview_notification_templates.py
@@ -5,32 +5,40 @@ from frappe import _
def execute():
- if not frappe.db.exists('Email Template', _('Interview Reminder')):
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'interview/interview_reminder_notification_template.html'))
+ if not frappe.db.exists("Email Template", _("Interview Reminder")):
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "interview/interview_reminder_notification_template.html")
+ )
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Interview Reminder'),
- 'response': response,
- 'subject': _('Interview Reminder'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Interview Reminder"),
+ "response": response,
+ "subject": _("Interview Reminder"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
- if not frappe.db.exists('Email Template', _('Interview Feedback Reminder')):
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'interview/interview_feedback_reminder_template.html'))
+ if not frappe.db.exists("Email Template", _("Interview Feedback Reminder")):
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "interview/interview_feedback_reminder_template.html")
+ )
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Interview Feedback Reminder'),
- 'response': response,
- 'subject': _('Interview Feedback Reminder'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Interview Feedback Reminder"),
+ "response": response,
+ "subject": _("Interview Feedback Reminder"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
- hr_settings = frappe.get_doc('HR Settings')
- hr_settings.interview_reminder_template = _('Interview Reminder')
- hr_settings.feedback_reminder_notification_template = _('Interview Feedback Reminder')
+ hr_settings = frappe.get_doc("HR Settings")
+ hr_settings.interview_reminder_template = _("Interview Reminder")
+ hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder")
hr_settings.flags.ignore_links = True
hr_settings.save()
diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py
index 8cee378d90..5f5974f65d 100644
--- a/erpnext/patches/v13_0/add_doctype_to_sla.py
+++ b/erpnext/patches/v13_0/add_doctype_to_sla.py
@@ -7,15 +7,15 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.reload_doc('support', 'doctype', 'sla_fulfilled_on_status')
- frappe.reload_doc('support', 'doctype', 'service_level_agreement')
- if frappe.db.has_column('Service Level Agreement', 'enable'):
- rename_field('Service Level Agreement', 'enable', 'enabled')
+ frappe.reload_doc("support", "doctype", "sla_fulfilled_on_status")
+ frappe.reload_doc("support", "doctype", "service_level_agreement")
+ if frappe.db.has_column("Service Level Agreement", "enable"):
+ rename_field("Service Level Agreement", "enable", "enabled")
- for sla in frappe.get_all('Service Level Agreement'):
- agreement = frappe.get_doc('Service Level Agreement', sla.name)
- agreement.document_type = 'Issue'
+ for sla in frappe.get_all("Service Level Agreement"):
+ agreement = frappe.get_doc("Service Level Agreement", sla.name)
+ agreement.document_type = "Issue"
agreement.apply_sla_for_resolution = 1
- agreement.append('sla_fulfilled_on', {'status': 'Resolved'})
- agreement.append('sla_fulfilled_on', {'status': 'Closed'})
+ agreement.append("sla_fulfilled_on", {"status": "Resolved"})
+ agreement.append("sla_fulfilled_on", {"status": "Closed"})
agreement.save()
diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
index bd18b9bd17..517a14a830 100644
--- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
+++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
@@ -9,31 +9,34 @@ from erpnext.stock.stock_ledger import make_sl_entries
def execute():
- if not frappe.db.has_column('Work Order', 'has_batch_no'):
+ if not frappe.db.has_column("Work Order", "has_batch_no"):
return
- frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
- if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
+ frappe.reload_doc("manufacturing", "doctype", "manufacturing_settings")
+ if cint(
+ frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")
+ ):
return
- frappe.reload_doc('manufacturing', 'doctype', 'work_order')
+ frappe.reload_doc("manufacturing", "doctype", "work_order")
filters = {
- 'docstatus': 1,
- 'produced_qty': ('>', 0),
- 'creation': ('>=', '2021-06-29 00:00:00'),
- 'has_batch_no': 1
+ "docstatus": 1,
+ "produced_qty": (">", 0),
+ "creation": (">=", "2021-06-29 00:00:00"),
+ "has_batch_no": 1,
}
- fields = ['name', 'production_item']
+ fields = ["name", "production_item"]
- work_orders = [d.name for d in frappe.get_all('Work Order', filters = filters, fields=fields)]
+ work_orders = [d.name for d in frappe.get_all("Work Order", filters=filters, fields=fields)]
if not work_orders:
return
repost_stock_entries = []
- stock_entries = frappe.db.sql_list('''
+ stock_entries = frappe.db.sql_list(
+ """
SELECT
se.name
FROM
@@ -45,18 +48,20 @@ def execute():
)
ORDER BY
se.posting_date, se.posting_time
- ''', (work_orders,))
+ """,
+ (work_orders,),
+ )
if stock_entries:
- print('Length of stock entries', len(stock_entries))
+ print("Length of stock entries", len(stock_entries))
for stock_entry in stock_entries:
- doc = frappe.get_doc('Stock Entry', stock_entry)
+ doc = frappe.get_doc("Stock Entry", stock_entry)
doc.set_work_order_details()
doc.load_items_from_bom()
doc.calculate_rate_and_amount()
set_expense_account(doc)
- doc.make_batches('t_warehouse')
+ doc.make_batches("t_warehouse")
if doc.docstatus == 0:
doc.save()
@@ -67,10 +72,14 @@ def execute():
for repost_doc in repost_stock_entries:
repost_future_sle_and_gle(repost_doc)
+
def set_expense_account(doc):
for row in doc.items:
if row.is_finished_item and not row.expense_account:
- row.expense_account = frappe.get_cached_value('Company', doc.company, 'stock_adjustment_account')
+ row.expense_account = frappe.get_cached_value(
+ "Company", doc.company, "stock_adjustment_account"
+ )
+
def repost_stock_entry(doc):
doc.db_update()
@@ -86,29 +95,36 @@ def repost_stock_entry(doc):
try:
make_sl_entries(sl_entries, True)
except Exception:
- print(f'SLE entries not posted for the stock entry {doc.name}')
+ print(f"SLE entries not posted for the stock entry {doc.name}")
traceback = frappe.get_traceback()
frappe.log_error(traceback)
+
def get_sle_for_target_warehouse(doc, sl_entries, finished_item_row):
- for d in doc.get('items'):
+ for d in doc.get("items"):
if cstr(d.t_warehouse) and finished_item_row and d.name == finished_item_row.name:
- sle = doc.get_sl_entries(d, {
- "warehouse": cstr(d.t_warehouse),
- "actual_qty": flt(d.transfer_qty),
- "incoming_rate": flt(d.valuation_rate)
- })
+ sle = doc.get_sl_entries(
+ d,
+ {
+ "warehouse": cstr(d.t_warehouse),
+ "actual_qty": flt(d.transfer_qty),
+ "incoming_rate": flt(d.valuation_rate),
+ },
+ )
sle.recalculate_rate = 1
sl_entries.append(sle)
+
def repost_future_sle_and_gle(doc):
- args = frappe._dict({
- "posting_date": doc.posting_date,
- "posting_time": doc.posting_time,
- "voucher_type": doc.doctype,
- "voucher_no": doc.name,
- "company": doc.company
- })
+ args = frappe._dict(
+ {
+ "posting_date": doc.posting_date,
+ "posting_time": doc.posting_time,
+ "voucher_type": doc.doctype,
+ "voucher_no": doc.name,
+ "company": doc.company,
+ }
+ )
create_repost_item_valuation_entry(args)
diff --git a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py
index 71abe2e98e..7dce95c1b8 100644
--- a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py
+++ b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py
@@ -4,8 +4,10 @@ import frappe
def execute():
frappe.reload_doc("projects", "doctype", "project")
- frappe.db.sql("""UPDATE `tabProject`
+ frappe.db.sql(
+ """UPDATE `tabProject`
SET
naming_series = 'PROJ-.####'
WHERE
- naming_series is NULL""")
+ naming_series is NULL"""
+ )
diff --git a/erpnext/patches/v13_0/add_po_to_global_search.py b/erpnext/patches/v13_0/add_po_to_global_search.py
index 7fbaffb23c..514cd34390 100644
--- a/erpnext/patches/v13_0/add_po_to_global_search.py
+++ b/erpnext/patches/v13_0/add_po_to_global_search.py
@@ -2,15 +2,13 @@ import frappe
def execute():
- global_search_settings = frappe.get_single("Global Search Settings")
+ global_search_settings = frappe.get_single("Global Search Settings")
- if "Purchase Order" in (
- dt.document_type for dt in global_search_settings.allowed_in_global_search
- ):
- return
+ if "Purchase Order" in (
+ dt.document_type for dt in global_search_settings.allowed_in_global_search
+ ):
+ return
- global_search_settings.append(
- "allowed_in_global_search", {"document_type": "Purchase Order"}
- )
+ global_search_settings.append("allowed_in_global_search", {"document_type": "Purchase Order"})
- global_search_settings.save(ignore_permissions=True)
+ global_search_settings.save(ignore_permissions=True)
diff --git a/erpnext/patches/v13_0/change_default_pos_print_format.py b/erpnext/patches/v13_0/change_default_pos_print_format.py
index 23cad31f59..be478a2b67 100644
--- a/erpnext/patches/v13_0/change_default_pos_print_format.py
+++ b/erpnext/patches/v13_0/change_default_pos_print_format.py
@@ -5,4 +5,5 @@ def execute():
frappe.db.sql(
"""UPDATE `tabPOS Profile` profile
SET profile.`print_format` = 'POS Invoice'
- WHERE profile.`print_format` = 'Point of Sale'""")
+ WHERE profile.`print_format` = 'Point of Sale'"""
+ )
diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py
index 5e1df14d4e..0ae3a3e3bd 100644
--- a/erpnext/patches/v13_0/check_is_income_tax_component.py
+++ b/erpnext/patches/v13_0/check_is_income_tax_component.py
@@ -10,33 +10,36 @@ import erpnext
def execute():
- doctypes = ['salary_component',
- 'Employee Tax Exemption Declaration',
- 'Employee Tax Exemption Proof Submission',
- 'Employee Tax Exemption Declaration Category',
- 'Employee Tax Exemption Proof Submission Detail',
- 'gratuity_rule',
- 'gratuity_rule_slab',
- 'gratuity_applicable_component'
+ doctypes = [
+ "salary_component",
+ "Employee Tax Exemption Declaration",
+ "Employee Tax Exemption Proof Submission",
+ "Employee Tax Exemption Declaration Category",
+ "Employee Tax Exemption Proof Submission Detail",
+ "gratuity_rule",
+ "gratuity_rule_slab",
+ "gratuity_applicable_component",
]
for doctype in doctypes:
- frappe.reload_doc('Payroll', 'doctype', doctype, force=True)
+ frappe.reload_doc("Payroll", "doctype", doctype, force=True)
-
- reports = ['Professional Tax Deductions', 'Provident Fund Deductions', 'E-Invoice Summary']
+ reports = ["Professional Tax Deductions", "Provident Fund Deductions", "E-Invoice Summary"]
for report in reports:
- frappe.reload_doc('Regional', 'Report', report)
- frappe.reload_doc('Regional', 'Report', report)
+ frappe.reload_doc("Regional", "Report", report)
+ frappe.reload_doc("Regional", "Report", report)
if erpnext.get_region() == "India":
- create_custom_field('Salary Component',
- dict(fieldname='component_type',
- label='Component Type',
- fieldtype='Select',
- insert_after='description',
- options='\nProvident Fund\nAdditional Provident Fund\nProvident Fund Loan\nProfessional Tax',
- depends_on='eval:doc.type == "Deduction"')
+ create_custom_field(
+ "Salary Component",
+ dict(
+ fieldname="component_type",
+ label="Component Type",
+ fieldtype="Select",
+ insert_after="description",
+ options="\nProvident Fund\nAdditional Provident Fund\nProvident Fund Loan\nProfessional Tax",
+ depends_on='eval:doc.type == "Deduction"',
+ ),
)
if frappe.db.exists("Salary Component", "Income Tax"):
@@ -44,7 +47,9 @@ def execute():
if frappe.db.exists("Salary Component", "TDS"):
frappe.db.set_value("Salary Component", "TDS", "is_income_tax_component", 1)
- components = frappe.db.sql("select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1)
+ components = frappe.db.sql(
+ "select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1
+ )
for component in components:
frappe.db.set_value("Salary Component", component.name, "is_income_tax_component", 1)
@@ -52,4 +57,6 @@ def execute():
if frappe.db.exists("Salary Component", "Provident Fund"):
frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund")
if frappe.db.exists("Salary Component", "Professional Tax"):
- frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax")
+ frappe.db.set_value(
+ "Salary Component", "Professional Tax", "component_type", "Professional Tax"
+ )
diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
index bc64c63772..efbb96c100 100644
--- a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
+++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
@@ -2,22 +2,24 @@ import frappe
def execute():
- frappe.reload_doc('stock', 'doctype', 'quality_inspection_parameter')
+ frappe.reload_doc("stock", "doctype", "quality_inspection_parameter")
# get all distinct parameters from QI readigs table
- reading_params = frappe.db.get_all("Quality Inspection Reading", fields=["distinct specification"])
+ reading_params = frappe.db.get_all(
+ "Quality Inspection Reading", fields=["distinct specification"]
+ )
reading_params = [d.specification for d in reading_params]
# get all distinct parameters from QI Template as some may be unused in QI
- template_params = frappe.db.get_all("Item Quality Inspection Parameter", fields=["distinct specification"])
+ template_params = frappe.db.get_all(
+ "Item Quality Inspection Parameter", fields=["distinct specification"]
+ )
template_params = [d.specification for d in template_params]
params = list(set(reading_params + template_params))
for parameter in params:
if not frappe.db.exists("Quality Inspection Parameter", parameter):
- frappe.get_doc({
- "doctype": "Quality Inspection Parameter",
- "parameter": parameter,
- "description": parameter
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
+ ).insert(ignore_permissions=True)
diff --git a/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py b/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py
index d3ee3f8276..020521d5b9 100644
--- a/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py
+++ b/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py
@@ -7,51 +7,57 @@ from erpnext.e_commerce.doctype.website_item.website_item import make_website_it
def execute():
- """
- Convert all Item links to Website Item link values in
- exisitng 'Item Card Group' Web Page Block data.
- """
- frappe.reload_doc("e_commerce", "web_template", "item_card_group")
+ """
+ Convert all Item links to Website Item link values in
+ exisitng 'Item Card Group' Web Page Block data.
+ """
+ frappe.reload_doc("e_commerce", "web_template", "item_card_group")
- blocks = frappe.db.get_all(
- "Web Page Block",
- filters={"web_template": "Item Card Group"},
- fields=["parent", "web_template_values", "name"]
- )
+ blocks = frappe.db.get_all(
+ "Web Page Block",
+ filters={"web_template": "Item Card Group"},
+ fields=["parent", "web_template_values", "name"],
+ )
- fields = generate_fields_to_edit()
+ fields = generate_fields_to_edit()
- for block in blocks:
- web_template_value = json.loads(block.get('web_template_values'))
+ for block in blocks:
+ web_template_value = json.loads(block.get("web_template_values"))
- for field in fields:
- item = web_template_value.get(field)
- if not item:
- continue
+ for field in fields:
+ item = web_template_value.get(field)
+ if not item:
+ continue
- if frappe.db.exists("Website Item", {"item_code": item}):
- website_item = frappe.db.get_value("Website Item", {"item_code": item})
- else:
- website_item = make_new_website_item(item)
+ if frappe.db.exists("Website Item", {"item_code": item}):
+ website_item = frappe.db.get_value("Website Item", {"item_code": item})
+ else:
+ website_item = make_new_website_item(item)
- if website_item:
- web_template_value[field] = website_item
+ if website_item:
+ web_template_value[field] = website_item
+
+ frappe.db.set_value(
+ "Web Page Block", block.name, "web_template_values", json.dumps(web_template_value)
+ )
- frappe.db.set_value("Web Page Block", block.name, "web_template_values", json.dumps(web_template_value))
def generate_fields_to_edit() -> List:
- fields = []
- for i in range(1, 13):
- fields.append(f"card_{i}_item") # fields like 'card_1_item', etc.
+ fields = []
+ for i in range(1, 13):
+ fields.append(f"card_{i}_item") # fields like 'card_1_item', etc.
+
+ return fields
- return fields
def make_new_website_item(item: str) -> Union[str, None]:
- try:
- doc = frappe.get_doc("Item", item)
- web_item = make_website_item(doc) # returns [website_item.name, item_name]
- return web_item[0]
- except Exception:
- title = f"{item}: Error while converting to Website Item "
- frappe.log_error(title + "for Item Card Group Template" + "\n\n" + frappe.get_traceback(), title=title)
- return None
+ try:
+ doc = frappe.get_doc("Item", item)
+ web_item = make_website_item(doc) # returns [website_item.name, item_name]
+ return web_item[0]
+ except Exception:
+ title = f"{item}: Error while converting to Website Item "
+ frappe.log_error(
+ title + "for Item Card Group Template" + "\n\n" + frappe.get_traceback(), title=title
+ )
+ return None
diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py
index 4450108810..51ab0e8b65 100644
--- a/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py
+++ b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py
@@ -3,9 +3,12 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
- frappe.reload_doc('accounts', 'doctype', 'accounting_dimension')
- accounting_dimensions = frappe.db.sql("""select fieldname, label, document_type, disabled from
- `tabAccounting Dimension`""", as_dict=1)
+ frappe.reload_doc("accounts", "doctype", "accounting_dimension")
+ accounting_dimensions = frappe.db.sql(
+ """select fieldname, label, document_type, disabled from
+ `tabAccounting Dimension`""",
+ as_dict=1,
+ )
if not accounting_dimensions:
return
@@ -14,9 +17,9 @@ def execute():
for d in accounting_dimensions:
if count % 2 == 0:
- insert_after_field = 'dimension_col_break'
+ insert_after_field = "dimension_col_break"
else:
- insert_after_field = 'accounting_dimensions_section'
+ insert_after_field = "accounting_dimensions_section"
for doctype in ["POS Invoice", "POS Invoice Item"]:
@@ -32,10 +35,10 @@ def execute():
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
- "insert_after": insert_after_field
+ "insert_after": insert_after_field,
}
- if df['fieldname'] not in fieldnames:
+ if df["fieldname"] not in fieldnames:
create_custom_field(doctype, df)
frappe.clear_cache(doctype=doctype)
diff --git a/erpnext/patches/v13_0/create_custom_field_for_finance_book.py b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py
index 313b0e9a2e..2b8666d21b 100644
--- a/erpnext/patches/v13_0/create_custom_field_for_finance_book.py
+++ b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py
@@ -3,18 +3,18 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
custom_field = {
- 'Finance Book': [
+ "Finance Book": [
{
- 'fieldname': 'for_income_tax',
- 'label': 'For Income Tax',
- 'fieldtype': 'Check',
- 'insert_after': 'finance_book_name',
- 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.'
+ "fieldname": "for_income_tax",
+ "label": "For Income Tax",
+ "fieldtype": "Check",
+ "insert_after": "finance_book_name",
+ "description": "If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.",
}
]
}
diff --git a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
index 416694559c..bef2516f6d 100644
--- a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
+++ b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
@@ -6,32 +6,75 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges')
- frappe.reload_doc('accounts', 'doctype', 'payment_entry')
+ frappe.reload_doc("accounts", "doctype", "advance_taxes_and_charges")
+ frappe.reload_doc("accounts", "doctype", "payment_entry")
- if frappe.db.exists('Company', {'country': 'India'}):
+ if frappe.db.exists("Company", {"country": "India"}):
custom_fields = {
- 'Payment Entry': [
- dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
- print_hide=1, collapsible=1),
- dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
- print_hide=1, options='Address'),
- dict(fieldname='company_gstin', label='Company GSTIN',
- fieldtype='Data', insert_after='company_address',
- fetch_from='company_address.gstin', print_hide=1, read_only=1),
- dict(fieldname='place_of_supply', label='Place of Supply',
- fieldtype='Data', insert_after='company_gstin',
- print_hide=1, read_only=1),
- dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
- print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
- dict(fieldname='customer_gstin', label='Customer GSTIN',
- fieldtype='Data', insert_after='customer_address',
- fetch_from='customer_address.gstin', print_hide=1, read_only=1)
+ "Payment Entry": [
+ dict(
+ fieldname="gst_section",
+ label="GST Details",
+ fieldtype="Section Break",
+ insert_after="deductions",
+ print_hide=1,
+ collapsible=1,
+ ),
+ dict(
+ fieldname="company_address",
+ label="Company Address",
+ fieldtype="Link",
+ insert_after="gst_section",
+ print_hide=1,
+ options="Address",
+ ),
+ dict(
+ fieldname="company_gstin",
+ label="Company GSTIN",
+ fieldtype="Data",
+ insert_after="company_address",
+ fetch_from="company_address.gstin",
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="place_of_supply",
+ label="Place of Supply",
+ fieldtype="Data",
+ insert_after="company_gstin",
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="customer_address",
+ label="Customer Address",
+ fieldtype="Link",
+ insert_after="place_of_supply",
+ print_hide=1,
+ options="Address",
+ depends_on='eval:doc.party_type == "Customer"',
+ ),
+ dict(
+ fieldname="customer_gstin",
+ label="Customer GSTIN",
+ fieldtype="Data",
+ insert_after="customer_address",
+ fetch_from="customer_address.gstin",
+ print_hide=1,
+ read_only=1,
+ ),
]
}
create_custom_fields(custom_fields, update=True)
else:
- fields = ['gst_section', 'company_address', 'company_gstin', 'place_of_supply', 'customer_address', 'customer_gstin']
+ fields = [
+ "gst_section",
+ "company_address",
+ "company_gstin",
+ "place_of_supply",
+ "customer_address",
+ "customer_gstin",
+ ]
for field in fields:
- frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}")
\ No newline at end of file
+ frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}")
diff --git a/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py b/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py
index f33b4b3ea0..093463a12e 100644
--- a/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py
+++ b/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py
@@ -4,9 +4,8 @@ from erpnext.regional.saudi_arabia.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
+ company = frappe.get_all("Company", filters={"country": "Saudi Arabia"})
if not company:
return
make_custom_fields()
-
diff --git a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
index 6f9031fc50..59b17eea9f 100644
--- a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
+++ b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
@@ -6,69 +6,89 @@ import frappe
def execute():
- frappe.reload_doc('hr', 'doctype', 'leave_policy_assignment')
- frappe.reload_doc('hr', 'doctype', 'employee_grade')
- employee_with_assignment = []
- leave_policy = []
+ frappe.reload_doc("hr", "doctype", "leave_policy_assignment")
+ frappe.reload_doc("hr", "doctype", "employee_grade")
+ employee_with_assignment = []
+ leave_policy = []
- if "leave_policy" in frappe.db.get_table_columns("Employee"):
- employees_with_leave_policy = frappe.db.sql("SELECT name, leave_policy FROM `tabEmployee` WHERE leave_policy IS NOT NULL and leave_policy != ''", as_dict = 1)
+ if "leave_policy" in frappe.db.get_table_columns("Employee"):
+ employees_with_leave_policy = frappe.db.sql(
+ "SELECT name, leave_policy FROM `tabEmployee` WHERE leave_policy IS NOT NULL and leave_policy != ''",
+ as_dict=1,
+ )
- for employee in employees_with_leave_policy:
- alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": employee.leave_policy, "docstatus": 1})
- if not alloc:
- create_assignment(employee.name, employee.leave_policy)
+ for employee in employees_with_leave_policy:
+ alloc = frappe.db.exists(
+ "Leave Allocation",
+ {"employee": employee.name, "leave_policy": employee.leave_policy, "docstatus": 1},
+ )
+ if not alloc:
+ create_assignment(employee.name, employee.leave_policy)
- employee_with_assignment.append(employee.name)
- leave_policy.append(employee.leave_policy)
+ employee_with_assignment.append(employee.name)
+ leave_policy.append(employee.leave_policy)
- if "default_leave_policy" in frappe.db.get_table_columns("Employee Grade"):
- employee_grade_with_leave_policy = frappe.db.sql("SELECT name, default_leave_policy FROM `tabEmployee Grade` WHERE default_leave_policy IS NOT NULL and default_leave_policy!=''", as_dict = 1)
+ if "default_leave_policy" in frappe.db.get_table_columns("Employee Grade"):
+ employee_grade_with_leave_policy = frappe.db.sql(
+ "SELECT name, default_leave_policy FROM `tabEmployee Grade` WHERE default_leave_policy IS NOT NULL and default_leave_policy!=''",
+ as_dict=1,
+ )
- #for whole employee Grade
- for grade in employee_grade_with_leave_policy:
- employees = get_employee_with_grade(grade.name)
- for employee in employees:
+ # for whole employee Grade
+ for grade in employee_grade_with_leave_policy:
+ employees = get_employee_with_grade(grade.name)
+ for employee in employees:
- if employee not in employee_with_assignment: #Will ensure no duplicate
- alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": grade.default_leave_policy, "docstatus": 1})
- if not alloc:
- create_assignment(employee.name, grade.default_leave_policy)
- leave_policy.append(grade.default_leave_policy)
+ if employee not in employee_with_assignment: # Will ensure no duplicate
+ alloc = frappe.db.exists(
+ "Leave Allocation",
+ {"employee": employee.name, "leave_policy": grade.default_leave_policy, "docstatus": 1},
+ )
+ if not alloc:
+ create_assignment(employee.name, grade.default_leave_policy)
+ leave_policy.append(grade.default_leave_policy)
- #for old Leave allocation and leave policy from allocation, which may got updated in employee grade.
- leave_allocations = frappe.db.sql("SELECT leave_policy, leave_period, employee FROM `tabLeave Allocation` WHERE leave_policy IS NOT NULL and leave_policy != '' and docstatus = 1 ", as_dict = 1)
+ # for old Leave allocation and leave policy from allocation, which may got updated in employee grade.
+ leave_allocations = frappe.db.sql(
+ "SELECT leave_policy, leave_period, employee FROM `tabLeave Allocation` WHERE leave_policy IS NOT NULL and leave_policy != '' and docstatus = 1 ",
+ as_dict=1,
+ )
- for allocation in leave_allocations:
- if allocation.leave_policy not in leave_policy:
- create_assignment(allocation.employee, allocation.leave_policy, leave_period=allocation.leave_period,
- allocation_exists=True)
+ for allocation in leave_allocations:
+ if allocation.leave_policy not in leave_policy:
+ create_assignment(
+ allocation.employee,
+ allocation.leave_policy,
+ leave_period=allocation.leave_period,
+ allocation_exists=True,
+ )
-def create_assignment(employee, leave_policy, leave_period=None, allocation_exists = False):
- if frappe.db.get_value("Leave Policy", leave_policy, "docstatus") == 2:
- return
- filters = {"employee":employee, "leave_policy": leave_policy}
- if leave_period:
- filters["leave_period"] = leave_period
+def create_assignment(employee, leave_policy, leave_period=None, allocation_exists=False):
+ if frappe.db.get_value("Leave Policy", leave_policy, "docstatus") == 2:
+ return
- if not frappe.db.exists("Leave Policy Assignment" , filters):
- lpa = frappe.new_doc("Leave Policy Assignment")
- lpa.employee = employee
- lpa.leave_policy = leave_policy
+ filters = {"employee": employee, "leave_policy": leave_policy}
+ if leave_period:
+ filters["leave_period"] = leave_period
- lpa.flags.ignore_mandatory = True
- if allocation_exists:
- lpa.assignment_based_on = 'Leave Period'
- lpa.leave_period = leave_period
- lpa.leaves_allocated = 1
+ if not frappe.db.exists("Leave Policy Assignment", filters):
+ lpa = frappe.new_doc("Leave Policy Assignment")
+ lpa.employee = employee
+ lpa.leave_policy = leave_policy
- lpa.save()
- if allocation_exists:
- lpa.submit()
- #Updating old Leave Allocation
- frappe.db.sql("Update `tabLeave Allocation` set leave_policy_assignment = %s", lpa.name)
+ lpa.flags.ignore_mandatory = True
+ if allocation_exists:
+ lpa.assignment_based_on = "Leave Period"
+ lpa.leave_period = leave_period
+ lpa.leaves_allocated = 1
+
+ lpa.save()
+ if allocation_exists:
+ lpa.submit()
+ # Updating old Leave Allocation
+ frappe.db.sql("Update `tabLeave Allocation` set leave_policy_assignment = %s", lpa.name)
def get_employee_with_grade(grade):
- return frappe.get_list("Employee", filters = {"grade": grade})
+ return frappe.get_list("Employee", filters={"grade": grade})
diff --git a/erpnext/patches/v13_0/create_pan_field_for_india.py b/erpnext/patches/v13_0/create_pan_field_for_india.py
index 6df6e1eb32..48c688e19c 100644
--- a/erpnext/patches/v13_0/create_pan_field_for_india.py
+++ b/erpnext/patches/v13_0/create_pan_field_for_india.py
@@ -3,27 +3,17 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- frappe.reload_doc('buying', 'doctype', 'supplier', force=True)
- frappe.reload_doc('selling', 'doctype', 'customer', force=True)
- frappe.reload_doc('core', 'doctype', 'doctype', force=True)
+ frappe.reload_doc("buying", "doctype", "supplier", force=True)
+ frappe.reload_doc("selling", "doctype", "customer", force=True)
+ frappe.reload_doc("core", "doctype", "doctype", force=True)
custom_fields = {
- 'Supplier': [
- {
- 'fieldname': 'pan',
- 'label': 'PAN',
- 'fieldtype': 'Data',
- 'insert_after': 'supplier_type'
- }
+ "Supplier": [
+ {"fieldname": "pan", "label": "PAN", "fieldtype": "Data", "insert_after": "supplier_type"}
+ ],
+ "Customer": [
+ {"fieldname": "pan", "label": "PAN", "fieldtype": "Data", "insert_after": "customer_type"}
],
- 'Customer': [
- {
- 'fieldname': 'pan',
- 'label': 'PAN',
- 'fieldtype': 'Data',
- 'insert_after': 'customer_type'
- }
- ]
}
create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
index 87c9cf1ebd..66aae9a30a 100644
--- a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
+++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
@@ -8,12 +8,13 @@ from erpnext.regional.united_arab_emirates.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': ['in', ['Saudi Arabia', 'United Arab Emirates']]})
+ company = frappe.get_all(
+ "Company", filters={"country": ["in", ["Saudi Arabia", "United Arab Emirates"]]}
+ )
if not company:
return
-
- frappe.reload_doc('accounts', 'doctype', 'pos_invoice')
- frappe.reload_doc('accounts', 'doctype', 'pos_invoice_item')
+ frappe.reload_doc("accounts", "doctype", "pos_invoice")
+ frappe.reload_doc("accounts", "doctype", "pos_invoice_item")
make_custom_fields()
diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py
index da162a3ab1..cb7bfc30af 100644
--- a/erpnext/patches/v13_0/create_website_items.py
+++ b/erpnext/patches/v13_0/create_website_items.py
@@ -11,14 +11,31 @@ def execute():
frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings")
frappe.reload_doc("stock", "doctype", "item")
- item_fields = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
- "has_variants", "variant_of", "description", "weightage"]
- web_fields_to_map = ["route", "slideshow", "website_image_alt",
- "website_warehouse", "web_long_description", "website_content", "thumbnail"]
+ item_fields = [
+ "item_code",
+ "item_name",
+ "item_group",
+ "stock_uom",
+ "brand",
+ "image",
+ "has_variants",
+ "variant_of",
+ "description",
+ "weightage",
+ ]
+ web_fields_to_map = [
+ "route",
+ "slideshow",
+ "website_image_alt",
+ "website_warehouse",
+ "web_long_description",
+ "website_content",
+ "thumbnail",
+ ]
# get all valid columns (fields) from Item master DB schema
- item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) # nosemgrep
- item_table_fields = [d.get('Field') for d in item_table_fields]
+ item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) # nosemgrep
+ item_table_fields = [d.get("Field") for d in item_table_fields]
# prepare fields to query from Item, check if the web field exists in Item master
web_query_fields = []
@@ -38,11 +55,7 @@ def execute():
# most likely a fresh installation that doesnt need this patch
return
- items = frappe.db.get_all(
- "Item",
- fields=item_fields,
- or_filters=or_filters
- )
+ items = frappe.db.get_all("Item", fields=item_fields, or_filters=or_filters)
total_count = len(items)
for count, item in enumerate(items, start=1):
@@ -62,11 +75,11 @@ def execute():
for doctype in ("Website Item Group", "Item Website Specification"):
frappe.db.set_value(
doctype,
- {"parenttype": "Item", "parent": item.item_code}, # filters
- {"parenttype": "Website Item", "parent": website_item.name} # value dict
+ {"parenttype": "Item", "parent": item.item_code}, # filters
+ {"parenttype": "Website Item", "parent": website_item.name}, # value dict
)
- if count % 20 == 0: # commit after every 20 items
+ if count % 20 == 0: # commit after every 20 items
frappe.db.commit()
- frappe.utils.update_progress_bar('Creating Website Items', count, total_count)
+ frappe.utils.update_progress_bar("Creating Website Items", count, total_count)
diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py
index 078c558d88..5cbd0b5fcb 100644
--- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py
+++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py
@@ -5,35 +5,68 @@ from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import
def execute():
- company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name'])
+ company = frappe.get_all("Company", filters={"country": "United States"}, fields=["name"])
if not company:
return
- TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
+ TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value(
+ "TaxJar Settings", "taxjar_create_transactions"
+ )
TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox")
- if (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE):
+ if not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE:
return
custom_fields = {
- 'Sales Invoice Item': [
- dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category',
- label='Product Tax Category', fetch_from='item_code.product_tax_category'),
- dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount',
- label='Tax Collectable', read_only=1, options='currency'),
- dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable',
- label='Taxable Amount', read_only=1, options='currency')
+ "Sales Invoice Item": [
+ dict(
+ fieldname="product_tax_category",
+ fieldtype="Link",
+ insert_after="description",
+ options="Product Tax Category",
+ label="Product Tax Category",
+ fetch_from="item_code.product_tax_category",
+ ),
+ dict(
+ fieldname="tax_collectable",
+ fieldtype="Currency",
+ insert_after="net_amount",
+ label="Tax Collectable",
+ read_only=1,
+ options="currency",
+ ),
+ dict(
+ fieldname="taxable_amount",
+ fieldtype="Currency",
+ insert_after="tax_collectable",
+ label="Taxable Amount",
+ read_only=1,
+ options="currency",
+ ),
],
- 'Item': [
- dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category',
- label='Product Tax Category')
+ "Item": [
+ dict(
+ fieldname="product_tax_category",
+ fieldtype="Link",
+ insert_after="item_group",
+ options="Product Tax Category",
+ label="Product Tax Category",
+ )
+ ],
+ "TaxJar Settings": [
+ dict(
+ fieldname="company",
+ fieldtype="Link",
+ insert_after="configuration",
+ options="Company",
+ label="Company",
+ )
],
- 'TaxJar Settings': [
- dict(fieldname='company', fieldtype='Link', insert_after='configuration', options='Company',
- label='Company')
- ]
}
create_custom_fields(custom_fields, update=True)
add_permissions()
- frappe.enqueue('erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings.add_product_tax_categories', now=True)
+ frappe.enqueue(
+ "erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings.add_product_tax_categories",
+ now=True,
+ )
diff --git a/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py
index 75953b0e30..c53eb79437 100644
--- a/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py
+++ b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py
@@ -7,7 +7,8 @@ import frappe
def execute():
- if frappe.db.exists('DocType', 'Bank Reconciliation Detail') and \
- frappe.db.exists('DocType', 'Bank Clearance Detail'):
+ if frappe.db.exists("DocType", "Bank Reconciliation Detail") and frappe.db.exists(
+ "DocType", "Bank Clearance Detail"
+ ):
- frappe.delete_doc("DocType", 'Bank Reconciliation Detail', force=1)
+ frappe.delete_doc("DocType", "Bank Reconciliation Detail", force=1)
diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
index 2c5c577978..3755315813 100644
--- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
+++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
@@ -22,7 +22,7 @@ def execute():
frappe.delete_doc("Page", "bank-reconciliation", force=1)
- frappe.reload_doc('accounts', 'doctype', 'bank_transaction')
+ frappe.reload_doc("accounts", "doctype", "bank_transaction")
rename_field("Bank Transaction", "debit", "deposit")
rename_field("Bank Transaction", "credit", "withdrawal")
diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py
index e57d6d0d3e..987f53f37c 100644
--- a/erpnext/patches/v13_0/delete_old_purchase_reports.py
+++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py
@@ -8,9 +8,12 @@ from erpnext.accounts.utils import check_and_delete_linked_reports
def execute():
- reports_to_delete = ["Requested Items To Be Ordered",
- "Purchase Order Items To Be Received or Billed","Purchase Order Items To Be Received",
- "Purchase Order Items To Be Billed"]
+ reports_to_delete = [
+ "Requested Items To Be Ordered",
+ "Purchase Order Items To Be Received or Billed",
+ "Purchase Order Items To Be Received",
+ "Purchase Order Items To Be Billed",
+ ]
for report in reports_to_delete:
if frappe.db.exists("Report", report):
@@ -19,8 +22,9 @@ def execute():
frappe.delete_doc("Report", report)
+
def delete_auto_email_reports(report):
- """ Check for one or multiple Auto Email Reports and delete """
+ """Check for one or multiple Auto Email Reports and delete"""
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0])
diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py
index e6eba0a608..b31c9d17d7 100644
--- a/erpnext/patches/v13_0/delete_old_sales_reports.py
+++ b/erpnext/patches/v13_0/delete_old_sales_reports.py
@@ -18,14 +18,16 @@ def execute():
frappe.delete_doc("Report", report)
+
def delete_auto_email_reports(report):
- """ Check for one or multiple Auto Email Reports and delete """
+ """Check for one or multiple Auto Email Reports and delete"""
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0])
+
def delete_links_from_desktop_icons(report):
- """ Check for one or multiple Desktop Icons and delete """
+ """Check for one or multiple Desktop Icons and delete"""
desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
for desktop_icon in desktop_icons:
- frappe.delete_doc("Desktop Icon", desktop_icon[0])
\ No newline at end of file
+ frappe.delete_doc("Desktop Icon", desktop_icon[0])
diff --git a/erpnext/patches/v13_0/delete_orphaned_tables.py b/erpnext/patches/v13_0/delete_orphaned_tables.py
index c32f83067b..794be098a9 100644
--- a/erpnext/patches/v13_0/delete_orphaned_tables.py
+++ b/erpnext/patches/v13_0/delete_orphaned_tables.py
@@ -7,63 +7,66 @@ from frappe.utils import getdate
def execute():
- frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record')
+ frappe.reload_doc("setup", "doctype", "transaction_deletion_record")
- if has_deleted_company_transactions():
- child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected()
+ if has_deleted_company_transactions():
+ child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected()
- for doctype in child_doctypes:
- docs = frappe.get_all(doctype, fields=['name', 'parent', 'parenttype', 'creation'])
+ for doctype in child_doctypes:
+ docs = frappe.get_all(doctype, fields=["name", "parent", "parenttype", "creation"])
- for doc in docs:
- if not frappe.db.exists(doc['parenttype'], doc['parent']):
- frappe.db.delete(doctype, {'name': doc['name']})
+ for doc in docs:
+ if not frappe.db.exists(doc["parenttype"], doc["parent"]):
+ frappe.db.delete(doctype, {"name": doc["name"]})
+
+ elif check_for_new_doc_with_same_name_as_deleted_parent(doc):
+ frappe.db.delete(doctype, {"name": doc["name"]})
- elif check_for_new_doc_with_same_name_as_deleted_parent(doc):
- frappe.db.delete(doctype, {'name': doc['name']})
def has_deleted_company_transactions():
- return frappe.get_all('Transaction Deletion Record')
+ return frappe.get_all("Transaction Deletion Record")
+
def get_child_doctypes_whose_parent_doctypes_were_affected():
- parent_doctypes = get_affected_doctypes()
- child_doctypes = frappe.get_all(
- 'DocField',
- filters={
- 'fieldtype': 'Table',
- 'parent':['in', parent_doctypes]
- }, pluck='options')
+ parent_doctypes = get_affected_doctypes()
+ child_doctypes = frappe.get_all(
+ "DocField", filters={"fieldtype": "Table", "parent": ["in", parent_doctypes]}, pluck="options"
+ )
+
+ return child_doctypes
- return child_doctypes
def get_affected_doctypes():
- affected_doctypes = []
- tdr_docs = frappe.get_all('Transaction Deletion Record', pluck="name")
+ affected_doctypes = []
+ tdr_docs = frappe.get_all("Transaction Deletion Record", pluck="name")
- for tdr in tdr_docs:
- tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr)
+ for tdr in tdr_docs:
+ tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr)
- for doctype in tdr_doc.doctypes:
- if is_not_child_table(doctype.doctype_name):
- affected_doctypes.append(doctype.doctype_name)
+ for doctype in tdr_doc.doctypes:
+ if is_not_child_table(doctype.doctype_name):
+ affected_doctypes.append(doctype.doctype_name)
+
+ affected_doctypes = remove_duplicate_items(affected_doctypes)
+ return affected_doctypes
- affected_doctypes = remove_duplicate_items(affected_doctypes)
- return affected_doctypes
def is_not_child_table(doctype):
- return not bool(frappe.get_value('DocType', doctype, 'istable'))
+ return not bool(frappe.get_value("DocType", doctype, "istable"))
+
def remove_duplicate_items(affected_doctypes):
- return list(set(affected_doctypes))
+ return list(set(affected_doctypes))
+
def check_for_new_doc_with_same_name_as_deleted_parent(doc):
- """
- Compares creation times of parent and child docs.
- Since Transaction Deletion Record resets the naming series after deletion,
- it allows the creation of new docs with the same names as the deleted ones.
- """
+ """
+ Compares creation times of parent and child docs.
+ Since Transaction Deletion Record resets the naming series after deletion,
+ it allows the creation of new docs with the same names as the deleted ones.
+ """
- parent_creation_time = frappe.db.get_value(doc['parenttype'], doc['parent'], 'creation')
- child_creation_time = doc['creation']
+ parent_creation_time = frappe.db.get_value(doc["parenttype"], doc["parent"], "creation")
+ child_creation_time = doc["creation"]
- return getdate(parent_creation_time) > getdate(child_creation_time)
+ return getdate(parent_creation_time) > getdate(child_creation_time)
diff --git a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py
index 87565f0fe4..430a3056cd 100644
--- a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py
+++ b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py
@@ -2,12 +2,16 @@ import frappe
def execute():
- """ Check for one or multiple Auto Email Reports and delete """
- auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": "Requested Items to Order"}, ["name"])
+ """Check for one or multiple Auto Email Reports and delete"""
+ auto_email_reports = frappe.db.get_values(
+ "Auto Email Report", {"report": "Requested Items to Order"}, ["name"]
+ )
for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0])
- frappe.db.sql("""
+ frappe.db.sql(
+ """
DELETE FROM `tabReport`
WHERE name = 'Requested Items to Order'
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py
index aa2a2d3b78..84b6c37dd9 100644
--- a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py
+++ b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py
@@ -7,13 +7,13 @@ from erpnext.regional.saudi_arabia.setup import add_print_formats
def execute():
- company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
+ company = frappe.get_all("Company", filters={"country": "Saudi Arabia"})
if company:
add_print_formats()
return
- if frappe.db.exists('DocType', 'Print Format'):
+ if frappe.db.exists("DocType", "Print Format"):
frappe.reload_doc("regional", "print_format", "ksa_vat_invoice", force=True)
frappe.reload_doc("regional", "print_format", "ksa_pos_invoice", force=True)
- for d in ('KSA VAT Invoice', 'KSA POS Invoice'):
+ for d in ("KSA VAT Invoice", "KSA POS Invoice"):
frappe.db.set_value("Print Format", d, "disabled", 1)
diff --git a/erpnext/patches/v13_0/drop_razorpay_payload_column.py b/erpnext/patches/v13_0/drop_razorpay_payload_column.py
index 611ba7e324..ca166cee74 100644
--- a/erpnext/patches/v13_0/drop_razorpay_payload_column.py
+++ b/erpnext/patches/v13_0/drop_razorpay_payload_column.py
@@ -3,5 +3,5 @@ import frappe
def execute():
if frappe.db.exists("DocType", "Membership"):
- if 'webhook_payload' in frappe.db.get_table_columns("Membership"):
+ if "webhook_payload" in frappe.db.get_table_columns("Membership"):
frappe.db.sql("alter table `tabMembership` drop column webhook_payload")
diff --git a/erpnext/patches/v13_0/enable_ksa_vat_docs.py b/erpnext/patches/v13_0/enable_ksa_vat_docs.py
index 3f482620e1..4adf4d71db 100644
--- a/erpnext/patches/v13_0/enable_ksa_vat_docs.py
+++ b/erpnext/patches/v13_0/enable_ksa_vat_docs.py
@@ -4,9 +4,9 @@ from erpnext.regional.saudi_arabia.setup import add_permissions, add_print_forma
def execute():
- company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
+ company = frappe.get_all("Company", filters={"country": "Saudi Arabia"})
if not company:
return
add_print_formats()
- add_permissions()
\ No newline at end of file
+ add_permissions()
diff --git a/erpnext/patches/v13_0/enable_provisional_accounting.py b/erpnext/patches/v13_0/enable_provisional_accounting.py
index 8e222700f8..dd18167b71 100644
--- a/erpnext/patches/v13_0/enable_provisional_accounting.py
+++ b/erpnext/patches/v13_0/enable_provisional_accounting.py
@@ -6,14 +6,11 @@ def execute():
company = frappe.qb.DocType("Company")
- frappe.qb.update(
- company
- ).set(
- company.enable_provisional_accounting_for_non_stock_items, company.enable_perpetual_inventory_for_non_stock_items
- ).set(
- company.default_provisional_account, company.service_received_but_not_billed
- ).where(
+ frappe.qb.update(company).set(
+ company.enable_provisional_accounting_for_non_stock_items,
+ company.enable_perpetual_inventory_for_non_stock_items,
+ ).set(company.default_provisional_account, company.service_received_but_not_billed).where(
company.enable_perpetual_inventory_for_non_stock_items == 1
).where(
company.service_received_but_not_billed.isnotnull()
- ).run()
\ No newline at end of file
+ ).run()
diff --git a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py
index 7a51b43211..68b5cde920 100644
--- a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py
+++ b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py
@@ -2,7 +2,6 @@ import frappe
def execute():
- frappe.reload_doc('core', 'doctype', 'scheduled_job_type')
- if frappe.db.exists('Scheduled Job Type', 'repost_item_valuation.repost_entries'):
- frappe.db.set_value('Scheduled Job Type',
- 'repost_item_valuation.repost_entries', 'stopped', 0)
+ frappe.reload_doc("core", "doctype", "scheduled_job_type")
+ if frappe.db.exists("Scheduled Job Type", "repost_item_valuation.repost_entries"):
+ frappe.db.set_value("Scheduled Job Type", "repost_item_valuation.repost_entries", "stopped", 0)
diff --git a/erpnext/patches/v13_0/enable_uoms.py b/erpnext/patches/v13_0/enable_uoms.py
index 4d3f637630..8efd67e280 100644
--- a/erpnext/patches/v13_0/enable_uoms.py
+++ b/erpnext/patches/v13_0/enable_uoms.py
@@ -2,12 +2,12 @@ import frappe
def execute():
- frappe.reload_doc('setup', 'doctype', 'uom')
+ frappe.reload_doc("setup", "doctype", "uom")
uom = frappe.qb.DocType("UOM")
- (frappe.qb
- .update(uom)
+ (
+ frappe.qb.update(uom)
.set(uom.enabled, 1)
.where(uom.creation >= "2021-10-18") # date when this field was released
).run()
diff --git a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py
index 32ad542cf8..9197d86058 100644
--- a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py
+++ b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py
@@ -2,15 +2,10 @@ import frappe
def execute():
- if frappe.db.has_column("Item", "thumbnail"):
- website_item = frappe.qb.DocType("Website Item").as_("wi")
- item = frappe.qb.DocType("Item")
+ if frappe.db.has_column("Item", "thumbnail"):
+ website_item = frappe.qb.DocType("Website Item").as_("wi")
+ item = frappe.qb.DocType("Item")
- frappe.qb.update(website_item).inner_join(item).on(
- website_item.item_code == item.item_code
- ).set(
- website_item.thumbnail, item.thumbnail
- ).where(
- website_item.website_image.notnull()
- & website_item.thumbnail.isnull()
- ).run()
+ frappe.qb.update(website_item).inner_join(item).on(website_item.item_code == item.item_code).set(
+ website_item.thumbnail, item.thumbnail
+ ).where(website_item.website_image.notnull() & website_item.thumbnail.isnull()).run()
diff --git a/erpnext/patches/v13_0/fix_invoice_statuses.py b/erpnext/patches/v13_0/fix_invoice_statuses.py
index 4395757159..253b425c58 100644
--- a/erpnext/patches/v13_0/fix_invoice_statuses.py
+++ b/erpnext/patches/v13_0/fix_invoice_statuses.py
@@ -8,6 +8,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
TODAY = getdate()
+
def execute():
# This fix is not related to Party Specific Item,
# but it is needed for code introduced after Party Specific Item was
@@ -35,39 +36,28 @@ def execute():
fields=fields,
filters={
"docstatus": 1,
- "status": ("in", (
- "Overdue",
- "Overdue and Discounted",
- "Partly Paid",
- "Partly Paid and Discounted"
- )),
+ "status": (
+ "in",
+ ("Overdue", "Overdue and Discounted", "Partly Paid", "Partly Paid and Discounted"),
+ ),
"outstanding_amount": (">", 0),
"modified": (">", "2021-01-01")
# an assumption is being made that only invoices modified
# after 2021 got affected as incorrectly overdue.
# required for performance reasons.
- }
+ },
)
- invoices_to_update = {
- invoice.name: invoice for invoice in invoices_to_update
- }
+ invoices_to_update = {invoice.name: invoice for invoice in invoices_to_update}
payment_schedule_items = frappe.get_all(
"Payment Schedule",
- fields=(
- "due_date",
- "payment_amount",
- "base_payment_amount",
- "parent"
- ),
- filters={"parent": ("in", invoices_to_update)}
+ fields=("due_date", "payment_amount", "base_payment_amount", "parent"),
+ filters={"parent": ("in", invoices_to_update)},
)
for item in payment_schedule_items:
- invoices_to_update[item.parent].setdefault(
- "payment_schedule", []
- ).append(item)
+ invoices_to_update[item.parent].setdefault("payment_schedule", []).append(item)
status_map = {}
@@ -81,19 +71,11 @@ def execute():
status_map.setdefault(correct_status, []).append(doc.name)
for status, docs in status_map.items():
- frappe.db.set_value(
- doctype, {"name": ("in", docs)},
- "status",
- status,
- update_modified=False
- )
-
+ frappe.db.set_value(doctype, {"name": ("in", docs)}, "status", status, update_modified=False)
def get_correct_status(doc):
- outstanding_amount = flt(
- doc.outstanding_amount, doc.precision("outstanding_amount")
- )
+ outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
total = get_total_in_party_account_currency(doc)
status = ""
diff --git a/erpnext/patches/v13_0/fix_non_unique_represents_company.py b/erpnext/patches/v13_0/fix_non_unique_represents_company.py
index e91c1db4dd..c604f9cb24 100644
--- a/erpnext/patches/v13_0/fix_non_unique_represents_company.py
+++ b/erpnext/patches/v13_0/fix_non_unique_represents_company.py
@@ -2,8 +2,10 @@ import frappe
def execute():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update tabCustomer
set represents_company = NULL
where represents_company = ''
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
index 72cda751e6..fc3e68ac67 100644
--- a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
+++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
@@ -13,18 +13,24 @@ def execute():
"DATEV". This is no longer necessary. The reference ID for DATEV will be
stored in a new custom field "debtor_creditor_number".
"""
- company_list = frappe.get_all('Company', filters={'country': 'Germany'})
+ company_list = frappe.get_all("Company", filters={"country": "Germany"})
for company in company_list:
- party_account_list = frappe.get_all('Party Account', filters={'company': company.name}, fields=['name', 'account', 'debtor_creditor_number'])
+ party_account_list = frappe.get_all(
+ "Party Account",
+ filters={"company": company.name},
+ fields=["name", "account", "debtor_creditor_number"],
+ )
for party_account in party_account_list:
if (not party_account.account) or party_account.debtor_creditor_number:
# account empty or debtor_creditor_number already filled
continue
- account_number = frappe.db.get_value('Account', party_account.account, 'account_number')
+ account_number = frappe.db.get_value("Account", party_account.account, "account_number")
if not account_number:
continue
- frappe.db.set_value('Party Account', party_account.name, 'debtor_creditor_number', account_number)
- frappe.db.set_value('Party Account', party_account.name, 'account', '')
+ frappe.db.set_value(
+ "Party Account", party_account.name, "debtor_creditor_number", account_number
+ )
+ frappe.db.set_value("Party Account", party_account.name, "account", "")
diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py
index 80b6a3954a..cc358135ac 100644
--- a/erpnext/patches/v13_0/germany_make_custom_fields.py
+++ b/erpnext/patches/v13_0/germany_make_custom_fields.py
@@ -13,7 +13,7 @@ def execute():
It is usually run once at setup of a new company. Since it's new, run it
once for existing companies as well.
"""
- company_list = frappe.get_all('Company', filters = {'country': 'Germany'})
+ company_list = frappe.get_all("Company", filters={"country": "Germany"})
if not company_list:
return
diff --git a/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py
index 76f8b27499..efd2c21d6a 100644
--- a/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py
+++ b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py
@@ -3,40 +3,85 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name'])
+ company = frappe.get_all("Company", filters={"country": "India"}, fields=["name"])
if not company:
return
- hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
- fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
- allow_on_submit=1, print_hide=1, fetch_if_empty=1)
- nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted',
- fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code',
- print_hide=1)
- is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
- fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
- print_hide=1)
- taxable_value = dict(fieldname='taxable_value', label='Taxable Value',
- fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
- print_hide=1)
+ hsn_sac_field = dict(
+ fieldname="gst_hsn_code",
+ label="HSN/SAC",
+ fieldtype="Data",
+ fetch_from="item_code.gst_hsn_code",
+ insert_after="description",
+ allow_on_submit=1,
+ print_hide=1,
+ fetch_if_empty=1,
+ )
+ nil_rated_exempt = dict(
+ fieldname="is_nil_exempt",
+ label="Is Nil Rated or Exempted",
+ fieldtype="Check",
+ fetch_from="item_code.is_nil_exempt",
+ insert_after="gst_hsn_code",
+ print_hide=1,
+ )
+ is_non_gst = dict(
+ fieldname="is_non_gst",
+ label="Is Non GST",
+ fieldtype="Check",
+ fetch_from="item_code.is_non_gst",
+ insert_after="is_nil_exempt",
+ print_hide=1,
+ )
+ taxable_value = dict(
+ fieldname="taxable_value",
+ label="Taxable Value",
+ fieldtype="Currency",
+ insert_after="base_net_amount",
+ hidden=1,
+ options="Company:company:default_currency",
+ print_hide=1,
+ )
sales_invoice_gst_fields = [
- dict(fieldname='billing_address_gstin', label='Billing Address GSTIN',
- fieldtype='Data', insert_after='customer_address', read_only=1,
- fetch_from='customer_address.gstin', print_hide=1),
- dict(fieldname='customer_gstin', label='Customer GSTIN',
- fieldtype='Data', insert_after='shipping_address_name',
- fetch_from='shipping_address_name.gstin', print_hide=1),
- dict(fieldname='place_of_supply', label='Place of Supply',
- fieldtype='Data', insert_after='customer_gstin',
- print_hide=1, read_only=1),
- dict(fieldname='company_gstin', label='Company GSTIN',
- fieldtype='Data', insert_after='company_address',
- fetch_from='company_address.gstin', print_hide=1, read_only=1),
- ]
+ dict(
+ fieldname="billing_address_gstin",
+ label="Billing Address GSTIN",
+ fieldtype="Data",
+ insert_after="customer_address",
+ read_only=1,
+ fetch_from="customer_address.gstin",
+ print_hide=1,
+ ),
+ dict(
+ fieldname="customer_gstin",
+ label="Customer GSTIN",
+ fieldtype="Data",
+ insert_after="shipping_address_name",
+ fetch_from="shipping_address_name.gstin",
+ print_hide=1,
+ ),
+ dict(
+ fieldname="place_of_supply",
+ label="Place of Supply",
+ fieldtype="Data",
+ insert_after="customer_gstin",
+ print_hide=1,
+ read_only=1,
+ ),
+ dict(
+ fieldname="company_gstin",
+ label="Company GSTIN",
+ fieldtype="Data",
+ insert_after="company_address",
+ fetch_from="company_address.gstin",
+ print_hide=1,
+ read_only=1,
+ ),
+ ]
custom_fields = {
- 'POS Invoice': sales_invoice_gst_fields,
- 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
+ "POS Invoice": sales_invoice_gst_fields,
+ "POS Invoice Item": [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
}
- create_custom_fields(custom_fields, update=True)
\ No newline at end of file
+ create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
index 98ce12bc20..30b84accf3 100644
--- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
+++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
@@ -3,88 +3,92 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- if frappe.db.exists('DocType', 'Lab Test') and frappe.db.exists('DocType', 'Lab Test Template'):
+ if frappe.db.exists("DocType", "Lab Test") and frappe.db.exists("DocType", "Lab Test Template"):
# rename child doctypes
doctypes = {
- 'Lab Test Groups': 'Lab Test Group Template',
- 'Normal Test Items': 'Normal Test Result',
- 'Sensitivity Test Items': 'Sensitivity Test Result',
- 'Special Test Items': 'Descriptive Test Result',
- 'Special Test Template': 'Descriptive Test Template'
+ "Lab Test Groups": "Lab Test Group Template",
+ "Normal Test Items": "Normal Test Result",
+ "Sensitivity Test Items": "Sensitivity Test Result",
+ "Special Test Items": "Descriptive Test Result",
+ "Special Test Template": "Descriptive Test Template",
}
- frappe.reload_doc('healthcare', 'doctype', 'lab_test')
- frappe.reload_doc('healthcare', 'doctype', 'lab_test_template')
+ frappe.reload_doc("healthcare", "doctype", "lab_test")
+ frappe.reload_doc("healthcare", "doctype", "lab_test_template")
for old_dt, new_dt in doctypes.items():
frappe.flags.link_fields = {}
- should_rename = (
- frappe.db.table_exists(old_dt)
- and not frappe.db.table_exists(new_dt)
- )
+ should_rename = frappe.db.table_exists(old_dt) and not frappe.db.table_exists(new_dt)
if should_rename:
- frappe.reload_doc('healthcare', 'doctype', frappe.scrub(old_dt))
- frappe.rename_doc('DocType', old_dt, new_dt, force=True)
- frappe.reload_doc('healthcare', 'doctype', frappe.scrub(new_dt))
- frappe.delete_doc_if_exists('DocType', old_dt)
+ frappe.reload_doc("healthcare", "doctype", frappe.scrub(old_dt))
+ frappe.rename_doc("DocType", old_dt, new_dt, force=True)
+ frappe.reload_doc("healthcare", "doctype", frappe.scrub(new_dt))
+ frappe.delete_doc_if_exists("DocType", old_dt)
parent_fields = {
- 'Lab Test Group Template': 'lab_test_groups',
- 'Descriptive Test Template': 'descriptive_test_templates',
- 'Normal Test Result': 'normal_test_items',
- 'Sensitivity Test Result': 'sensitivity_test_items',
- 'Descriptive Test Result': 'descriptive_test_items'
+ "Lab Test Group Template": "lab_test_groups",
+ "Descriptive Test Template": "descriptive_test_templates",
+ "Normal Test Result": "normal_test_items",
+ "Sensitivity Test Result": "sensitivity_test_items",
+ "Descriptive Test Result": "descriptive_test_items",
}
for doctype, parentfield in parent_fields.items():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tab{0}`
SET parentfield = %(parentfield)s
- """.format(doctype), {'parentfield': parentfield})
+ """.format(
+ doctype
+ ),
+ {"parentfield": parentfield},
+ )
# copy renamed child table fields (fields were already renamed in old doctype json, hence sql)
rename_fields = {
- 'lab_test_name': 'test_name',
- 'lab_test_event': 'test_event',
- 'lab_test_uom': 'test_uom',
- 'lab_test_comment': 'test_comment'
+ "lab_test_name": "test_name",
+ "lab_test_event": "test_event",
+ "lab_test_uom": "test_uom",
+ "lab_test_comment": "test_comment",
}
for new, old in rename_fields.items():
- if frappe.db.has_column('Normal Test Result', old):
- frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}"""
- .format(new, old))
+ if frappe.db.has_column("Normal Test Result", old):
+ frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}""".format(new, old))
- if frappe.db.has_column('Normal Test Template', 'test_event'):
+ if frappe.db.has_column("Normal Test Template", "test_event"):
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""")
- if frappe.db.has_column('Normal Test Template', 'test_uom'):
+ if frappe.db.has_column("Normal Test Template", "test_uom"):
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""")
- if frappe.db.has_column('Descriptive Test Result', 'test_particulars'):
- frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
+ if frappe.db.has_column("Descriptive Test Result", "test_particulars"):
+ frappe.db.sql(
+ """UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars"""
+ )
rename_fields = {
- 'lab_test_template': 'test_template',
- 'lab_test_description': 'test_description',
- 'lab_test_rate': 'test_rate'
+ "lab_test_template": "test_template",
+ "lab_test_description": "test_description",
+ "lab_test_rate": "test_rate",
}
for new, old in rename_fields.items():
- if frappe.db.has_column('Lab Test Group Template', old):
- frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}"""
- .format(new, old))
+ if frappe.db.has_column("Lab Test Group Template", old):
+ frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}""".format(new, old))
# rename field
- frappe.reload_doc('healthcare', 'doctype', 'lab_test')
- if frappe.db.has_column('Lab Test', 'special_toggle'):
- rename_field('Lab Test', 'special_toggle', 'descriptive_toggle')
+ frappe.reload_doc("healthcare", "doctype", "lab_test")
+ if frappe.db.has_column("Lab Test", "special_toggle"):
+ rename_field("Lab Test", "special_toggle", "descriptive_toggle")
- if frappe.db.exists('DocType', 'Lab Test Group Template'):
+ if frappe.db.exists("DocType", "Lab Test Group Template"):
# fix select field option
- frappe.reload_doc('healthcare', 'doctype', 'lab_test_group_template')
- frappe.db.sql("""
+ frappe.reload_doc("healthcare", "doctype", "lab_test_group_template")
+ frappe.db.sql(
+ """
UPDATE `tabLab Test Group Template`
SET template_or_new_line = 'Add New Line'
WHERE template_or_new_line = 'Add new line'
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/item_naming_series_not_mandatory.py b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py
index 5fe85a4830..33fb8f963c 100644
--- a/erpnext/patches/v13_0/item_naming_series_not_mandatory.py
+++ b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py
@@ -7,5 +7,10 @@ def execute():
stock_settings = frappe.get_doc("Stock Settings")
- set_by_naming_series("Item", "item_code",
- stock_settings.get("item_naming_by")=="Naming Series", hide_name_field=True, make_mandatory=0)
+ set_by_naming_series(
+ "Item",
+ "item_code",
+ stock_settings.get("item_naming_by") == "Naming Series",
+ hide_name_field=True,
+ make_mandatory=0,
+ )
diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
index 0f2ac4b451..f6427ca55a 100644
--- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
+++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
@@ -7,18 +7,18 @@ from erpnext.stock.stock_ledger import update_entries_after
def execute():
doctypes_to_reload = [
- ("stock", "repost_item_valuation"),
- ("stock", "stock_entry_detail"),
- ("stock", "purchase_receipt_item"),
- ("stock", "delivery_note_item"),
- ("stock", "packed_item"),
- ("accounts", "sales_invoice_item"),
- ("accounts", "purchase_invoice_item"),
- ("buying", "purchase_receipt_item_supplied")
- ]
+ ("stock", "repost_item_valuation"),
+ ("stock", "stock_entry_detail"),
+ ("stock", "purchase_receipt_item"),
+ ("stock", "delivery_note_item"),
+ ("stock", "packed_item"),
+ ("accounts", "sales_invoice_item"),
+ ("accounts", "purchase_invoice_item"),
+ ("buying", "purchase_receipt_item_supplied"),
+ ]
for module, doctype in doctypes_to_reload:
- frappe.reload_doc(module, 'doctype', doctype)
+ frappe.reload_doc(module, "doctype", doctype)
reposting_project_deployed_on = get_creation_time()
posting_date = getdate(reposting_project_deployed_on)
@@ -32,7 +32,8 @@ def execute():
company_list = []
- data = frappe.db.sql('''
+ data = frappe.db.sql(
+ """
SELECT
name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time, company
FROM
@@ -41,7 +42,10 @@ def execute():
creation > %s
and is_cancelled = 0
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
- ''', reposting_project_deployed_on, as_dict=1)
+ """,
+ reposting_project_deployed_on,
+ as_dict=1,
+ )
frappe.db.auto_commit_on_many_writes = 1
print("Reposting Stock Ledger Entries...")
@@ -51,30 +55,36 @@ def execute():
if d.company not in company_list:
company_list.append(d.company)
- update_entries_after({
- "item_code": d.item_code,
- "warehouse": d.warehouse,
- "posting_date": d.posting_date,
- "posting_time": d.posting_time,
- "voucher_type": d.voucher_type,
- "voucher_no": d.voucher_no,
- "sle_id": d.name
- }, allow_negative_stock=True)
+ update_entries_after(
+ {
+ "item_code": d.item_code,
+ "warehouse": d.warehouse,
+ "posting_date": d.posting_date,
+ "posting_time": d.posting_time,
+ "voucher_type": d.voucher_type,
+ "voucher_no": d.voucher_no,
+ "sle_id": d.name,
+ },
+ allow_negative_stock=True,
+ )
i += 1
- if i%100 == 0:
+ if i % 100 == 0:
print(i, "/", total_sle)
-
print("Reposting General Ledger Entries...")
if data:
- for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
+ for row in frappe.get_all("Company", filters={"enable_perpetual_inventory": 1}):
if row.name in company_list:
update_gl_entries_after(posting_date, posting_time, company=row.name)
frappe.db.auto_commit_on_many_writes = 0
+
def get_creation_time():
- return frappe.db.sql(''' SELECT create_time FROM
- INFORMATION_SCHEMA.TABLES where TABLE_NAME = "tabRepost Item Valuation" ''', as_list=1)[0][0]
+ return frappe.db.sql(
+ """ SELECT create_time FROM
+ INFORMATION_SCHEMA.TABLES where TABLE_NAME = "tabRepost Item Valuation" """,
+ as_list=1,
+ )[0][0]
diff --git a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py
index 68bcd8a8da..69a695ef30 100644
--- a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py
+++ b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py
@@ -6,15 +6,16 @@ import frappe
def execute():
- '''`sales_invoice` field from loyalty point entry is splitted into `invoice_type` & `invoice` fields'''
+ """`sales_invoice` field from loyalty point entry is splitted into `invoice_type` & `invoice` fields"""
frappe.reload_doc("Accounts", "doctype", "loyalty_point_entry")
- if not frappe.db.has_column('Loyalty Point Entry', 'sales_invoice'):
+ if not frappe.db.has_column("Loyalty Point Entry", "sales_invoice"):
return
frappe.db.sql(
"""UPDATE `tabLoyalty Point Entry` lpe
SET lpe.`invoice_type` = 'Sales Invoice', lpe.`invoice` = lpe.`sales_invoice`
WHERE lpe.`sales_invoice` IS NOT NULL
- AND (lpe.`invoice` IS NULL OR lpe.`invoice` = '')""")
+ AND (lpe.`invoice` IS NULL OR lpe.`invoice` = '')"""
+ )
diff --git a/erpnext/patches/v13_0/make_homepage_products_website_items.py b/erpnext/patches/v13_0/make_homepage_products_website_items.py
index 7a7ddba12d..50bfd358ea 100644
--- a/erpnext/patches/v13_0/make_homepage_products_website_items.py
+++ b/erpnext/patches/v13_0/make_homepage_products_website_items.py
@@ -12,4 +12,4 @@ def execute():
row.item_code = web_item
homepage.flags.ignore_mandatory = True
- homepage.save()
\ No newline at end of file
+ homepage.save()
diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py
index ff241a3fd9..e2c068567b 100644
--- a/erpnext/patches/v13_0/make_non_standard_user_type.py
+++ b/erpnext/patches/v13_0/make_non_standard_user_type.py
@@ -9,22 +9,34 @@ from erpnext.setup.install import add_non_standard_user_types
def execute():
doctype_dict = {
- 'projects': ['Timesheet'],
- 'payroll': [
- 'Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission',
- 'Employee Benefit Application', 'Employee Benefit Claim'
+ "projects": ["Timesheet"],
+ "payroll": [
+ "Salary Slip",
+ "Employee Tax Exemption Declaration",
+ "Employee Tax Exemption Proof Submission",
+ "Employee Benefit Application",
+ "Employee Benefit Claim",
+ ],
+ "hr": [
+ "Employee",
+ "Expense Claim",
+ "Leave Application",
+ "Attendance Request",
+ "Compensatory Leave Request",
+ "Holiday List",
+ "Employee Advance",
+ "Training Program",
+ "Training Feedback",
+ "Shift Request",
+ "Employee Grievance",
+ "Employee Referral",
+ "Travel Request",
],
- 'hr': [
- 'Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request',
- 'Holiday List', 'Employee Advance', 'Training Program', 'Training Feedback',
- 'Shift Request', 'Employee Grievance', 'Employee Referral', 'Travel Request'
- ]
}
for module, doctypes in doctype_dict.items():
for doctype in doctypes:
- frappe.reload_doc(module, 'doctype', doctype)
-
+ frappe.reload_doc(module, "doctype", doctype)
frappe.flags.ignore_select_perm = True
frappe.flags.update_select_perm_after_migrate = True
diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py
index 1c65998009..492e0403ec 100644
--- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py
+++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py
@@ -4,10 +4,11 @@ import frappe
def execute():
- frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_advance')
- frappe.reload_doc('accounts', 'doctype', 'sales_invoice_advance')
+ frappe.reload_doc("accounts", "doctype", "purchase_invoice_advance")
+ frappe.reload_doc("accounts", "doctype", "sales_invoice_advance")
- purchase_invoices = frappe.db.sql("""
+ purchase_invoices = frappe.db.sql(
+ """
select
parenttype as type, parent as name
from
@@ -18,9 +19,12 @@ def execute():
and ifnull(exchange_gain_loss, 0) != 0
group by
parent
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
- sales_invoices = frappe.db.sql("""
+ sales_invoices = frappe.db.sql(
+ """
select
parenttype as type, parent as name
from
@@ -31,14 +35,16 @@ def execute():
and ifnull(exchange_gain_loss, 0) != 0
group by
parent
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
if purchase_invoices + sales_invoices:
frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log")
- acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
+ acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
if acc_frozen_upto:
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
+ frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
for invoice in purchase_invoices + sales_invoices:
try:
@@ -47,13 +53,13 @@ def execute():
doc.make_gl_entries()
for advance in doc.advances:
if advance.ref_exchange_rate == 1:
- advance.db_set('exchange_gain_loss', 0, False)
+ advance.db_set("exchange_gain_loss", 0, False)
doc.docstatus = 1
doc.make_gl_entries()
frappe.db.commit()
except Exception:
frappe.db.rollback()
- print(f'Failed to correct gl entries of {invoice.name}')
+ print(f"Failed to correct gl entries of {invoice.name}")
if acc_frozen_upto:
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto)
\ No newline at end of file
+ frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", acc_frozen_upto)
diff --git a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py
index 350744fd41..2406127148 100644
--- a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py
+++ b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py
@@ -7,11 +7,15 @@ import frappe
def execute():
- frappe.reload_doc('accounts', 'doctype', 'bank_account')
- frappe.reload_doc('accounts', 'doctype', 'bank')
+ frappe.reload_doc("accounts", "doctype", "bank_account")
+ frappe.reload_doc("accounts", "doctype", "bank")
- if frappe.db.has_column('Bank', 'branch_code') and frappe.db.has_column('Bank Account', 'branch_code'):
- frappe.db.sql("""UPDATE `tabBank` b, `tabBank Account` ba
+ if frappe.db.has_column("Bank", "branch_code") and frappe.db.has_column(
+ "Bank Account", "branch_code"
+ ):
+ frappe.db.sql(
+ """UPDATE `tabBank` b, `tabBank Account` ba
SET ba.branch_code = b.branch_code
WHERE ba.bank = b.name AND
- ifnull(b.branch_code, '') != '' AND ifnull(ba.branch_code, '') = ''""")
+ ifnull(b.branch_code, '') != '' AND ifnull(ba.branch_code, '') = ''"""
+ )
diff --git a/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py b/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py
index c07caaef66..0290af0f73 100644
--- a/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py
+++ b/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py
@@ -6,47 +6,47 @@ import frappe
def execute():
- frappe.db.sql("""UPDATE `tabPrint Format`
+ frappe.db.sql(
+ """UPDATE `tabPrint Format`
SET module = 'Payroll'
WHERE name IN ('Salary Slip Based On Timesheet', 'Salary Slip Standard')"""
- )
+ )
- frappe.db.sql("""UPDATE `tabNotification` SET module='Payroll' WHERE name='Retention Bonus';"""
- )
+ frappe.db.sql("""UPDATE `tabNotification` SET module='Payroll' WHERE name='Retention Bonus';""")
- doctypes_moved = [
- 'Employee Benefit Application Detail',
- 'Employee Tax Exemption Declaration Category',
- 'Salary Component',
- 'Employee Tax Exemption Proof Submission Detail',
- 'Income Tax Slab Other Charges',
- 'Taxable Salary Slab',
- 'Payroll Period Date',
- 'Salary Slip Timesheet',
- 'Payroll Employee Detail',
- 'Salary Detail',
- 'Employee Tax Exemption Sub Category',
- 'Employee Tax Exemption Category',
- 'Employee Benefit Claim',
- 'Employee Benefit Application',
- 'Employee Other Income',
- 'Employee Tax Exemption Proof Submission',
- 'Employee Tax Exemption Declaration',
- 'Employee Incentive',
- 'Retention Bonus',
- 'Additional Salary',
- 'Income Tax Slab',
- 'Payroll Period',
- 'Salary Slip',
- 'Payroll Entry',
- 'Salary Structure Assignment',
- 'Salary Structure'
- ]
+ doctypes_moved = [
+ "Employee Benefit Application Detail",
+ "Employee Tax Exemption Declaration Category",
+ "Salary Component",
+ "Employee Tax Exemption Proof Submission Detail",
+ "Income Tax Slab Other Charges",
+ "Taxable Salary Slab",
+ "Payroll Period Date",
+ "Salary Slip Timesheet",
+ "Payroll Employee Detail",
+ "Salary Detail",
+ "Employee Tax Exemption Sub Category",
+ "Employee Tax Exemption Category",
+ "Employee Benefit Claim",
+ "Employee Benefit Application",
+ "Employee Other Income",
+ "Employee Tax Exemption Proof Submission",
+ "Employee Tax Exemption Declaration",
+ "Employee Incentive",
+ "Retention Bonus",
+ "Additional Salary",
+ "Income Tax Slab",
+ "Payroll Period",
+ "Salary Slip",
+ "Payroll Entry",
+ "Salary Structure Assignment",
+ "Salary Structure",
+ ]
- for doctype in doctypes_moved:
- frappe.delete_doc_if_exists("DocType", doctype)
+ for doctype in doctypes_moved:
+ frappe.delete_doc_if_exists("DocType", doctype)
- reports = ["Salary Register", "Bank Remittance"]
+ reports = ["Salary Register", "Bank Remittance"]
- for report in reports:
- frappe.delete_doc_if_exists("Report", report)
+ for report in reports:
+ frappe.delete_doc_if_exists("Report", report)
diff --git a/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py b/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py
index fca7c09c91..37a3c357b3 100644
--- a/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py
+++ b/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py
@@ -6,7 +6,8 @@ import frappe
def execute():
- data = frappe.db.sql('''SELECT *
+ data = frappe.db.sql(
+ """SELECT *
FROM `tabSingles`
WHERE
doctype = "HR Settings"
@@ -21,7 +22,9 @@ def execute():
"payroll_based_on",
"password_policy"
)
- ''', as_dict=1)
+ """,
+ as_dict=1,
+ )
- for d in data:
- frappe.db.set_value("Payroll Settings", None, d.field, d.value)
+ for d in data:
+ frappe.db.set_value("Payroll Settings", None, d.field, d.value)
diff --git a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
index d1ea22f7f2..f84a739d74 100644
--- a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
+++ b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
@@ -6,28 +6,42 @@ import frappe
def execute():
- if not (frappe.db.table_exists("Payroll Period") and frappe.db.table_exists("Taxable Salary Slab")):
+ if not (
+ frappe.db.table_exists("Payroll Period") and frappe.db.table_exists("Taxable Salary Slab")
+ ):
return
- for doctype in ("income_tax_slab", "salary_structure_assignment", "employee_other_income", "income_tax_slab_other_charges"):
+ for doctype in (
+ "income_tax_slab",
+ "salary_structure_assignment",
+ "employee_other_income",
+ "income_tax_slab_other_charges",
+ ):
frappe.reload_doc("Payroll", "doctype", doctype)
-
- standard_tax_exemption_amount_exists = frappe.db.has_column("Payroll Period", "standard_tax_exemption_amount")
+ standard_tax_exemption_amount_exists = frappe.db.has_column(
+ "Payroll Period", "standard_tax_exemption_amount"
+ )
select_fields = "name, start_date, end_date"
if standard_tax_exemption_amount_exists:
select_fields = "name, start_date, end_date, standard_tax_exemption_amount"
for company in frappe.get_all("Company"):
- payroll_periods = frappe.db.sql("""
+ payroll_periods = frappe.db.sql(
+ """
SELECT
{0}
FROM
`tabPayroll Period`
WHERE company=%s
ORDER BY start_date DESC
- """.format(select_fields), company.name, as_dict = 1)
+ """.format(
+ select_fields
+ ),
+ company.name,
+ as_dict=1,
+ )
for i, period in enumerate(payroll_periods):
income_tax_slab = frappe.new_doc("Income Tax Slab")
@@ -48,13 +62,17 @@ def execute():
income_tax_slab.submit()
frappe.db.sql(
- """ UPDATE `tabTaxable Salary Slab`
+ """ UPDATE `tabTaxable Salary Slab`
SET parent = %s , parentfield = 'slabs' , parenttype = "Income Tax Slab"
WHERE parent = %s
- """, (income_tax_slab.name, period.name), as_dict = 1)
+ """,
+ (income_tax_slab.name, period.name),
+ as_dict=1,
+ )
if i == 0:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabSalary Structure Assignment`
set
@@ -63,16 +81,19 @@ def execute():
company = %s
and from_date >= %s
and docstatus < 2
- """, (income_tax_slab.name, company.name, period.start_date))
+ """,
+ (income_tax_slab.name, company.name, period.start_date),
+ )
# move other incomes to separate document
if not frappe.db.table_exists("Employee Tax Exemption Proof Submission"):
return
migrated = []
- proofs = frappe.get_all("Employee Tax Exemption Proof Submission",
- filters = {'docstatus': 1},
- fields =['payroll_period', 'employee', 'company', 'income_from_other_sources']
+ proofs = frappe.get_all(
+ "Employee Tax Exemption Proof Submission",
+ filters={"docstatus": 1},
+ fields=["payroll_period", "employee", "company", "income_from_other_sources"],
)
for proof in proofs:
if proof.income_from_other_sources:
@@ -91,14 +112,17 @@ def execute():
if not frappe.db.table_exists("Employee Tax Exemption Declaration"):
return
- declerations = frappe.get_all("Employee Tax Exemption Declaration",
- filters = {'docstatus': 1},
- fields =['payroll_period', 'employee', 'company', 'income_from_other_sources']
+ declerations = frappe.get_all(
+ "Employee Tax Exemption Declaration",
+ filters={"docstatus": 1},
+ fields=["payroll_period", "employee", "company", "income_from_other_sources"],
)
for declaration in declerations:
- if declaration.income_from_other_sources \
- and [declaration.employee, declaration.payroll_period] not in migrated:
+ if (
+ declaration.income_from_other_sources
+ and [declaration.employee, declaration.payroll_period] not in migrated
+ ):
employee_other_income = frappe.new_doc("Employee Other Income")
employee_other_income.employee = declaration.employee
employee_other_income.payroll_period = declaration.payroll_period
diff --git a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py
index 7c10a313a5..edd0a9706b 100644
--- a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py
+++ b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py
@@ -10,43 +10,54 @@ def execute():
frappe.reload_doc("hr", "doctype", "Leave Encashment")
- additional_salaries = frappe.get_all("Additional Salary",
- fields = ['name', "salary_slip", "type", "salary_component"],
- filters = {'salary_slip': ['!=', '']},
- group_by = 'salary_slip'
+ additional_salaries = frappe.get_all(
+ "Additional Salary",
+ fields=["name", "salary_slip", "type", "salary_component"],
+ filters={"salary_slip": ["!=", ""]},
+ group_by="salary_slip",
)
- leave_encashments = frappe.get_all("Leave Encashment",
- fields = ["name","additional_salary"],
- filters = {'additional_salary': ['!=', '']}
+ leave_encashments = frappe.get_all(
+ "Leave Encashment",
+ fields=["name", "additional_salary"],
+ filters={"additional_salary": ["!=", ""]},
)
- employee_incentives = frappe.get_all("Employee Incentive",
- fields= ["name", "additional_salary"],
- filters = {'additional_salary': ['!=', '']}
+ employee_incentives = frappe.get_all(
+ "Employee Incentive",
+ fields=["name", "additional_salary"],
+ filters={"additional_salary": ["!=", ""]},
)
for incentive in employee_incentives:
- frappe.db.sql(""" UPDATE `tabAdditional Salary`
+ frappe.db.sql(
+ """ UPDATE `tabAdditional Salary`
SET ref_doctype = 'Employee Incentive', ref_docname = %s
WHERE name = %s
- """, (incentive['name'], incentive['additional_salary']))
-
+ """,
+ (incentive["name"], incentive["additional_salary"]),
+ )
for leave_encashment in leave_encashments:
- frappe.db.sql(""" UPDATE `tabAdditional Salary`
+ frappe.db.sql(
+ """ UPDATE `tabAdditional Salary`
SET ref_doctype = 'Leave Encashment', ref_docname = %s
WHERE name = %s
- """, (leave_encashment['name'], leave_encashment['additional_salary']))
+ """,
+ (leave_encashment["name"], leave_encashment["additional_salary"]),
+ )
salary_slips = [sal["salary_slip"] for sal in additional_salaries]
for salary in additional_salaries:
- comp_type = "earnings" if salary['type'] == 'Earning' else 'deductions'
+ comp_type = "earnings" if salary["type"] == "Earning" else "deductions"
if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabSalary Detail`
SET additional_salary = %s
WHERE parenttype = 'Salary Slip'
and parentfield = %s
and parent = %s
and salary_component = %s
- """, (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]))
+ """,
+ (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]),
+ )
diff --git a/erpnext/patches/v13_0/populate_e_commerce_settings.py b/erpnext/patches/v13_0/populate_e_commerce_settings.py
index 8f9ee512fd..ecf512b011 100644
--- a/erpnext/patches/v13_0/populate_e_commerce_settings.py
+++ b/erpnext/patches/v13_0/populate_e_commerce_settings.py
@@ -8,17 +8,30 @@ def execute():
frappe.reload_doc("portal", "doctype", "website_attribute")
products_settings_fields = [
- "hide_variants", "products_per_page",
- "enable_attribute_filters", "enable_field_filters"
+ "hide_variants",
+ "products_per_page",
+ "enable_attribute_filters",
+ "enable_field_filters",
]
shopping_cart_settings_fields = [
- "enabled", "show_attachments", "show_price",
- "show_stock_availability", "enable_variants", "show_contact_us_button",
- "show_quantity_in_website", "show_apply_coupon_code_in_website",
- "allow_items_not_in_stock", "company", "price_list", "default_customer_group",
- "quotation_series", "enable_checkout", "payment_success_url",
- "payment_gateway_account", "save_quotations_as_draft"
+ "enabled",
+ "show_attachments",
+ "show_price",
+ "show_stock_availability",
+ "enable_variants",
+ "show_contact_us_button",
+ "show_quantity_in_website",
+ "show_apply_coupon_code_in_website",
+ "allow_items_not_in_stock",
+ "company",
+ "price_list",
+ "default_customer_group",
+ "quotation_series",
+ "enable_checkout",
+ "payment_success_url",
+ "payment_gateway_account",
+ "save_quotations_as_draft",
]
settings = frappe.get_doc("E Commerce Settings")
@@ -27,12 +40,8 @@ def execute():
singles = frappe.qb.DocType("Singles")
query = (
frappe.qb.from_(singles)
- .select(
- singles["field"], singles.value
- ).where(
- (singles.doctype == doctype)
- & (singles["field"].isin(fields))
- )
+ .select(singles["field"], singles.value)
+ .where((singles.doctype == doctype) & (singles["field"].isin(fields)))
)
data = query.run(as_dict=True)
@@ -54,9 +63,6 @@ def execute():
frappe.db.set_value(
doctype,
{"parent": "Products Settings"},
- {
- "parenttype": "E Commerce Settings",
- "parent": "E Commerce Settings"
- },
- update_modified=False
+ {"parenttype": "E Commerce Settings", "parent": "E Commerce Settings"},
+ update_modified=False,
)
diff --git a/erpnext/patches/v13_0/print_uom_after_quantity_patch.py b/erpnext/patches/v13_0/print_uom_after_quantity_patch.py
index 3da6f749af..a16f909fc3 100644
--- a/erpnext/patches/v13_0/print_uom_after_quantity_patch.py
+++ b/erpnext/patches/v13_0/print_uom_after_quantity_patch.py
@@ -6,4 +6,4 @@ from erpnext.setup.install import create_print_uom_after_qty_custom_field
def execute():
- create_print_uom_after_qty_custom_field()
+ create_print_uom_after_qty_custom_field()
diff --git a/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py
index bbe3eb5815..4efbe4df96 100644
--- a/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py
+++ b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py
@@ -5,5 +5,7 @@ def execute():
"""Remove has_variants and attribute fields from item variant settings."""
frappe.reload_doc("stock", "doctype", "Item Variant Settings")
- frappe.db.sql("""delete from `tabVariant Field`
- where field_name in ('attributes', 'has_variants')""")
+ frappe.db.sql(
+ """delete from `tabVariant Field`
+ where field_name in ('attributes', 'has_variants')"""
+ )
diff --git a/erpnext/patches/v13_0/remove_bad_selling_defaults.py b/erpnext/patches/v13_0/remove_bad_selling_defaults.py
index 02625396dd..efd2098d9e 100644
--- a/erpnext/patches/v13_0/remove_bad_selling_defaults.py
+++ b/erpnext/patches/v13_0/remove_bad_selling_defaults.py
@@ -3,7 +3,7 @@ from frappe import _
def execute():
- frappe.reload_doctype('Selling Settings')
+ frappe.reload_doctype("Selling Settings")
selling_settings = frappe.get_single("Selling Settings")
if selling_settings.customer_group in (_("All Customer Groups"), "All Customer Groups"):
@@ -12,5 +12,5 @@ def execute():
if selling_settings.territory in (_("All Territories"), "All Territories"):
selling_settings.territory = None
- selling_settings.flags.ignore_mandatory=True
+ selling_settings.flags.ignore_mandatory = True
selling_settings.save(ignore_permissions=True)
diff --git a/erpnext/patches/v13_0/remove_unknown_links_to_prod_plan_items.py b/erpnext/patches/v13_0/remove_unknown_links_to_prod_plan_items.py
index 317e85e63d..3f4a51b442 100644
--- a/erpnext/patches/v13_0/remove_unknown_links_to_prod_plan_items.py
+++ b/erpnext/patches/v13_0/remove_unknown_links_to_prod_plan_items.py
@@ -10,25 +10,23 @@ def execute():
pp_item = frappe.qb.DocType("Production Plan Item")
broken_work_orders = (
- frappe.qb
- .from_(work_order)
- .left_join(pp_item).on(work_order.production_plan_item == pp_item.name)
- .select(work_order.name)
- .where(
- (work_order.docstatus == 0)
- & (work_order.production_plan_item.notnull())
- & (work_order.production_plan_item.like("new-production-plan%"))
- & (pp_item.name.isnull())
- )
+ frappe.qb.from_(work_order)
+ .left_join(pp_item)
+ .on(work_order.production_plan_item == pp_item.name)
+ .select(work_order.name)
+ .where(
+ (work_order.docstatus == 0)
+ & (work_order.production_plan_item.notnull())
+ & (work_order.production_plan_item.like("new-production-plan%"))
+ & (pp_item.name.isnull())
+ )
).run(pluck=True)
if not broken_work_orders:
return
- (frappe.qb
- .update(work_order)
+ (
+ frappe.qb.update(work_order)
.set(work_order.production_plan_item, None)
.where(work_order.name.isin(broken_work_orders))
).run()
-
-
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index 80d51652ab..a9b6df7098 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -7,63 +7,78 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- if frappe.db.exists('DocType', 'Issue'):
- issues = frappe.db.get_all('Issue', fields=['name', 'response_by_variance', 'resolution_by_variance', 'mins_to_first_response'],
- order_by='creation desc')
- frappe.reload_doc('support', 'doctype', 'issue')
+ if frappe.db.exists("DocType", "Issue"):
+ issues = frappe.db.get_all(
+ "Issue",
+ fields=["name", "response_by_variance", "resolution_by_variance", "mins_to_first_response"],
+ order_by="creation desc",
+ )
+ frappe.reload_doc("support", "doctype", "issue")
# rename fields
rename_map = {
- 'agreement_fulfilled': 'agreement_status',
- 'mins_to_first_response': 'first_response_time'
+ "agreement_fulfilled": "agreement_status",
+ "mins_to_first_response": "first_response_time",
}
for old, new in rename_map.items():
- rename_field('Issue', old, new)
+ rename_field("Issue", old, new)
# change fieldtype to duration
count = 0
for entry in issues:
- response_by_variance = convert_to_seconds(entry.response_by_variance, 'Hours')
- resolution_by_variance = convert_to_seconds(entry.resolution_by_variance, 'Hours')
- mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
- frappe.db.set_value('Issue', entry.name, {
- 'response_by_variance': response_by_variance,
- 'resolution_by_variance': resolution_by_variance,
- 'first_response_time': mins_to_first_response
- }, update_modified=False)
+ response_by_variance = convert_to_seconds(entry.response_by_variance, "Hours")
+ resolution_by_variance = convert_to_seconds(entry.resolution_by_variance, "Hours")
+ mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, "Minutes")
+ frappe.db.set_value(
+ "Issue",
+ entry.name,
+ {
+ "response_by_variance": response_by_variance,
+ "resolution_by_variance": resolution_by_variance,
+ "first_response_time": mins_to_first_response,
+ },
+ update_modified=False,
+ )
# commit after every 100 updates
count += 1
- if count%100 == 0:
+ if count % 100 == 0:
frappe.db.commit()
- if frappe.db.exists('DocType', 'Opportunity'):
- opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
- frappe.reload_doctype('Opportunity', force=True)
- rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
+ if frappe.db.exists("DocType", "Opportunity"):
+ opportunities = frappe.db.get_all(
+ "Opportunity", fields=["name", "mins_to_first_response"], order_by="creation desc"
+ )
+ frappe.reload_doctype("Opportunity", force=True)
+ rename_field("Opportunity", "mins_to_first_response", "first_response_time")
# change fieldtype to duration
- frappe.reload_doc('crm', 'doctype', 'opportunity', force=True)
+ frappe.reload_doc("crm", "doctype", "opportunity", force=True)
count = 0
for entry in opportunities:
- mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
- frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response, update_modified=False)
+ mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, "Minutes")
+ frappe.db.set_value(
+ "Opportunity", entry.name, "first_response_time", mins_to_first_response, update_modified=False
+ )
# commit after every 100 updates
count += 1
- if count%100 == 0:
+ if count % 100 == 0:
frappe.db.commit()
# renamed reports from "Minutes to First Response for Issues" to "First Response Time for Issues". Same for Opportunity
- for report in ['Minutes to First Response for Issues', 'Minutes to First Response for Opportunity']:
- if frappe.db.exists('Report', report):
- frappe.delete_doc('Report', report, ignore_permissions=True)
+ for report in [
+ "Minutes to First Response for Issues",
+ "Minutes to First Response for Opportunity",
+ ]:
+ if frappe.db.exists("Report", report):
+ frappe.delete_doc("Report", report, ignore_permissions=True)
def convert_to_seconds(value, unit):
seconds = 0
if not value:
return seconds
- if unit == 'Hours':
+ if unit == "Hours":
seconds = value * 3600
- if unit == 'Minutes':
+ if unit == "Minutes":
seconds = value * 60
return seconds
diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
index b129cbe80b..d3896dd0a8 100644
--- a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
+++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
@@ -6,16 +6,19 @@ import frappe
def execute():
- if frappe.db.exists('DocType', 'Issue'):
+ if frappe.db.exists("DocType", "Issue"):
frappe.reload_doc("support", "doctype", "issue")
rename_status()
+
def rename_status():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabIssue`
SET
status = 'On Hold'
WHERE
status = 'Hold'
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/rename_ksa_qr_field.py b/erpnext/patches/v13_0/rename_ksa_qr_field.py
index f4f9b17fb8..e4b91412ee 100644
--- a/erpnext/patches/v13_0/rename_ksa_qr_field.py
+++ b/erpnext/patches/v13_0/rename_ksa_qr_field.py
@@ -7,26 +7,30 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
+ company = frappe.get_all("Company", filters={"country": "Saudi Arabia"})
if not company:
return
- if frappe.db.exists('DocType', 'Sales Invoice'):
- frappe.reload_doc('accounts', 'doctype', 'sales_invoice', force=True)
+ if frappe.db.exists("DocType", "Sales Invoice"):
+ frappe.reload_doc("accounts", "doctype", "sales_invoice", force=True)
# rename_field method assumes that the field already exists or the doc is synced
- if not frappe.db.has_column('Sales Invoice', 'ksa_einv_qr'):
- create_custom_fields({
- 'Sales Invoice': [
- dict(
- fieldname='ksa_einv_qr',
- label='KSA E-Invoicing QR',
- fieldtype='Attach Image',
- read_only=1, no_copy=1, hidden=1
- )
- ]
- })
+ if not frappe.db.has_column("Sales Invoice", "ksa_einv_qr"):
+ create_custom_fields(
+ {
+ "Sales Invoice": [
+ dict(
+ fieldname="ksa_einv_qr",
+ label="KSA E-Invoicing QR",
+ fieldtype="Attach Image",
+ read_only=1,
+ no_copy=1,
+ hidden=1,
+ )
+ ]
+ }
+ )
- if frappe.db.has_column('Sales Invoice', 'qr_code'):
- rename_field('Sales Invoice', 'qr_code', 'ksa_einv_qr')
+ if frappe.db.has_column("Sales Invoice", "qr_code"):
+ rename_field("Sales Invoice", "qr_code", "ksa_einv_qr")
frappe.delete_doc_if_exists("Custom Field", "Sales Invoice-qr_code")
diff --git a/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py
index 265e2a9b0c..0e5423444a 100644
--- a/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py
+++ b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py
@@ -15,7 +15,7 @@ def execute():
"enable_razorpay": "enable_razorpay_for_memberships",
"debit_account": "membership_debit_account",
"payment_account": "membership_payment_account",
- "webhook_secret": "membership_webhook_secret"
+ "webhook_secret": "membership_webhook_secret",
}
for old_name, new_name in rename_fields_map.items():
diff --git a/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py b/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py
index 813fbd2d5c..434dbb47e7 100644
--- a/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py
+++ b/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py
@@ -3,20 +3,17 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.reload_doc('hr', 'doctype', 'hr_settings')
+ frappe.reload_doc("hr", "doctype", "hr_settings")
try:
# Rename the field
- rename_field('HR Settings', 'stop_birthday_reminders', 'send_birthday_reminders')
+ rename_field("HR Settings", "stop_birthday_reminders", "send_birthday_reminders")
# Reverse the value
- old_value = frappe.db.get_single_value('HR Settings', 'send_birthday_reminders')
+ old_value = frappe.db.get_single_value("HR Settings", "send_birthday_reminders")
frappe.db.set_value(
- 'HR Settings',
- 'HR Settings',
- 'send_birthday_reminders',
- 1 if old_value == 0 else 0
+ "HR Settings", "HR Settings", "send_birthday_reminders", 1 if old_value == 0 else 0
)
except Exception as e:
diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
index a2c960c8f3..ba2feb3a4d 100644
--- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
+++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
@@ -10,9 +10,13 @@ def execute():
pos_profiles = frappe.get_all("POS Profile")
for pos_profile in pos_profiles:
- payments = frappe.db.sql("""
+ payments = frappe.db.sql(
+ """
select idx, parentfield, parenttype, parent, mode_of_payment, `default` from `tabSales Invoice Payment` where parent=%s
- """, pos_profile.name, as_dict=1)
+ """,
+ pos_profile.name,
+ as_dict=1,
+ )
if payments:
for payment_mode in payments:
pos_payment_method = frappe.new_doc("POS Payment Method")
diff --git a/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py
index ba96fdd226..bf82f44308 100644
--- a/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py
+++ b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py
@@ -5,7 +5,7 @@ import frappe
def execute():
- if frappe.db.table_exists('Supplier Item Group'):
+ if frappe.db.table_exists("Supplier Item Group"):
frappe.reload_doc("selling", "doctype", "party_specific_item")
sig = frappe.db.get_all("Supplier Item Group", fields=["name", "supplier", "item_group"])
for item in sig:
diff --git a/erpnext/patches/v13_0/requeue_failed_reposts.py b/erpnext/patches/v13_0/requeue_failed_reposts.py
index 213cb9e26e..752490da30 100644
--- a/erpnext/patches/v13_0/requeue_failed_reposts.py
+++ b/erpnext/patches/v13_0/requeue_failed_reposts.py
@@ -4,9 +4,11 @@ from frappe.utils import cstr
def execute():
- reposts = frappe.get_all("Repost Item Valuation",
- {"status": "Failed", "modified": [">", "2021-10-05"] },
- ["name", "modified", "error_log"])
+ reposts = frappe.get_all(
+ "Repost Item Valuation",
+ {"status": "Failed", "modified": [">", "2021-10-05"]},
+ ["name", "modified", "error_log"],
+ )
for repost in reposts:
if "check_freezing_date" in cstr(repost.error_log):
diff --git a/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
index 69fc6a2dbc..5dfea5eaed 100644
--- a/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
+++ b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
@@ -6,40 +6,35 @@ import frappe
def execute():
- """
- Reset Clearance Date for Payment Entries of type Internal Transfer that have only been reconciled with one Bank Transaction.
- This will allow the Payment Entries to be reconciled with the second Bank Transaction using the Bank Reconciliation Tool.
- """
+ """
+ Reset Clearance Date for Payment Entries of type Internal Transfer that have only been reconciled with one Bank Transaction.
+ This will allow the Payment Entries to be reconciled with the second Bank Transaction using the Bank Reconciliation Tool.
+ """
- intra_company_pe = get_intra_company_payment_entries_with_clearance_dates()
- reconciled_bank_transactions = get_reconciled_bank_transactions(intra_company_pe)
+ intra_company_pe = get_intra_company_payment_entries_with_clearance_dates()
+ reconciled_bank_transactions = get_reconciled_bank_transactions(intra_company_pe)
+
+ for payment_entry in reconciled_bank_transactions:
+ if len(reconciled_bank_transactions[payment_entry]) == 1:
+ frappe.db.set_value("Payment Entry", payment_entry, "clearance_date", None)
- for payment_entry in reconciled_bank_transactions:
- if len(reconciled_bank_transactions[payment_entry]) == 1:
- frappe.db.set_value('Payment Entry', payment_entry, 'clearance_date', None)
def get_intra_company_payment_entries_with_clearance_dates():
- return frappe.get_all(
- 'Payment Entry',
- filters = {
- 'payment_type': 'Internal Transfer',
- 'clearance_date': ["not in", None]
- },
- pluck = 'name'
- )
+ return frappe.get_all(
+ "Payment Entry",
+ filters={"payment_type": "Internal Transfer", "clearance_date": ["not in", None]},
+ pluck="name",
+ )
+
def get_reconciled_bank_transactions(intra_company_pe):
- """Returns dictionary where each key:value pair is Payment Entry : List of Bank Transactions reconciled with Payment Entry"""
+ """Returns dictionary where each key:value pair is Payment Entry : List of Bank Transactions reconciled with Payment Entry"""
- reconciled_bank_transactions = {}
+ reconciled_bank_transactions = {}
- for payment_entry in intra_company_pe:
- reconciled_bank_transactions[payment_entry] = frappe.get_all(
- 'Bank Transaction Payments',
- filters = {
- 'payment_entry': payment_entry
- },
- pluck='parent'
- )
+ for payment_entry in intra_company_pe:
+ reconciled_bank_transactions[payment_entry] = frappe.get_all(
+ "Bank Transaction Payments", filters={"payment_entry": payment_entry}, pluck="parent"
+ )
- return reconciled_bank_transactions
+ return reconciled_bank_transactions
diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
index f82a0d56e0..bc2d1b94f7 100644
--- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
+++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
@@ -2,9 +2,24 @@ import frappe
def execute():
- company = frappe.db.get_single_value('Global Defaults', 'default_company')
- doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection', 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
+ company = frappe.db.get_single_value("Global Defaults", "default_company")
+ doctypes = [
+ "Clinical Procedure",
+ "Inpatient Record",
+ "Lab Test",
+ "Sample Collection",
+ "Patient Appointment",
+ "Patient Encounter",
+ "Vital Signs",
+ "Therapy Session",
+ "Therapy Plan",
+ "Patient Assessment",
+ ]
for entry in doctypes:
- if frappe.db.exists('DocType', entry):
- frappe.reload_doc('Healthcare', 'doctype', entry)
- frappe.db.sql("update `tab{dt}` set company = {company} where ifnull(company, '') = ''".format(dt=entry, company=frappe.db.escape(company)))
+ if frappe.db.exists("DocType", entry):
+ frappe.reload_doc("Healthcare", "doctype", entry)
+ frappe.db.sql(
+ "update `tab{dt}` set company = {company} where ifnull(company, '') = ''".format(
+ dt=entry, company=frappe.db.escape(company)
+ )
+ )
diff --git a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py
index c744f35b72..adc8784b5b 100644
--- a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py
+++ b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py
@@ -2,7 +2,11 @@ import frappe
def execute():
- frappe.reload_doc('HR', 'doctype', 'Leave Allocation')
- frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry')
- frappe.db.sql("""update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)""")
- frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""")
+ frappe.reload_doc("HR", "doctype", "Leave Allocation")
+ frappe.reload_doc("HR", "doctype", "Leave Ledger Entry")
+ frappe.db.sql(
+ """update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)"""
+ )
+ frappe.db.sql(
+ """update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)"""
+ )
diff --git a/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py
index e26285e508..ef02e250c8 100644
--- a/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py
+++ b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py
@@ -2,10 +2,11 @@ import frappe
def execute():
- frappe.reload_doc('manufacturing', 'doctype', 'bom')
- frappe.reload_doc('manufacturing', 'doctype', 'bom_operation')
+ frappe.reload_doc("manufacturing", "doctype", "bom")
+ frappe.reload_doc("manufacturing", "doctype", "bom_operation")
- frappe.db.sql('''
+ frappe.db.sql(
+ """
UPDATE
`tabBOM Operation`
SET
@@ -13,4 +14,5 @@ def execute():
WHERE
time_in_mins = 0 AND operating_cost > 0
AND hour_rate > 0 AND docstatus = 1 AND parenttype = "BOM"
- ''')
+ """
+ )
diff --git a/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py
index 87b3389448..0cefa028ec 100644
--- a/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py
+++ b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py
@@ -10,8 +10,11 @@ def execute():
frappe.reload_doc("Accounts", "doctype", "Payment Gateway Account")
set_payment_channel_as_email()
+
def set_payment_channel_as_email():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabPayment Gateway Account`
SET `payment_channel` = "Email"
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py
index 6a3785d837..e2226c1cf0 100644
--- a/erpnext/patches/v13_0/set_pos_closing_as_failed.py
+++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py
@@ -2,6 +2,6 @@ import frappe
def execute():
- frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry')
+ frappe.reload_doc("accounts", "doctype", "pos_closing_entry")
- frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'")
+ frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'")
diff --git a/erpnext/patches/v13_0/set_return_against_in_pos_invoice_references.py b/erpnext/patches/v13_0/set_return_against_in_pos_invoice_references.py
index 6c24f52027..d29b7396a5 100644
--- a/erpnext/patches/v13_0/set_return_against_in_pos_invoice_references.py
+++ b/erpnext/patches/v13_0/set_return_against_in_pos_invoice_references.py
@@ -2,18 +2,17 @@ import frappe
def execute():
- '''
+ """
Fetch and Set is_return & return_against from POS Invoice in POS Invoice References table.
- '''
+ """
POSClosingEntry = frappe.qb.DocType("POS Closing Entry")
open_pos_closing_entries = (
- frappe.qb
- .from_(POSClosingEntry)
- .select(POSClosingEntry.name)
- .where(POSClosingEntry.docstatus == 0)
- .run(pluck=True)
- )
+ frappe.qb.from_(POSClosingEntry)
+ .select(POSClosingEntry.name)
+ .where(POSClosingEntry.docstatus == 0)
+ .run(pluck=True)
+ )
if not open_pos_closing_entries:
return
@@ -21,13 +20,12 @@ def execute():
POSInvoiceReference = frappe.qb.DocType("POS Invoice Reference")
POSInvoice = frappe.qb.DocType("POS Invoice")
pos_invoice_references = (
- frappe.qb
- .from_(POSInvoiceReference)
- .join(POSInvoice)
- .on(POSInvoiceReference.pos_invoice == POSInvoice.name)
- .select(POSInvoiceReference.name, POSInvoice.is_return, POSInvoice.return_against)
- .where(POSInvoiceReference.parent.isin(open_pos_closing_entries))
- .run(as_dict=True)
+ frappe.qb.from_(POSInvoiceReference)
+ .join(POSInvoice)
+ .on(POSInvoiceReference.pos_invoice == POSInvoice.name)
+ .select(POSInvoiceReference.name, POSInvoice.is_return, POSInvoice.return_against)
+ .where(POSInvoiceReference.parent.isin(open_pos_closing_entries))
+ .run(as_dict=True)
)
for row in pos_invoice_references:
diff --git a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py
index 9887ad9df0..e1c8526f10 100644
--- a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py
+++ b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py
@@ -3,8 +3,10 @@ import frappe
def execute():
frappe.reload_doc("maintenance", "doctype", "Maintenance Schedule Detail")
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabMaintenance Schedule Detail`
SET completion_status = 'Pending'
WHERE docstatus < 2
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/set_training_event_attendance.py b/erpnext/patches/v13_0/set_training_event_attendance.py
index e44f32157d..7b55758972 100644
--- a/erpnext/patches/v13_0/set_training_event_attendance.py
+++ b/erpnext/patches/v13_0/set_training_event_attendance.py
@@ -2,8 +2,10 @@ import frappe
def execute():
- frappe.reload_doc('hr', 'doctype', 'training_event')
- frappe.reload_doc('hr', 'doctype', 'training_event_employee')
+ frappe.reload_doc("hr", "doctype", "training_event")
+ frappe.reload_doc("hr", "doctype", "training_event_employee")
- frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'")
- frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'")
+ frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'")
+ frappe.db.sql(
+ "update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'"
+ )
diff --git a/erpnext/patches/v13_0/set_work_order_qty_in_so_from_mr.py b/erpnext/patches/v13_0/set_work_order_qty_in_so_from_mr.py
index f097ab9297..1adf0d8453 100644
--- a/erpnext/patches/v13_0/set_work_order_qty_in_so_from_mr.py
+++ b/erpnext/patches/v13_0/set_work_order_qty_in_so_from_mr.py
@@ -2,35 +2,37 @@ import frappe
def execute():
- """
- 1. Get submitted Work Orders with MR, MR Item and SO set
- 2. Get SO Item detail from MR Item detail in WO, and set in WO
- 3. Update work_order_qty in SO
- """
- work_order = frappe.qb.DocType("Work Order")
- query = (
- frappe.qb.from_(work_order)
- .select(
- work_order.name, work_order.produced_qty,
- work_order.material_request,
- work_order.material_request_item,
- work_order.sales_order
- ).where(
- (work_order.material_request.isnotnull())
- & (work_order.material_request_item.isnotnull())
- & (work_order.sales_order.isnotnull())
- & (work_order.docstatus == 1)
- & (work_order.produced_qty > 0)
- )
- )
- results = query.run(as_dict=True)
+ """
+ 1. Get submitted Work Orders with MR, MR Item and SO set
+ 2. Get SO Item detail from MR Item detail in WO, and set in WO
+ 3. Update work_order_qty in SO
+ """
+ work_order = frappe.qb.DocType("Work Order")
+ query = (
+ frappe.qb.from_(work_order)
+ .select(
+ work_order.name,
+ work_order.produced_qty,
+ work_order.material_request,
+ work_order.material_request_item,
+ work_order.sales_order,
+ )
+ .where(
+ (work_order.material_request.isnotnull())
+ & (work_order.material_request_item.isnotnull())
+ & (work_order.sales_order.isnotnull())
+ & (work_order.docstatus == 1)
+ & (work_order.produced_qty > 0)
+ )
+ )
+ results = query.run(as_dict=True)
- for row in results:
- so_item = frappe.get_value(
- "Material Request Item", row.material_request_item, "sales_order_item"
- )
- frappe.db.set_value("Work Order", row.name, "sales_order_item", so_item)
+ for row in results:
+ so_item = frappe.get_value(
+ "Material Request Item", row.material_request_item, "sales_order_item"
+ )
+ frappe.db.set_value("Work Order", row.name, "sales_order_item", so_item)
- if so_item:
- wo = frappe.get_doc("Work Order", row.name)
- wo.update_work_order_qty_in_so()
+ if so_item:
+ wo = frappe.get_doc("Work Order", row.name)
+ wo.update_work_order_qty_in_so()
diff --git a/erpnext/patches/v13_0/set_youtube_video_id.py b/erpnext/patches/v13_0/set_youtube_video_id.py
index e1eb1b9bc2..9766bb871c 100644
--- a/erpnext/patches/v13_0/set_youtube_video_id.py
+++ b/erpnext/patches/v13_0/set_youtube_video_id.py
@@ -4,7 +4,7 @@ from erpnext.utilities.doctype.video.video import get_id_from_url
def execute():
- frappe.reload_doc("utilities", "doctype","video")
+ frappe.reload_doc("utilities", "doctype", "video")
for video in frappe.get_all("Video", fields=["name", "url", "youtube_video_id"]):
if video.url and not video.youtube_video_id:
diff --git a/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py
index dc3f8aadc1..40c10f3035 100644
--- a/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py
+++ b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py
@@ -4,8 +4,8 @@ from erpnext.regional.india.setup import add_custom_roles_for_reports
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
- if not company:
- return
+ company = frappe.get_all("Company", filters={"country": "India"})
+ if not company:
+ return
- add_custom_roles_for_reports()
+ add_custom_roles_for_reports()
diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
index 2d35ea3458..1c36b53684 100644
--- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
+++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
@@ -4,15 +4,13 @@ from erpnext.regional.india.setup import make_custom_fields
def execute():
- if frappe.get_all('Company', filters = {'country': 'India'}):
- frappe.reload_doc('accounts', 'doctype', 'POS Invoice')
- frappe.reload_doc('accounts', 'doctype', 'POS Invoice Item')
+ if frappe.get_all("Company", filters={"country": "India"}):
+ frappe.reload_doc("accounts", "doctype", "POS Invoice")
+ frappe.reload_doc("accounts", "doctype", "POS Invoice Item")
make_custom_fields()
- if not frappe.db.exists('Party Type', 'Donor'):
- frappe.get_doc({
- 'doctype': 'Party Type',
- 'party_type': 'Donor',
- 'account_type': 'Receivable'
- }).insert(ignore_permissions=True)
+ if not frappe.db.exists("Party Type", "Donor"):
+ frappe.get_doc(
+ {"doctype": "Party Type", "party_type": "Donor", "account_type": "Receivable"}
+ ).insert(ignore_permissions=True)
diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py
index 82cc1ff771..093e8a7646 100644
--- a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py
+++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py
@@ -6,12 +6,14 @@ import frappe
def execute():
- frappe.reload_doc('payroll', 'doctype', 'gratuity_rule')
- frappe.reload_doc('payroll', 'doctype', 'gratuity_rule_slab')
- frappe.reload_doc('payroll', 'doctype', 'gratuity_applicable_component')
- if frappe.db.exists("Company", {"country": "India"}):
- from erpnext.regional.india.setup import create_gratuity_rule
- create_gratuity_rule()
- if frappe.db.exists("Company", {"country": "United Arab Emirates"}):
- from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule
- create_gratuity_rule()
+ frappe.reload_doc("payroll", "doctype", "gratuity_rule")
+ frappe.reload_doc("payroll", "doctype", "gratuity_rule_slab")
+ frappe.reload_doc("payroll", "doctype", "gratuity_applicable_component")
+ if frappe.db.exists("Company", {"country": "India"}):
+ from erpnext.regional.india.setup import create_gratuity_rule
+
+ create_gratuity_rule()
+ if frappe.db.exists("Company", {"country": "United Arab Emirates"}):
+ from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule
+
+ create_gratuity_rule()
diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py
index d89e0521d8..70466465e4 100644
--- a/erpnext/patches/v13_0/setup_uae_vat_fields.py
+++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py
@@ -7,12 +7,12 @@ from erpnext.regional.united_arab_emirates.setup import setup
def execute():
- company = frappe.get_all('Company', filters = {'country': 'United Arab Emirates'})
+ company = frappe.get_all("Company", filters={"country": "United Arab Emirates"})
if not company:
return
- frappe.reload_doc('regional', 'report', 'uae_vat_201')
- frappe.reload_doc('regional', 'doctype', 'uae_vat_settings')
- frappe.reload_doc('regional', 'doctype', 'uae_vat_account')
+ frappe.reload_doc("regional", "report", "uae_vat_201")
+ frappe.reload_doc("regional", "doctype", "uae_vat_settings")
+ frappe.reload_doc("regional", "doctype", "uae_vat_account")
setup()
diff --git a/erpnext/patches/v13_0/stock_entry_enhancements.py b/erpnext/patches/v13_0/stock_entry_enhancements.py
index 968a83a421..005980e80a 100644
--- a/erpnext/patches/v13_0/stock_entry_enhancements.py
+++ b/erpnext/patches/v13_0/stock_entry_enhancements.py
@@ -6,27 +6,31 @@ import frappe
def execute():
- frappe.reload_doc("stock", "doctype", "stock_entry")
- if frappe.db.has_column("Stock Entry", "add_to_transit"):
- frappe.db.sql("""
+ frappe.reload_doc("stock", "doctype", "stock_entry")
+ if frappe.db.has_column("Stock Entry", "add_to_transit"):
+ frappe.db.sql(
+ """
UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer',
add_to_transit = 1 WHERE stock_entry_type = 'Send to Warehouse'
- """)
+ """
+ )
- frappe.db.sql("""UPDATE `tabStock Entry` SET
+ frappe.db.sql(
+ """UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer'
WHERE stock_entry_type = 'Receive at Warehouse'
- """)
+ """
+ )
- frappe.reload_doc("stock", "doctype", "warehouse_type")
- if not frappe.db.exists('Warehouse Type', 'Transit'):
- doc = frappe.new_doc('Warehouse Type')
- doc.name = 'Transit'
- doc.insert()
+ frappe.reload_doc("stock", "doctype", "warehouse_type")
+ if not frappe.db.exists("Warehouse Type", "Transit"):
+ doc = frappe.new_doc("Warehouse Type")
+ doc.name = "Transit"
+ doc.insert()
- frappe.reload_doc("stock", "doctype", "stock_entry_type")
- frappe.delete_doc_if_exists("Stock Entry Type", "Send to Warehouse")
- frappe.delete_doc_if_exists("Stock Entry Type", "Receive at Warehouse")
+ frappe.reload_doc("stock", "doctype", "stock_entry_type")
+ frappe.delete_doc_if_exists("Stock Entry Type", "Send to Warehouse")
+ frappe.delete_doc_if_exists("Stock Entry Type", "Receive at Warehouse")
diff --git a/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py
index fd48c0d902..5f3fc5761a 100644
--- a/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py
+++ b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py
@@ -7,12 +7,10 @@ from erpnext.regional.india.setup import create_custom_fields, get_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- custom_fields = {
- 'Sales Invoice': get_custom_fields().get('Sales Invoice')
- }
+ custom_fields = {"Sales Invoice": get_custom_fields().get("Sales Invoice")}
create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
index 4ec22e9d0e..b69a408e65 100644
--- a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
+++ b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
@@ -4,7 +4,8 @@ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
def execute():
- broken_sles = frappe.db.sql("""
+ broken_sles = frappe.db.sql(
+ """
select name, serial_no
from `tabStock Ledger Entry`
where
@@ -12,15 +13,15 @@ def execute():
and ( serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s
or serial_no = %s )
""",
- (
- " %", # leading whitespace
- "% ", # trailing whitespace
- "%\n %", # leading whitespace on newline
- "% \n%", # trailing whitespace on newline
- "\n", # just new line
- ),
- as_dict=True,
- )
+ (
+ " %", # leading whitespace
+ "% ", # trailing whitespace
+ "%\n %", # leading whitespace on newline
+ "% \n%", # trailing whitespace on newline
+ "\n", # just new line
+ ),
+ as_dict=True,
+ )
frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sles)
@@ -37,7 +38,9 @@ def execute():
if correct_sr_no == sle.serial_no:
continue
- frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_no", correct_sr_no, update_modified=False)
+ frappe.db.set_value(
+ "Stock Ledger Entry", sle.name, "serial_no", correct_sr_no, update_modified=False
+ )
broken_serial_nos.update(serial_no_list)
if not broken_serial_nos:
@@ -45,14 +48,15 @@ def execute():
# Patch serial No documents if they don't have purchase info
# Purchase info is used for fetching incoming rate
- broken_sr_no_records = frappe.get_list("Serial No",
- filters={
- "status":"Active",
- "name": ("in", broken_serial_nos),
- "purchase_document_type": ("is", "not set")
- },
- pluck="name",
- )
+ broken_sr_no_records = frappe.get_list(
+ "Serial No",
+ filters={
+ "status": "Active",
+ "name": ("in", broken_serial_nos),
+ "purchase_document_type": ("is", "not set"),
+ },
+ pluck="name",
+ )
frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sr_no_records)
diff --git a/erpnext/patches/v13_0/update_accounts_in_loan_docs.py b/erpnext/patches/v13_0/update_accounts_in_loan_docs.py
index 440f912be2..bf98e9ea42 100644
--- a/erpnext/patches/v13_0/update_accounts_in_loan_docs.py
+++ b/erpnext/patches/v13_0/update_accounts_in_loan_docs.py
@@ -6,31 +6,13 @@ def execute():
lr = frappe.qb.DocType("Loan Repayment").as_("lr")
loan = frappe.qb.DocType("Loan")
- frappe.qb.update(
- ld
- ).inner_join(
- loan
- ).on(
- loan.name == ld.against_loan
- ).set(
+ frappe.qb.update(ld).inner_join(loan).on(loan.name == ld.against_loan).set(
ld.disbursement_account, loan.disbursement_account
- ).set(
- ld.loan_account, loan.loan_account
- ).where(
- ld.docstatus < 2
- ).run()
+ ).set(ld.loan_account, loan.loan_account).where(ld.docstatus < 2).run()
- frappe.qb.update(
- lr
- ).inner_join(
- loan
- ).on(
- loan.name == lr.against_loan
- ).set(
+ frappe.qb.update(lr).inner_join(loan).on(loan.name == lr.against_loan).set(
lr.payment_account, loan.payment_account
- ).set(
- lr.loan_account, loan.loan_account
- ).set(
+ ).set(lr.loan_account, loan.loan_account).set(
lr.penalty_income_account, loan.penalty_income_account
).where(
lr.docstatus < 2
diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
index 60466eb86a..3c6c5b5b75 100644
--- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
+++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
@@ -11,11 +11,9 @@ def execute():
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
frappe.reload_doc("manufacturing", "doctype", "job_card")
- data = frappe.get_all("Work Order",
- filters = {
- "docstatus": 1,
- "status": ("in", ["In Process", "Completed"])
- })
+ data = frappe.get_all(
+ "Work Order", filters={"docstatus": 1, "status": ("in", ["In Process", "Completed"])}
+ )
for d in data:
doc = frappe.get_doc("Work Order", d.name)
@@ -23,18 +21,22 @@ def execute():
doc.db_set("actual_start_date", doc.actual_start_date, update_modified=False)
if doc.status == "Completed":
- frappe.db.set_value("Work Order", d.name, {
- "actual_end_date": doc.actual_end_date,
- "lead_time": doc.lead_time
- }, update_modified=False)
+ frappe.db.set_value(
+ "Work Order",
+ d.name,
+ {"actual_end_date": doc.actual_end_date, "lead_time": doc.lead_time},
+ update_modified=False,
+ )
if not doc.planned_end_date:
planned_end_date = add_to_date(doc.planned_start_date, minutes=doc.lead_time)
doc.db_set("planned_end_date", doc.actual_start_date, update_modified=False)
- frappe.db.sql(""" UPDATE `tabJob Card` as jc, `tabWork Order` as wo
+ frappe.db.sql(
+ """ UPDATE `tabJob Card` as jc, `tabWork Order` as wo
SET
jc.production_item = wo.production_item, jc.item_name = wo.item_name
WHERE
jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
- """)
\ No newline at end of file
+ """
+ )
diff --git a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
index dc973a9d45..e37f291233 100644
--- a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
+++ b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
@@ -2,7 +2,7 @@ import frappe
def execute():
- """ Correct amount in child table of required items table."""
+ """Correct amount in child table of required items table."""
frappe.reload_doc("manufacturing", "doctype", "work_order")
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
diff --git a/erpnext/patches/v13_0/update_asset_quantity_field.py b/erpnext/patches/v13_0/update_asset_quantity_field.py
index 47884d1968..265a3bb54c 100644
--- a/erpnext/patches/v13_0/update_asset_quantity_field.py
+++ b/erpnext/patches/v13_0/update_asset_quantity_field.py
@@ -2,7 +2,7 @@ import frappe
def execute():
- if frappe.db.count('Asset'):
+ if frappe.db.count("Asset"):
frappe.reload_doc("assets", "doctype", "Asset")
- asset = frappe.qb.DocType('Asset')
- frappe.qb.update(asset).set(asset.asset_quantity, 1).run()
\ No newline at end of file
+ asset = frappe.qb.DocType("Asset")
+ frappe.qb.update(asset).set(asset.asset_quantity, 1).run()
diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py
index a5f5a23449..5a0873e0e5 100644
--- a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py
+++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py
@@ -2,19 +2,15 @@ import frappe
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- frappe.reload_doc('regional', 'doctype', 'lower_deduction_certificate')
+ frappe.reload_doc("regional", "doctype", "lower_deduction_certificate")
ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc")
supplier = frappe.qb.DocType("Supplier")
- frappe.qb.update(ldc).inner_join(supplier).on(
- ldc.supplier == supplier.name
- ).set(
+ frappe.qb.update(ldc).inner_join(supplier).on(ldc.supplier == supplier.name).set(
ldc.tax_withholding_category, supplier.tax_withholding_category
- ).where(
- ldc.tax_withholding_category.isnull()
- ).run()
\ No newline at end of file
+ ).where(ldc.tax_withholding_category.isnull()).run()
diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py
index 90fb50fb42..c538476edb 100644
--- a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py
+++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py
@@ -5,22 +5,23 @@ import frappe
def execute():
- frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate')
+ frappe.reload_doc("accounts", "doctype", "Tax Withholding Rate")
- if frappe.db.has_column('Tax Withholding Rate', 'fiscal_year'):
- tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year'])
+ if frappe.db.has_column("Tax Withholding Rate", "fiscal_year"):
+ tds_category_rates = frappe.get_all("Tax Withholding Rate", fields=["name", "fiscal_year"])
fiscal_year_map = {}
- fiscal_year_details = frappe.get_all('Fiscal Year', fields=['name', 'year_start_date', 'year_end_date'])
+ fiscal_year_details = frappe.get_all(
+ "Fiscal Year", fields=["name", "year_start_date", "year_end_date"]
+ )
for d in fiscal_year_details:
fiscal_year_map.setdefault(d.name, d)
for rate in tds_category_rates:
- from_date = fiscal_year_map.get(rate.fiscal_year).get('year_start_date')
- to_date = fiscal_year_map.get(rate.fiscal_year).get('year_end_date')
+ from_date = fiscal_year_map.get(rate.fiscal_year).get("year_start_date")
+ to_date = fiscal_year_map.get(rate.fiscal_year).get("year_end_date")
- frappe.db.set_value('Tax Withholding Rate', rate.name, {
- 'from_date': from_date,
- 'to_date': to_date
- })
\ No newline at end of file
+ frappe.db.set_value(
+ "Tax Withholding Rate", rate.name, {"from_date": from_date, "to_date": to_date}
+ )
diff --git a/erpnext/patches/v13_0/update_deferred_settings.py b/erpnext/patches/v13_0/update_deferred_settings.py
index 1b63635b67..03fe66ffe8 100644
--- a/erpnext/patches/v13_0/update_deferred_settings.py
+++ b/erpnext/patches/v13_0/update_deferred_settings.py
@@ -5,8 +5,8 @@ import frappe
def execute():
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.book_deferred_entries_based_on = 'Days'
+ accounts_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
+ accounts_settings.book_deferred_entries_based_on = "Days"
accounts_settings.book_deferred_entries_via_journal_entry = 0
accounts_settings.submit_journal_entries = 0
accounts_settings.save()
diff --git a/erpnext/patches/v13_0/update_disbursement_account.py b/erpnext/patches/v13_0/update_disbursement_account.py
index c56fa8fdc6..d6aba4720e 100644
--- a/erpnext/patches/v13_0/update_disbursement_account.py
+++ b/erpnext/patches/v13_0/update_disbursement_account.py
@@ -9,14 +9,6 @@ def execute():
loan_type = frappe.qb.DocType("Loan Type")
loan = frappe.qb.DocType("Loan")
- frappe.qb.update(
- loan_type
- ).set(
- loan_type.disbursement_account, loan_type.payment_account
- ).run()
+ frappe.qb.update(loan_type).set(loan_type.disbursement_account, loan_type.payment_account).run()
- frappe.qb.update(
- loan
- ).set(
- loan.disbursement_account, loan.payment_account
- ).run()
\ No newline at end of file
+ frappe.qb.update(loan).set(loan.disbursement_account, loan.payment_account).run()
diff --git a/erpnext/patches/v13_0/update_expense_claim_status_for_paid_advances.py b/erpnext/patches/v13_0/update_expense_claim_status_for_paid_advances.py
index 063de1637d..2bc17ae86b 100644
--- a/erpnext/patches/v13_0/update_expense_claim_status_for_paid_advances.py
+++ b/erpnext/patches/v13_0/update_expense_claim_status_for_paid_advances.py
@@ -4,18 +4,21 @@ import frappe
def execute():
"""
Update Expense Claim status to Paid if:
- - the entire required amount is already covered via linked advances
- - the claim is partially paid via advances and the rest is reimbursed
+ - the entire required amount is already covered via linked advances
+ - the claim is partially paid via advances and the rest is reimbursed
"""
- ExpenseClaim = frappe.qb.DocType('Expense Claim')
+ ExpenseClaim = frappe.qb.DocType("Expense Claim")
- (frappe.qb
- .update(ExpenseClaim)
- .set(ExpenseClaim.status, 'Paid')
+ (
+ frappe.qb.update(ExpenseClaim)
+ .set(ExpenseClaim.status, "Paid")
.where(
- ((ExpenseClaim.grand_total == 0) | (ExpenseClaim.grand_total == ExpenseClaim.total_amount_reimbursed))
- & (ExpenseClaim.approval_status == 'Approved')
+ (
+ (ExpenseClaim.grand_total == 0)
+ | (ExpenseClaim.grand_total == ExpenseClaim.total_amount_reimbursed)
+ )
+ & (ExpenseClaim.approval_status == "Approved")
& (ExpenseClaim.docstatus == 1)
& (ExpenseClaim.total_sanctioned_amount > 0)
)
diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py
index de578612f7..62368584dc 100644
--- a/erpnext/patches/v13_0/update_export_type_for_gst.py
+++ b/erpnext/patches/v13_0/update_export_type_for_gst.py
@@ -2,32 +2,39 @@ import frappe
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
# Update custom fields
- fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
+ fieldname = frappe.db.get_value("Custom Field", {"dt": "Customer", "fieldname": "export_type"})
if fieldname:
- frappe.db.set_value('Custom Field', fieldname,
+ frappe.db.set_value(
+ "Custom Field",
+ fieldname,
{
- 'default': '',
- 'mandatory_depends_on': 'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
- })
+ "default": "",
+ "mandatory_depends_on": 'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
+ },
+ )
- fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
+ fieldname = frappe.db.get_value("Custom Field", {"dt": "Supplier", "fieldname": "export_type"})
if fieldname:
- frappe.db.set_value('Custom Field', fieldname,
- {
- 'default': '',
- 'mandatory_depends_on': 'eval:in_list(["SEZ", "Overseas"], doc.gst_category)'
- })
+ frappe.db.set_value(
+ "Custom Field",
+ fieldname,
+ {"default": "", "mandatory_depends_on": 'eval:in_list(["SEZ", "Overseas"], doc.gst_category)'},
+ )
# Update Customer/Supplier Masters
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
- """)
+ """
+ )
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/update_job_card_details.py b/erpnext/patches/v13_0/update_job_card_details.py
index 12f9006b76..73baecf0df 100644
--- a/erpnext/patches/v13_0/update_job_card_details.py
+++ b/erpnext/patches/v13_0/update_job_card_details.py
@@ -10,8 +10,10 @@ def execute():
frappe.reload_doc("manufacturing", "doctype", "job_card_item")
frappe.reload_doc("manufacturing", "doctype", "work_order_operation")
- frappe.db.sql(""" update `tabJob Card` jc, `tabWork Order Operation` wo
+ frappe.db.sql(
+ """ update `tabJob Card` jc, `tabWork Order Operation` wo
SET jc.hour_rate = wo.hour_rate
WHERE
jc.operation_id = wo.name and jc.docstatus < 2 and wo.hour_rate > 0
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/update_job_card_status.py b/erpnext/patches/v13_0/update_job_card_status.py
index 797a3e2ae3..f2d12da119 100644
--- a/erpnext/patches/v13_0/update_job_card_status.py
+++ b/erpnext/patches/v13_0/update_job_card_status.py
@@ -7,8 +7,8 @@ import frappe
def execute():
job_card = frappe.qb.DocType("Job Card")
- (frappe.qb
- .update(job_card)
+ (
+ frappe.qb.update(job_card)
.set(job_card.status, "Completed")
.where(
(job_card.docstatus == 1)
diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
index 7a8c1c6135..136d34e6ff 100644
--- a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
+++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
@@ -1,25 +1,20 @@
-
import frappe
def execute():
- frappe.reload_doctype('Maintenance Visit')
- frappe.reload_doctype('Maintenance Visit Purpose')
+ frappe.reload_doctype("Maintenance Visit")
+ frappe.reload_doctype("Maintenance Visit Purpose")
# Updates the Maintenance Schedule link to fetch serial nos
from frappe.query_builder.functions import Coalesce
- mvp = frappe.qb.DocType('Maintenance Visit Purpose')
- mv = frappe.qb.DocType('Maintenance Visit')
- frappe.qb.update(
- mv
- ).join(
- mvp
- ).on(mvp.parent == mv.name).set(
- mv.maintenance_schedule,
- Coalesce(mvp.prevdoc_docname, '')
+ mvp = frappe.qb.DocType("Maintenance Visit Purpose")
+ mv = frappe.qb.DocType("Maintenance Visit")
+
+ frappe.qb.update(mv).join(mvp).on(mvp.parent == mv.name).set(
+ mv.maintenance_schedule, Coalesce(mvp.prevdoc_docname, "")
).where(
- (mv.maintenance_type == "Scheduled")
- & (mvp.prevdoc_docname.notnull())
- & (mv.docstatus < 2)
- ).run(as_dict=1)
+ (mv.maintenance_type == "Scheduled") & (mvp.prevdoc_docname.notnull()) & (mv.docstatus < 2)
+ ).run(
+ as_dict=1
+ )
diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py
index e226f1dd56..a1d40b739e 100644
--- a/erpnext/patches/v13_0/update_old_loans.py
+++ b/erpnext/patches/v13_0/update_old_loans.py
@@ -16,69 +16,110 @@ def execute():
# Create a penalty account for loan types
- frappe.reload_doc('loan_management', 'doctype', 'loan_type')
- frappe.reload_doc('loan_management', 'doctype', 'loan')
- frappe.reload_doc('loan_management', 'doctype', 'repayment_schedule')
- frappe.reload_doc('loan_management', 'doctype', 'process_loan_interest_accrual')
- frappe.reload_doc('loan_management', 'doctype', 'loan_repayment')
- frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
- frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
- frappe.reload_doc('accounts', 'doctype', 'gl_entry')
- frappe.reload_doc('accounts', 'doctype', 'journal_entry_account')
+ frappe.reload_doc("loan_management", "doctype", "loan_type")
+ frappe.reload_doc("loan_management", "doctype", "loan")
+ frappe.reload_doc("loan_management", "doctype", "repayment_schedule")
+ frappe.reload_doc("loan_management", "doctype", "process_loan_interest_accrual")
+ frappe.reload_doc("loan_management", "doctype", "loan_repayment")
+ frappe.reload_doc("loan_management", "doctype", "loan_repayment_detail")
+ frappe.reload_doc("loan_management", "doctype", "loan_interest_accrual")
+ frappe.reload_doc("accounts", "doctype", "gl_entry")
+ frappe.reload_doc("accounts", "doctype", "journal_entry_account")
updated_loan_types = []
loans_to_close = []
# Update old loan status as closed
- if frappe.db.has_column('Repayment Schedule', 'paid'):
- loans_list = frappe.db.sql("""SELECT distinct parent from `tabRepayment Schedule`
- where paid = 0 and docstatus = 1""", as_dict=1)
+ if frappe.db.has_column("Repayment Schedule", "paid"):
+ loans_list = frappe.db.sql(
+ """SELECT distinct parent from `tabRepayment Schedule`
+ where paid = 0 and docstatus = 1""",
+ as_dict=1,
+ )
loans_to_close = [d.parent for d in loans_list]
if loans_to_close:
- frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close))
+ frappe.db.sql(
+ "UPDATE `tabLoan` set status = 'Closed' where name not in (%s)"
+ % (", ".join(["%s"] * len(loans_to_close))),
+ tuple(loans_to_close),
+ )
- loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
- 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'],
- filters={'docstatus': 1, 'status': ('!=', 'Closed')})
+ loans = frappe.get_all(
+ "Loan",
+ fields=[
+ "name",
+ "loan_type",
+ "company",
+ "status",
+ "mode_of_payment",
+ "applicant_type",
+ "applicant",
+ "loan_account",
+ "payment_account",
+ "interest_income_account",
+ ],
+ filters={"docstatus": 1, "status": ("!=", "Closed")},
+ )
for loan in loans:
# Update details in Loan Types and Loan
- loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
+ loan_type_company = frappe.db.get_value("Loan Type", loan.loan_type, "company")
loan_type = loan.loan_type
- group_income_account = frappe.get_value('Account', {'company': loan.company,
- 'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
+ group_income_account = frappe.get_value(
+ "Account",
+ {
+ "company": loan.company,
+ "is_group": 1,
+ "root_type": "Income",
+ "account_name": _("Indirect Income"),
+ },
+ )
if not group_income_account:
- group_income_account = frappe.get_value('Account', {'company': loan.company,
- 'is_group': 1, 'root_type': 'Income'})
+ group_income_account = frappe.get_value(
+ "Account", {"company": loan.company, "is_group": 1, "root_type": "Income"}
+ )
- penalty_account = create_account(company=loan.company, account_type='Income Account',
- account_name='Penalty Account', parent_account=group_income_account)
+ penalty_account = create_account(
+ company=loan.company,
+ account_type="Income Account",
+ account_name="Penalty Account",
+ parent_account=group_income_account,
+ )
# Same loan type used for multiple companies
if loan_type_company and loan_type_company != loan.company:
# get loan type for appropriate company
- loan_type_name = frappe.get_value('Loan Type', {'company': loan.company,
- 'mode_of_payment': loan.mode_of_payment, 'loan_account': loan.loan_account,
- 'payment_account': loan.payment_account, 'interest_income_account': loan.interest_income_account,
- 'penalty_income_account': loan.penalty_income_account}, 'name')
+ loan_type_name = frappe.get_value(
+ "Loan Type",
+ {
+ "company": loan.company,
+ "mode_of_payment": loan.mode_of_payment,
+ "loan_account": loan.loan_account,
+ "payment_account": loan.payment_account,
+ "interest_income_account": loan.interest_income_account,
+ "penalty_income_account": loan.penalty_income_account,
+ },
+ "name",
+ )
if not loan_type_name:
loan_type_name = create_loan_type(loan, loan_type_name, penalty_account)
# update loan type in loan
- frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name,
- loan.name))
+ frappe.db.sql(
+ "UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name, loan.name)
+ )
loan_type = loan_type_name
if loan_type_name not in updated_loan_types:
updated_loan_types.append(loan_type_name)
elif not loan_type_company:
- loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
+ loan_type_doc = frappe.get_doc("Loan Type", loan.loan_type)
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment
@@ -91,26 +132,29 @@ def execute():
loan_type = loan.loan_type
if loan_type in updated_loan_types:
- if loan.status == 'Fully Disbursed':
- status = 'Disbursed'
- elif loan.status == 'Repaid/Closed':
- status = 'Closed'
+ if loan.status == "Fully Disbursed":
+ status = "Disbursed"
+ elif loan.status == "Repaid/Closed":
+ status = "Closed"
else:
status = loan.status
- frappe.db.set_value('Loan', loan.name, {
- 'is_term_loan': 1,
- 'penalty_income_account': penalty_account,
- 'status': status
- })
+ frappe.db.set_value(
+ "Loan",
+ loan.name,
+ {"is_term_loan": 1, "penalty_income_account": penalty_account, "status": status},
+ )
- process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type,
- loan=loan.name)
+ process_loan_interest_accrual_for_term_loans(
+ posting_date=nowdate(), loan_type=loan_type, loan=loan.name
+ )
-
- if frappe.db.has_column('Repayment Schedule', 'paid'):
- total_principal, total_interest = frappe.db.get_value('Repayment Schedule', {'paid': 1, 'parent': loan.name},
- ['sum(principal_amount) as total_principal', 'sum(interest_amount) as total_interest'])
+ if frappe.db.has_column("Repayment Schedule", "paid"):
+ total_principal, total_interest = frappe.db.get_value(
+ "Repayment Schedule",
+ {"paid": 1, "parent": loan.name},
+ ["sum(principal_amount) as total_principal", "sum(interest_amount) as total_interest"],
+ )
accrued_entries = get_accrued_interest_entries(loan.name)
for entry in accrued_entries:
@@ -127,17 +171,20 @@ def execute():
else:
principal_paid = flt(total_principal)
- frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
+ frappe.db.sql(
+ """ UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
- (principal_paid, interest_paid, entry.name))
+ (principal_paid, interest_paid, entry.name),
+ )
total_principal = flt(total_principal) - principal_paid
total_interest = flt(total_interest) - interest_paid
+
def create_loan_type(loan, loan_type_name, penalty_account):
- loan_type_doc = frappe.new_doc('Loan Type')
+ loan_type_doc = frappe.new_doc("Loan Type")
loan_type_doc.loan_name = make_autoname("Loan Type-.####")
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
diff --git a/erpnext/patches/v13_0/update_payment_terms_outstanding.py b/erpnext/patches/v13_0/update_payment_terms_outstanding.py
index aea09ad7a3..d0c25f37ad 100644
--- a/erpnext/patches/v13_0/update_payment_terms_outstanding.py
+++ b/erpnext/patches/v13_0/update_payment_terms_outstanding.py
@@ -7,10 +7,12 @@ import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "Payment Schedule")
- if frappe.db.count('Payment Schedule'):
- frappe.db.sql('''
+ if frappe.db.count("Payment Schedule"):
+ frappe.db.sql(
+ """
UPDATE
`tabPayment Schedule` ps
SET
ps.outstanding = (ps.payment_amount - ps.paid_amount)
- ''')
+ """
+ )
diff --git a/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py
index b2e3559197..49826dfd26 100644
--- a/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py
+++ b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py
@@ -8,8 +8,9 @@ import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "POS Invoice Merge Log")
frappe.reload_doc("accounts", "doctype", "POS Closing Entry")
- if frappe.db.count('POS Invoice Merge Log'):
- frappe.db.sql('''
+ if frappe.db.count("POS Invoice Merge Log"):
+ frappe.db.sql(
+ """
UPDATE
`tabPOS Invoice Merge Log` log, `tabPOS Invoice Reference` log_ref
SET
@@ -20,7 +21,8 @@ def execute():
)
WHERE
log_ref.parent = log.name
- ''')
+ """
+ )
- frappe.db.sql('''UPDATE `tabPOS Closing Entry` SET status = 'Submitted' where docstatus = 1''')
- frappe.db.sql('''UPDATE `tabPOS Closing Entry` SET status = 'Cancelled' where docstatus = 2''')
+ frappe.db.sql("""UPDATE `tabPOS Closing Entry` SET status = 'Submitted' where docstatus = 1""")
+ frappe.db.sql("""UPDATE `tabPOS Closing Entry` SET status = 'Cancelled' where docstatus = 2""")
diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py
index 29debc6ad1..c9a2322242 100644
--- a/erpnext/patches/v13_0/update_project_template_tasks.py
+++ b/erpnext/patches/v13_0/update_project_template_tasks.py
@@ -11,38 +11,39 @@ def execute():
frappe.reload_doc("projects", "doctype", "task")
# Update property setter status if any
- property_setter = frappe.db.get_value('Property Setter', {'doc_type': 'Task',
- 'field_name': 'status', 'property': 'options'})
+ property_setter = frappe.db.get_value(
+ "Property Setter", {"doc_type": "Task", "field_name": "status", "property": "options"}
+ )
if property_setter:
- property_setter_doc = frappe.get_doc('Property Setter', {'doc_type': 'Task',
- 'field_name': 'status', 'property': 'options'})
+ property_setter_doc = frappe.get_doc(
+ "Property Setter", {"doc_type": "Task", "field_name": "status", "property": "options"}
+ )
property_setter_doc.value += "\nTemplate"
property_setter_doc.save()
- for template_name in frappe.get_all('Project Template'):
+ for template_name in frappe.get_all("Project Template"):
template = frappe.get_doc("Project Template", template_name.name)
replace_tasks = False
new_tasks = []
for task in template.tasks:
if task.subject:
replace_tasks = True
- new_task = frappe.get_doc(dict(
- doctype = "Task",
- subject = task.subject,
- start = task.start,
- duration = task.duration,
- task_weight = task.task_weight,
- description = task.description,
- is_template = 1
- )).insert()
+ new_task = frappe.get_doc(
+ dict(
+ doctype="Task",
+ subject=task.subject,
+ start=task.start,
+ duration=task.duration,
+ task_weight=task.task_weight,
+ description=task.description,
+ is_template=1,
+ )
+ ).insert()
new_tasks.append(new_task)
if replace_tasks:
template.tasks = []
for tsk in new_tasks:
- template.append("tasks", {
- "task": tsk.name,
- "subject": tsk.subject
- })
+ template.append("tasks", {"task": tsk.name, "subject": tsk.subject})
template.save()
diff --git a/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py b/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py
index f9bfc54502..31aa29274d 100644
--- a/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py
+++ b/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py
@@ -6,10 +6,12 @@ import frappe
def execute():
- frappe.reload_doc("hr", "doctype", "employee")
+ frappe.reload_doc("hr", "doctype", "employee")
- if frappe.db.has_column("Employee", "reason_for_resignation"):
- frappe.db.sql(""" UPDATE `tabEmployee`
+ if frappe.db.has_column("Employee", "reason_for_resignation"):
+ frappe.db.sql(
+ """ UPDATE `tabEmployee`
SET reason_for_leaving = reason_for_resignation
WHERE status = 'Left' and reason_for_leaving is null and reason_for_resignation is not null
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/update_recipient_email_digest.py b/erpnext/patches/v13_0/update_recipient_email_digest.py
index d4d45afaba..af7f3ff327 100644
--- a/erpnext/patches/v13_0/update_recipient_email_digest.py
+++ b/erpnext/patches/v13_0/update_recipient_email_digest.py
@@ -6,17 +6,19 @@ import frappe
def execute():
- frappe.reload_doc("setup", "doctype", "Email Digest")
- frappe.reload_doc("setup", "doctype", "Email Digest Recipient")
- email_digests = frappe.db.get_list('Email Digest', fields=['name', 'recipient_list'])
- for email_digest in email_digests:
- if email_digest.recipient_list:
- for recipient in email_digest.recipient_list.split("\n"):
- doc = frappe.get_doc({
- 'doctype': 'Email Digest Recipient',
- 'parenttype': 'Email Digest',
- 'parentfield': 'recipients',
- 'parent': email_digest.name,
- 'recipient': recipient
- })
- doc.insert()
+ frappe.reload_doc("setup", "doctype", "Email Digest")
+ frappe.reload_doc("setup", "doctype", "Email Digest Recipient")
+ email_digests = frappe.db.get_list("Email Digest", fields=["name", "recipient_list"])
+ for email_digest in email_digests:
+ if email_digest.recipient_list:
+ for recipient in email_digest.recipient_list.split("\n"):
+ doc = frappe.get_doc(
+ {
+ "doctype": "Email Digest Recipient",
+ "parenttype": "Email Digest",
+ "parentfield": "recipients",
+ "parent": email_digest.name,
+ "recipient": recipient,
+ }
+ )
+ doc.insert()
diff --git a/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py b/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py
index 00926b0924..72e77fe216 100644
--- a/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py
+++ b/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py
@@ -9,15 +9,12 @@ def execute():
wo_item = frappe.qb.DocType("Work Order Item")
incorrect_item_wh = (
- frappe.qb
- .from_(wo)
- .join(wo_item).on(wo.name == wo_item.parent)
- .select(wo_item.item_code, wo.source_warehouse).distinct()
- .where(
- (wo.status == "Closed")
- & (wo.docstatus == 1)
- & (wo.source_warehouse.notnull())
- )
+ frappe.qb.from_(wo)
+ .join(wo_item)
+ .on(wo.name == wo_item.parent)
+ .select(wo_item.item_code, wo.source_warehouse)
+ .distinct()
+ .where((wo.status == "Closed") & (wo.docstatus == 1) & (wo.source_warehouse.notnull()))
).run()
for item_code, warehouse in incorrect_item_wh:
diff --git a/erpnext/patches/v13_0/update_response_by_variance.py b/erpnext/patches/v13_0/update_response_by_variance.py
index d65e9035c0..7304455f9f 100644
--- a/erpnext/patches/v13_0/update_response_by_variance.py
+++ b/erpnext/patches/v13_0/update_response_by_variance.py
@@ -6,27 +6,49 @@ import frappe
def execute():
- if frappe.db.exists('DocType', 'Issue') and frappe.db.count('Issue'):
- invalid_issues = frappe.get_all('Issue', {
- 'first_responded_on': ['is', 'set'],
- 'response_by_variance': ['<', 0]
- }, ["name", "response_by_variance", "timestampdiff(Second, `first_responded_on`, `response_by`) as variance"])
+ if frappe.db.exists("DocType", "Issue") and frappe.db.count("Issue"):
+ invalid_issues = frappe.get_all(
+ "Issue",
+ {"first_responded_on": ["is", "set"], "response_by_variance": ["<", 0]},
+ [
+ "name",
+ "response_by_variance",
+ "timestampdiff(Second, `first_responded_on`, `response_by`) as variance",
+ ],
+ )
# issues which has response_by_variance set as -ve
# but diff between first_responded_on & response_by is +ve i.e SLA isn't failed
- invalid_issues = [d for d in invalid_issues if d.get('variance') > 0]
+ invalid_issues = [d for d in invalid_issues if d.get("variance") > 0]
for issue in invalid_issues:
- frappe.db.set_value('Issue', issue.get('name'), 'response_by_variance', issue.get('variance'), update_modified=False)
+ frappe.db.set_value(
+ "Issue",
+ issue.get("name"),
+ "response_by_variance",
+ issue.get("variance"),
+ update_modified=False,
+ )
- invalid_issues = frappe.get_all('Issue', {
- 'resolution_date': ['is', 'set'],
- 'resolution_by_variance': ['<', 0]
- }, ["name", "resolution_by_variance", "timestampdiff(Second, `resolution_date`, `resolution_by`) as variance"])
+ invalid_issues = frappe.get_all(
+ "Issue",
+ {"resolution_date": ["is", "set"], "resolution_by_variance": ["<", 0]},
+ [
+ "name",
+ "resolution_by_variance",
+ "timestampdiff(Second, `resolution_date`, `resolution_by`) as variance",
+ ],
+ )
# issues which has resolution_by_variance set as -ve
# but diff between resolution_date & resolution_by is +ve i.e SLA isn't failed
- invalid_issues = [d for d in invalid_issues if d.get('variance') > 0]
+ invalid_issues = [d for d in invalid_issues if d.get("variance") > 0]
for issue in invalid_issues:
- frappe.db.set_value('Issue', issue.get('name'), 'resolution_by_variance', issue.get('variance'), update_modified=False)
+ frappe.db.set_value(
+ "Issue",
+ issue.get("name"),
+ "resolution_by_variance",
+ issue.get("variance"),
+ update_modified=False,
+ )
diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
index dd64e05ec1..9b5845f494 100644
--- a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
+++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
@@ -6,14 +6,16 @@ from erpnext.controllers.status_updater import OverAllowanceError
def execute():
- frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
- frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
- frappe.reload_doc('stock', 'doctype', 'delivery_note')
- frappe.reload_doc('stock', 'doctype', 'delivery_note_item')
- frappe.reload_doc('stock', 'doctype', 'stock_settings')
+ frappe.reload_doc("stock", "doctype", "purchase_receipt")
+ frappe.reload_doc("stock", "doctype", "purchase_receipt_item")
+ frappe.reload_doc("stock", "doctype", "delivery_note")
+ frappe.reload_doc("stock", "doctype", "delivery_note_item")
+ frappe.reload_doc("stock", "doctype", "stock_settings")
def update_from_return_docs(doctype):
- for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1, 'return_against': ('!=', '')}):
+ for return_doc in frappe.get_all(
+ doctype, filters={"is_return": 1, "docstatus": 1, "return_against": ("!=", "")}
+ ):
# Update original receipt/delivery document from return
return_doc = frappe.get_cached_doc(doctype, return_doc.name)
try:
@@ -27,9 +29,11 @@ def execute():
frappe.db.commit()
# Set received qty in stock uom in PR, as returned qty is checked against it
- frappe.db.sql(""" update `tabPurchase Receipt Item`
+ frappe.db.sql(
+ """ update `tabPurchase Receipt Item`
set received_stock_qty = received_qty * conversion_factor
- where docstatus = 1 """)
+ where docstatus = 1 """
+ )
- for doctype in ('Purchase Receipt', 'Delivery Note'):
+ for doctype in ("Purchase Receipt", "Delivery Note"):
update_from_return_docs(doctype)
diff --git a/erpnext/patches/v13_0/update_sane_transfer_against.py b/erpnext/patches/v13_0/update_sane_transfer_against.py
index a163d38584..45691e2ded 100644
--- a/erpnext/patches/v13_0/update_sane_transfer_against.py
+++ b/erpnext/patches/v13_0/update_sane_transfer_against.py
@@ -4,8 +4,8 @@ import frappe
def execute():
bom = frappe.qb.DocType("BOM")
- (frappe.qb
- .update(bom)
+ (
+ frappe.qb.update(bom)
.set(bom.transfer_material_against, "Work Order")
.where(bom.with_operations == 0)
).run()
diff --git a/erpnext/patches/v13_0/update_shipment_status.py b/erpnext/patches/v13_0/update_shipment_status.py
index f2d7d1d1e3..d21caf70ad 100644
--- a/erpnext/patches/v13_0/update_shipment_status.py
+++ b/erpnext/patches/v13_0/update_shipment_status.py
@@ -5,11 +5,15 @@ def execute():
frappe.reload_doc("stock", "doctype", "shipment")
# update submitted status
- frappe.db.sql("""UPDATE `tabShipment`
+ frappe.db.sql(
+ """UPDATE `tabShipment`
SET status = "Submitted"
- WHERE status = "Draft" AND docstatus = 1""")
+ WHERE status = "Draft" AND docstatus = 1"""
+ )
# update cancelled status
- frappe.db.sql("""UPDATE `tabShipment`
+ frappe.db.sql(
+ """UPDATE `tabShipment`
SET status = "Cancelled"
- WHERE status = "Draft" AND docstatus = 2""")
+ WHERE status = "Draft" AND docstatus = 2"""
+ )
diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py
index 7f61020309..84c683acd2 100644
--- a/erpnext/patches/v13_0/update_sla_enhancements.py
+++ b/erpnext/patches/v13_0/update_sla_enhancements.py
@@ -8,78 +8,94 @@ import frappe
def execute():
# add holiday list and employee group fields in SLA
# change response and resolution time in priorities child table
- if frappe.db.exists('DocType', 'Service Level Agreement'):
- sla_details = frappe.db.get_all('Service Level Agreement', fields=['name', 'service_level'])
- priorities = frappe.db.get_all('Service Level Priority', fields=['*'], filters={
- 'parenttype': ('in', ['Service Level Agreement', 'Service Level'])
- })
+ if frappe.db.exists("DocType", "Service Level Agreement"):
+ sla_details = frappe.db.get_all("Service Level Agreement", fields=["name", "service_level"])
+ priorities = frappe.db.get_all(
+ "Service Level Priority",
+ fields=["*"],
+ filters={"parenttype": ("in", ["Service Level Agreement", "Service Level"])},
+ )
- frappe.reload_doc('support', 'doctype', 'service_level_agreement')
- frappe.reload_doc('support', 'doctype', 'pause_sla_on_status')
- frappe.reload_doc('support', 'doctype', 'service_level_priority')
- frappe.reload_doc('support', 'doctype', 'service_day')
+ frappe.reload_doc("support", "doctype", "service_level_agreement")
+ frappe.reload_doc("support", "doctype", "pause_sla_on_status")
+ frappe.reload_doc("support", "doctype", "service_level_priority")
+ frappe.reload_doc("support", "doctype", "service_day")
for entry in sla_details:
- values = frappe.db.get_value('Service Level', entry.service_level, ['holiday_list', 'employee_group'])
+ values = frappe.db.get_value(
+ "Service Level", entry.service_level, ["holiday_list", "employee_group"]
+ )
if values:
holiday_list = values[0]
employee_group = values[1]
- frappe.db.set_value('Service Level Agreement', entry.name, {
- 'holiday_list': holiday_list,
- 'employee_group': employee_group
- })
+ frappe.db.set_value(
+ "Service Level Agreement",
+ entry.name,
+ {"holiday_list": holiday_list, "employee_group": employee_group},
+ )
priority_dict = {}
for priority in priorities:
- if priority.parenttype == 'Service Level Agreement':
+ if priority.parenttype == "Service Level Agreement":
response_time = convert_to_seconds(priority.response_time, priority.response_time_period)
resolution_time = convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
- frappe.db.set_value('Service Level Priority', priority.name, {
- 'response_time': response_time,
- 'resolution_time': resolution_time
- })
- if priority.parenttype == 'Service Level':
+ frappe.db.set_value(
+ "Service Level Priority",
+ priority.name,
+ {"response_time": response_time, "resolution_time": resolution_time},
+ )
+ if priority.parenttype == "Service Level":
if not priority.parent in priority_dict:
priority_dict[priority.parent] = []
priority_dict[priority.parent].append(priority)
-
# copy Service Levels to Service Level Agreements
sl = [entry.service_level for entry in sla_details]
- if frappe.db.exists('DocType', 'Service Level'):
- service_levels = frappe.db.get_all('Service Level', filters={'service_level': ('not in', sl)}, fields=['*'])
+ if frappe.db.exists("DocType", "Service Level"):
+ service_levels = frappe.db.get_all(
+ "Service Level", filters={"service_level": ("not in", sl)}, fields=["*"]
+ )
for entry in service_levels:
- sla = frappe.new_doc('Service Level Agreement')
+ sla = frappe.new_doc("Service Level Agreement")
sla.service_level = entry.service_level
sla.holiday_list = entry.holiday_list
sla.employee_group = entry.employee_group
sla.flags.ignore_validate = True
sla = sla.insert(ignore_mandatory=True)
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tabService Day`
SET
parent = %(new_parent)s , parentfield = 'support_and_resolution', parenttype = 'Service Level Agreement'
WHERE
parent = %(old_parent)s
- """, {'new_parent': sla.name, 'old_parent': entry.name}, as_dict = 1)
+ """,
+ {"new_parent": sla.name, "old_parent": entry.name},
+ as_dict=1,
+ )
priority_list = priority_dict.get(entry.name)
if priority_list:
- sla = frappe.get_doc('Service Level Agreement', sla.name)
+ sla = frappe.get_doc("Service Level Agreement", sla.name)
for priority in priority_list:
- row = sla.append('priorities', {
- 'priority': priority.priority,
- 'default_priority': priority.default_priority,
- 'response_time': convert_to_seconds(priority.response_time, priority.response_time_period),
- 'resolution_time': convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
- })
+ row = sla.append(
+ "priorities",
+ {
+ "priority": priority.priority,
+ "default_priority": priority.default_priority,
+ "response_time": convert_to_seconds(priority.response_time, priority.response_time_period),
+ "resolution_time": convert_to_seconds(
+ priority.resolution_time, priority.resolution_time_period
+ ),
+ },
+ )
row.db_update()
sla.db_update()
- frappe.delete_doc_if_exists('DocType', 'Service Level')
+ frappe.delete_doc_if_exists("DocType", "Service Level")
def convert_to_seconds(value, unit):
diff --git a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py
index 665cc39923..6d26ac543f 100644
--- a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py
+++ b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py
@@ -6,8 +6,10 @@ import frappe
def execute():
- frappe.reload_doc('hr', 'doctype', 'shift_assignment')
- if frappe.db.has_column('Shift Assignment', 'date'):
- frappe.db.sql("""update `tabShift Assignment`
+ frappe.reload_doc("hr", "doctype", "shift_assignment")
+ if frappe.db.has_column("Shift Assignment", "date"):
+ frappe.db.sql(
+ """update `tabShift Assignment`
set end_date=date, start_date=date
- where date IS NOT NULL and start_date IS NULL and end_date IS NULL;""")
+ where date IS NOT NULL and start_date IS NULL and end_date IS NULL;"""
+ )
diff --git a/erpnext/patches/v13_0/update_subscription.py b/erpnext/patches/v13_0/update_subscription.py
index b67c74de1d..66b3def79c 100644
--- a/erpnext/patches/v13_0/update_subscription.py
+++ b/erpnext/patches/v13_0/update_subscription.py
@@ -7,12 +7,13 @@ import frappe
def execute():
- frappe.reload_doc('accounts', 'doctype', 'subscription')
- frappe.reload_doc('accounts', 'doctype', 'subscription_invoice')
- frappe.reload_doc('accounts', 'doctype', 'subscription_plan')
+ frappe.reload_doc("accounts", "doctype", "subscription")
+ frappe.reload_doc("accounts", "doctype", "subscription_invoice")
+ frappe.reload_doc("accounts", "doctype", "subscription_plan")
- if frappe.db.has_column('Subscription', 'customer'):
- frappe.db.sql("""
+ if frappe.db.has_column("Subscription", "customer"):
+ frappe.db.sql(
+ """
UPDATE `tabSubscription`
SET
start_date = start,
@@ -20,22 +21,28 @@ def execute():
party = customer,
sales_tax_template = tax_template
WHERE IFNULL(party,'') = ''
- """)
+ """
+ )
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabSubscription Invoice`
SET document_type = 'Sales Invoice'
WHERE IFNULL(document_type, '') = ''
- """)
+ """
+ )
price_determination_map = {
- 'Fixed rate': 'Fixed Rate',
- 'Based on price list': 'Based On Price List'
+ "Fixed rate": "Fixed Rate",
+ "Based on price list": "Based On Price List",
}
for key, value in price_determination_map.items():
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabSubscription Plan`
SET price_determination = %s
WHERE price_determination = %s
- """, (value, key))
+ """,
+ (value, key),
+ )
diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py
index e21fe57821..d7c849956e 100644
--- a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py
+++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py
@@ -2,9 +2,11 @@ import frappe
def execute():
- if frappe.db.exists('DocType', 'Member'):
- frappe.reload_doc('Non Profit', 'doctype', 'Member')
+ if frappe.db.exists("DocType", "Member"):
+ frappe.reload_doc("Non Profit", "doctype", "Member")
- if frappe.db.has_column('Member', 'subscription_activated'):
- frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1')
- frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated')
+ if frappe.db.has_column("Member", "subscription_activated"):
+ frappe.db.sql(
+ 'UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1'
+ )
+ frappe.db.sql_ddl("ALTER table `tabMember` DROP COLUMN subscription_activated")
diff --git a/erpnext/patches/v13_0/update_tax_category_for_rcm.py b/erpnext/patches/v13_0/update_tax_category_for_rcm.py
index 7af2366bf0..8ac9534889 100644
--- a/erpnext/patches/v13_0/update_tax_category_for_rcm.py
+++ b/erpnext/patches/v13_0/update_tax_category_for_rcm.py
@@ -5,27 +5,46 @@ from erpnext.regional.india import states
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- create_custom_fields({
- 'Tax Category': [
- dict(fieldname='is_inter_state', label='Is Inter State',
- fieldtype='Check', insert_after='disabled', print_hide=1),
- dict(fieldname='is_reverse_charge', label='Is Reverse Charge', fieldtype='Check',
- insert_after='is_inter_state', print_hide=1),
- dict(fieldname='tax_category_column_break', fieldtype='Column Break',
- insert_after='is_reverse_charge'),
- dict(fieldname='gst_state', label='Source State', fieldtype='Select',
- options='\n'.join(states), insert_after='company')
- ]
- }, update=True)
+ create_custom_fields(
+ {
+ "Tax Category": [
+ dict(
+ fieldname="is_inter_state",
+ label="Is Inter State",
+ fieldtype="Check",
+ insert_after="disabled",
+ print_hide=1,
+ ),
+ dict(
+ fieldname="is_reverse_charge",
+ label="Is Reverse Charge",
+ fieldtype="Check",
+ insert_after="is_inter_state",
+ print_hide=1,
+ ),
+ dict(
+ fieldname="tax_category_column_break",
+ fieldtype="Column Break",
+ insert_after="is_reverse_charge",
+ ),
+ dict(
+ fieldname="gst_state",
+ label="Source State",
+ fieldtype="Select",
+ options="\n".join(states),
+ insert_after="company",
+ ),
+ ]
+ },
+ update=True,
+ )
tax_category = frappe.qb.DocType("Tax Category")
- frappe.qb.update(tax_category).set(
- tax_category.is_reverse_charge, 1
- ).where(
- tax_category.name.isin(['Reverse Charge Out-State', 'Reverse Charge In-State'])
- ).run()
\ No newline at end of file
+ frappe.qb.update(tax_category).set(tax_category.is_reverse_charge, 1).where(
+ tax_category.name.isin(["Reverse Charge Out-State", "Reverse Charge In-State"])
+ ).run()
diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py
index 436d2e6a6d..0505266b3c 100644
--- a/erpnext/patches/v13_0/update_tds_check_field.py
+++ b/erpnext/patches/v13_0/update_tds_check_field.py
@@ -2,9 +2,12 @@ import frappe
def execute():
- if frappe.db.has_table("Tax Withholding Category") \
- and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
- frappe.db.sql("""
+ if frappe.db.has_table("Tax Withholding Category") and frappe.db.has_column(
+ "Tax Withholding Category", "round_off_tax_amount"
+ ):
+ frappe.db.sql(
+ """
UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
WHERE round_off_tax_amount IS NULL
- """)
+ """
+ )
diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py
index a5e3391d53..02654c11d3 100644
--- a/erpnext/patches/v13_0/update_timesheet_changes.py
+++ b/erpnext/patches/v13_0/update_timesheet_changes.py
@@ -9,17 +9,23 @@ def execute():
if frappe.db.has_column("Timesheet Detail", "billable"):
rename_field("Timesheet Detail", "billable", "is_billable")
- base_currency = frappe.defaults.get_global_default('currency')
+ base_currency = frappe.defaults.get_global_default("currency")
- frappe.db.sql("""UPDATE `tabTimesheet Detail`
+ frappe.db.sql(
+ """UPDATE `tabTimesheet Detail`
SET base_billing_rate = billing_rate,
base_billing_amount = billing_amount,
base_costing_rate = costing_rate,
- base_costing_amount = costing_amount""")
+ base_costing_amount = costing_amount"""
+ )
- frappe.db.sql("""UPDATE `tabTimesheet`
+ frappe.db.sql(
+ """UPDATE `tabTimesheet`
SET currency = '{0}',
exchange_rate = 1.0,
base_total_billable_amount = total_billable_amount,
base_total_billed_amount = total_billed_amount,
- base_total_costing_amount = total_costing_amount""".format(base_currency))
+ base_total_costing_amount = total_costing_amount""".format(
+ base_currency
+ )
+ )
diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
index 902707b4b6..326fc579f4 100644
--- a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
+++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
@@ -2,10 +2,10 @@ import frappe
def execute():
- frappe.reload_doc('custom', 'doctype', 'custom_field', force=True)
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ frappe.reload_doc("custom", "doctype", "custom_field", force=True)
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- if frappe.db.exists('Custom Field', { 'fieldname': 'vehicle_no' }):
- frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', '')
+ if frappe.db.exists("Custom Field", {"fieldname": "vehicle_no"}):
+ frappe.db.set_value("Custom Field", {"fieldname": "vehicle_no"}, "mandatory_depends_on", "")
diff --git a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
index c760a6a52f..b395c01c1d 100644
--- a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
+++ b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
@@ -8,122 +8,116 @@ from frappe.model.utils.rename_field import rename_field
def execute():
- frappe.reload_doc('Accounts', 'doctype', 'Salary Component Account')
- if frappe.db.has_column('Salary Component Account', 'default_account'):
+ frappe.reload_doc("Accounts", "doctype", "Salary Component Account")
+ if frappe.db.has_column("Salary Component Account", "default_account"):
rename_field("Salary Component Account", "default_account", "account")
doctype_list = [
- {
- 'module':'HR',
- 'doctype':'Employee Advance'
- },
- {
- 'module':'HR',
- 'doctype':'Leave Encashment'
- },
- {
- 'module':'Payroll',
- 'doctype':'Additional Salary'
- },
- {
- 'module':'Payroll',
- 'doctype':'Employee Benefit Application'
- },
- {
- 'module':'Payroll',
- 'doctype':'Employee Benefit Claim'
- },
- {
- 'module':'Payroll',
- 'doctype':'Employee Incentive'
- },
- {
- 'module':'Payroll',
- 'doctype':'Employee Tax Exemption Declaration'
- },
- {
- 'module':'Payroll',
- 'doctype':'Employee Tax Exemption Proof Submission'
- },
- {
- 'module':'Payroll',
- 'doctype':'Income Tax Slab'
- },
- {
- 'module':'Payroll',
- 'doctype':'Payroll Entry'
- },
- {
- 'module':'Payroll',
- 'doctype':'Retention Bonus'
- },
- {
- 'module':'Payroll',
- 'doctype':'Salary Structure'
- },
- {
- 'module':'Payroll',
- 'doctype':'Salary Structure Assignment'
- },
- {
- 'module':'Payroll',
- 'doctype':'Salary Slip'
- },
+ {"module": "HR", "doctype": "Employee Advance"},
+ {"module": "HR", "doctype": "Leave Encashment"},
+ {"module": "Payroll", "doctype": "Additional Salary"},
+ {"module": "Payroll", "doctype": "Employee Benefit Application"},
+ {"module": "Payroll", "doctype": "Employee Benefit Claim"},
+ {"module": "Payroll", "doctype": "Employee Incentive"},
+ {"module": "Payroll", "doctype": "Employee Tax Exemption Declaration"},
+ {"module": "Payroll", "doctype": "Employee Tax Exemption Proof Submission"},
+ {"module": "Payroll", "doctype": "Income Tax Slab"},
+ {"module": "Payroll", "doctype": "Payroll Entry"},
+ {"module": "Payroll", "doctype": "Retention Bonus"},
+ {"module": "Payroll", "doctype": "Salary Structure"},
+ {"module": "Payroll", "doctype": "Salary Structure Assignment"},
+ {"module": "Payroll", "doctype": "Salary Slip"},
]
for item in doctype_list:
- frappe.reload_doc(item['module'], 'doctype', item['doctype'])
+ frappe.reload_doc(item["module"], "doctype", item["doctype"])
# update company in employee advance based on employee company
- for dt in ['Employee Incentive', 'Leave Encashment', 'Employee Benefit Application', 'Employee Benefit Claim']:
- frappe.db.sql("""
+ for dt in [
+ "Employee Incentive",
+ "Leave Encashment",
+ "Employee Benefit Application",
+ "Employee Benefit Claim",
+ ]:
+ frappe.db.sql(
+ """
update `tab{doctype}`
set company = (select company from tabEmployee where name=`tab{doctype}`.employee)
- """.format(doctype=dt))
+ """.format(
+ doctype=dt
+ )
+ )
# update exchange rate for employee advance
frappe.db.sql("update `tabEmployee Advance` set exchange_rate=1")
# get all companies and it's currency
- all_companies = frappe.db.get_all("Company", fields=["name", "default_currency", "default_payroll_payable_account"])
+ all_companies = frappe.db.get_all(
+ "Company", fields=["name", "default_currency", "default_payroll_payable_account"]
+ )
for d in all_companies:
company = d.name
company_currency = d.default_currency
default_payroll_payable_account = d.default_payroll_payable_account
if not default_payroll_payable_account:
- default_payroll_payable_account = frappe.db.get_value("Account",
- {"account_name": _("Payroll Payable"), "company": company, "account_currency": company_currency, "is_group": 0})
+ default_payroll_payable_account = frappe.db.get_value(
+ "Account",
+ {
+ "account_name": _("Payroll Payable"),
+ "company": company,
+ "account_currency": company_currency,
+ "is_group": 0,
+ },
+ )
# update currency in following doctypes based on company currency
- doctypes_for_currency = ['Employee Advance', 'Leave Encashment', 'Employee Benefit Application',
- 'Employee Benefit Claim', 'Employee Incentive', 'Additional Salary',
- 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission',
- 'Income Tax Slab', 'Retention Bonus', 'Salary Structure']
+ doctypes_for_currency = [
+ "Employee Advance",
+ "Leave Encashment",
+ "Employee Benefit Application",
+ "Employee Benefit Claim",
+ "Employee Incentive",
+ "Additional Salary",
+ "Employee Tax Exemption Declaration",
+ "Employee Tax Exemption Proof Submission",
+ "Income Tax Slab",
+ "Retention Bonus",
+ "Salary Structure",
+ ]
for dt in doctypes_for_currency:
- frappe.db.sql("""update `tab{doctype}` set currency = %s where company=%s"""
- .format(doctype=dt), (company_currency, company))
+ frappe.db.sql(
+ """update `tab{doctype}` set currency = %s where company=%s""".format(doctype=dt),
+ (company_currency, company),
+ )
# update fields in payroll entry
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabPayroll Entry`
set currency = %s,
exchange_rate = 1,
payroll_payable_account=%s
where company=%s
- """, (company_currency, default_payroll_payable_account, company))
+ """,
+ (company_currency, default_payroll_payable_account, company),
+ )
# update fields in Salary Structure Assignment
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabSalary Structure Assignment`
set currency = %s,
payroll_payable_account=%s
where company=%s
- """, (company_currency, default_payroll_payable_account, company))
+ """,
+ (company_currency, default_payroll_payable_account, company),
+ )
# update fields in Salary Slip
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabSalary Slip`
set currency = %s,
exchange_rate = 1,
@@ -134,4 +128,6 @@ def execute():
base_rounded_total = rounded_total,
base_total_in_words = total_in_words
where company=%s
- """, (company_currency, company))
+ """,
+ (company_currency, company),
+ )
diff --git a/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py
index e43a8bad8e..693d06dc1a 100644
--- a/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py
+++ b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py
@@ -11,8 +11,6 @@ def execute():
sr_item = frappe.qb.DocType(doctype)
- (frappe.qb
- .update(sr_item)
- .set(sr_item.current_serial_no, None)
- .where(sr_item.current_qty == 0)
+ (
+ frappe.qb.update(sr_item).set(sr_item.current_serial_no, None).where(sr_item.current_qty == 0)
).run()
diff --git a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
index 2a8b6ef746..001c8efd55 100644
--- a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
+++ b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
@@ -8,15 +8,19 @@ def execute():
template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
if not template:
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
- response = frappe.read_file(os.path.join(base_path, "exit_interview/exit_questionnaire_notification_template.html"))
+ response = frappe.read_file(
+ os.path.join(base_path, "exit_interview/exit_questionnaire_notification_template.html")
+ )
- template = frappe.get_doc({
- "doctype": "Email Template",
- "name": _("Exit Questionnaire Notification"),
- "response": response,
- "subject": _("Exit Questionnaire Notification"),
- "owner": frappe.session.user,
- }).insert(ignore_permissions=True)
+ template = frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Exit Questionnaire Notification"),
+ "response": response,
+ "subject": _("Exit Questionnaire Notification"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
template = template.name
hr_settings = frappe.get_doc("HR Settings")
diff --git a/erpnext/patches/v14_0/delete_agriculture_doctypes.py b/erpnext/patches/v14_0/delete_agriculture_doctypes.py
index d7fe832f9a..e0b12a2579 100644
--- a/erpnext/patches/v14_0/delete_agriculture_doctypes.py
+++ b/erpnext/patches/v14_0/delete_agriculture_doctypes.py
@@ -6,14 +6,16 @@ def execute():
frappe.delete_doc("Workspace", "Agriculture", ignore_missing=True, force=True)
- reports = frappe.get_all("Report", {"module": "agriculture", "is_standard": "Yes"}, pluck='name')
+ reports = frappe.get_all("Report", {"module": "agriculture", "is_standard": "Yes"}, pluck="name")
for report in reports:
frappe.delete_doc("Report", report, ignore_missing=True, force=True)
- dashboards = frappe.get_all("Dashboard", {"module": "agriculture", "is_standard": 1}, pluck='name')
+ dashboards = frappe.get_all(
+ "Dashboard", {"module": "agriculture", "is_standard": 1}, pluck="name"
+ )
for dashboard in dashboards:
frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
- doctypes = frappe.get_all("DocType", {"module": "agriculture", "custom": 0}, pluck='name')
+ doctypes = frappe.get_all("DocType", {"module": "agriculture", "custom": 0}, pluck="name")
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
diff --git a/erpnext/patches/v14_0/delete_amazon_mws_doctype.py b/erpnext/patches/v14_0/delete_amazon_mws_doctype.py
index 525da6cbe5..636e7436c6 100644
--- a/erpnext/patches/v14_0/delete_amazon_mws_doctype.py
+++ b/erpnext/patches/v14_0/delete_amazon_mws_doctype.py
@@ -2,4 +2,4 @@ import frappe
def execute():
- frappe.delete_doc("DocType", "Amazon MWS Settings", ignore_missing=True)
\ No newline at end of file
+ frappe.delete_doc("DocType", "Amazon MWS Settings", ignore_missing=True)
diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
index 3a4f8f537d..2c699e4a9f 100644
--- a/erpnext/patches/v14_0/delete_healthcare_doctypes.py
+++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
@@ -7,58 +7,59 @@ def execute():
frappe.delete_doc("Workspace", "Healthcare", ignore_missing=True, force=True)
- pages = frappe.get_all("Page", {"module": "healthcare"}, pluck='name')
+ pages = frappe.get_all("Page", {"module": "healthcare"}, pluck="name")
for page in pages:
frappe.delete_doc("Page", page, ignore_missing=True, force=True)
- reports = frappe.get_all("Report", {"module": "healthcare", "is_standard": "Yes"}, pluck='name')
+ reports = frappe.get_all("Report", {"module": "healthcare", "is_standard": "Yes"}, pluck="name")
for report in reports:
frappe.delete_doc("Report", report, ignore_missing=True, force=True)
- print_formats = frappe.get_all("Print Format", {"module": "healthcare", "standard": "Yes"}, pluck='name')
+ print_formats = frappe.get_all(
+ "Print Format", {"module": "healthcare", "standard": "Yes"}, pluck="name"
+ )
for print_format in print_formats:
frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True)
frappe.reload_doc("website", "doctype", "website_settings")
- forms = frappe.get_all("Web Form", {"module": "healthcare", "is_standard": 1}, pluck='name')
+ forms = frappe.get_all("Web Form", {"module": "healthcare", "is_standard": 1}, pluck="name")
for form in forms:
frappe.delete_doc("Web Form", form, ignore_missing=True, force=True)
- dashboards = frappe.get_all("Dashboard", {"module": "healthcare", "is_standard": 1}, pluck='name')
+ dashboards = frappe.get_all("Dashboard", {"module": "healthcare", "is_standard": 1}, pluck="name")
for dashboard in dashboards:
frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
- dashboards = frappe.get_all("Dashboard Chart", {"module": "healthcare", "is_standard": 1}, pluck='name')
+ dashboards = frappe.get_all(
+ "Dashboard Chart", {"module": "healthcare", "is_standard": 1}, pluck="name"
+ )
for dashboard in dashboards:
frappe.delete_doc("Dashboard Chart", dashboard, ignore_missing=True, force=True)
frappe.reload_doc("desk", "doctype", "number_card")
- cards = frappe.get_all("Number Card", {"module": "healthcare", "is_standard": 1}, pluck='name')
+ cards = frappe.get_all("Number Card", {"module": "healthcare", "is_standard": 1}, pluck="name")
for card in cards:
frappe.delete_doc("Number Card", card, ignore_missing=True, force=True)
- titles = ['Lab Test', 'Prescription', 'Patient Appointment']
- items = frappe.get_all('Portal Menu Item', filters=[['title', 'in', titles]], pluck='name')
+ titles = ["Lab Test", "Prescription", "Patient Appointment"]
+ items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name")
for item in items:
frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True)
- doctypes = frappe.get_all("DocType", {"module": "healthcare", "custom": 0}, pluck='name')
+ doctypes = frappe.get_all("DocType", {"module": "healthcare", "custom": 0}, pluck="name")
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True)
custom_fields = {
- 'Sales Invoice': ['patient', 'patient_name', 'ref_practitioner'],
- 'Sales Invoice Item': ['reference_dt', 'reference_dn'],
- 'Stock Entry': ['inpatient_medication_entry'],
- 'Stock Entry Detail': ['patient', 'inpatient_medication_entry_child'],
+ "Sales Invoice": ["patient", "patient_name", "ref_practitioner"],
+ "Sales Invoice Item": ["reference_dt", "reference_dn"],
+ "Stock Entry": ["inpatient_medication_entry"],
+ "Stock Entry Detail": ["patient", "inpatient_medication_entry_child"],
}
for doc, fields in custom_fields.items():
- filters = {
- 'dt': doc,
- 'fieldname': ['in', fields]
- }
- records = frappe.get_all('Custom Field', filters=filters, pluck='name')
+ filters = {"dt": doc, "fieldname": ["in", fields]}
+ records = frappe.get_all("Custom Field", filters=filters, pluck="name")
for record in records:
- frappe.delete_doc('Custom Field', record, ignore_missing=True, force=True)
+ frappe.delete_doc("Custom Field", record, ignore_missing=True, force=True)
diff --git a/erpnext/patches/v14_0/delete_hospitality_doctypes.py b/erpnext/patches/v14_0/delete_hospitality_doctypes.py
index d0216f80d3..8fa3fe02a4 100644
--- a/erpnext/patches/v14_0/delete_hospitality_doctypes.py
+++ b/erpnext/patches/v14_0/delete_hospitality_doctypes.py
@@ -2,22 +2,22 @@ import frappe
def execute():
- modules = ['Hotels', 'Restaurant']
+ modules = ["Hotels", "Restaurant"]
for module in modules:
frappe.delete_doc("Module Def", module, ignore_missing=True, force=True)
frappe.delete_doc("Workspace", module, ignore_missing=True, force=True)
- reports = frappe.get_all("Report", {"module": module, "is_standard": "Yes"}, pluck='name')
+ reports = frappe.get_all("Report", {"module": module, "is_standard": "Yes"}, pluck="name")
for report in reports:
frappe.delete_doc("Report", report, ignore_missing=True, force=True)
- dashboards = frappe.get_all("Dashboard", {"module": module, "is_standard": 1}, pluck='name')
+ dashboards = frappe.get_all("Dashboard", {"module": module, "is_standard": 1}, pluck="name")
for dashboard in dashboards:
frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
- doctypes = frappe.get_all("DocType", {"module": module, "custom": 0}, pluck='name')
+ doctypes = frappe.get_all("DocType", {"module": module, "custom": 0}, pluck="name")
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
diff --git a/erpnext/patches/v14_0/delete_hub_doctypes.py b/erpnext/patches/v14_0/delete_hub_doctypes.py
index d1e9e31f0c..08f4445fe2 100644
--- a/erpnext/patches/v14_0/delete_hub_doctypes.py
+++ b/erpnext/patches/v14_0/delete_hub_doctypes.py
@@ -3,7 +3,7 @@ import frappe
def execute():
- doctypes = frappe.get_all("DocType", {"module": "Hub Node", "custom": 0}, pluck='name')
+ doctypes = frappe.get_all("DocType", {"module": "Hub Node", "custom": 0}, pluck="name")
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
diff --git a/erpnext/patches/v14_0/delete_non_profit_doctypes.py b/erpnext/patches/v14_0/delete_non_profit_doctypes.py
index 565b10cbb8..e9ea8b19e1 100644
--- a/erpnext/patches/v14_0/delete_non_profit_doctypes.py
+++ b/erpnext/patches/v14_0/delete_non_profit_doctypes.py
@@ -6,31 +6,33 @@ def execute():
frappe.delete_doc("Workspace", "Non Profit", ignore_missing=True, force=True)
- print_formats = frappe.get_all("Print Format", {"module": "Non Profit", "standard": "Yes"}, pluck='name')
+ print_formats = frappe.get_all(
+ "Print Format", {"module": "Non Profit", "standard": "Yes"}, pluck="name"
+ )
for print_format in print_formats:
frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True)
- print_formats = ['80G Certificate for Membership', '80G Certificate for Donation']
+ print_formats = ["80G Certificate for Membership", "80G Certificate for Donation"]
for print_format in print_formats:
frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True)
- reports = frappe.get_all("Report", {"module": "Non Profit", "is_standard": "Yes"}, pluck='name')
+ reports = frappe.get_all("Report", {"module": "Non Profit", "is_standard": "Yes"}, pluck="name")
for report in reports:
frappe.delete_doc("Report", report, ignore_missing=True, force=True)
- dashboards = frappe.get_all("Dashboard", {"module": "Non Profit", "is_standard": 1}, pluck='name')
+ dashboards = frappe.get_all("Dashboard", {"module": "Non Profit", "is_standard": 1}, pluck="name")
for dashboard in dashboards:
frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
- doctypes = frappe.get_all("DocType", {"module": "Non Profit", "custom": 0}, pluck='name')
+ doctypes = frappe.get_all("DocType", {"module": "Non Profit", "custom": 0}, pluck="name")
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
- doctypes = ['Tax Exemption 80G Certificate', 'Tax Exemption 80G Certificate Detail']
+ doctypes = ["Tax Exemption 80G Certificate", "Tax Exemption 80G Certificate Detail"]
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
- forms = ['grant-application', 'certification-application', 'certification-application-usd']
+ forms = ["grant-application", "certification-application", "certification-application-usd"]
for form in forms:
frappe.delete_doc("Web Form", form, ignore_missing=True, force=True)
@@ -40,24 +42,24 @@ def execute():
]
for record in custom_records:
try:
- frappe.delete_doc(record['doctype'], record['name'], ignore_missing=True)
+ frappe.delete_doc(record["doctype"], record["name"], ignore_missing=True)
except frappe.LinkExistsError:
pass
custom_fields = {
- 'Member': ['pan_number'],
- 'Donor': ['pan_number'],
- 'Company': [
- 'non_profit_section', 'company_80g_number', 'with_effect_from',
- 'non_profit_column_break', 'pan_details'
+ "Member": ["pan_number"],
+ "Donor": ["pan_number"],
+ "Company": [
+ "non_profit_section",
+ "company_80g_number",
+ "with_effect_from",
+ "non_profit_column_break",
+ "pan_details",
],
}
for doc, fields in custom_fields.items():
- filters = {
- 'dt': doc,
- 'fieldname': ['in', fields]
- }
- records = frappe.get_all('Custom Field', filters=filters, pluck='name')
+ filters = {"dt": doc, "fieldname": ["in", fields]}
+ records = frappe.get_all("Custom Field", filters=filters, pluck="name")
for record in records:
- frappe.delete_doc('Custom Field', record, ignore_missing=True, force=True)
+ frappe.delete_doc("Custom Field", record, ignore_missing=True, force=True)
diff --git a/erpnext/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
index c4f097fdd9..3bd26933ba 100644
--- a/erpnext/patches/v14_0/migrate_cost_center_allocations.py
+++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
@@ -4,13 +4,14 @@ from frappe.utils import today
def execute():
for dt in ("cost_center_allocation", "cost_center_allocation_percentage"):
- frappe.reload_doc('accounts', 'doctype', dt)
+ frappe.reload_doc("accounts", "doctype", dt)
cc_allocations = get_existing_cost_center_allocations()
if cc_allocations:
create_new_cost_center_allocation_records(cc_allocations)
- frappe.delete_doc('DocType', 'Distributed Cost Center', ignore_missing=True)
+ frappe.delete_doc("DocType", "Distributed Cost Center", ignore_missing=True)
+
def create_new_cost_center_allocation_records(cc_allocations):
for main_cc, allocations in cc_allocations.items():
@@ -19,13 +20,11 @@ def create_new_cost_center_allocation_records(cc_allocations):
cca.valid_from = today()
for child_cc, percentage in allocations.items():
- cca.append("allocation_percentages", ({
- "cost_center": child_cc,
- "percentage": percentage
- }))
+ cca.append("allocation_percentages", ({"cost_center": child_cc, "percentage": percentage}))
cca.save()
cca.submit()
+
def get_existing_cost_center_allocations():
if not frappe.db.exists("DocType", "Distributed Cost Center"):
return
@@ -35,14 +34,16 @@ def get_existing_cost_center_allocations():
records = (
frappe.qb.from_(par)
- .inner_join(child).on(par.name == child.parent)
+ .inner_join(child)
+ .on(par.name == child.parent)
.select(par.name, child.cost_center, child.percentage_allocation)
.where(par.enable_distributed_cost_center == 1)
).run(as_dict=True)
cc_allocations = frappe._dict()
for d in records:
- cc_allocations.setdefault(d.name, frappe._dict())\
- .setdefault(d.cost_center, d.percentage_allocation)
+ cc_allocations.setdefault(d.name, frappe._dict()).setdefault(
+ d.cost_center, d.percentage_allocation
+ )
- return cc_allocations
\ No newline at end of file
+ return cc_allocations
diff --git a/erpnext/patches/v14_0/migrate_crm_settings.py b/erpnext/patches/v14_0/migrate_crm_settings.py
index 0c7785367c..696a1009df 100644
--- a/erpnext/patches/v14_0/migrate_crm_settings.py
+++ b/erpnext/patches/v14_0/migrate_crm_settings.py
@@ -2,16 +2,21 @@ import frappe
def execute():
- settings = frappe.db.get_value('Selling Settings', 'Selling Settings', [
- 'campaign_naming_by',
- 'close_opportunity_after_days',
- 'default_valid_till'
- ], as_dict=True)
+ settings = frappe.db.get_value(
+ "Selling Settings",
+ "Selling Settings",
+ ["campaign_naming_by", "close_opportunity_after_days", "default_valid_till"],
+ as_dict=True,
+ )
- frappe.reload_doc('crm', 'doctype', 'crm_settings')
+ frappe.reload_doc("crm", "doctype", "crm_settings")
if settings:
- frappe.db.set_value('CRM Settings', 'CRM Settings', {
- 'campaign_naming_by': settings.campaign_naming_by,
- 'close_opportunity_after_days': settings.close_opportunity_after_days,
- 'default_valid_till': settings.default_valid_till
- })
+ frappe.db.set_value(
+ "CRM Settings",
+ "CRM Settings",
+ {
+ "campaign_naming_by": settings.campaign_naming_by,
+ "close_opportunity_after_days": settings.close_opportunity_after_days,
+ "default_valid_till": settings.default_valid_till,
+ },
+ )
diff --git a/erpnext/patches/v14_0/rearrange_company_fields.py b/erpnext/patches/v14_0/rearrange_company_fields.py
index fd7eb7f750..d188a0c6f3 100644
--- a/erpnext/patches/v14_0/rearrange_company_fields.py
+++ b/erpnext/patches/v14_0/rearrange_company_fields.py
@@ -3,25 +3,64 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
custom_fields = {
- 'Company': [
- dict(fieldname='hra_section', label='HRA Settings',
- fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
- dict(fieldname='basic_component', label='Basic Component',
- fieldtype='Link', options='Salary Component', insert_after='hra_section'),
- dict(fieldname='hra_component', label='HRA Component',
- fieldtype='Link', options='Salary Component', insert_after='basic_component'),
- dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
- dict(fieldname='arrear_component', label='Arrear Component',
- fieldtype='Link', options='Salary Component', insert_after='hra_column_break'),
- dict(fieldname='non_profit_section', label='Non Profit Settings',
- fieldtype='Section Break', insert_after='arrear_component', collapsible=1),
- dict(fieldname='company_80g_number', label='80G Number',
- fieldtype='Data', insert_after='non_profit_section'),
- dict(fieldname='with_effect_from', label='80G With Effect From',
- fieldtype='Date', insert_after='company_80g_number'),
- dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
- dict(fieldname='pan_details', label='PAN Number',
- fieldtype='Data', insert_after='non_profit_column_break')
+ "Company": [
+ dict(
+ fieldname="hra_section",
+ label="HRA Settings",
+ fieldtype="Section Break",
+ insert_after="asset_received_but_not_billed",
+ collapsible=1,
+ ),
+ dict(
+ fieldname="basic_component",
+ label="Basic Component",
+ fieldtype="Link",
+ options="Salary Component",
+ insert_after="hra_section",
+ ),
+ dict(
+ fieldname="hra_component",
+ label="HRA Component",
+ fieldtype="Link",
+ options="Salary Component",
+ insert_after="basic_component",
+ ),
+ dict(fieldname="hra_column_break", fieldtype="Column Break", insert_after="hra_component"),
+ dict(
+ fieldname="arrear_component",
+ label="Arrear Component",
+ fieldtype="Link",
+ options="Salary Component",
+ insert_after="hra_column_break",
+ ),
+ dict(
+ fieldname="non_profit_section",
+ label="Non Profit Settings",
+ fieldtype="Section Break",
+ insert_after="arrear_component",
+ collapsible=1,
+ ),
+ dict(
+ fieldname="company_80g_number",
+ label="80G Number",
+ fieldtype="Data",
+ insert_after="non_profit_section",
+ ),
+ dict(
+ fieldname="with_effect_from",
+ label="80G With Effect From",
+ fieldtype="Date",
+ insert_after="company_80g_number",
+ ),
+ dict(
+ fieldname="non_profit_column_break", fieldtype="Column Break", insert_after="with_effect_from"
+ ),
+ dict(
+ fieldname="pan_details",
+ label="PAN Number",
+ fieldtype="Data",
+ insert_after="non_profit_column_break",
+ ),
]
}
diff --git a/erpnext/patches/v14_0/rename_ongoing_status_in_sla_documents.py b/erpnext/patches/v14_0/rename_ongoing_status_in_sla_documents.py
index 1cc5f38f42..2eb6becb09 100644
--- a/erpnext/patches/v14_0/rename_ongoing_status_in_sla_documents.py
+++ b/erpnext/patches/v14_0/rename_ongoing_status_in_sla_documents.py
@@ -2,26 +2,20 @@ import frappe
def execute():
- active_sla_documents = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])]
+ active_sla_documents = [
+ sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
+ ]
for doctype in active_sla_documents:
doctype = frappe.qb.DocType(doctype)
try:
- frappe.qb.update(
- doctype
- ).set(
- doctype.agreement_status, 'First Response Due'
- ).where(
+ frappe.qb.update(doctype).set(doctype.agreement_status, "First Response Due").where(
doctype.first_responded_on.isnull()
).run()
- frappe.qb.update(
- doctype
- ).set(
- doctype.agreement_status, 'Resolution Due'
- ).where(
- doctype.agreement_status == 'Ongoing'
+ frappe.qb.update(doctype).set(doctype.agreement_status, "Resolution Due").where(
+ doctype.agreement_status == "Ongoing"
).run()
except Exception:
- frappe.log_error(title='Failed to Patch SLA Status')
\ No newline at end of file
+ frappe.log_error(title="Failed to Patch SLA Status")
diff --git a/erpnext/patches/v14_0/restore_einvoice_fields.py b/erpnext/patches/v14_0/restore_einvoice_fields.py
index c4431fb9db..8c7627cbb0 100644
--- a/erpnext/patches/v14_0/restore_einvoice_fields.py
+++ b/erpnext/patches/v14_0/restore_einvoice_fields.py
@@ -6,17 +6,34 @@ from erpnext.regional.india.setup import add_permissions, add_print_formats
def execute():
# restores back the 2 custom fields that was deleted while removing e-invoicing from v14
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
custom_fields = {
- 'Sales Invoice': [
- dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
- depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
-
- dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
- depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+ "Sales Invoice": [
+ dict(
+ fieldname="irn_cancelled",
+ label="IRN Cancelled",
+ fieldtype="Check",
+ no_copy=1,
+ print_hide=1,
+ depends_on="eval:(doc.irn_cancelled === 1)",
+ read_only=1,
+ allow_on_submit=1,
+ insert_after="customer",
+ ),
+ dict(
+ fieldname="eway_bill_cancelled",
+ label="E-Way Bill Cancelled",
+ fieldtype="Check",
+ no_copy=1,
+ print_hide=1,
+ depends_on="eval:(doc.eway_bill_cancelled === 1)",
+ read_only=1,
+ allow_on_submit=1,
+ insert_after="customer",
+ ),
]
}
create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v14_0/set_payroll_cost_centers.py b/erpnext/patches/v14_0/set_payroll_cost_centers.py
index 89b305bb6f..0951e39bc3 100644
--- a/erpnext/patches/v14_0/set_payroll_cost_centers.py
+++ b/erpnext/patches/v14_0/set_payroll_cost_centers.py
@@ -2,8 +2,8 @@ import frappe
def execute():
- frappe.reload_doc('payroll', 'doctype', 'employee_cost_center')
- frappe.reload_doc('payroll', 'doctype', 'salary_structure_assignment')
+ frappe.reload_doc("payroll", "doctype", "employee_cost_center")
+ frappe.reload_doc("payroll", "doctype", "salary_structure_assignment")
employees = frappe.get_all("Employee", fields=["department", "payroll_cost_center", "name"])
@@ -16,17 +16,14 @@ def execute():
if cost_center:
employee_cost_center.setdefault(d.name, cost_center)
- salary_structure_assignments = frappe.get_all("Salary Structure Assignment",
- filters = {"docstatus": ["!=", 2]},
- fields=["name", "employee"])
+ salary_structure_assignments = frappe.get_all(
+ "Salary Structure Assignment", filters={"docstatus": ["!=", 2]}, fields=["name", "employee"]
+ )
for d in salary_structure_assignments:
cost_center = employee_cost_center.get(d.employee)
if cost_center:
assignment = frappe.get_doc("Salary Structure Assignment", d.name)
if not assignment.get("payroll_cost_centers"):
- assignment.append("payroll_cost_centers", {
- "cost_center": cost_center,
- "percentage": 100
- })
- assignment.save()
\ No newline at end of file
+ assignment.append("payroll_cost_centers", {"cost_center": cost_center, "percentage": 100})
+ assignment.save()
diff --git a/erpnext/patches/v14_0/update_employee_advance_status.py b/erpnext/patches/v14_0/update_employee_advance_status.py
index a20e35a9f6..fc9e05e836 100644
--- a/erpnext/patches/v14_0/update_employee_advance_status.py
+++ b/erpnext/patches/v14_0/update_employee_advance_status.py
@@ -2,25 +2,28 @@ import frappe
def execute():
- frappe.reload_doc('hr', 'doctype', 'employee_advance')
+ frappe.reload_doc("hr", "doctype", "employee_advance")
- advance = frappe.qb.DocType('Employee Advance')
- (frappe.qb
- .update(advance)
- .set(advance.status, 'Returned')
+ advance = frappe.qb.DocType("Employee Advance")
+ (
+ frappe.qb.update(advance)
+ .set(advance.status, "Returned")
.where(
(advance.docstatus == 1)
& ((advance.return_amount) & (advance.paid_amount == advance.return_amount))
- & (advance.status == 'Paid')
+ & (advance.status == "Paid")
)
).run()
- (frappe.qb
- .update(advance)
- .set(advance.status, 'Partly Claimed and Returned')
+ (
+ frappe.qb.update(advance)
+ .set(advance.status, "Partly Claimed and Returned")
.where(
(advance.docstatus == 1)
- & ((advance.claimed_amount & advance.return_amount) & (advance.paid_amount == (advance.return_amount + advance.claimed_amount)))
- & (advance.status == 'Paid')
+ & (
+ (advance.claimed_amount & advance.return_amount)
+ & (advance.paid_amount == (advance.return_amount + advance.claimed_amount))
+ )
+ & (advance.status == "Paid")
)
- ).run()
\ No newline at end of file
+ ).run()
diff --git a/erpnext/patches/v14_0/update_leave_notification_template.py b/erpnext/patches/v14_0/update_leave_notification_template.py
index e744054a2f..aec5f8b016 100644
--- a/erpnext/patches/v14_0/update_leave_notification_template.py
+++ b/erpnext/patches/v14_0/update_leave_notification_template.py
@@ -6,7 +6,9 @@ from frappe import _
def execute():
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
- response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html"))
+ response = frappe.read_file(
+ os.path.join(base_path, "leave_application/leave_application_email_template.html")
+ )
template = frappe.db.exists("Email Template", _("Leave Approval Notification"))
if template:
diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
index 0d11c7aaa0..076de52619 100644
--- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py
+++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
@@ -7,9 +7,11 @@ from erpnext.setup.utils import get_exchange_rate
def execute():
frappe.reload_doctype("Opportunity")
- opportunities = frappe.db.get_list('Opportunity', filters={
- 'opportunity_amount': ['>', 0]
- }, fields=['name', 'company', 'currency', 'opportunity_amount'])
+ opportunities = frappe.db.get_list(
+ "Opportunity",
+ filters={"opportunity_amount": [">", 0]},
+ fields=["name", "company", "currency", "opportunity_amount"],
+ )
for opportunity in opportunities:
company_currency = erpnext.get_company_currency(opportunity.company)
@@ -22,7 +24,9 @@ def execute():
conversion_rate = 1
base_opportunity_amount = flt(opportunity.opportunity_amount)
- frappe.db.set_value('Opportunity', opportunity.name, {
- 'conversion_rate': conversion_rate,
- 'base_opportunity_amount': base_opportunity_amount
- }, update_modified=False)
+ frappe.db.set_value(
+ "Opportunity",
+ opportunity.name,
+ {"conversion_rate": conversion_rate, "base_opportunity_amount": base_opportunity_amount},
+ update_modified=False,
+ )
diff --git a/erpnext/patches/v4_2/repost_reserved_qty.py b/erpnext/patches/v4_2/repost_reserved_qty.py
index ed4b19d07d..72ac524a5c 100644
--- a/erpnext/patches/v4_2/repost_reserved_qty.py
+++ b/erpnext/patches/v4_2/repost_reserved_qty.py
@@ -11,7 +11,8 @@ def execute():
for doctype in ("Sales Order Item", "Bin"):
frappe.reload_doctype(doctype)
- repost_for = frappe.db.sql("""
+ repost_for = frappe.db.sql(
+ """
select
distinct item_code, warehouse
from
@@ -26,17 +27,18 @@ def execute():
) so_item
where
exists(select name from tabItem where name=so_item.item_code and ifnull(is_stock_item, 0)=1)
- """)
+ """
+ )
for item_code, warehouse in repost_for:
if not (item_code and warehouse):
continue
- update_bin_qty(item_code, warehouse, {
- "reserved_qty": get_reserved_qty(item_code, warehouse)
- })
+ update_bin_qty(item_code, warehouse, {"reserved_qty": get_reserved_qty(item_code, warehouse)})
- frappe.db.sql("""delete from tabBin
+ frappe.db.sql(
+ """delete from tabBin
where exists(
select name from tabItem where name=tabBin.item_code and ifnull(is_stock_item, 0) = 0
)
- """)
+ """
+ )
diff --git a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
index dd79410ba5..8ebc649aee 100644
--- a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
+++ b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
@@ -8,20 +8,26 @@ import frappe
def execute():
from erpnext.stock.stock_balance import get_indented_qty, get_ordered_qty, update_bin_qty
- count=0
- for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse from
+ count = 0
+ for item_code, warehouse in frappe.db.sql(
+ """select distinct item_code, warehouse from
(select item_code, warehouse from tabBin
union
- select item_code, warehouse from `tabStock Ledger Entry`) a"""):
- try:
- if not (item_code and warehouse):
- continue
- count += 1
- update_bin_qty(item_code, warehouse, {
+ select item_code, warehouse from `tabStock Ledger Entry`) a"""
+ ):
+ try:
+ if not (item_code and warehouse):
+ continue
+ count += 1
+ update_bin_qty(
+ item_code,
+ warehouse,
+ {
"indented_qty": get_indented_qty(item_code, warehouse),
- "ordered_qty": get_ordered_qty(item_code, warehouse)
- })
- if count % 200 == 0:
- frappe.db.commit()
- except Exception:
- frappe.db.rollback()
+ "ordered_qty": get_ordered_qty(item_code, warehouse),
+ },
+ )
+ if count % 200 == 0:
+ frappe.db.commit()
+ except Exception:
+ frappe.db.rollback()
diff --git a/erpnext/patches/v5_7/update_item_description_based_on_item_master.py b/erpnext/patches/v5_7/update_item_description_based_on_item_master.py
index c46187ca11..edb0eaa6b9 100644
--- a/erpnext/patches/v5_7/update_item_description_based_on_item_master.py
+++ b/erpnext/patches/v5_7/update_item_description_based_on_item_master.py
@@ -2,12 +2,16 @@ import frappe
def execute():
- name = frappe.db.sql(""" select name from `tabPatch Log` \
+ name = frappe.db.sql(
+ """ select name from `tabPatch Log` \
where \
- patch like 'execute:frappe.db.sql("update `tabProduction Order` pro set description%' """)
+ patch like 'execute:frappe.db.sql("update `tabProduction Order` pro set description%' """
+ )
if not name:
- frappe.db.sql("update `tabProduction Order` pro \
+ frappe.db.sql(
+ "update `tabProduction Order` pro \
set \
description = (select description from tabItem where name=pro.production_item) \
where \
- ifnull(description, '') = ''")
+ ifnull(description, '') = ''"
+ )
diff --git a/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py b/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py
index ed1dffe75c..a8108745e9 100644
--- a/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py
+++ b/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py
@@ -6,14 +6,16 @@ import frappe
def execute():
- frappe.reload_doc('core', 'doctype', 'has_role')
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ frappe.reload_doc("core", "doctype", "has_role")
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
delete from
`tabHas Role`
where
parenttype = 'Report' and parent in('GST Sales Register',
'GST Purchase Register', 'GST Itemised Sales Register',
- 'GST Itemised Purchase Register', 'Eway Bill')""")
+ 'GST Itemised Purchase Register', 'Eway Bill')"""
+ )
diff --git a/erpnext/patches/v8_1/setup_gst_india.py b/erpnext/patches/v8_1/setup_gst_india.py
index ff9e6a48e8..b03439842f 100644
--- a/erpnext/patches/v8_1/setup_gst_india.py
+++ b/erpnext/patches/v8_1/setup_gst_india.py
@@ -3,34 +3,43 @@ from frappe.email import sendmail_to_system_managers
def execute():
- frappe.reload_doc('stock', 'doctype', 'item')
+ frappe.reload_doc("stock", "doctype", "item")
frappe.reload_doc("stock", "doctype", "customs_tariff_number")
frappe.reload_doc("accounts", "doctype", "payment_terms_template")
frappe.reload_doc("accounts", "doctype", "payment_schedule")
- company = frappe.get_all('Company', filters = {'country': 'India'})
+ company = frappe.get_all("Company", filters={"country": "India"})
if not company:
return
- frappe.reload_doc('regional', 'doctype', 'gst_settings')
- frappe.reload_doc('regional', 'doctype', 'gst_hsn_code')
+ frappe.reload_doc("regional", "doctype", "gst_settings")
+ frappe.reload_doc("regional", "doctype", "gst_hsn_code")
- for report_name in ('GST Sales Register', 'GST Purchase Register',
- 'GST Itemised Sales Register', 'GST Itemised Purchase Register'):
+ for report_name in (
+ "GST Sales Register",
+ "GST Purchase Register",
+ "GST Itemised Sales Register",
+ "GST Itemised Purchase Register",
+ ):
- frappe.reload_doc('regional', 'report', frappe.scrub(report_name))
+ frappe.reload_doc("regional", "report", frappe.scrub(report_name))
from erpnext.regional.india.setup import setup
+
delete_custom_field_tax_id_if_exists()
setup(patch=True)
send_gst_update_email()
+
def delete_custom_field_tax_id_if_exists():
- for field in frappe.db.sql_list("""select name from `tabCustom Field` where fieldname='tax_id'
- and dt in ('Sales Order', 'Sales Invoice', 'Delivery Note')"""):
+ for field in frappe.db.sql_list(
+ """select name from `tabCustom Field` where fieldname='tax_id'
+ and dt in ('Sales Order', 'Sales Invoice', 'Delivery Note')"""
+ ):
frappe.delete_doc("Custom Field", field, ignore_permissions=True)
frappe.db.commit()
+
def send_gst_update_email():
message = """Hello,
@@ -45,7 +54,9 @@ Templates and update your Customer's and Supplier's GST Numbers.
Thanks,
ERPNext Team. - """.format(gst_document_link=" ERPNext GST Document ") + """.format( + gst_document_link=" ERPNext GST Document " + ) try: sendmail_to_system_managers("[Important] ERPNext GST updates", message) diff --git a/erpnext/patches/v8_7/sync_india_custom_fields.py b/erpnext/patches/v8_7/sync_india_custom_fields.py index 808c833f6f..e1b9a732de 100644 --- a/erpnext/patches/v8_7/sync_india_custom_fields.py +++ b/erpnext/patches/v8_7/sync_india_custom_fields.py @@ -4,32 +4,39 @@ from erpnext.regional.india.setup import make_custom_fields def execute(): - company = frappe.get_all('Company', filters = {'country': 'India'}) + company = frappe.get_all("Company", filters={"country": "India"}) if not company: return - frappe.reload_doc('Payroll', 'doctype', 'payroll_period') - frappe.reload_doc('Payroll', 'doctype', 'employee_tax_exemption_declaration') - frappe.reload_doc('Payroll', 'doctype', 'employee_tax_exemption_proof_submission') - frappe.reload_doc('Payroll', 'doctype', 'employee_tax_exemption_declaration_category') - frappe.reload_doc('Payroll', 'doctype', 'employee_tax_exemption_proof_submission_detail') + frappe.reload_doc("Payroll", "doctype", "payroll_period") + frappe.reload_doc("Payroll", "doctype", "employee_tax_exemption_declaration") + frappe.reload_doc("Payroll", "doctype", "employee_tax_exemption_proof_submission") + frappe.reload_doc("Payroll", "doctype", "employee_tax_exemption_declaration_category") + frappe.reload_doc("Payroll", "doctype", "employee_tax_exemption_proof_submission_detail") - frappe.reload_doc('accounts', 'doctype', 'tax_category') + frappe.reload_doc("accounts", "doctype", "tax_category") for doctype in ["Sales Invoice", "Delivery Note", "Purchase Invoice"]: - frappe.db.sql("""delete from `tabCustom Field` where dt = %s - and fieldname in ('port_code', 'shipping_bill_number', 'shipping_bill_date')""", doctype) + frappe.db.sql( + """delete from `tabCustom Field` where dt = %s + and fieldname in ('port_code', 'shipping_bill_number', 'shipping_bill_date')""", + doctype, + ) make_custom_fields() - frappe.db.sql(""" + frappe.db.sql( + """ update `tabCustom Field` set reqd = 0, `default` = '' where fieldname = 'reason_for_issuing_document' - """) + """ + ) - frappe.db.sql(""" + frappe.db.sql( + """ update tabAddress set gst_state_number=concat("0", gst_state_number) where ifnull(gst_state_number, '') != '' and gst_state_number<10 - """) + """ + ) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index d618568416..256a3b5530 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -30,12 +30,17 @@ class AdditionalSalary(Document): frappe.throw(_("Amount should not be less than zero")) def validate_salary_structure(self): - if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}): - frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee)) + if not frappe.db.exists("Salary Structure Assignment", {"employee": self.employee}): + frappe.throw( + _("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format( + self.employee + ) + ) def validate_recurring_additional_salary_overlap(self): if self.is_recurring: - additional_salaries = frappe.db.sql(""" + additional_salaries = frappe.db.sql( + """ SELECT name FROM `tabAdditional Salary` @@ -47,22 +52,28 @@ class AdditionalSalary(Document): AND salary_component = %s AND to_date >= %s AND from_date <= %s""", - (self.employee, self.name, self.salary_component, self.from_date, self.to_date), as_dict = 1) + (self.employee, self.name, self.salary_component, self.from_date, self.to_date), + as_dict=1, + ) additional_salaries = [salary.name for salary in additional_salaries] if additional_salaries and len(additional_salaries): - frappe.throw(_("Additional Salary: {0} already exist for Salary Component: {1} for period {2} and {3}").format( - bold(comma_and(additional_salaries)), - bold(self.salary_component), - bold(formatdate(self.from_date)), - bold(formatdate(self.to_date) - ))) - + frappe.throw( + _( + "Additional Salary: {0} already exist for Salary Component: {1} for period {2} and {3}" + ).format( + bold(comma_and(additional_salaries)), + bold(self.salary_component), + bold(formatdate(self.from_date)), + bold(formatdate(self.to_date)), + ) + ) def validate_dates(self): - date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee, - ["date_of_joining", "relieving_date"]) + date_of_joining, relieving_date = frappe.db.get_value( + "Employee", self.employee, ["date_of_joining", "relieving_date"] + ) if getdate(self.from_date) > getdate(self.to_date): frappe.throw(_("From Date can not be greater than To Date.")) @@ -81,19 +92,27 @@ class AdditionalSalary(Document): def validate_employee_referral(self): if self.ref_doctype == "Employee Referral": - referral_details = frappe.db.get_value("Employee Referral", self.ref_docname, - ["is_applicable_for_referral_bonus", "status"], as_dict=1) + referral_details = frappe.db.get_value( + "Employee Referral", + self.ref_docname, + ["is_applicable_for_referral_bonus", "status"], + as_dict=1, + ) if not referral_details.is_applicable_for_referral_bonus: - frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format( - self.ref_docname)) + frappe.throw( + _("Employee Referral {0} is not applicable for referral bonus.").format(self.ref_docname) + ) if self.type == "Deduction": frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus.")) if referral_details.status != "Accepted": - frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format( - frappe.bold("Accepted"))) + frappe.throw( + _( + "Additional Salary for referral bonus can only be created against Employee Referral with status {0}" + ).format(frappe.bold("Accepted")) + ) def update_return_amount_in_employee_advance(self): if self.ref_doctype == "Employee Advance" and self.ref_docname: @@ -125,28 +144,37 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days + @frappe.whitelist() def get_additional_salaries(employee, start_date, end_date, component_type): - comp_type = 'Earning' if component_type == 'earnings' else 'Deduction' + comp_type = "Earning" if component_type == "earnings" else "Deduction" - additional_sal = frappe.qb.DocType('Additional Salary') - component_field = additional_sal.salary_component.as_('component') - overwrite_field = additional_sal.overwrite_salary_structure_amount.as_('overwrite') + additional_sal = frappe.qb.DocType("Additional Salary") + component_field = additional_sal.salary_component.as_("component") + overwrite_field = additional_sal.overwrite_salary_structure_amount.as_("overwrite") - additional_salary_list = frappe.qb.from_( - additional_sal - ).select( - additional_sal.name, component_field, additional_sal.type, - additional_sal.amount, additional_sal.is_recurring, overwrite_field, - additional_sal.deduct_full_tax_on_selected_payroll_date - ).where( - (additional_sal.employee == employee) - & (additional_sal.docstatus == 1) - & (additional_sal.type == comp_type) - ).where( - additional_sal.payroll_date[start_date: end_date] - | ((additional_sal.from_date <= end_date) & (additional_sal.to_date >= end_date)) - ).run(as_dict=True) + additional_salary_list = ( + frappe.qb.from_(additional_sal) + .select( + additional_sal.name, + component_field, + additional_sal.type, + additional_sal.amount, + additional_sal.is_recurring, + overwrite_field, + additional_sal.deduct_full_tax_on_selected_payroll_date, + ) + .where( + (additional_sal.employee == employee) + & (additional_sal.docstatus == 1) + & (additional_sal.type == comp_type) + ) + .where( + additional_sal.payroll_date[start_date:end_date] + | ((additional_sal.from_date <= end_date) & (additional_sal.to_date >= end_date)) + ) + .run(as_dict=True) + ) additional_salaries = [] components_to_overwrite = [] @@ -154,8 +182,12 @@ def get_additional_salaries(employee, start_date, end_date, component_type): for d in additional_salary_list: if d.overwrite: if d.component in components_to_overwrite: - frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format( - frappe.bold(d.component), start_date, end_date), title=_("Error")) + frappe.throw( + _( + "Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}." + ).format(frappe.bold(d.component), start_date, end_date), + title=_("Error"), + ) components_to_overwrite.append(d.component) diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py index 84de912e43..7d5d9e02f3 100644 --- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py @@ -17,12 +17,16 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_ class TestAdditionalSalary(unittest.TestCase): - def setUp(self): setup_test() def tearDown(self): - for dt in ["Salary Slip", "Additional Salary", "Salary Structure Assignment", "Salary Structure"]: + for dt in [ + "Salary Slip", + "Additional Salary", + "Salary Structure Assignment", + "Salary Structure", + ]: frappe.db.sql("delete from `tab%s`" % dt) def test_recurring_additional_salary(self): @@ -30,10 +34,14 @@ class TestAdditionalSalary(unittest.TestCase): salary_component = None emp_id = make_employee("test_additional@salary.com") frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800)) - salary_structure = make_salary_structure("Test Salary Structure Additional Salary", "Monthly", employee=emp_id) + salary_structure = make_salary_structure( + "Test Salary Structure Additional Salary", "Monthly", employee=emp_id + ) add_sal = get_additional_salary(emp_id) - ss = make_employee_salary_slip("test_additional@salary.com", "Monthly", salary_structure=salary_structure.name) + ss = make_employee_salary_slip( + "test_additional@salary.com", "Monthly", salary_structure=salary_structure.name + ) for earning in ss.earnings: if earning.salary_component == "Recurring Salary Component": amount = earning.amount @@ -42,6 +50,7 @@ class TestAdditionalSalary(unittest.TestCase): self.assertEqual(amount, add_sal.amount) self.assertEqual(salary_component, add_sal.salary_component) + def get_additional_salary(emp_id): create_salary_component("Recurring Salary Component") add_sal = frappe.new_doc("Additional Salary") diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py index eda50150eb..0acd44711b 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py @@ -34,19 +34,30 @@ class EmployeeBenefitApplication(Document): if self.remaining_benefit > 0: self.validate_remaining_benefit_amount() else: - frappe.throw(_("As per your assigned Salary Structure you cannot apply for benefits").format(self.employee)) + frappe.throw( + _("As per your assigned Salary Structure you cannot apply for benefits").format(self.employee) + ) def validate_prev_benefit_claim(self): if self.employee_benefits: for benefit in self.employee_benefits: if benefit.pay_against_benefit_claim == 1: payroll_period = frappe.get_doc("Payroll Period", self.payroll_period) - benefit_claimed = get_previous_claimed_amount(self.employee, payroll_period, component = benefit.earning_component) - benefit_given = get_sal_slip_total_benefit_given(self.employee, payroll_period, component = benefit.earning_component) + benefit_claimed = get_previous_claimed_amount( + self.employee, payroll_period, component=benefit.earning_component + ) + benefit_given = get_sal_slip_total_benefit_given( + self.employee, payroll_period, component=benefit.earning_component + ) benefit_claim_remining = benefit_claimed - benefit_given if benefit_claimed > 0 and benefit_claim_remining > benefit.amount: - frappe.throw(_("An amount of {0} already claimed for the component {1}, set the amount equal or greater than {2}").format( - benefit_claimed, benefit.earning_component, benefit_claim_remining)) + frappe.throw( + _( + "An amount of {0} already claimed for the component {1}, set the amount equal or greater than {2}" + ).format( + benefit_claimed, benefit.earning_component, benefit_claim_remining + ) + ) def validate_remaining_benefit_amount(self): # check salary structure earnings have flexi component (sum of max_benefit_amount) @@ -65,20 +76,34 @@ class EmployeeBenefitApplication(Document): if salary_structure.earnings: for earnings in salary_structure.earnings: if earnings.is_flexible_benefit == 1 and earnings.salary_component not in benefit_components: - pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", earnings.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"]) + pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value( + "Salary Component", + earnings.salary_component, + ["pay_against_benefit_claim", "max_benefit_amount"], + ) if pay_against_benefit_claim != 1: pro_rata_amount += max_benefit_amount else: non_pro_rata_amount += max_benefit_amount - if pro_rata_amount == 0 and non_pro_rata_amount == 0: - frappe.throw(_("Please add the remaining benefits {0} to any of the existing component").format(self.remaining_benefit)) + if pro_rata_amount == 0 and non_pro_rata_amount == 0: + frappe.throw( + _("Please add the remaining benefits {0} to any of the existing component").format( + self.remaining_benefit + ) + ) elif non_pro_rata_amount > 0 and non_pro_rata_amount < rounded(self.remaining_benefit): - frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application as pro-rata component").format( - non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount)) + frappe.throw( + _( + "You can claim only an amount of {0}, the rest amount {1} should be in the application as pro-rata component" + ).format(non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount) + ) elif non_pro_rata_amount == 0: - frappe.throw(_("Please add the remaining benefits {0} to the application as pro-rata component").format( - self.remaining_benefit)) + frappe.throw( + _("Please add the remaining benefits {0} to the application as pro-rata component").format( + self.remaining_benefit + ) + ) def validate_max_benefit_for_component(self): if self.employee_benefits: @@ -87,30 +112,43 @@ class EmployeeBenefitApplication(Document): self.validate_max_benefit(employee_benefit.earning_component) max_benefit_amount += employee_benefit.amount if max_benefit_amount > self.max_benefits: - frappe.throw(_("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, self.max_benefits)) + frappe.throw( + _("Maximum benefit amount of employee {0} exceeds {1}").format( + self.employee, self.max_benefits + ) + ) def validate_max_benefit(self, earning_component_name): - max_benefit_amount = frappe.db.get_value("Salary Component", earning_component_name, "max_benefit_amount") + max_benefit_amount = frappe.db.get_value( + "Salary Component", earning_component_name, "max_benefit_amount" + ) benefit_amount = 0 for employee_benefit in self.employee_benefits: if employee_benefit.earning_component == earning_component_name: benefit_amount += employee_benefit.amount - prev_sal_slip_flexi_amount = get_sal_slip_total_benefit_given(self.employee, frappe.get_doc("Payroll Period", self.payroll_period), earning_component_name) + prev_sal_slip_flexi_amount = get_sal_slip_total_benefit_given( + self.employee, frappe.get_doc("Payroll Period", self.payroll_period), earning_component_name + ) benefit_amount += prev_sal_slip_flexi_amount if rounded(benefit_amount, 2) > max_benefit_amount: - frappe.throw(_("Maximum benefit amount of component {0} exceeds {1}").format(earning_component_name, max_benefit_amount)) + frappe.throw( + _("Maximum benefit amount of component {0} exceeds {1}").format( + earning_component_name, max_benefit_amount + ) + ) def validate_duplicate_on_payroll_period(self): application = frappe.db.exists( "Employee Benefit Application", - { - 'employee': self.employee, - 'payroll_period': self.payroll_period, - 'docstatus': 1 - } + {"employee": self.employee, "payroll_period": self.payroll_period, "docstatus": 1}, ) if application: - frappe.throw(_("Employee {0} already submited an apllication {1} for the payroll period {2}").format(self.employee, application, self.payroll_period)) + frappe.throw( + _("Employee {0} already submited an apllication {1} for the payroll period {2}").format( + self.employee, application, self.payroll_period + ) + ) + @frappe.whitelist() def get_max_benefits(employee, on_date): @@ -121,6 +159,7 @@ def get_max_benefits(employee, on_date): return max_benefits return False + @frappe.whitelist() def get_max_benefits_remaining(employee, on_date, payroll_period): max_benefits = get_max_benefits(employee, on_date) @@ -141,9 +180,14 @@ def get_max_benefits_remaining(employee, on_date, payroll_period): sal_struct = frappe.get_doc("Salary Structure", sal_struct_name) for sal_struct_row in sal_struct.get("earnings"): salary_component = frappe.get_doc("Salary Component", sal_struct_row.salary_component) - if salary_component.depends_on_payment_days == 1 and salary_component.pay_against_benefit_claim != 1: + if ( + salary_component.depends_on_payment_days == 1 + and salary_component.pay_against_benefit_claim != 1 + ): have_depends_on_payment_days = True - benefit_amount = get_benefit_amount_based_on_pro_rata(sal_struct, salary_component.max_benefit_amount) + benefit_amount = get_benefit_amount_based_on_pro_rata( + sal_struct, salary_component.max_benefit_amount + ) amount_per_day = benefit_amount / payroll_period_days per_day_amount_total += amount_per_day @@ -159,12 +203,14 @@ def get_max_benefits_remaining(employee, on_date, payroll_period): return max_benefits - prev_sal_slip_flexi_total return max_benefits + def calculate_lwp(employee, start_date, holidays, working_days): lwp = 0 holidays = "','".join(holidays) for d in range(working_days): dt = add_days(cstr(getdate(start_date)), d) - leave = frappe.db.sql(""" + leave = frappe.db.sql( + """ select t1.name, t1.half_day from `tabLeave Application` t1, `tabLeave Type` t2 where t2.name = t1.leave_type @@ -174,56 +220,77 @@ def calculate_lwp(employee, start_date, holidays, working_days): and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date WHEN t2.include_holiday THEN %(dt)s between from_date and to_date END - """.format(holidays), {"employee": employee, "dt": dt}) + """.format( + holidays + ), + {"employee": employee, "dt": dt}, + ) if leave: lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1) return lwp -def get_benefit_component_amount(employee, start_date, end_date, salary_component, sal_struct, payroll_frequency, payroll_period): + +def get_benefit_component_amount( + employee, start_date, end_date, salary_component, sal_struct, payroll_frequency, payroll_period +): if not payroll_period: - frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}") - .format(salary_component)) + frappe.msgprint( + _("Start and end dates not in a valid Payroll Period, cannot calculate {0}").format( + salary_component + ) + ) return False # Considering there is only one application for a year - benefit_application = frappe.db.sql(""" + benefit_application = frappe.db.sql( + """ select name from `tabEmployee Benefit Application` where payroll_period=%(payroll_period)s and employee=%(employee)s and docstatus = 1 - """, { - 'employee': employee, - 'payroll_period': payroll_period.name - }) + """, + {"employee": employee, "payroll_period": payroll_period.name}, + ) current_benefit_amount = 0.0 - component_max_benefit, depends_on_payment_days = frappe.db.get_value("Salary Component", - salary_component, ["max_benefit_amount", "depends_on_payment_days"]) + component_max_benefit, depends_on_payment_days = frappe.db.get_value( + "Salary Component", salary_component, ["max_benefit_amount", "depends_on_payment_days"] + ) benefit_amount = 0 if benefit_application: - benefit_amount = frappe.db.get_value("Employee Benefit Application Detail", - {"parent": benefit_application[0][0], "earning_component": salary_component}, "amount") + benefit_amount = frappe.db.get_value( + "Employee Benefit Application Detail", + {"parent": benefit_application[0][0], "earning_component": salary_component}, + "amount", + ) elif component_max_benefit: benefit_amount = get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit) current_benefit_amount = 0 if benefit_amount: - total_sub_periods = get_period_factor(employee, - start_date, end_date, payroll_frequency, payroll_period, depends_on_payment_days)[0] + total_sub_periods = get_period_factor( + employee, start_date, end_date, payroll_frequency, payroll_period, depends_on_payment_days + )[0] current_benefit_amount = benefit_amount / total_sub_periods return current_benefit_amount + def get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit): max_benefits_total = 0 benefit_amount = 0 for d in sal_struct.get("earnings"): if d.is_flexible_benefit == 1: - component = frappe.db.get_value("Salary Component", d.salary_component, ["max_benefit_amount", "pay_against_benefit_claim"], as_dict=1) + component = frappe.db.get_value( + "Salary Component", + d.salary_component, + ["max_benefit_amount", "pay_against_benefit_claim"], + as_dict=1, + ) if not component.pay_against_benefit_claim: max_benefits_total += component.max_benefit_amount @@ -234,34 +301,46 @@ def get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit): return benefit_amount + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_earning_components(doctype, txt, searchfield, start, page_len, filters): if len(filters) < 2: return {} - salary_structure = get_assigned_salary_structure(filters['employee'], filters['date']) + salary_structure = get_assigned_salary_structure(filters["employee"], filters["date"]) if salary_structure: - return frappe.db.sql(""" + return frappe.db.sql( + """ select salary_component from `tabSalary Detail` where parent = %s and is_flexible_benefit = 1 order by name - """, salary_structure) + """, + salary_structure, + ) else: - frappe.throw(_("Salary Structure not found for employee {0} and date {1}") - .format(filters['employee'], filters['date'])) + frappe.throw( + _("Salary Structure not found for employee {0} and date {1}").format( + filters["employee"], filters["date"] + ) + ) + @frappe.whitelist() def get_earning_components_max_benefits(employee, date, earning_component): salary_structure = get_assigned_salary_structure(employee, date) - amount = frappe.db.sql(""" + amount = frappe.db.sql( + """ select amount from `tabSalary Detail` where parent = %s and is_flexible_benefit = 1 and salary_component = %s order by name - """, salary_structure, earning_component) + """, + salary_structure, + earning_component, + ) return amount if amount else 0 diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py index 801ce4ba36..31f26b25e7 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -23,9 +23,15 @@ class EmployeeBenefitClaim(Document): max_benefits = get_max_benefits(self.employee, self.claim_date) if not max_benefits or max_benefits <= 0: frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee)) - payroll_period = get_payroll_period(self.claim_date, self.claim_date, frappe.db.get_value("Employee", self.employee, "company")) + payroll_period = get_payroll_period( + self.claim_date, self.claim_date, frappe.db.get_value("Employee", self.employee, "company") + ) if not payroll_period: - frappe.throw(_("{0} is not in a valid Payroll Period").format(frappe.format(self.claim_date, dict(fieldtype='Date')))) + frappe.throw( + _("{0} is not in a valid Payroll Period").format( + frappe.format(self.claim_date, dict(fieldtype="Date")) + ) + ) self.validate_max_benefit_for_component(payroll_period) self.validate_max_benefit_for_sal_struct(max_benefits) self.validate_benefit_claim_amount(max_benefits, payroll_period) @@ -36,21 +42,31 @@ class EmployeeBenefitClaim(Document): claimed_amount = self.claimed_amount claimed_amount += get_previous_claimed_amount(self.employee, payroll_period) if max_benefits < claimed_amount: - frappe.throw(_("Maximum benefit of employee {0} exceeds {1} by the sum {2} of previous claimed\ - amount").format(self.employee, max_benefits, claimed_amount-max_benefits)) + frappe.throw( + _( + "Maximum benefit of employee {0} exceeds {1} by the sum {2} of previous claimed\ + amount" + ).format(self.employee, max_benefits, claimed_amount - max_benefits) + ) def validate_max_benefit_for_sal_struct(self, max_benefits): if self.claimed_amount > max_benefits: - frappe.throw(_("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, max_benefits)) + frappe.throw( + _("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, max_benefits) + ) def validate_max_benefit_for_component(self, payroll_period): if self.max_amount_eligible: claimed_amount = self.claimed_amount - claimed_amount += get_previous_claimed_amount(self.employee, - payroll_period, component = self.earning_component) + claimed_amount += get_previous_claimed_amount( + self.employee, payroll_period, component=self.earning_component + ) if claimed_amount > self.max_amount_eligible: - frappe.throw(_("Maximum amount eligible for the component {0} exceeds {1}") - .format(self.earning_component, self.max_amount_eligible)) + frappe.throw( + _("Maximum amount eligible for the component {0} exceeds {1}").format( + self.earning_component, self.max_amount_eligible + ) + ) def validate_non_pro_rata_benefit_claim(self, max_benefits, payroll_period): claimed_amount = self.claimed_amount @@ -64,30 +80,39 @@ class EmployeeBenefitClaim(Document): sal_struct = frappe.get_doc("Salary Structure", sal_struct_name) pro_rata_amount = get_benefit_pro_rata_ratio_amount(self.employee, self.claim_date, sal_struct) - claimed_amount += get_previous_claimed_amount(self.employee, payroll_period, non_pro_rata = True) + claimed_amount += get_previous_claimed_amount(self.employee, payroll_period, non_pro_rata=True) if max_benefits < pro_rata_amount + claimed_amount: - frappe.throw(_("Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component\ - amount and previous claimed amount").format(self.employee, max_benefits, pro_rata_amount+claimed_amount-max_benefits)) + frappe.throw( + _( + "Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component\ + amount and previous claimed amount" + ).format( + self.employee, max_benefits, pro_rata_amount + claimed_amount - max_benefits + ) + ) def get_pro_rata_amount_in_application(self, payroll_period): application = frappe.db.exists( "Employee Benefit Application", - { - 'employee': self.employee, - 'payroll_period': payroll_period, - 'docstatus': 1 - } + {"employee": self.employee, "payroll_period": payroll_period, "docstatus": 1}, ) if application: - return frappe.db.get_value("Employee Benefit Application", application, "pro_rata_dispensed_amount") + return frappe.db.get_value( + "Employee Benefit Application", application, "pro_rata_dispensed_amount" + ) return False + def get_benefit_pro_rata_ratio_amount(employee, on_date, sal_struct): total_pro_rata_max = 0 benefit_amount_total = 0 for sal_struct_row in sal_struct.get("earnings"): try: - pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"]) + pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value( + "Salary Component", + sal_struct_row.salary_component, + ["pay_against_benefit_claim", "max_benefit_amount"], + ) except TypeError: # show the error in tests? frappe.throw(_("Unable to find Salary Component {0}").format(sal_struct_row.salary_component)) @@ -95,7 +120,11 @@ def get_benefit_pro_rata_ratio_amount(employee, on_date, sal_struct): total_pro_rata_max += max_benefit_amount if total_pro_rata_max > 0: for sal_struct_row in sal_struct.get("earnings"): - pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"]) + pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value( + "Salary Component", + sal_struct_row.salary_component, + ["pay_against_benefit_claim", "max_benefit_amount"], + ) if sal_struct_row.is_flexible_benefit == 1 and pay_against_benefit_claim != 1: component_max = max_benefit_amount @@ -105,6 +134,7 @@ def get_benefit_pro_rata_ratio_amount(employee, on_date, sal_struct): benefit_amount_total += benefit_amount return benefit_amount_total + def get_benefit_claim_amount(employee, start_date, end_date, salary_component=None): query = """ select sum(claimed_amount) @@ -119,41 +149,54 @@ def get_benefit_claim_amount(employee, start_date, end_date, salary_component=No if salary_component: query += " and earning_component = %(earning_component)s" - claimed_amount = flt(frappe.db.sql(query, { - 'employee': employee, - 'start_date': start_date, - 'end_date': end_date, - 'earning_component': salary_component - })[0][0]) + claimed_amount = flt( + frappe.db.sql( + query, + { + "employee": employee, + "start_date": start_date, + "end_date": end_date, + "earning_component": salary_component, + }, + )[0][0] + ) return claimed_amount + def get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payroll_period): pro_rata_amount = 0 claimed_amount = 0 application = frappe.db.exists( "Employee Benefit Application", - { - 'employee': employee, - 'payroll_period': payroll_period.name, - 'docstatus': 1 - } + {"employee": employee, "payroll_period": payroll_period.name, "docstatus": 1}, ) if application: application_obj = frappe.get_doc("Employee Benefit Application", application) - pro_rata_amount = application_obj.pro_rata_dispensed_amount + application_obj.max_benefits - application_obj.remaining_benefit + pro_rata_amount = ( + application_obj.pro_rata_dispensed_amount + + application_obj.max_benefits + - application_obj.remaining_benefit + ) else: pro_rata_amount = get_benefit_pro_rata_ratio_amount(employee, sal_slip_start_date, sal_struct) - claimed_amount += get_benefit_claim_amount(employee, payroll_period.start_date, payroll_period.end_date) + claimed_amount += get_benefit_claim_amount( + employee, payroll_period.start_date, payroll_period.end_date + ) return claimed_amount + pro_rata_amount -def get_last_payroll_period_benefits(employee, sal_slip_start_date, sal_slip_end_date, payroll_period, sal_struct): + +def get_last_payroll_period_benefits( + employee, sal_slip_start_date, sal_slip_end_date, payroll_period, sal_struct +): max_benefits = get_max_benefits(employee, payroll_period.end_date) if not max_benefits: max_benefits = 0 - remaining_benefit = max_benefits - get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payroll_period) + remaining_benefit = max_benefits - get_total_benefit_dispensed( + employee, sal_struct, sal_slip_start_date, payroll_period + ) if remaining_benefit > 0: have_remaining = True # Set the remaining benefits to flexi non pro-rata component in the salary structure @@ -162,7 +205,9 @@ def get_last_payroll_period_benefits(employee, sal_slip_start_date, sal_slip_end if d.is_flexible_benefit == 1: salary_component = frappe.get_doc("Salary Component", d.salary_component) if salary_component.pay_against_benefit_claim == 1: - claimed_amount = get_benefit_claim_amount(employee, payroll_period.start_date, sal_slip_end_date, d.salary_component) + claimed_amount = get_benefit_claim_amount( + employee, payroll_period.start_date, sal_slip_end_date, d.salary_component + ) amount_fit_to_component = salary_component.max_benefit_amount - claimed_amount if amount_fit_to_component > 0: if remaining_benefit > amount_fit_to_component: @@ -171,19 +216,23 @@ def get_last_payroll_period_benefits(employee, sal_slip_start_date, sal_slip_end else: amount = remaining_benefit have_remaining = False - current_claimed_amount = get_benefit_claim_amount(employee, sal_slip_start_date, sal_slip_end_date, d.salary_component) + current_claimed_amount = get_benefit_claim_amount( + employee, sal_slip_start_date, sal_slip_end_date, d.salary_component + ) amount += current_claimed_amount struct_row = {} salary_components_dict = {} - struct_row['depends_on_payment_days'] = salary_component.depends_on_payment_days - struct_row['salary_component'] = salary_component.name - struct_row['abbr'] = salary_component.salary_component_abbr - struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total - struct_row['is_tax_applicable'] = salary_component.is_tax_applicable, - struct_row['is_flexible_benefit'] = salary_component.is_flexible_benefit, - struct_row['variable_based_on_taxable_salary'] = salary_component.variable_based_on_taxable_salary - salary_components_dict['amount'] = amount - salary_components_dict['struct_row'] = struct_row + struct_row["depends_on_payment_days"] = salary_component.depends_on_payment_days + struct_row["salary_component"] = salary_component.name + struct_row["abbr"] = salary_component.salary_component_abbr + struct_row["do_not_include_in_total"] = salary_component.do_not_include_in_total + struct_row["is_tax_applicable"] = (salary_component.is_tax_applicable,) + struct_row["is_flexible_benefit"] = (salary_component.is_flexible_benefit,) + struct_row[ + "variable_based_on_taxable_salary" + ] = salary_component.variable_based_on_taxable_salary + salary_components_dict["amount"] = amount + salary_components_dict["struct_row"] = struct_row salary_components_array.append(salary_components_dict) if not have_remaining: break diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py index a37e22425f..7686185349 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py @@ -15,13 +15,17 @@ class EmployeeIncentive(Document): self.validate_salary_structure() def validate_salary_structure(self): - if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}): - frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee)) + if not frappe.db.exists("Salary Structure Assignment", {"employee": self.employee}): + frappe.throw( + _("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format( + self.employee + ) + ) def on_submit(self): - company = frappe.db.get_value('Employee', self.employee, 'company') + company = frappe.db.get_value("Employee", self.employee, "company") - additional_salary = frappe.new_doc('Additional Salary') + additional_salary = frappe.new_doc("Additional Salary") additional_salary.employee = self.employee additional_salary.currency = self.currency additional_salary.salary_component = self.salary_component diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index 9b5eab636f..c0ef2eee78 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -20,7 +20,9 @@ class EmployeeTaxExemptionDeclaration(Document): def validate(self): validate_active_employee(self.employee) validate_tax_declaration(self.declarations) - validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee) + validate_duplicate_exemption_for_payroll_period( + self.doctype, self.name, self.payroll_period, self.employee + ) self.set_total_declared_amount() self.set_total_exemption_amount() self.calculate_hra_exemption() @@ -43,17 +45,23 @@ class EmployeeTaxExemptionDeclaration(Document): self.annual_hra_exemption = hra_exemption["annual_exemption"] self.monthly_hra_exemption = hra_exemption["monthly_exemption"] + @frappe.whitelist() def make_proof_submission(source_name, target_doc=None): - doclist = get_mapped_doc("Employee Tax Exemption Declaration", source_name, { - "Employee Tax Exemption Declaration": { - "doctype": "Employee Tax Exemption Proof Submission", - "field_no_map": ["monthly_house_rent", "monthly_hra_exemption"] + doclist = get_mapped_doc( + "Employee Tax Exemption Declaration", + source_name, + { + "Employee Tax Exemption Declaration": { + "doctype": "Employee Tax Exemption Proof Submission", + "field_no_map": ["monthly_house_rent", "monthly_hra_exemption"], + }, + "Employee Tax Exemption Declaration Category": { + "doctype": "Employee Tax Exemption Proof Submission Detail", + "add_if_empty": True, + }, }, - "Employee Tax Exemption Declaration Category": { - "doctype": "Employee Tax Exemption Proof Submission Detail", - "add_if_empty": True - } - }, target_doc) + target_doc, + ) return doclist diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index fc28afdc3e..1d90e7383f 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -19,112 +19,147 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): frappe.db.sql("""delete from `tabEmployee Tax Exemption Declaration`""") def test_duplicate_category_in_declaration(self): - declaration = frappe.get_doc({ - "doctype": "Employee Tax Exemption Declaration", - "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), - "payroll_period": "_Test Payroll Period", - "currency": erpnext.get_default_currency(), - "declarations": [ - dict(exemption_sub_category = "_Test Sub Category", - exemption_category = "_Test Category", - amount = 100000), - dict(exemption_sub_category = "_Test Sub Category", - exemption_category = "_Test Category", - amount = 50000) - ] - }) + declaration = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"), + "company": erpnext.get_default_company(), + "payroll_period": "_Test Payroll Period", + "currency": erpnext.get_default_currency(), + "declarations": [ + dict( + exemption_sub_category="_Test Sub Category", + exemption_category="_Test Category", + amount=100000, + ), + dict( + exemption_sub_category="_Test Sub Category", + exemption_category="_Test Category", + amount=50000, + ), + ], + } + ) self.assertRaises(frappe.ValidationError, declaration.save) def test_duplicate_entry_for_payroll_period(self): - declaration = frappe.get_doc({ - "doctype": "Employee Tax Exemption Declaration", - "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), - "payroll_period": "_Test Payroll Period", - "currency": erpnext.get_default_currency(), - "declarations": [ - dict(exemption_sub_category = "_Test Sub Category", - exemption_category = "_Test Category", - amount = 100000), - dict(exemption_sub_category = "_Test1 Sub Category", - exemption_category = "_Test Category", - amount = 50000), - ] - }).insert() + declaration = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"), + "company": erpnext.get_default_company(), + "payroll_period": "_Test Payroll Period", + "currency": erpnext.get_default_currency(), + "declarations": [ + dict( + exemption_sub_category="_Test Sub Category", + exemption_category="_Test Category", + amount=100000, + ), + dict( + exemption_sub_category="_Test1 Sub Category", + exemption_category="_Test Category", + amount=50000, + ), + ], + } + ).insert() - duplicate_declaration = frappe.get_doc({ - "doctype": "Employee Tax Exemption Declaration", - "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), - "payroll_period": "_Test Payroll Period", - "currency": erpnext.get_default_currency(), - "declarations": [ - dict(exemption_sub_category = "_Test Sub Category", - exemption_category = "_Test Category", - amount = 100000) - ] - }) + duplicate_declaration = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"), + "company": erpnext.get_default_company(), + "payroll_period": "_Test Payroll Period", + "currency": erpnext.get_default_currency(), + "declarations": [ + dict( + exemption_sub_category="_Test Sub Category", + exemption_category="_Test Category", + amount=100000, + ) + ], + } + ) self.assertRaises(DuplicateDeclarationError, duplicate_declaration.insert) - duplicate_declaration.employee = frappe.get_value("Employee", {"user_id":"employee1@taxexepmtion.com"}, "name") + duplicate_declaration.employee = frappe.get_value( + "Employee", {"user_id": "employee1@taxexepmtion.com"}, "name" + ) self.assertTrue(duplicate_declaration.insert) def test_exemption_amount(self): - declaration = frappe.get_doc({ - "doctype": "Employee Tax Exemption Declaration", - "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), - "payroll_period": "_Test Payroll Period", - "currency": erpnext.get_default_currency(), - "declarations": [ - dict(exemption_sub_category = "_Test Sub Category", - exemption_category = "_Test Category", - amount = 80000), - dict(exemption_sub_category = "_Test1 Sub Category", - exemption_category = "_Test Category", - amount = 60000), - ] - }).insert() + declaration = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"), + "company": erpnext.get_default_company(), + "payroll_period": "_Test Payroll Period", + "currency": erpnext.get_default_currency(), + "declarations": [ + dict( + exemption_sub_category="_Test Sub Category", + exemption_category="_Test Category", + amount=80000, + ), + dict( + exemption_sub_category="_Test1 Sub Category", + exemption_category="_Test Category", + amount=60000, + ), + ], + } + ).insert() self.assertEqual(declaration.total_exemption_amount, 100000) + def create_payroll_period(**args): args = frappe._dict(args) name = args.name or "_Test Payroll Period" if not frappe.db.exists("Payroll Period", name): from datetime import date - payroll_period = frappe.get_doc(dict( - doctype = 'Payroll Period', - name = name, - company = args.company or erpnext.get_default_company(), - start_date = args.start_date or date(date.today().year, 1, 1), - end_date = args.end_date or date(date.today().year, 12, 31) - )).insert() + + payroll_period = frappe.get_doc( + dict( + doctype="Payroll Period", + name=name, + company=args.company or erpnext.get_default_company(), + start_date=args.start_date or date(date.today().year, 1, 1), + end_date=args.end_date or date(date.today().year, 12, 31), + ) + ).insert() return payroll_period else: return frappe.get_doc("Payroll Period", name) + def create_exemption_category(): if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"): - category = frappe.get_doc({ - "doctype": "Employee Tax Exemption Category", - "name": "_Test Category", - "deduction_component": "Income Tax", - "max_amount": 100000 - }).insert() + category = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Category", + "name": "_Test Category", + "deduction_component": "Income Tax", + "max_amount": 100000, + } + ).insert() if not frappe.db.exists("Employee Tax Exemption Sub Category", "_Test Sub Category"): - frappe.get_doc({ - "doctype": "Employee Tax Exemption Sub Category", - "name": "_Test Sub Category", - "exemption_category": "_Test Category", - "max_amount": 100000, - "is_active": 1 - }).insert() + frappe.get_doc( + { + "doctype": "Employee Tax Exemption Sub Category", + "name": "_Test Sub Category", + "exemption_category": "_Test Category", + "max_amount": 100000, + "is_active": 1, + } + ).insert() if not frappe.db.exists("Employee Tax Exemption Sub Category", "_Test1 Sub Category"): - frappe.get_doc({ - "doctype": "Employee Tax Exemption Sub Category", - "name": "_Test1 Sub Category", - "exemption_category": "_Test Category", - "max_amount": 50000, - "is_active": 1 - }).insert() + frappe.get_doc( + { + "doctype": "Employee Tax Exemption Sub Category", + "name": "_Test1 Sub Category", + "exemption_category": "_Test Category", + "max_amount": 50000, + "is_active": 1, + } + ).insert() diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py index 56e73b37df..c52efaba59 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py @@ -21,7 +21,9 @@ class EmployeeTaxExemptionProofSubmission(Document): self.set_total_actual_amount() self.set_total_exemption_amount() self.calculate_hra_exemption() - validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee) + validate_duplicate_exemption_for_payroll_period( + self.doctype, self.name, self.payroll_period, self.employee + ) def set_total_actual_amount(self): self.total_actual_amount = flt(self.get("house_rent_payment_amount")) diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py index f2aa64c287..58b2c1af05 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py @@ -19,40 +19,59 @@ class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase): frappe.db.sql("""delete from `tabEmployee Tax Exemption Proof Submission`""") def test_exemption_amount_lesser_than_category_max(self): - declaration = frappe.get_doc({ - "doctype": "Employee Tax Exemption Proof Submission", - "employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"), - "payroll_period": "Test Payroll Period", - "tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category", - type_of_proof = "Test Proof", - exemption_category = "_Test Category", - amount = 150000)] - }) + declaration = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Proof Submission", + "employee": frappe.get_value("Employee", {"user_id": "employee@proofsubmission.com"}, "name"), + "payroll_period": "Test Payroll Period", + "tax_exemption_proofs": [ + dict( + exemption_sub_category="_Test Sub Category", + type_of_proof="Test Proof", + exemption_category="_Test Category", + amount=150000, + ) + ], + } + ) self.assertRaises(frappe.ValidationError, declaration.save) - declaration = frappe.get_doc({ - "doctype": "Employee Tax Exemption Proof Submission", - "payroll_period": "Test Payroll Period", - "employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"), - "tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category", - type_of_proof = "Test Proof", - exemption_category = "_Test Category", - amount = 100000)] - }) + declaration = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Proof Submission", + "payroll_period": "Test Payroll Period", + "employee": frappe.get_value("Employee", {"user_id": "employee@proofsubmission.com"}, "name"), + "tax_exemption_proofs": [ + dict( + exemption_sub_category="_Test Sub Category", + type_of_proof="Test Proof", + exemption_category="_Test Category", + amount=100000, + ) + ], + } + ) self.assertTrue(declaration.save) self.assertTrue(declaration.submit) def test_duplicate_category_in_proof_submission(self): - declaration = frappe.get_doc({ - "doctype": "Employee Tax Exemption Proof Submission", - "employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"), - "payroll_period": "Test Payroll Period", - "tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category", - exemption_category = "_Test Category", - type_of_proof = "Test Proof", - amount = 100000), - dict(exemption_sub_category = "_Test Sub Category", - exemption_category = "_Test Category", - amount = 50000), - ] - }) + declaration = frappe.get_doc( + { + "doctype": "Employee Tax Exemption Proof Submission", + "employee": frappe.get_value("Employee", {"user_id": "employee@proofsubmission.com"}, "name"), + "payroll_period": "Test Payroll Period", + "tax_exemption_proofs": [ + dict( + exemption_sub_category="_Test Sub Category", + exemption_category="_Test Category", + type_of_proof="Test Proof", + amount=100000, + ), + dict( + exemption_sub_category="_Test Sub Category", + exemption_category="_Test Category", + amount=50000, + ), + ], + } + ) self.assertRaises(frappe.ValidationError, declaration.save) diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py index 4ac11f7112..fb75d6706c 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py @@ -10,7 +10,12 @@ from frappe.utils import flt class EmployeeTaxExemptionSubCategory(Document): def validate(self): - category_max_amount = frappe.db.get_value("Employee Tax Exemption Category", self.exemption_category, "max_amount") + category_max_amount = frappe.db.get_value( + "Employee Tax Exemption Category", self.exemption_category, "max_amount" + ) if flt(self.max_amount) > flt(category_max_amount): - frappe.throw(_("Max Exemption Amount cannot be greater than maximum exemption amount {0} of Tax Exemption Category {1}") - .format(category_max_amount, self.exemption_category)) + frappe.throw( + _( + "Max Exemption Amount cannot be greater than maximum exemption amount {0} of Tax Exemption Category {1}" + ).format(category_max_amount, self.exemption_category) + ) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 939634a931..91740ae8c6 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -27,7 +27,7 @@ class Gratuity(AccountsController): self.create_gl_entries() def on_cancel(self): - self.ignore_linked_doctypes = ['GL Entry'] + self.ignore_linked_doctypes = ["GL Entry"] self.create_gl_entries(cancel=True) def create_gl_entries(self, cancel=False): @@ -39,28 +39,34 @@ class Gratuity(AccountsController): # payable entry if self.amount: gl_entry.append( - self.get_gl_dict({ - "account": self.payable_account, - "credit": self.amount, - "credit_in_account_currency": self.amount, - "against": self.expense_account, - "party_type": "Employee", - "party": self.employee, - "against_voucher_type": self.doctype, - "against_voucher": self.name, - "cost_center": self.cost_center - }, item=self) + self.get_gl_dict( + { + "account": self.payable_account, + "credit": self.amount, + "credit_in_account_currency": self.amount, + "against": self.expense_account, + "party_type": "Employee", + "party": self.employee, + "against_voucher_type": self.doctype, + "against_voucher": self.name, + "cost_center": self.cost_center, + }, + item=self, + ) ) # expense entries gl_entry.append( - self.get_gl_dict({ - "account": self.expense_account, - "debit": self.amount, - "debit_in_account_currency": self.amount, - "against": self.payable_account, - "cost_center": self.cost_center - }, item=self) + self.get_gl_dict( + { + "account": self.expense_account, + "debit": self.amount, + "debit_in_account_currency": self.amount, + "against": self.payable_account, + "cost_center": self.cost_center, + }, + item=self, + ) ) else: frappe.throw(_("Total Amount can not be zero")) @@ -69,7 +75,7 @@ class Gratuity(AccountsController): def create_additional_salary(self): if self.pay_via_salary_slip: - additional_salary = frappe.new_doc('Additional Salary') + additional_salary = frappe.new_doc("Additional Salary") additional_salary.employee = self.employee additional_salary.salary_component = self.salary_component additional_salary.overwrite_salary_structure_amount = 0 @@ -81,19 +87,22 @@ class Gratuity(AccountsController): additional_salary.submit() def set_total_advance_paid(self): - paid_amount = frappe.db.sql(""" + paid_amount = frappe.db.sql( + """ select ifnull(sum(debit_in_account_currency), 0) as paid_amount from `tabGL Entry` where against_voucher_type = 'Gratuity' and against_voucher = %s and party_type = 'Employee' and party = %s - """, (self.name, self.employee), as_dict=1)[0].paid_amount + """, + (self.name, self.employee), + as_dict=1, + )[0].paid_amount if flt(paid_amount) > self.amount: frappe.throw(_("Row {0}# Paid Amount cannot be greater than Total amount")) - self.db_set("paid_amount", paid_amount) if self.amount == self.paid_amount: self.db_set("status", "Paid") @@ -104,69 +113,97 @@ def calculate_work_experience_and_amount(employee, gratuity_rule): current_work_experience = calculate_work_experience(employee, gratuity_rule) or 0 gratuity_amount = calculate_gratuity_amount(employee, gratuity_rule, current_work_experience) or 0 - return {'current_work_experience': current_work_experience, "amount": gratuity_amount} + return {"current_work_experience": current_work_experience, "amount": gratuity_amount} + def calculate_work_experience(employee, gratuity_rule): - total_working_days_per_year, minimum_year_for_gratuity = frappe.db.get_value("Gratuity Rule", gratuity_rule, ["total_working_days_per_year", "minimum_year_for_gratuity"]) + total_working_days_per_year, minimum_year_for_gratuity = frappe.db.get_value( + "Gratuity Rule", gratuity_rule, ["total_working_days_per_year", "minimum_year_for_gratuity"] + ) - date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) + date_of_joining, relieving_date = frappe.db.get_value( + "Employee", employee, ["date_of_joining", "relieving_date"] + ) if not relieving_date: - frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(get_link_to_form("Employee", employee)))) + frappe.throw( + _("Please set Relieving Date for employee: {0}").format( + bold(get_link_to_form("Employee", employee)) + ) + ) - method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function") - employee_total_workings_days = calculate_employee_total_workings_days(employee, date_of_joining, relieving_date) + method = frappe.db.get_value( + "Gratuity Rule", gratuity_rule, "work_experience_calculation_function" + ) + employee_total_workings_days = calculate_employee_total_workings_days( + employee, date_of_joining, relieving_date + ) - current_work_experience = employee_total_workings_days/total_working_days_per_year or 1 - current_work_experience = get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee) + current_work_experience = employee_total_workings_days / total_working_days_per_year or 1 + current_work_experience = get_work_experience_using_method( + method, current_work_experience, minimum_year_for_gratuity, employee + ) return current_work_experience -def calculate_employee_total_workings_days(employee, date_of_joining, relieving_date ): + +def calculate_employee_total_workings_days(employee, date_of_joining, relieving_date): employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days payroll_based_on = frappe.db.get_value("Payroll Settings", None, "payroll_based_on") or "Leave" if payroll_based_on == "Leave": total_lwp = get_non_working_days(employee, relieving_date, "On Leave") employee_total_workings_days -= total_lwp - elif payroll_based_on == "Attendance": + elif payroll_based_on == "Attendance": total_absents = get_non_working_days(employee, relieving_date, "Absent") employee_total_workings_days -= total_absents return employee_total_workings_days -def get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee): + +def get_work_experience_using_method( + method, current_work_experience, minimum_year_for_gratuity, employee +): if method == "Round off Work Experience": current_work_experience = round(current_work_experience) else: current_work_experience = floor(current_work_experience) if current_work_experience < minimum_year_for_gratuity: - frappe.throw(_("Employee: {0} have to complete minimum {1} years for gratuity").format(bold(employee), minimum_year_for_gratuity)) + frappe.throw( + _("Employee: {0} have to complete minimum {1} years for gratuity").format( + bold(employee), minimum_year_for_gratuity + ) + ) return current_work_experience + def get_non_working_days(employee, relieving_date, status): - filters={ - "docstatus": 1, - "status": status, - "employee": employee, - "attendance_date": ("<=", get_datetime(relieving_date)) - } + filters = { + "docstatus": 1, + "status": status, + "employee": employee, + "attendance_date": ("<=", get_datetime(relieving_date)), + } if status == "On Leave": - lwp_leave_types = frappe.get_list("Leave Type", filters = {"is_lwp":1}) + lwp_leave_types = frappe.get_list("Leave Type", filters={"is_lwp": 1}) lwp_leave_types = [leave_type.name for leave_type in lwp_leave_types] - filters["leave_type"] = ("IN", lwp_leave_types) + filters["leave_type"] = ("IN", lwp_leave_types) - - record = frappe.get_all("Attendance", filters=filters, fields = ["COUNT(name) as total_lwp"]) + record = frappe.get_all("Attendance", filters=filters, fields=["COUNT(name) as total_lwp"]) return record[0].total_lwp if len(record) else 0 + def calculate_gratuity_amount(employee, gratuity_rule, experience): applicable_earnings_component = get_applicable_components(gratuity_rule) - total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule) + total_applicable_components_amount = get_total_applicable_component_amount( + employee, applicable_earnings_component, gratuity_rule + ) - calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") + calculate_gratuity_amount_based_on = frappe.db.get_value( + "Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on" + ) gratuity_amount = 0 slabs = get_gratuity_rule_slabs(gratuity_rule) slab_found = False @@ -174,49 +211,78 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): for slab in slabs: if calculate_gratuity_amount_based_on == "Current Slab": - slab_found, gratuity_amount = calculate_amount_based_on_current_slab(slab.from_year, slab.to_year, - experience, total_applicable_components_amount, slab.fraction_of_applicable_earnings) + slab_found, gratuity_amount = calculate_amount_based_on_current_slab( + slab.from_year, + slab.to_year, + experience, + total_applicable_components_amount, + slab.fraction_of_applicable_earnings, + ) if slab_found: - break + break elif calculate_gratuity_amount_based_on == "Sum of all previous slabs": if slab.to_year == 0 and slab.from_year == 0: - gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + gratuity_amount += ( + year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + ) slab_found = True break - if experience > slab.to_year and experience > slab.from_year and slab.to_year !=0: - gratuity_amount += (slab.to_year - slab.from_year) * total_applicable_components_amount * slab.fraction_of_applicable_earnings - year_left -= (slab.to_year - slab.from_year) + if experience > slab.to_year and experience > slab.from_year and slab.to_year != 0: + gratuity_amount += ( + (slab.to_year - slab.from_year) + * total_applicable_components_amount + * slab.fraction_of_applicable_earnings + ) + year_left -= slab.to_year - slab.from_year slab_found = True elif slab.from_year <= experience and (experience < slab.to_year or slab.to_year == 0): - gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + gratuity_amount += ( + year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + ) slab_found = True if not slab_found: - frappe.throw(_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(bold(gratuity_rule))) + frappe.throw( + _("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format( + bold(gratuity_rule) + ) + ) return gratuity_amount + def get_applicable_components(gratuity_rule): - applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"]) + applicable_earnings_component = frappe.get_all( + "Gratuity Applicable Component", filters={"parent": gratuity_rule}, fields=["salary_component"] + ) if len(applicable_earnings_component) == 0: - frappe.throw(_("No Applicable Earnings Component found for Gratuity Rule: {0}").format(bold(get_link_to_form("Gratuity Rule",gratuity_rule)))) - applicable_earnings_component = [component.salary_component for component in applicable_earnings_component] + frappe.throw( + _("No Applicable Earnings Component found for Gratuity Rule: {0}").format( + bold(get_link_to_form("Gratuity Rule", gratuity_rule)) + ) + ) + applicable_earnings_component = [ + component.salary_component for component in applicable_earnings_component + ] return applicable_earnings_component + def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule): - sal_slip = get_last_salary_slip(employee) + sal_slip = get_last_salary_slip(employee) if not sal_slip: frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) - component_and_amounts = frappe.get_all("Salary Detail", + component_and_amounts = frappe.get_all( + "Salary Detail", filters={ "docstatus": 1, - 'parent': sal_slip, + "parent": sal_slip, "parentfield": "earnings", - 'salary_component': ('in', applicable_earnings_component) + "salary_component": ("in", applicable_earnings_component), }, - fields=["amount"]) + fields=["amount"], + ) total_applicable_components_amount = 0 if not len(component_and_amounts): frappe.throw(_("No Applicable Component is present in last month salary slip")) @@ -224,30 +290,44 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen total_applicable_components_amount += data.amount return total_applicable_components_amount -def calculate_amount_based_on_current_slab(from_year, to_year, experience, total_applicable_components_amount, fraction_of_applicable_earnings): - slab_found = False; gratuity_amount = 0 + +def calculate_amount_based_on_current_slab( + from_year, + to_year, + experience, + total_applicable_components_amount, + fraction_of_applicable_earnings, +): + slab_found = False + gratuity_amount = 0 if experience >= from_year and (to_year == 0 or experience < to_year): - gratuity_amount = total_applicable_components_amount * experience * fraction_of_applicable_earnings + gratuity_amount = ( + total_applicable_components_amount * experience * fraction_of_applicable_earnings + ) if fraction_of_applicable_earnings: slab_found = True return slab_found, gratuity_amount + def get_gratuity_rule_slabs(gratuity_rule): - return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx") + return frappe.get_all( + "Gratuity Rule Slab", filters={"parent": gratuity_rule}, fields=["*"], order_by="idx" + ) + def get_salary_structure(employee): - return frappe.get_list("Salary Structure Assignment", filters = { - "employee": employee, 'docstatus': 1 - }, + return frappe.get_list( + "Salary Structure Assignment", + filters={"employee": employee, "docstatus": 1}, fields=["from_date", "salary_structure"], - order_by = "from_date desc")[0].salary_structure + order_by="from_date desc", + )[0].salary_structure + def get_last_salary_slip(employee): - salary_slips = frappe.get_list("Salary Slip", filters = { - "employee": employee, 'docstatus': 1 - }, - order_by = "start_date desc" + salary_slips = frappe.get_list( + "Salary Slip", filters={"employee": employee, "docstatus": 1}, order_by="start_date desc" ) if not salary_slips: return diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py index 771a6fea84..9396461f1d 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py +++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py @@ -3,14 +3,9 @@ from frappe import _ def get_data(): return { - 'fieldname': 'reference_name', - 'non_standard_fieldnames': { - 'Additional Salary': 'ref_docname', + "fieldname": "reference_name", + "non_standard_fieldnames": { + "Additional Salary": "ref_docname", }, - 'transactions': [ - { - 'label': _('Payment'), - 'items': ['Payment Entry', 'Additional Salary'] - } - ] + "transactions": [{"label": _("Payment"), "items": ["Payment Entry", "Additional Salary"]}], } diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 90e8061fed..67bb447e91 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -17,16 +17,18 @@ from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule test_dependencies = ["Salary Component", "Salary Slip", "Account"] + + class TestGratuity(unittest.TestCase): def setUp(self): frappe.db.delete("Gratuity") frappe.db.delete("Additional Salary", {"ref_doctype": "Gratuity"}) - make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) - make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) + make_earning_salary_component(setup=True, test_tax=True, company_list=["_Test Company"]) + make_deduction_salary_component(setup=True, test_tax=True, company_list=["_Test Company"]) def test_get_last_salary_slip_should_return_none_for_new_employee(self): - new_employee = make_employee("new_employee@salary.com", company='_Test Company') + new_employee = make_employee("new_employee@salary.com", company="_Test Company") salary_slip = get_last_salary_slip(new_employee) assert salary_slip is None @@ -37,25 +39,32 @@ class TestGratuity(unittest.TestCase): gratuity = create_gratuity(pay_via_salary_slip=1, employee=employee, rule=rule.name) # work experience calculation - date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) - employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + date_of_joining, relieving_date = frappe.db.get_value( + "Employee", employee, ["date_of_joining", "relieving_date"] + ) + employee_total_workings_days = ( + get_datetime(relieving_date) - get_datetime(date_of_joining) + ).days - experience = employee_total_workings_days/rule.total_working_days_per_year + experience = employee_total_workings_days / rule.total_working_days_per_year gratuity.reload() from math import floor + self.assertEqual(floor(experience), gratuity.current_work_experience) - #amount Calculation - component_amount = frappe.get_all("Salary Detail", - filters={ - "docstatus": 1, - 'parent': sal_slip, - "parentfield": "earnings", - 'salary_component': "Basic Salary" - }, - fields=["amount"]) + # amount Calculation + component_amount = frappe.get_all( + "Salary Detail", + filters={ + "docstatus": 1, + "parent": sal_slip, + "parentfield": "earnings", + "salary_component": "Basic Salary", + }, + fields=["amount"], + ) - ''' 5 - 0 fraction is 1 ''' + """ 5 - 0 fraction is 1 """ gratuity_amount = component_amount[0].amount * experience gratuity.reload() @@ -70,13 +79,19 @@ class TestGratuity(unittest.TestCase): rule = get_gratuity_rule("Rule Under Limited Contract (UAE)") set_mode_of_payment_account() - gratuity = create_gratuity(expense_account = 'Payment Account - _TC', mode_of_payment='Cash', employee=employee) + gratuity = create_gratuity( + expense_account="Payment Account - _TC", mode_of_payment="Cash", employee=employee + ) - #work experience calculation - date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) - employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + # work experience calculation + date_of_joining, relieving_date = frappe.db.get_value( + "Employee", employee, ["date_of_joining", "relieving_date"] + ) + employee_total_workings_days = ( + get_datetime(relieving_date) - get_datetime(date_of_joining) + ).days - experience = employee_total_workings_days/rule.total_working_days_per_year + experience = employee_total_workings_days / rule.total_working_days_per_year gratuity.reload() @@ -84,29 +99,32 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) - #amount Calculation - component_amount = frappe.get_all("Salary Detail", - filters={ - "docstatus": 1, - 'parent': sal_slip, - "parentfield": "earnings", - 'salary_component': "Basic Salary" - }, - fields=["amount"]) + # amount Calculation + component_amount = frappe.get_all( + "Salary Detail", + filters={ + "docstatus": 1, + "parent": sal_slip, + "parentfield": "earnings", + "salary_component": "Basic Salary", + }, + fields=["amount"], + ) - ''' range | Fraction + """ range | Fraction 0-1 | 0 1-5 | 0.7 5-0 | 1 - ''' + """ - gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount + gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount gratuity.reload() self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) self.assertEqual(gratuity.status, "Unpaid") from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + pay_entry = get_payment_entry("Gratuity", gratuity.name) pay_entry.reference_no = "123467" pay_entry.reference_date = getdate() @@ -115,7 +133,7 @@ class TestGratuity(unittest.TestCase): gratuity.reload() self.assertEqual(gratuity.status, "Paid") - self.assertEqual(flt(gratuity.paid_amount,2), flt(gratuity.amount, 2)) + self.assertEqual(flt(gratuity.paid_amount, 2), flt(gratuity.amount, 2)) def tearDown(self): frappe.db.rollback() @@ -127,14 +145,13 @@ def get_gratuity_rule(name): create_gratuity_rule() rule = frappe.get_doc("Gratuity Rule", name) rule.applicable_earnings_component = [] - rule.append("applicable_earnings_component", { - "salary_component": "Basic Salary" - }) + rule.append("applicable_earnings_component", {"salary_component": "Basic Salary"}) rule.save() rule.reload() return rule + def create_gratuity(**args): if args: args = frappe._dict(args) @@ -147,15 +164,16 @@ def create_gratuity(**args): gratuity.payroll_date = getdate() gratuity.salary_component = "Performance Bonus" else: - gratuity.expense_account = args.expense_account or 'Payment Account - _TC' + gratuity.expense_account = args.expense_account or "Payment Account - _TC" gratuity.payable_account = args.payable_account or get_payable_account("_Test Company") - gratuity.mode_of_payment = args.mode_of_payment or 'Cash' + gratuity.mode_of_payment = args.mode_of_payment or "Cash" gratuity.save() gratuity.submit() return gratuity + def set_mode_of_payment_account(): if not frappe.db.exists("Account", "Payment Account - _TC"): mode_of_payment = create_account() @@ -163,14 +181,15 @@ def set_mode_of_payment_account(): mode_of_payment = frappe.get_doc("Mode of Payment", "Cash") mode_of_payment.accounts = [] - mode_of_payment.append("accounts", { - "company": "_Test Company", - "default_account": "_Test Bank - _TC" - }) + mode_of_payment.append( + "accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"} + ) mode_of_payment.save() + def create_account(): - return frappe.get_doc({ + return frappe.get_doc( + { "doctype": "Account", "company": "_Test Company", "account_name": "Payment Account", @@ -179,13 +198,15 @@ def create_account(): "currency": "INR", "parent_account": "Bank Accounts - _TC", "account_type": "Bank", - }).insert(ignore_permissions=True) + } + ).insert(ignore_permissions=True) + def create_employee_and_get_last_salary_slip(): - employee = make_employee("test_employee@salary.com", company='_Test Company') + employee = make_employee("test_employee@salary.com", company="_Test Company") frappe.db.set_value("Employee", employee, "relieving_date", getdate()) - frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), - (6*365))) - if not frappe.db.exists("Salary Slip", {"employee":employee}): + frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), -(6 * 365))) + if not frappe.db.exists("Salary Slip", {"employee": employee}): salary_slip = make_employee_salary_slip("test_employee@salary.com", "Monthly") salary_slip.submit() salary_slip = salary_slip.name @@ -194,7 +215,10 @@ def create_employee_and_get_last_salary_slip(): if not frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list"): from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + make_holiday_list() - frappe.db.set_value("Company", '_Test Company', "default_holiday_list", "Salary Slip Test Holiday List") + frappe.db.set_value( + "Company", "_Test Company", "default_holiday_list", "Salary Slip Test Holiday List" + ) return employee, salary_slip diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py index d30cfc6484..5cde79a162 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py @@ -8,25 +8,34 @@ from frappe.model.document import Document class GratuityRule(Document): - def validate(self): for current_slab in self.gratuity_rule_slabs: if (current_slab.from_year > current_slab.to_year) and current_slab.to_year != 0: - frappe.throw(_("Row {0}: From (Year) can not be greater than To (Year)").format(current_slab.idx)) + frappe.throw( + _("Row {0}: From (Year) can not be greater than To (Year)").format(current_slab.idx) + ) + + if ( + current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1 + ): + frappe.throw( + _("You can not define multiple slabs if you have a slab with no lower and upper limits.") + ) - if current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1: - frappe.throw(_("You can not define multiple slabs if you have a slab with no lower and upper limits.")) def get_gratuity_rule(name, slabs, **args): args = frappe._dict(args) rule = frappe.new_doc("Gratuity Rule") rule.name = name - rule.calculate_gratuity_amount_based_on = args.calculate_gratuity_amount_based_on or "Current Slab" - rule.work_experience_calculation_method = args.work_experience_calculation_method or "Take Exact Completed Years" + rule.calculate_gratuity_amount_based_on = ( + args.calculate_gratuity_amount_based_on or "Current Slab" + ) + rule.work_experience_calculation_method = ( + args.work_experience_calculation_method or "Take Exact Completed Years" + ) rule.minimum_year_for_gratuity = 1 - for slab in slabs: slab = frappe._dict(slab) rule.append("gratuity_rule_slabs", slab) diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py index e7c67fbe11..fa5a9dedd3 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py @@ -3,11 +3,6 @@ from frappe import _ def get_data(): return { - 'fieldname': 'gratuity_rule', - 'transactions': [ - { - 'label': _('Gratuity'), - 'items': ['Gratuity'] - } - ] + "fieldname": "gratuity_rule", + "transactions": [{"label": _("Gratuity"), "items": ["Gratuity"]}], } diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py index 040b2c8935..e62d61f4c2 100644 --- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py +++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py @@ -4,7 +4,7 @@ from frappe.model.document import Document -#import frappe +# import frappe import erpnext diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 9061c5f1ee..54d56f9612 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -29,11 +29,11 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class PayrollEntry(Document): def onload(self): - if not self.docstatus==1 or self.salary_slips_submitted: + if not self.docstatus == 1 or self.salary_slips_submitted: return # check if salary slips were manually submitted - entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name']) + entries = frappe.db.count("Salary Slip", {"payroll_entry": self.name, "docstatus": 1}, ["name"]) if cint(entries) == len(self.employees): self.set_onload("submitted_ss", True) @@ -52,33 +52,51 @@ class PayrollEntry(Document): def validate_employee_details(self): emp_with_sal_slip = [] for employee_details in self.employees: - if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): + if frappe.db.exists( + "Salary Slip", + { + "employee": employee_details.employee, + "start_date": self.start_date, + "end_date": self.end_date, + "docstatus": 1, + }, + ): emp_with_sal_slip.append(employee_details.employee) if len(emp_with_sal_slip): frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip))) def on_cancel(self): - frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip` - where payroll_entry=%s """, (self.name))) + frappe.delete_doc( + "Salary Slip", + frappe.db.sql_list( + """select name from `tabSalary Slip` + where payroll_entry=%s """, + (self.name), + ), + ) self.db_set("salary_slips_created", 0) self.db_set("salary_slips_submitted", 0) def get_emp_list(self): """ - Returns list of active employees based on selected criteria - and for which salary structure exists + Returns list of active employees based on selected criteria + and for which salary structure exists """ self.check_mandatory() filters = self.make_filters() cond = get_filter_condition(filters) cond += get_joining_relieving_condition(self.start_date, self.end_date) - condition = '' + condition = "" if self.payroll_frequency: - condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} + condition = """and payroll_frequency = '%(payroll_frequency)s'""" % { + "payroll_frequency": self.payroll_frequency + } - sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition) + sal_struct = get_sal_struct( + self.company, self.currency, self.salary_slip_based_on_timesheet, condition + ) if sal_struct: cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " @@ -89,20 +107,25 @@ class PayrollEntry(Document): def make_filters(self): filters = frappe._dict() - filters['company'] = self.company - filters['branch'] = self.branch - filters['department'] = self.department - filters['designation'] = self.designation + filters["company"] = self.company + filters["branch"] = self.branch + filters["department"] = self.department + filters["designation"] = self.designation return filters @frappe.whitelist() def fill_employee_details(self): - self.set('employees', []) + self.set("employees", []) employees = self.get_emp_list() if not employees: - error_msg = _("No employees found for the mentioned criteria:Project Name: " + project_name + "
Frequency: " + " " + frequency + "
Update Reminder:" + " " + str(date_start) + "
Expected Date End:" + " " + str(date_end) + "
Percent Progress:" + " " + str(progress) + "
Number of Updates:" + " " + str(len(update)) + "
" + "Number of drafts:" + " " + str(number_of_drafts) + "
" - msg += """Project ID | Date Updated | Time Updated | Project Status | Notes | """ - for updates in update: - msg += "
---|---|---|---|---|
" + str(updates[0]) + " | " + str(updates[1]) + " | " + str(updates[2]) + " | " + str(updates[3]) + " | " + "" + str(updates[4]) + " |
" + + str(updates[0]) + + " | " + + str(updates[1]) + + " | " + + str(updates[2]) + + " | " + + str(updates[3]) + + " | " + + "" + + str(updates[4]) + + " |
Hello,
Please help us send you GST Ready Invoices.
@@ -109,7 +123,9 @@ def _send_gstin_reminder(party_type, party, default_email_id=None, sent_to=None)
ERPNext is a free and open source ERP system.
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin